Annotation of src/usr.bin/less/prompt.c, Revision 1.21
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.16 tedu 78: prproto[0] = estrdup(s_proto);
79: prproto[1] = estrdup(less_is_more ? more_proto : m_proto);
80: prproto[2] = estrdup(less_is_more ? more_M_proto : M_proto);
81: eqproto = estrdup(e_proto);
82: hproto = estrdup(h_proto);
83: wproto = estrdup(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.20 mmcc 123: postoa(pos, buf, sizeof(buf));
1.7 millert 124: ap_str(buf);
1.1 etheisen 125: }
126:
127: /*
1.7 millert 128: * Append an integer to the end of the message.
1.1 etheisen 129: */
1.14 nicm 130: static void
131: ap_int(int num)
1.1 etheisen 132: {
1.7 millert 133: char buf[INT_STRLEN_BOUND(num) + 2];
134:
1.14 nicm 135: inttoa(num, buf, sizeof buf);
1.7 millert 136: ap_str(buf);
1.1 etheisen 137: }
138:
139: /*
140: * Append a question mark to the end of the message.
141: */
1.14 nicm 142: static void
143: ap_quest(void)
1.1 etheisen 144: {
1.7 millert 145: ap_str("?");
1.1 etheisen 146: }
147:
148: /*
149: * Return the "current" byte offset in the file.
150: */
1.14 nicm 151: static off_t
152: curr_byte(int where)
1.1 etheisen 153: {
1.14 nicm 154: off_t pos;
1.1 etheisen 155:
156: pos = position(where);
1.14 nicm 157: while (pos == -1 && where >= 0 && where < sc_height-1)
1.1 etheisen 158: pos = position(++where);
1.14 nicm 159: if (pos == -1)
1.1 etheisen 160: pos = ch_length();
161: return (pos);
162: }
163:
164: /*
165: * Return the value of a prototype conditional.
1.14 nicm 166: * A prototype string may include conditionals which consist of a
1.1 etheisen 167: * question mark followed by a single letter.
168: * Here we decode that letter and return the appropriate boolean value.
169: */
1.14 nicm 170: static int
171: cond(char c, int where)
1.1 etheisen 172: {
1.14 nicm 173: off_t len;
1.7 millert 174:
1.14 nicm 175: switch (c) {
1.1 etheisen 176: case 'a': /* Anything in the message yet? */
1.21 ! mmcc 177: return (*message != '\0');
1.1 etheisen 178: case 'b': /* Current byte offset known? */
1.14 nicm 179: return (curr_byte(where) != -1);
1.7 millert 180: case 'c':
181: return (hshift != 0);
1.1 etheisen 182: case 'e': /* At end of file? */
1.12 shadchin 183: return (eof_displayed());
1.1 etheisen 184: case 'f': /* Filename known? */
185: return (strcmp(get_filename(curr_ifile), "-") != 0);
186: case 'l': /* Line number known? */
1.7 millert 187: case 'd': /* Same as l */
1.1 etheisen 188: return (linenums);
189: case 'L': /* Final line number known? */
1.12 shadchin 190: case 'D': /* Final page number known? */
1.14 nicm 191: return (linenums && ch_length() != -1);
1.1 etheisen 192: case 'm': /* More than one file? */
1.7 millert 193: return (ntags() ? (ntags() > 1) : (nifile() > 1));
1.1 etheisen 194: case 'n': /* First prompt in a new file? */
1.7 millert 195: return (ntags() ? 1 : new_file);
196: case 'p': /* Percent into file (bytes) known? */
1.14 nicm 197: return (curr_byte(where) != -1 && ch_length() > 0);
1.7 millert 198: case 'P': /* Percent into file (lines) known? */
199: return (currline(where) != 0 &&
1.14 nicm 200: (len = ch_length()) > 0 && find_linenum(len) != 0);
1.1 etheisen 201: case 's': /* Size of file known? */
202: case 'B':
1.14 nicm 203: return (ch_length() != -1);
1.1 etheisen 204: case 'x': /* Is there a "next" file? */
1.7 millert 205: if (ntags())
206: return (0);
1.17 deraadt 207: return (next_ifile(curr_ifile) != NULL);
1.1 etheisen 208: }
209: return (0);
210: }
211:
212: /*
213: * Decode a "percent" prototype character.
214: * A prototype string may include various "percent" escapes;
215: * that is, a percent sign followed by a single letter.
216: * Here we decode that letter and take the appropriate action,
217: * usually by appending something to the message being built.
218: */
1.14 nicm 219: static void
220: protochar(int c, int where)
1.1 etheisen 221: {
1.14 nicm 222: off_t pos;
223: off_t len;
1.1 etheisen 224: int n;
1.18 mmcc 225: off_t linenum;
226: off_t last_linenum;
1.1 etheisen 227: IFILE h;
228:
1.14 nicm 229: #undef PAGE_NUM
230: #define PAGE_NUM(linenum) ((((linenum) - 1) / (sc_height - 1)) + 1)
1.12 shadchin 231:
1.14 nicm 232: switch (c) {
1.1 etheisen 233: case 'b': /* Current byte offset */
234: pos = curr_byte(where);
1.14 nicm 235: if (pos != -1)
1.1 etheisen 236: ap_pos(pos);
237: else
238: ap_quest();
239: break;
1.7 millert 240: case 'c':
241: ap_int(hshift);
242: break;
243: case 'd': /* Current page number */
244: linenum = currline(where);
245: if (linenum > 0 && sc_height > 1)
1.20 mmcc 246: ap_pos(PAGE_NUM(linenum));
1.7 millert 247: else
248: ap_quest();
249: break;
1.12 shadchin 250: case 'D': /* Final page number */
251: /* Find the page number of the last byte in the file (len-1). */
1.7 millert 252: len = ch_length();
1.14 nicm 253: if (len == -1) {
1.7 millert 254: ap_quest();
1.14 nicm 255: } else if (len == 0) {
1.12 shadchin 256: /* An empty file has no pages. */
1.20 mmcc 257: ap_pos(0);
1.14 nicm 258: } else {
1.12 shadchin 259: linenum = find_linenum(len - 1);
260: if (linenum <= 0)
261: ap_quest();
1.14 nicm 262: else
1.20 mmcc 263: ap_pos(PAGE_NUM(linenum));
1.12 shadchin 264: }
1.7 millert 265: break;
1.1 etheisen 266: case 'E': /* Editor name */
267: ap_str(editor);
268: break;
269: case 'f': /* File name */
270: ap_str(get_filename(curr_ifile));
271: break;
1.12 shadchin 272: case 'F': /* Last component of file name */
273: ap_str(last_component(get_filename(curr_ifile)));
274: break;
1.1 etheisen 275: case 'i': /* Index into list of files */
1.7 millert 276: if (ntags())
277: ap_int(curr_tag());
278: else
279: ap_int(get_index(curr_ifile));
1.1 etheisen 280: break;
281: case 'l': /* Current line number */
1.7 millert 282: linenum = currline(where);
283: if (linenum != 0)
1.20 mmcc 284: ap_pos(linenum);
1.1 etheisen 285: else
286: ap_quest();
287: break;
288: case 'L': /* Final line number */
289: len = ch_length();
1.14 nicm 290: if (len == -1 || len == ch_zero() ||
1.7 millert 291: (linenum = find_linenum(len)) <= 0)
1.1 etheisen 292: ap_quest();
293: else
1.20 mmcc 294: ap_pos(linenum-1);
1.1 etheisen 295: break;
296: case 'm': /* Number of files */
1.7 millert 297: n = ntags();
298: if (n)
299: ap_int(n);
300: else
301: ap_int(nifile());
1.1 etheisen 302: break;
1.7 millert 303: case 'p': /* Percent into file (bytes) */
1.1 etheisen 304: pos = curr_byte(where);
305: len = ch_length();
1.14 nicm 306: if (pos != -1 && len > 0)
307: ap_int(percentage(pos, len));
1.1 etheisen 308: else
309: ap_quest();
310: break;
1.7 millert 311: case 'P': /* Percent into file (lines) */
312: linenum = currline(where);
313: if (linenum == 0 ||
1.14 nicm 314: (len = ch_length()) == -1 || len == ch_zero() ||
1.7 millert 315: (last_linenum = find_linenum(len)) <= 0)
316: ap_quest();
317: else
318: ap_int(percentage(linenum, last_linenum));
319: break;
1.1 etheisen 320: case 's': /* Size of file */
321: case 'B':
322: len = ch_length();
1.14 nicm 323: if (len != -1)
1.1 etheisen 324: ap_pos(len);
325: else
326: ap_quest();
327: break;
328: case 't': /* Truncate trailing spaces in the message */
329: while (mp > message && mp[-1] == ' ')
330: mp--;
1.10 jasper 331: *mp = '\0';
1.1 etheisen 332: break;
1.7 millert 333: case 'T': /* Type of list */
334: if (ntags())
335: ap_str("tag");
336: else
337: ap_str("file");
338: break;
1.1 etheisen 339: case 'x': /* Name of next file */
340: h = next_ifile(curr_ifile);
1.17 deraadt 341: if (h != NULL)
1.1 etheisen 342: ap_str(get_filename(h));
343: else
344: ap_quest();
345: break;
346: }
347: }
348:
349: /*
350: * Skip a false conditional.
1.14 nicm 351: * When a false condition is found (either a false IF or the ELSE part
1.1 etheisen 352: * of a true IF), this routine scans the prototype string to decide
353: * where to resume parsing the string.
354: * We must keep track of nested IFs and skip them properly.
355: */
1.14 nicm 356: static const char *
357: skipcond(const char *p)
1.1 etheisen 358: {
1.14 nicm 359: int iflevel;
1.1 etheisen 360:
361: /*
362: * We came in here after processing a ? or :,
363: * so we start nested one level deep.
364: */
365: iflevel = 1;
366:
1.14 nicm 367: for (;;) {
368: switch (*++p) {
369: case '?':
370: /*
371: * Start of a nested IF.
372: */
373: iflevel++;
374: break;
375: case ':':
376: /*
377: * Else.
378: * If this matches the IF we came in here with,
379: * then we're done.
380: */
381: if (iflevel == 1)
382: return (p);
383: break;
384: case '.':
385: /*
386: * Endif.
387: * If this matches the IF we came in here with,
388: * then we're done.
389: */
390: if (--iflevel == 0)
391: return (p);
392: break;
393: case '\\':
394: /*
395: * Backslash escapes the next character.
396: */
397: ++p;
398: break;
399: case '\0':
400: /*
401: * Whoops. Hit end of string.
402: * This is a malformed conditional, but just treat it
403: * as if all active conditionals ends here.
404: */
405: return (p-1);
406: }
1.1 etheisen 407: }
408: }
409:
1.7 millert 410: /*
411: * Decode a char that represents a position on the screen.
412: */
1.14 nicm 413: static const char *
414: wherechar(const char *p, int *wp)
1.1 etheisen 415: {
1.14 nicm 416: switch (*p) {
1.7 millert 417: case 'b': case 'd': case 'l': case 'p': case 'P':
1.14 nicm 418: switch (*++p) {
1.1 etheisen 419: case 't': *wp = TOP; break;
420: case 'm': *wp = MIDDLE; break;
421: case 'b': *wp = BOTTOM; break;
422: case 'B': *wp = BOTTOM_PLUS_ONE; break;
423: case 'j': *wp = adjsline(jump_sline); break;
424: default: *wp = TOP; p--; break;
425: }
426: }
427: return (p);
428: }
429:
430: /*
431: * Construct a message based on a prototype string.
432: */
1.14 nicm 433: char *
434: pr_expand(const char *proto, int maxwidth)
1.1 etheisen 435: {
1.14 nicm 436: const char *p;
437: int c;
1.1 etheisen 438: int where;
439:
440: mp = message;
441:
442: if (*proto == '\0')
443: return ("");
444:
1.14 nicm 445: for (p = proto; *p != '\0'; p++) {
446: switch (*p) {
1.1 etheisen 447: default: /* Just put the character in the message */
1.7 millert 448: ap_char(*p);
1.1 etheisen 449: break;
450: case '\\': /* Backslash escapes the next character */
451: p++;
1.7 millert 452: ap_char(*p);
1.1 etheisen 453: break;
454: case '?': /* Conditional (IF) */
1.14 nicm 455: if ((c = *++p) == '\0') {
1.1 etheisen 456: --p;
1.14 nicm 457: } else {
1.7 millert 458: where = 0;
1.1 etheisen 459: p = wherechar(p, &where);
460: if (!cond(c, where))
461: p = skipcond(p);
462: }
463: break;
464: case ':': /* ELSE */
465: p = skipcond(p);
466: break;
467: case '.': /* ENDIF */
468: break;
469: case '%': /* Percent escape */
1.14 nicm 470: if ((c = *++p) == '\0') {
1.1 etheisen 471: --p;
1.14 nicm 472: } else {
1.7 millert 473: where = 0;
1.1 etheisen 474: p = wherechar(p, &where);
1.14 nicm 475: protochar(c, where);
1.1 etheisen 476: }
477: break;
478: }
479: }
480:
1.21 ! mmcc 481: if (*message == '\0')
1.12 shadchin 482: return ("");
1.14 nicm 483: if (maxwidth > 0 && mp >= message + maxwidth) {
1.1 etheisen 484: /*
485: * Message is too long.
486: * Return just the final portion of it.
487: */
488: return (mp - maxwidth);
489: }
490: return (message);
491: }
492:
493: /*
494: * Return a message suitable for printing by the "=" command.
495: */
1.14 nicm 496: char *
497: eq_message(void)
1.1 etheisen 498: {
499: return (pr_expand(eqproto, 0));
500: }
501:
502: /*
503: * Return a prompt.
504: * This depends on the prompt type (SHORT, MEDIUM, LONG), etc.
505: * If we can't come up with an appropriate prompt, return NULL
506: * and the caller will prompt with a colon.
507: */
1.14 nicm 508: char *
509: prompt_string(void)
1.1 etheisen 510: {
1.7 millert 511: char *prompt;
1.12 shadchin 512: int type;
1.7 millert 513:
1.14 nicm 514: type = pr_type;
1.12 shadchin 515: prompt = pr_expand((ch_getflags() & CH_HELPFILE) ?
1.14 nicm 516: hproto : prproto[type], sc_width-so_s_width-so_e_width-2);
1.7 millert 517: new_file = 0;
518: return (prompt);
519: }
520:
521: /*
522: * Return a message suitable for printing while waiting in the F command.
523: */
1.14 nicm 524: char *
525: wait_message(void)
1.7 millert 526: {
527: return (pr_expand(wproto, sc_width-so_s_width-so_e_width-2));
1.1 etheisen 528: }