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