1 /* 2 * Copyright (C) 1984-2012 Mark Nudelman 3 * Modified for use with illumos by Garrett D'Amore. 4 * Copyright 2014 Garrett D'Amore <garrett@damore.org> 5 * 6 * You may distribute under the terms of either the GNU General Public 7 * License or the Less License, as specified in the README file. 8 * 9 * For more information, see the README file. 10 */ 11 12 /* 13 * Prompting and other messages. 14 * There are three flavors of prompts, SHORT, MEDIUM and LONG, 15 * selected by the -m/-M options. 16 * There is also the "equals message", printed by the = command. 17 * A prompt is a message composed of various pieces, such as the 18 * name of the file being viewed, the percentage into the file, etc. 19 */ 20 21 #include "less.h" 22 #include "position.h" 23 24 extern int pr_type; 25 extern int new_file; 26 extern int sc_width; 27 extern int so_s_width, so_e_width; 28 extern int linenums; 29 extern int hshift; 30 extern int sc_height; 31 extern int jump_sline; 32 extern int less_is_more; 33 extern IFILE curr_ifile; 34 extern char *editor; 35 extern char *editproto; 36 37 /* 38 * Prototypes for the three flavors of prompts. 39 * These strings are expanded by pr_expand(). 40 */ 41 static const char s_proto[] = 42 "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x..%t"; 43 static const char m_proto[] = 44 "?n?f%f .?m(%T %i of %m) ..?e(END) " 45 "?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t"; 46 static const char M_proto[] = 47 "?f%f .?n?m(%T %i of %m) ..?" 48 "ltlines %lt-%lb?L/%L. :byte %bB?s/%s. .?e(END)" 49 " ?x- Next\\: %x.:?pB%pB\\%..%t"; 50 static const char e_proto[] = 51 "?f%f .?m(%T %i of %m) .?ltlines " 52 "%lt-%lb?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t"; 53 static const char h_proto[] = 54 "HELP -- ?eEND -- Press g to see it again:" 55 "Press RETURN for more., or q when done"; 56 static const char w_proto[] = 57 "Waiting for data"; 58 static const char more_proto[] = 59 "%f (?eEND ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t)"; 60 static const char more_M_proto[] = 61 "%f (?eEND ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t)" 62 "[Press space to continue, q to quit, h for help]"; 63 64 char *prproto[3]; 65 char const *eqproto = e_proto; 66 char const *hproto = h_proto; 67 char const *wproto = w_proto; 68 69 static char message[PROMPT_SIZE]; 70 static char *mp; 71 72 /* 73 * Initialize the prompt prototype strings. 74 */ 75 void 76 init_prompt(void) 77 { 78 prproto[0] = estrdup(s_proto); 79 prproto[1] = estrdup(less_is_more ? more_proto : m_proto); 80 prproto[2] = estrdup(less_is_more ? more_M_proto : M_proto); 81 eqproto = estrdup(e_proto); 82 hproto = estrdup(h_proto); 83 wproto = estrdup(w_proto); 84 } 85 86 /* 87 * Append a string to the end of the message. 88 */ 89 static void 90 ap_str(char *s) 91 { 92 int len; 93 94 len = strlen(s); 95 if (mp + len >= message + PROMPT_SIZE) 96 len = message + PROMPT_SIZE - mp - 1; 97 (void) strncpy(mp, s, len); 98 mp += len; 99 *mp = '\0'; 100 } 101 102 /* 103 * Append a character to the end of the message. 104 */ 105 static void 106 ap_char(char c) 107 { 108 char buf[2]; 109 110 buf[0] = c; 111 buf[1] = '\0'; 112 ap_str(buf); 113 } 114 115 /* 116 * Append a off_t (as a decimal integer) to the end of the message. 117 */ 118 static void 119 ap_pos(off_t pos) 120 { 121 char buf[23]; 122 123 postoa(pos, buf, sizeof(buf)); 124 ap_str(buf); 125 } 126 127 /* 128 * Append an integer to the end of the message. 129 */ 130 static void 131 ap_int(int num) 132 { 133 char buf[13]; 134 135 inttoa(num, buf, sizeof buf); 136 ap_str(buf); 137 } 138 139 /* 140 * Append a question mark to the end of the message. 141 */ 142 static void 143 ap_quest(void) 144 { 145 ap_str("?"); 146 } 147 148 /* 149 * Return the "current" byte offset in the file. 150 */ 151 static off_t 152 curr_byte(int where) 153 { 154 off_t pos; 155 156 pos = position(where); 157 while (pos == -1 && where >= 0 && where < sc_height-1) 158 pos = position(++where); 159 if (pos == -1) 160 pos = ch_length(); 161 return (pos); 162 } 163 164 /* 165 * Return the value of a prototype conditional. 166 * A prototype string may include conditionals which consist of a 167 * question mark followed by a single letter. 168 * Here we decode that letter and return the appropriate boolean value. 169 */ 170 static int 171 cond(char c, int where) 172 { 173 off_t len; 174 175 switch (c) { 176 case 'a': /* Anything in the message yet? */ 177 return (*message != '\0'); 178 case 'b': /* Current byte offset known? */ 179 return (curr_byte(where) != -1); 180 case 'c': 181 return (hshift != 0); 182 case 'e': /* At end of file? */ 183 return (eof_displayed()); 184 case 'f': /* Filename known? */ 185 return (strcmp(get_filename(curr_ifile), "-") != 0); 186 case 'l': /* Line number known? */ 187 case 'd': /* Same as l */ 188 return (linenums); 189 case 'L': /* Final line number known? */ 190 case 'D': /* Final page number known? */ 191 return (linenums && ch_length() != -1); 192 case 'm': /* More than one file? */ 193 return (ntags() ? (ntags() > 1) : (nifile() > 1)); 194 case 'n': /* First prompt in a new file? */ 195 return (ntags() ? 1 : new_file); 196 case 'p': /* Percent into file (bytes) known? */ 197 return (curr_byte(where) != -1 && ch_length() > 0); 198 case 'P': /* Percent into file (lines) known? */ 199 return (currline(where) != 0 && 200 (len = ch_length()) > 0 && find_linenum(len) != 0); 201 case 's': /* Size of file known? */ 202 case 'B': 203 return (ch_length() != -1); 204 case 'x': /* Is there a "next" file? */ 205 if (ntags()) 206 return (0); 207 return (next_ifile(curr_ifile) != NULL); 208 } 209 return (0); 210 } 211 212 /* 213 * Decode a "percent" prototype character. 214 * A prototype string may include various "percent" escapes; 215 * that is, a percent sign followed by a single letter. 216 * Here we decode that letter and take the appropriate action, 217 * usually by appending something to the message being built. 218 */ 219 static void 220 protochar(int c, int where) 221 { 222 off_t pos; 223 off_t len; 224 int n; 225 off_t linenum; 226 off_t last_linenum; 227 IFILE h; 228 229 #undef PAGE_NUM 230 #define PAGE_NUM(linenum) ((((linenum) - 1) / (sc_height - 1)) + 1) 231 232 switch (c) { 233 case 'b': /* Current byte offset */ 234 pos = curr_byte(where); 235 if (pos != -1) 236 ap_pos(pos); 237 else 238 ap_quest(); 239 break; 240 case 'c': 241 ap_int(hshift); 242 break; 243 case 'd': /* Current page number */ 244 linenum = currline(where); 245 if (linenum > 0 && sc_height > 1) 246 ap_pos(PAGE_NUM(linenum)); 247 else 248 ap_quest(); 249 break; 250 case 'D': /* Final page number */ 251 /* Find the page number of the last byte in the file (len-1). */ 252 len = ch_length(); 253 if (len == -1) { 254 ap_quest(); 255 } else if (len == 0) { 256 /* An empty file has no pages. */ 257 ap_pos(0); 258 } else { 259 linenum = find_linenum(len - 1); 260 if (linenum <= 0) 261 ap_quest(); 262 else 263 ap_pos(PAGE_NUM(linenum)); 264 } 265 break; 266 case 'E': /* Editor name */ 267 ap_str(editor); 268 break; 269 case 'f': /* File name */ 270 ap_str(get_filename(curr_ifile)); 271 break; 272 case 'F': /* Last component of file name */ 273 ap_str(last_component(get_filename(curr_ifile))); 274 break; 275 case 'i': /* Index into list of files */ 276 if (ntags()) 277 ap_int(curr_tag()); 278 else 279 ap_int(get_index(curr_ifile)); 280 break; 281 case 'l': /* Current line number */ 282 linenum = currline(where); 283 if (linenum != 0) 284 ap_pos(linenum); 285 else 286 ap_quest(); 287 break; 288 case 'L': /* Final line number */ 289 len = ch_length(); 290 if (len == -1 || len == ch_zero() || 291 (linenum = find_linenum(len)) <= 0) 292 ap_quest(); 293 else 294 ap_pos(linenum-1); 295 break; 296 case 'm': /* Number of files */ 297 n = ntags(); 298 if (n) 299 ap_int(n); 300 else 301 ap_int(nifile()); 302 break; 303 case 'p': /* Percent into file (bytes) */ 304 pos = curr_byte(where); 305 len = ch_length(); 306 if (pos != -1 && len > 0) 307 ap_int(percentage(pos, len)); 308 else 309 ap_quest(); 310 break; 311 case 'P': /* Percent into file (lines) */ 312 linenum = currline(where); 313 if (linenum == 0 || 314 (len = ch_length()) == -1 || len == ch_zero() || 315 (last_linenum = find_linenum(len)) <= 0) 316 ap_quest(); 317 else 318 ap_int(percentage(linenum, last_linenum)); 319 break; 320 case 's': /* Size of file */ 321 case 'B': 322 len = ch_length(); 323 if (len != -1) 324 ap_pos(len); 325 else 326 ap_quest(); 327 break; 328 case 't': /* Truncate trailing spaces in the message */ 329 while (mp > message && mp[-1] == ' ') 330 mp--; 331 *mp = '\0'; 332 break; 333 case 'T': /* Type of list */ 334 if (ntags()) 335 ap_str("tag"); 336 else 337 ap_str("file"); 338 break; 339 case 'x': /* Name of next file */ 340 h = next_ifile(curr_ifile); 341 if (h != NULL) 342 ap_str(get_filename(h)); 343 else 344 ap_quest(); 345 break; 346 } 347 } 348 349 /* 350 * Skip a false conditional. 351 * When a false condition is found (either a false IF or the ELSE part 352 * of a true IF), this routine scans the prototype string to decide 353 * where to resume parsing the string. 354 * We must keep track of nested IFs and skip them properly. 355 */ 356 static const char * 357 skipcond(const char *p) 358 { 359 int iflevel; 360 361 /* 362 * We came in here after processing a ? or :, 363 * so we start nested one level deep. 364 */ 365 iflevel = 1; 366 367 for (;;) { 368 switch (*++p) { 369 case '?': 370 /* 371 * Start of a nested IF. 372 */ 373 iflevel++; 374 break; 375 case ':': 376 /* 377 * Else. 378 * If this matches the IF we came in here with, 379 * then we're done. 380 */ 381 if (iflevel == 1) 382 return (p); 383 break; 384 case '.': 385 /* 386 * Endif. 387 * If this matches the IF we came in here with, 388 * then we're done. 389 */ 390 if (--iflevel == 0) 391 return (p); 392 break; 393 case '\\': 394 /* 395 * Backslash escapes the next character. 396 */ 397 ++p; 398 break; 399 case '\0': 400 /* 401 * Whoops. Hit end of string. 402 * This is a malformed conditional, but just treat it 403 * as if all active conditionals ends here. 404 */ 405 return (p-1); 406 } 407 } 408 } 409 410 /* 411 * Decode a char that represents a position on the screen. 412 */ 413 static const char * 414 wherechar(const char *p, int *wp) 415 { 416 switch (*p) { 417 case 'b': case 'd': case 'l': case 'p': case 'P': 418 switch (*++p) { 419 case 't': *wp = TOP; break; 420 case 'm': *wp = MIDDLE; break; 421 case 'b': *wp = BOTTOM; break; 422 case 'B': *wp = BOTTOM_PLUS_ONE; break; 423 case 'j': *wp = adjsline(jump_sline); break; 424 default: *wp = TOP; p--; break; 425 } 426 } 427 return (p); 428 } 429 430 /* 431 * Construct a message based on a prototype string. 432 */ 433 char * 434 pr_expand(const char *proto, int maxwidth) 435 { 436 const char *p; 437 int c; 438 int where; 439 440 mp = message; 441 442 if (*proto == '\0') 443 return (""); 444 445 for (p = proto; *p != '\0'; p++) { 446 switch (*p) { 447 default: /* Just put the character in the message */ 448 ap_char(*p); 449 break; 450 case '\\': /* Backslash escapes the next character */ 451 p++; 452 ap_char(*p); 453 break; 454 case '?': /* Conditional (IF) */ 455 if ((c = *++p) == '\0') { 456 --p; 457 } else { 458 where = 0; 459 p = wherechar(p, &where); 460 if (!cond(c, where)) 461 p = skipcond(p); 462 } 463 break; 464 case ':': /* ELSE */ 465 p = skipcond(p); 466 break; 467 case '.': /* ENDIF */ 468 break; 469 case '%': /* Percent escape */ 470 if ((c = *++p) == '\0') { 471 --p; 472 } else { 473 where = 0; 474 p = wherechar(p, &where); 475 protochar(c, where); 476 } 477 break; 478 } 479 } 480 481 if (*message == '\0') 482 return (""); 483 if (maxwidth > 0 && mp >= message + maxwidth) { 484 /* 485 * Message is too long. 486 * Return just the final portion of it. 487 */ 488 return (mp - maxwidth); 489 } 490 return (message); 491 } 492 493 /* 494 * Return a message suitable for printing by the "=" command. 495 */ 496 char * 497 eq_message(void) 498 { 499 return (pr_expand(eqproto, 0)); 500 } 501 502 /* 503 * Return a prompt. 504 * This depends on the prompt type (SHORT, MEDIUM, LONG), etc. 505 * If we can't come up with an appropriate prompt, return NULL 506 * and the caller will prompt with a colon. 507 */ 508 char * 509 prompt_string(void) 510 { 511 char *prompt; 512 int type; 513 514 type = pr_type; 515 prompt = pr_expand((ch_getflags() & CH_HELPFILE) ? 516 hproto : prproto[type], sc_width-so_s_width-so_e_width-2); 517 new_file = 0; 518 return (prompt); 519 } 520 521 /* 522 * Return a message suitable for printing while waiting in the F command. 523 */ 524 char * 525 wait_message(void) 526 { 527 return (pr_expand(wproto, sc_width-so_s_width-so_e_width-2)); 528 } 529