prompt.c revision 60786
1/* 2 * Copyright (C) 1984-2000 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 about less, or for information on how to 8 * contact the author, see the README file. 9 */ 10 11 12/* 13 * Prompting and other messages. 14 * There are three flavors of prompts, SHORT, MEDIUM and LONG, 15 * selected by the -m/-M options. 16 * There is also the "equals message", printed by the = command. 17 * A prompt is a message composed of various pieces, such as the 18 * name of the file being viewed, the percentage into the file, etc. 19 */ 20 21#include "less.h" 22#include "position.h" 23 24extern int pr_type; 25extern int hit_eof; 26extern int new_file; 27extern int sc_width; 28extern int so_s_width, so_e_width; 29extern int linenums; 30extern int hshift; 31extern int sc_height; 32extern int jump_sline; 33extern IFILE curr_ifile; 34#if EDITOR 35extern char *editor; 36extern char *editproto; 37#endif 38 39/* 40 * Prototypes for the three flavors of prompts. 41 * These strings are expanded by pr_expand(). 42 */ 43static constant char s_proto[] = 44 "?n?f%f .?m(file %i of %m) ..?e(END) ?x- Next\\: %x..%t"; 45static constant char m_proto[] = 46 "?n?f%f .?m(file %i of %m) ..?e(END) ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t"; 47static constant char M_proto[] = 48 "?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"; 49static constant char e_proto[] = 50 "?f%f .?m(file %i of %m) .?ltlines %lt-%lb?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t"; 51static constant char h_proto[] = 52 "HELP -- ?eEND -- Press g to see it again:Press RETURN for more., or q when done"; 53 54public char *prproto[3]; 55public char constant *eqproto = e_proto; 56public char constant *hproto = h_proto; 57 58static char message[PROMPT_SIZE]; 59static char *mp; 60 61/* 62 * Initialize the prompt prototype strings. 63 */ 64 public void 65init_prompt() 66{ 67 prproto[0] = save(s_proto); 68 prproto[1] = save(m_proto); 69 prproto[2] = save(M_proto); 70 eqproto = save(e_proto); 71 hproto = save(h_proto); 72} 73 74/* 75 * Append a string to the end of the message. 76 */ 77 static void 78ap_str(s) 79 char *s; 80{ 81 int len; 82 83 len = strlen(s); 84 if (mp + len >= message + PROMPT_SIZE) 85 len = message + PROMPT_SIZE - mp - 1; 86 strncpy(mp, s, len); 87 mp += len; 88 *mp = '\0'; 89} 90 91/* 92 * Append a character to the end of the message. 93 */ 94 static void 95ap_char(c) 96 char c; 97{ 98 char buf[2]; 99 100 buf[0] = c; 101 buf[1] = '\0'; 102 ap_str(buf); 103} 104 105/* 106 * Append a POSITION (as a decimal integer) to the end of the message. 107 */ 108 static void 109ap_pos(pos) 110 POSITION pos; 111{ 112 char buf[MAX_PRINT_POSITION]; 113 114 sprintf(buf, PR_POSITION, pos); 115 ap_str(buf); 116} 117 118/* 119 * Append an integer to the end of the message. 120 */ 121 static void 122ap_int(n) 123 int n; 124{ 125 char buf[MAX_PRINT_INT]; 126 127 sprintf(buf, "%d", n); 128 ap_str(buf); 129} 130 131/* 132 * Append a question mark to the end of the message. 133 */ 134 static void 135ap_quest() 136{ 137 ap_str("?"); 138} 139 140/* 141 * Return the "current" byte offset in the file. 142 */ 143 static POSITION 144curr_byte(where) 145 int where; 146{ 147 POSITION pos; 148 149 pos = position(where); 150 while (pos == NULL_POSITION && where >= 0 && where < sc_height) 151 pos = position(++where); 152 if (pos == NULL_POSITION) 153 pos = ch_length(); 154 return (pos); 155} 156 157/* 158 * Return the value of a prototype conditional. 159 * A prototype string may include conditionals which consist of a 160 * question mark followed by a single letter. 161 * Here we decode that letter and return the appropriate boolean value. 162 */ 163 static int 164cond(c, where) 165 char c; 166 int where; 167{ 168 POSITION len; 169 170 switch (c) 171 { 172 case 'a': /* Anything in the message yet? */ 173 return (mp > message); 174 case 'b': /* Current byte offset known? */ 175 return (curr_byte(where) != NULL_POSITION); 176 case 'c': 177 return (hshift != 0); 178 case 'e': /* At end of file? */ 179 return (hit_eof); 180 case 'f': /* Filename known? */ 181 return (strcmp(get_filename(curr_ifile), "-") != 0); 182 case 'l': /* Line number known? */ 183 case 'd': /* Same as l */ 184 return (linenums); 185 case 'L': /* Final line number known? */ 186 case 'D': /* Same as L */ 187 return (linenums && ch_length() != NULL_POSITION); 188 case 'm': /* More than one file? */ 189 return (nifile() > 1); 190 case 'n': /* First prompt in a new file? */ 191 return (new_file); 192 case 'p': /* Percent into file (bytes) known? */ 193 return (curr_byte(where) != NULL_POSITION && 194 ch_length() > 0); 195 case 'P': /* Percent into file (lines) known? */ 196 return (currline(where) != 0 && 197 (len = ch_length()) > 0 && 198 find_linenum(len) != 0); 199 case 's': /* Size of file known? */ 200 case 'B': 201 return (ch_length() != NULL_POSITION); 202 case 'x': /* Is there a "next" file? */ 203 return (next_ifile(curr_ifile) != NULL_IFILE); 204 } 205 return (0); 206} 207 208/* 209 * Decode a "percent" prototype character. 210 * A prototype string may include various "percent" escapes; 211 * that is, a percent sign followed by a single letter. 212 * Here we decode that letter and take the appropriate action, 213 * usually by appending something to the message being built. 214 */ 215 static void 216protochar(c, where, iseditproto) 217 int c; 218 int where; 219 int iseditproto; 220{ 221 POSITION pos; 222 POSITION len; 223 int n; 224 IFILE h; 225 char *s; 226 char *escs; 227 228 switch (c) 229 { 230 case 'b': /* Current byte offset */ 231 pos = curr_byte(where); 232 if (pos != NULL_POSITION) 233 ap_pos(pos); 234 else 235 ap_quest(); 236 break; 237 case 'c': 238 ap_int(hshift); 239 break; 240 case 'd': /* Current page number */ 241 n = currline(where); 242 if (n > 0 && sc_height > 1) 243 ap_int(((n - 1) / (sc_height - 1)) + 1); 244 else 245 ap_quest(); 246 break; 247 case 'D': /* Last page number */ 248 len = ch_length(); 249 if (len == NULL_POSITION || len == ch_zero() || 250 (n = find_linenum(len)) <= 0) 251 ap_quest(); 252 else 253 ap_int(((n - 1) / (sc_height - 1)) + 1); 254 break; 255#if EDITOR 256 case 'E': /* Editor name */ 257 ap_str(editor); 258 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