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