prompt.c revision 60786
160786Sps/* 260786Sps * Copyright (C) 1984-2000 Mark Nudelman 360786Sps * 460786Sps * You may distribute under the terms of either the GNU General Public 560786Sps * License or the Less License, as specified in the README file. 660786Sps * 760786Sps * For more information about less, or for information on how to 860786Sps * contact the author, see the README file. 960786Sps */ 1060786Sps 1160786Sps 1260786Sps/* 1360786Sps * Prompting and other messages. 1460786Sps * There are three flavors of prompts, SHORT, MEDIUM and LONG, 1560786Sps * selected by the -m/-M options. 1660786Sps * There is also the "equals message", printed by the = command. 1760786Sps * A prompt is a message composed of various pieces, such as the 1860786Sps * name of the file being viewed, the percentage into the file, etc. 1960786Sps */ 2060786Sps 2160786Sps#include "less.h" 2260786Sps#include "position.h" 2360786Sps 2460786Spsextern int pr_type; 2560786Spsextern int hit_eof; 2660786Spsextern int new_file; 2760786Spsextern int sc_width; 2860786Spsextern int so_s_width, so_e_width; 2960786Spsextern int linenums; 3060786Spsextern int hshift; 3160786Spsextern int sc_height; 3260786Spsextern int jump_sline; 3360786Spsextern IFILE curr_ifile; 3460786Sps#if EDITOR 3560786Spsextern char *editor; 3660786Spsextern char *editproto; 3760786Sps#endif 3860786Sps 3960786Sps/* 4060786Sps * Prototypes for the three flavors of prompts. 4160786Sps * These strings are expanded by pr_expand(). 4260786Sps */ 4360786Spsstatic constant char s_proto[] = 4460786Sps "?n?f%f .?m(file %i of %m) ..?e(END) ?x- Next\\: %x..%t"; 4560786Spsstatic constant char m_proto[] = 4660786Sps "?n?f%f .?m(file %i of %m) ..?e(END) ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t"; 4760786Spsstatic constant char M_proto[] = 4860786Sps "?f%f .?n?m(file %i of %m) ..?ltlines %lt-%lb?L/%L. :byte %bB?s/%s. .?e(END) ?x- Next\\: %x.:?pB%pB\\%..%t"; 4960786Spsstatic constant char e_proto[] = 5060786Sps "?f%f .?m(file %i of %m) .?ltlines %lt-%lb?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t"; 5160786Spsstatic constant char h_proto[] = 5260786Sps "HELP -- ?eEND -- Press g to see it again:Press RETURN for more., or q when done"; 5360786Sps 5460786Spspublic char *prproto[3]; 5560786Spspublic char constant *eqproto = e_proto; 5660786Spspublic char constant *hproto = h_proto; 5760786Sps 5860786Spsstatic char message[PROMPT_SIZE]; 5960786Spsstatic char *mp; 6060786Sps 6160786Sps/* 6260786Sps * Initialize the prompt prototype strings. 6360786Sps */ 6460786Sps public void 6560786Spsinit_prompt() 6660786Sps{ 6760786Sps prproto[0] = save(s_proto); 6860786Sps prproto[1] = save(m_proto); 6960786Sps prproto[2] = save(M_proto); 7060786Sps eqproto = save(e_proto); 7160786Sps hproto = save(h_proto); 7260786Sps} 7360786Sps 7460786Sps/* 7560786Sps * Append a string to the end of the message. 7660786Sps */ 7760786Sps static void 7860786Spsap_str(s) 7960786Sps char *s; 8060786Sps{ 8160786Sps int len; 8260786Sps 8360786Sps len = strlen(s); 8460786Sps if (mp + len >= message + PROMPT_SIZE) 8560786Sps len = message + PROMPT_SIZE - mp - 1; 8660786Sps strncpy(mp, s, len); 8760786Sps mp += len; 8860786Sps *mp = '\0'; 8960786Sps} 9060786Sps 9160786Sps/* 9260786Sps * Append a character to the end of the message. 9360786Sps */ 9460786Sps static void 9560786Spsap_char(c) 9660786Sps char c; 9760786Sps{ 9860786Sps char buf[2]; 9960786Sps 10060786Sps buf[0] = c; 10160786Sps buf[1] = '\0'; 10260786Sps ap_str(buf); 10360786Sps} 10460786Sps 10560786Sps/* 10660786Sps * Append a POSITION (as a decimal integer) to the end of the message. 10760786Sps */ 10860786Sps static void 10960786Spsap_pos(pos) 11060786Sps POSITION pos; 11160786Sps{ 11260786Sps char buf[MAX_PRINT_POSITION]; 11360786Sps 11460786Sps sprintf(buf, PR_POSITION, pos); 11560786Sps ap_str(buf); 11660786Sps} 11760786Sps 11860786Sps/* 11960786Sps * Append an integer to the end of the message. 12060786Sps */ 12160786Sps static void 12260786Spsap_int(n) 12360786Sps int n; 12460786Sps{ 12560786Sps char buf[MAX_PRINT_INT]; 12660786Sps 12760786Sps sprintf(buf, "%d", n); 12860786Sps ap_str(buf); 12960786Sps} 13060786Sps 13160786Sps/* 13260786Sps * Append a question mark to the end of the message. 13360786Sps */ 13460786Sps static void 13560786Spsap_quest() 13660786Sps{ 13760786Sps ap_str("?"); 13860786Sps} 13960786Sps 14060786Sps/* 14160786Sps * Return the "current" byte offset in the file. 14260786Sps */ 14360786Sps static POSITION 14460786Spscurr_byte(where) 14560786Sps int where; 14660786Sps{ 14760786Sps POSITION pos; 14860786Sps 14960786Sps pos = position(where); 15060786Sps while (pos == NULL_POSITION && where >= 0 && where < sc_height) 15160786Sps pos = position(++where); 15260786Sps if (pos == NULL_POSITION) 15360786Sps pos = ch_length(); 15460786Sps return (pos); 15560786Sps} 15660786Sps 15760786Sps/* 15860786Sps * Return the value of a prototype conditional. 15960786Sps * A prototype string may include conditionals which consist of a 16060786Sps * question mark followed by a single letter. 16160786Sps * Here we decode that letter and return the appropriate boolean value. 16260786Sps */ 16360786Sps static int 16460786Spscond(c, where) 16560786Sps char c; 16660786Sps int where; 16760786Sps{ 16860786Sps POSITION len; 16960786Sps 17060786Sps switch (c) 17160786Sps { 17260786Sps case 'a': /* Anything in the message yet? */ 17360786Sps return (mp > message); 17460786Sps case 'b': /* Current byte offset known? */ 17560786Sps return (curr_byte(where) != NULL_POSITION); 17660786Sps case 'c': 17760786Sps return (hshift != 0); 17860786Sps case 'e': /* At end of file? */ 17960786Sps return (hit_eof); 18060786Sps case 'f': /* Filename known? */ 18160786Sps return (strcmp(get_filename(curr_ifile), "-") != 0); 18260786Sps case 'l': /* Line number known? */ 18360786Sps case 'd': /* Same as l */ 18460786Sps return (linenums); 18560786Sps case 'L': /* Final line number known? */ 18660786Sps case 'D': /* Same as L */ 18760786Sps return (linenums && ch_length() != NULL_POSITION); 18860786Sps case 'm': /* More than one file? */ 18960786Sps return (nifile() > 1); 19060786Sps case 'n': /* First prompt in a new file? */ 19160786Sps return (new_file); 19260786Sps case 'p': /* Percent into file (bytes) known? */ 19360786Sps return (curr_byte(where) != NULL_POSITION && 19460786Sps ch_length() > 0); 19560786Sps case 'P': /* Percent into file (lines) known? */ 19660786Sps return (currline(where) != 0 && 19760786Sps (len = ch_length()) > 0 && 19860786Sps find_linenum(len) != 0); 19960786Sps case 's': /* Size of file known? */ 20060786Sps case 'B': 20160786Sps return (ch_length() != NULL_POSITION); 20260786Sps case 'x': /* Is there a "next" file? */ 20360786Sps return (next_ifile(curr_ifile) != NULL_IFILE); 20460786Sps } 20560786Sps return (0); 20660786Sps} 20760786Sps 20860786Sps/* 20960786Sps * Decode a "percent" prototype character. 21060786Sps * A prototype string may include various "percent" escapes; 21160786Sps * that is, a percent sign followed by a single letter. 21260786Sps * Here we decode that letter and take the appropriate action, 21360786Sps * usually by appending something to the message being built. 21460786Sps */ 21560786Sps static void 21660786Spsprotochar(c, where, iseditproto) 21760786Sps int c; 21860786Sps int where; 21960786Sps int iseditproto; 22060786Sps{ 22160786Sps POSITION pos; 22260786Sps POSITION len; 22360786Sps int n; 22460786Sps IFILE h; 22560786Sps char *s; 22660786Sps char *escs; 22760786Sps 22860786Sps switch (c) 22960786Sps { 23060786Sps case 'b': /* Current byte offset */ 23160786Sps pos = curr_byte(where); 23260786Sps if (pos != NULL_POSITION) 23360786Sps ap_pos(pos); 23460786Sps else 23560786Sps ap_quest(); 23660786Sps break; 23760786Sps case 'c': 23860786Sps ap_int(hshift); 23960786Sps break; 24060786Sps case 'd': /* Current page number */ 24160786Sps n = currline(where); 24260786Sps if (n > 0 && sc_height > 1) 24360786Sps ap_int(((n - 1) / (sc_height - 1)) + 1); 24460786Sps else 24560786Sps ap_quest(); 24660786Sps break; 24760786Sps case 'D': /* Last page number */ 24860786Sps len = ch_length(); 24960786Sps if (len == NULL_POSITION || len == ch_zero() || 25060786Sps (n = find_linenum(len)) <= 0) 25160786Sps ap_quest(); 25260786Sps else 25360786Sps ap_int(((n - 1) / (sc_height - 1)) + 1); 25460786Sps break; 25560786Sps#if EDITOR 25660786Sps case 'E': /* Editor name */ 25760786Sps ap_str(editor); 25860786Sps break; 259#endif 260 case 'f': /* File name */ 261 s = unquote_file(get_filename(curr_ifile)); 262 /* 263 * If we are expanding editproto then we escape metachars. 264 * This allows us to run the editor on files with funny names. 265 */ 266 if (iseditproto && (escs = esc_metachars(s)) != NULL) 267 { 268 free(s); 269 s = escs; 270 } 271 ap_str(s); 272 free(s); 273 break; 274 case 'i': /* Index into list of files */ 275 ap_int(get_index(curr_ifile)); 276 break; 277 case 'l': /* Current line number */ 278 n = currline(where); 279 if (n != 0) 280 ap_int(n); 281 else 282 ap_quest(); 283 break; 284 case 'L': /* Final line number */ 285 len = ch_length(); 286 if (len == NULL_POSITION || len == ch_zero() || 287 (n = find_linenum(len)) <= 0) 288 ap_quest(); 289 else 290 ap_int(n-1); 291 break; 292 case 'm': /* Number of files */ 293 ap_int(nifile()); 294 break; 295 case 'p': /* Percent into file (bytes) */ 296 pos = curr_byte(where); 297 len = ch_length(); 298 if (pos != NULL_POSITION && len > 0) 299 ap_int(percentage(pos,len)); 300 else 301 ap_quest(); 302 break; 303 case 'P': /* Percent into file (lines) */ 304 pos = (POSITION) currline(where); 305 if (pos == 0 || 306 (len = ch_length()) == NULL_POSITION || len == ch_zero() || 307 (n = find_linenum(len)) <= 0) 308 ap_quest(); 309 else 310 ap_int(percentage(pos, (POSITION)n)); 311 break; 312 case 's': /* Size of file */ 313 case 'B': 314 len = ch_length(); 315 if (len != NULL_POSITION) 316 ap_pos(len); 317 else 318 ap_quest(); 319 break; 320 case 't': /* Truncate trailing spaces in the message */ 321 while (mp > message && mp[-1] == ' ') 322 mp--; 323 break; 324 case 'x': /* Name of next file */ 325 h = next_ifile(curr_ifile); 326 if (h != NULL_IFILE) 327 { 328 s = unquote_file(get_filename(h)); 329 ap_str(s); 330 free(s); 331 } else 332 ap_quest(); 333 break; 334 } 335} 336 337/* 338 * Skip a false conditional. 339 * When a false condition is found (either a false IF or the ELSE part 340 * of a true IF), this routine scans the prototype string to decide 341 * where to resume parsing the string. 342 * We must keep track of nested IFs and skip them properly. 343 */ 344 static char * 345skipcond(p) 346 register char *p; 347{ 348 register int iflevel; 349 350 /* 351 * We came in here after processing a ? or :, 352 * so we start nested one level deep. 353 */ 354 iflevel = 1; 355 356 for (;;) switch (*++p) 357 { 358 case '?': 359 /* 360 * Start of a nested IF. 361 */ 362 iflevel++; 363 break; 364 case ':': 365 /* 366 * Else. 367 * If this matches the IF we came in here with, 368 * then we're done. 369 */ 370 if (iflevel == 1) 371 return (p); 372 break; 373 case '.': 374 /* 375 * Endif. 376 * If this matches the IF we came in here with, 377 * then we're done. 378 */ 379 if (--iflevel == 0) 380 return (p); 381 break; 382 case '\\': 383 /* 384 * Backslash escapes the next character. 385 */ 386 ++p; 387 break; 388 case '\0': 389 /* 390 * Whoops. Hit end of string. 391 * This is a malformed conditional, but just treat it 392 * as if all active conditionals ends here. 393 */ 394 return (p-1); 395 } 396 /*NOTREACHED*/ 397} 398 399/* 400 * Decode a char that represents a position on the screen. 401 */ 402 static char * 403wherechar(p, wp) 404 char *p; 405 int *wp; 406{ 407 switch (*p) 408 { 409 case 'b': case 'd': case 'l': case 'p': case 'P': 410 switch (*++p) 411 { 412 case 't': *wp = TOP; break; 413 case 'm': *wp = MIDDLE; break; 414 case 'b': *wp = BOTTOM; break; 415 case 'B': *wp = BOTTOM_PLUS_ONE; break; 416 case 'j': *wp = adjsline(jump_sline); break; 417 default: *wp = TOP; p--; break; 418 } 419 } 420 return (p); 421} 422 423/* 424 * Construct a message based on a prototype string. 425 */ 426 public char * 427pr_expand(proto, maxwidth) 428 char *proto; 429 int maxwidth; 430{ 431 register char *p; 432 register int c; 433 int where; 434 435 mp = message; 436 437 if (*proto == '\0') 438 return (""); 439 440 for (p = proto; *p != '\0'; p++) 441 { 442 switch (*p) 443 { 444 default: /* Just put the character in the message */ 445 ap_char(*p); 446 break; 447 case '\\': /* Backslash escapes the next character */ 448 p++; 449 ap_char(*p); 450 break; 451 case '?': /* Conditional (IF) */ 452 if ((c = *++p) == '\0') 453 --p; 454 else 455 { 456 where = 0; 457 p = wherechar(p, &where); 458 if (!cond(c, where)) 459 p = skipcond(p); 460 } 461 break; 462 case ':': /* ELSE */ 463 p = skipcond(p); 464 break; 465 case '.': /* ENDIF */ 466 break; 467 case '%': /* Percent escape */ 468 if ((c = *++p) == '\0') 469 --p; 470 else 471 { 472 where = 0; 473 p = wherechar(p, &where); 474 protochar(c, where, 475#if EDITOR 476 (proto == editproto)); 477#else 478 0); 479#endif 480 481 } 482 break; 483 } 484 } 485 486 new_file = 0; 487 if (mp == message) 488 return (NULL); 489 if (maxwidth > 0 && mp >= message + maxwidth) 490 { 491 /* 492 * Message is too long. 493 * Return just the final portion of it. 494 */ 495 return (mp - maxwidth); 496 } 497 return (message); 498} 499 500/* 501 * Return a message suitable for printing by the "=" command. 502 */ 503 public char * 504eq_message() 505{ 506 return (pr_expand(eqproto, 0)); 507} 508 509/* 510 * Return a prompt. 511 * This depends on the prompt type (SHORT, MEDIUM, LONG), etc. 512 * If we can't come up with an appropriate prompt, return NULL 513 * and the caller will prompt with a colon. 514 */ 515 public char * 516pr_string() 517{ 518 if (ch_getflags() & CH_HELPFILE) 519 return (pr_expand(hproto, sc_width-so_s_width-so_e_width-2)); 520 return (pr_expand(prproto[pr_type], sc_width-so_s_width-so_e_width-2)); 521} 522