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