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