1 /* 2 * Copyright (C) 1984-2026 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information, see the README file. 8 */ 9 10 11 /* 12 * Prompting and other messages. 13 * There are three flavors of prompts, SHORT, MEDIUM and LONG, 14 * selected by the -m/-M options. 15 * There is also the "equals message", printed by the = command. 16 * A prompt is a message composed of various pieces, such as the 17 * name of the file being viewed, the percentage into the file, etc. 18 */ 19 20 #include "less.h" 21 #include "position.h" 22 23 extern int pr_type; 24 extern lbool new_file; 25 extern int linenums; 26 extern int hshift; 27 extern int sc_width; 28 extern int sc_height; 29 extern int jump_sline; 30 extern lbool less_is_more; 31 extern int header_lines; 32 extern int utf_mode; 33 extern IFILE curr_ifile; 34 #if OSC8_LINK 35 extern POSITION osc8_linepos; 36 #endif 37 #if EDITOR 38 extern constant char *editor; 39 extern constant char *editproto; 40 #endif 41 42 /* 43 * Prototypes for the three flavors of prompts. 44 * These strings are expanded by pr_expand(). 45 */ 46 static constant char s_proto[] = 47 "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x..%t"; 48 static constant char m_proto[] = 49 "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t"; 50 static constant char M_proto[] = 51 "?f%f .?n?m(%T %i of %m) ..?ltlines %lt-%lb?L/%L. :byte %bB?s/%s. .?e(END) ?x- Next\\: %x.:?pB%pB\\%..?c (column %c).%t"; 52 static constant char e_proto[] = 53 "?f%f .?m(%T %i of %m) .?ltlines %lt-%lb?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..?c (column %c).%t"; 54 static constant char h_proto[] = 55 "HELP -- ?eEND -- Press g to see it again:Press RETURN for more., or q when done"; 56 static constant char w_proto[] = 57 "Waiting for data"; 58 static constant char more_proto[] = 59 "--More--(?eEND ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t)"; 60 61 public char *prproto[3]; 62 public char *eprproto[3]; 63 public char constant *eqproto = e_proto; 64 public char constant *hproto = h_proto; 65 public char constant *wproto = w_proto; 66 67 static char message[PROMPT_SIZE]; 68 static char *mp; 69 static int longest_line; 70 71 /* 72 * Initialize the prompt prototype strings. 73 */ 74 public void init_prompt(void) 75 { 76 prproto[0] = save(s_proto); 77 prproto[1] = save(less_is_more ? more_proto : m_proto); 78 prproto[2] = save(M_proto); 79 eprproto[0] = eprproto[1] = eprproto[2] = NULL; 80 eqproto = save(e_proto); 81 hproto = save(h_proto); 82 wproto = save(w_proto); 83 } 84 85 /* 86 * Get width of longest line currently on screen. 87 * Calling longest_line_width() is expensive, so we cache its value here 88 * in case it is called more than once per prompt expansion. 89 */ 90 static int pr_longest_line(void) 91 { 92 if (longest_line < 0) 93 longest_line = longest_line_width(); 94 return longest_line; 95 } 96 97 /* 98 * Append a string to the end of the message. 99 * nprt means the character *may* be nonprintable 100 * and should be converted to printable form. 101 */ 102 static void ap_estr(constant char *s, lbool nprt) 103 { 104 constant char *es = s + strlen(s); 105 while (*s != '\0') 106 { 107 LWCHAR ch = step_charc(&s, +1, es); 108 constant char *ps; 109 char ubuf[MAX_UTF_CHAR_LEN+1]; 110 size_t plen; 111 112 if (nprt) 113 { 114 ps = utf_mode ? prutfchar(ch) : prchar(ch); 115 } else 116 { 117 char *up = ubuf; 118 put_wchar(&up, ch); 119 *up = '\0'; 120 ps = ubuf; 121 } 122 plen = strlen(ps); 123 if (mp + plen >= message + PROMPT_SIZE) 124 break; 125 strcpy(mp, ps); 126 mp += plen; 127 } 128 *mp = '\0'; 129 } 130 131 static void ap_str(constant char *s) 132 { 133 ap_estr(s, FALSE); 134 } 135 136 /* 137 * Append a character to the end of the message. 138 */ 139 static void ap_char(char c) 140 { 141 if (mp + 1 >= message + PROMPT_SIZE) 142 return; 143 *mp++ = c; 144 *mp = '\0'; 145 } 146 147 /* 148 * Append a POSITION (as a decimal integer) to the end of the message. 149 */ 150 static void ap_pos(POSITION pos) 151 { 152 char buf[INT_STRLEN_BOUND(pos) + 2]; 153 154 postoa(pos, buf, 10); 155 ap_str(buf); 156 } 157 158 /* 159 * Append a line number to the end of the message. 160 */ 161 static void ap_linenum(LINENUM linenum) 162 { 163 char buf[INT_STRLEN_BOUND(linenum) + 2]; 164 165 linenumtoa(linenum, buf, 10); 166 ap_str(buf); 167 } 168 169 /* 170 * Append an integer to the end of the message. 171 */ 172 static void ap_int(int num) 173 { 174 char buf[INT_STRLEN_BOUND(num) + 2]; 175 176 inttoa(num, buf, 10); 177 ap_str(buf); 178 } 179 180 /* 181 * Append a question mark to the end of the message. 182 */ 183 static void ap_quest(void) 184 { 185 ap_str("?"); 186 } 187 188 /* 189 * Return the "current" byte offset in the file. 190 */ 191 static POSITION curr_byte(int where) 192 { 193 POSITION pos; 194 195 pos = position(where); 196 while (pos == NULL_POSITION && where >= 0 && where < sc_height-1) 197 pos = position(++where); 198 if (pos == NULL_POSITION) 199 pos = ch_length(); 200 return (pos); 201 } 202 203 /* 204 * Return the value of a prototype conditional. 205 * A prototype string may include conditionals which consist of a 206 * question mark followed by a single letter. 207 * Here we decode that letter and return the appropriate boolean value. 208 */ 209 static lbool cond(char c, int where) 210 { 211 POSITION len; 212 213 switch (c) 214 { 215 case 'a': /* Anything in the message yet? */ 216 return (mp > message); 217 case 'b': /* Current byte offset known? */ 218 return (curr_byte(where) != NULL_POSITION); 219 case 'c': 220 return (hshift != 0); 221 case 'e': /* At end of file? */ 222 return (eof_displayed(FALSE)); 223 case 'f': /* Filename known? */ 224 case 'g': 225 return (strcmp(get_filename(curr_ifile), "-") != 0); 226 case 'l': /* Line number known? */ 227 case 'd': /* Same as l */ 228 if (!linenums) 229 return FALSE; 230 return (currline(where) != 0); 231 case 'L': /* Final line number known? */ 232 case 'D': /* Final page number known? */ 233 return (linenums && ch_length() != NULL_POSITION); 234 case 'm': /* More than one file? */ 235 #if TAGS 236 return (ntags() ? (ntags() > 1) : (nifile() > 1)); 237 #else 238 return (nifile() > 1); 239 #endif 240 case 'n': /* First prompt in a new file? */ 241 #if TAGS 242 return (ntags() ? TRUE : new_file ? TRUE : FALSE); 243 #else 244 return (new_file ? TRUE : FALSE); 245 #endif 246 case 'O': /* OSC 8 link selected? */ 247 #if OSC8_LINK 248 return (osc8_linepos != NULL_POSITION); 249 #else 250 return FALSE; 251 #endif 252 case 'p': /* Percent into file (bytes) known? */ 253 return (curr_byte(where) != NULL_POSITION && ch_length() > 0); 254 case 'P': /* Percent into file (lines) known? */ 255 return (currline(where) != 0 && 256 (len = ch_length()) > 0 && 257 find_linenum(len) != 0); 258 case 'Q': /* Any on-screen line truncated? */ 259 return (hshift + sc_width < pr_longest_line()); 260 case 's': /* Size of file known? */ 261 case 'B': 262 return (ch_length() != NULL_POSITION); 263 case 'x': /* Is there a "next" file? */ 264 #if TAGS 265 if (ntags()) 266 return (FALSE); 267 #endif 268 return (next_ifile(curr_ifile) != NULL_IFILE); 269 } 270 return (FALSE); 271 } 272 273 /* 274 * Decode a "percent" prototype character. 275 * A prototype string may include various "percent" escapes; 276 * that is, a percent sign followed by a single letter. 277 * Here we decode that letter and take the appropriate action, 278 * usually by appending something to the message being built. 279 */ 280 static void protochar(char c, int where) 281 { 282 POSITION pos; 283 POSITION len; 284 LINENUM linenum; 285 LINENUM last_linenum; 286 IFILE h; 287 char *s; 288 289 #undef PAGE_NUM 290 #define PAGE_NUM(linenum) ((((linenum) - 1) / (sc_height - header_lines - 1)) + 1) 291 292 switch (c) 293 { 294 case 'b': /* Current byte offset */ 295 pos = curr_byte(where); 296 if (pos != NULL_POSITION) 297 ap_pos(pos); 298 else 299 ap_quest(); 300 break; 301 case 'c': 302 ap_int(hshift+1); 303 break; 304 case 'C': 305 ap_int(hshift + sc_width); 306 break; 307 case 'd': /* Current page number */ 308 linenum = currline(where); 309 if (linenum > 0 && sc_height > header_lines + 1) 310 ap_linenum(PAGE_NUM(linenum)); 311 else 312 ap_quest(); 313 break; 314 case 'D': /* Final page number */ 315 /* Find the page number of the last byte in the file (len-1). */ 316 len = ch_length(); 317 if (len == NULL_POSITION) 318 ap_quest(); 319 else if (len == 0) 320 /* An empty file has no pages. */ 321 ap_linenum(0); 322 else 323 { 324 linenum = find_linenum(len - 1); 325 if (linenum <= 0) 326 ap_quest(); 327 else 328 ap_linenum(PAGE_NUM(linenum)); 329 } 330 break; 331 #if EDITOR 332 case 'E': /* Editor name */ 333 ap_str(editor); 334 break; 335 #endif 336 case 'f': /* File name */ 337 ap_estr(get_filename(curr_ifile), TRUE); 338 break; 339 case 'F': /* Last component of file name */ 340 ap_estr(last_component(get_filename(curr_ifile)), TRUE); 341 break; 342 case 'g': /* Shell-escaped file name */ 343 s = shell_quote(get_filename(curr_ifile)); 344 if (s == NULL) 345 ap_quest(); 346 else 347 { 348 ap_str(s); 349 free(s); 350 } 351 break; 352 case 'G': /* Shell-escaped last component of file name */ 353 s = shell_quote(last_component(get_filename(curr_ifile))); 354 if (s == NULL) 355 ap_quest(); 356 else 357 { 358 ap_str(s); 359 free(s); 360 } 361 break; 362 case 'i': /* Index into list of files */ 363 #if TAGS 364 if (ntags()) 365 ap_int(curr_tag()); 366 else 367 #endif 368 ap_int(get_index(curr_ifile)); 369 break; 370 case 'l': /* Current line number */ 371 linenum = currline(where); 372 if (linenum != 0) 373 ap_linenum(vlinenum(linenum)); 374 else 375 ap_quest(); 376 break; 377 case 'L': /* Final line number */ 378 len = ch_length(); 379 if (len == NULL_POSITION || len == ch_zero() || 380 (linenum = find_linenum(len)) <= 0) 381 ap_quest(); 382 else 383 ap_linenum(vlinenum(linenum-1)); 384 break; 385 case 'm': { /* Number of files */ 386 #if TAGS 387 int n = ntags(); 388 if (n) 389 ap_int(n); 390 else 391 #endif 392 ap_int(nifile()); 393 break; } 394 case 'p': /* Percent into file (bytes) */ 395 pos = curr_byte(where); 396 len = ch_length(); 397 if (pos != NULL_POSITION && len > 0) 398 ap_int(percentage(pos,len)); 399 else 400 ap_quest(); 401 break; 402 case 'P': /* Percent into file (lines) */ 403 linenum = currline(where); 404 if (linenum == 0 || 405 (len = ch_length()) == NULL_POSITION || len == ch_zero() || 406 (last_linenum = find_linenum(len)) <= 0) 407 ap_quest(); 408 else 409 ap_int(percentage(linenum, last_linenum)); 410 break; 411 case 'Q': /* Percent shifted horizontally */ 412 ap_int(percentage(hshift + sc_width, pr_longest_line())); 413 break; 414 case 's': /* Size of file */ 415 case 'B': 416 len = ch_length(); 417 if (len != NULL_POSITION) 418 ap_pos(len); 419 else 420 ap_quest(); 421 break; 422 case 't': /* Truncate trailing spaces in the message */ 423 while (mp > message && mp[-1] == ' ') 424 mp--; 425 *mp = '\0'; 426 break; 427 case 'T': /* Type of list */ 428 #if TAGS 429 if (ntags()) 430 ap_str("tag"); 431 else 432 #endif 433 ap_str("file"); 434 break; 435 case 'W': /* Width of longest line on screen */ 436 ap_int(pr_longest_line()); 437 break; 438 case 'x': /* Name of next file */ 439 h = next_ifile(curr_ifile); 440 if (h != NULL_IFILE) 441 ap_str(get_filename(h)); 442 else 443 ap_quest(); 444 break; 445 case 'y': /* Shell-escaped name of next file */ 446 h = next_ifile(curr_ifile); 447 if (h != NULL_IFILE) 448 { 449 s = shell_quote(get_filename(h)); 450 if (s == NULL) 451 ap_quest(); 452 else 453 { 454 ap_str(s); 455 free(s); 456 } 457 } else 458 ap_quest(); 459 break; 460 case '%': /* Literal percent sign */ 461 ap_char('%'); 462 break; 463 } 464 } 465 466 /* 467 * Skip a false conditional. 468 * When a false condition is found (either a false IF or the ELSE part 469 * of a true IF), this routine scans the prototype string to decide 470 * where to resume parsing the string. 471 * We must keep track of nested IFs and skip them properly. 472 */ 473 static constant char * skipcond(constant char *p) 474 { 475 int iflevel; 476 477 /* 478 * We came in here after processing a ? or :, 479 * so we start nested one level deep. 480 */ 481 iflevel = 1; 482 483 for (;;) switch (*++p) 484 { 485 case '?': 486 /* 487 * Start of a nested IF. 488 */ 489 iflevel++; 490 break; 491 case ':': 492 /* 493 * Else. 494 * If this matches the IF we came in here with, 495 * then we're done. 496 */ 497 if (iflevel == 1) 498 return (p); 499 break; 500 case '.': 501 /* 502 * Endif. 503 * If this matches the IF we came in here with, 504 * then we're done. 505 */ 506 if (--iflevel == 0) 507 return (p); 508 break; 509 case '\\': 510 /* 511 * Backslash escapes the next character. 512 */ 513 if (p[1] != '\0') 514 ++p; 515 break; 516 case '\0': 517 /* 518 * Whoops. Hit end of string. 519 * This is a malformed conditional, but just treat it 520 * as if all active conditionals ends here. 521 */ 522 return (p-1); 523 } 524 /*NOTREACHED*/ 525 } 526 527 /* 528 * Decode a char that represents a position on the screen. 529 */ 530 static constant char * wherechar(char constant *p, int *wp) 531 { 532 switch (*p) 533 { 534 case 'b': case 'd': case 'l': case 'p': case 'P': 535 switch (*++p) 536 { 537 case 't': *wp = TOP; break; 538 case 'm': *wp = MIDDLE; break; 539 case 'b': *wp = BOTTOM; break; 540 case 'B': *wp = BOTTOM_PLUS_ONE; break; 541 case 'j': *wp = sindex_from_sline(jump_sline); break; 542 default: *wp = TOP; p--; break; 543 } 544 } 545 return (p); 546 } 547 548 /* 549 * Construct a message based on a prototype string. 550 */ 551 public constant char * pr_expand(constant char *proto) 552 { 553 constant char *p; 554 char c; 555 int where; 556 557 if (*proto == '\0') 558 return (""); 559 560 mp = message; 561 longest_line = -1; 562 563 for (p = proto; *p != '\0'; p++) 564 { 565 switch (*p) 566 { 567 default: /* Just put the character in the message */ 568 ap_char(*p); 569 break; 570 case '\\': /* Backslash escapes the next character */ 571 if (p[1] != '\0') 572 ap_char(*++p); 573 break; 574 case '?': /* Conditional (IF) */ 575 if ((c = *++p) == '\0') 576 --p; 577 else 578 { 579 where = 0; 580 p = wherechar(p, &where); 581 if (!cond(c, where)) 582 p = skipcond(p); 583 } 584 break; 585 case ':': /* ELSE */ 586 p = skipcond(p); 587 break; 588 case '.': /* ENDIF */ 589 break; 590 case '%': /* Percent escape */ 591 if ((c = *++p) == '\0') 592 --p; 593 else 594 { 595 where = 0; 596 p = wherechar(p, &where); 597 protochar(c, where); 598 } 599 break; 600 } 601 } 602 603 if (mp == message) 604 return (""); 605 return (message); 606 } 607 608 /* 609 * Return a message suitable for printing by the "=" command. 610 */ 611 public constant char * eq_message(void) 612 { 613 return (pr_expand(eqproto)); 614 } 615 616 /* 617 * Return a prompt. 618 * This depends on the prompt type (SHORT, MEDIUM, LONG), etc. 619 * If we can't come up with an appropriate prompt, return NULL 620 * and the caller will prompt with a colon. 621 */ 622 public constant char * pr_string(void) 623 { 624 constant char *prompt; 625 int type; 626 627 type = (!less_is_more) ? pr_type : pr_type ? 0 : 1; 628 prompt = pr_expand((ch_getflags() & CH_HELPFILE) ? 629 hproto : prproto[type]); 630 new_file = FALSE; 631 return (prompt); 632 } 633 634 /* 635 * Return the end prompt string. 636 */ 637 public constant char * end_pr_string(void) 638 { 639 int type; 640 641 if (ch_getflags() & CH_HELPFILE) 642 return NULL; 643 type = (!less_is_more) ? pr_type : pr_type ? 0 : 1; 644 if (eprproto[type] == NULL) 645 return NULL; 646 return pr_expand(eprproto[type]); 647 } 648 649 /* 650 * Return a message suitable for printing while waiting in the F command. 651 */ 652 public constant char * wait_message(void) 653 { 654 return (pr_expand(wproto)); 655 } 656