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