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