Annotation of src/usr.bin/less/prompt.c, Revision 1.2
1.1 etheisen 1: /*
2: * Copyright (c) 1984,1985,1989,1994,1995 Mark Nudelman
3: * All rights reserved.
4: *
5: * Redistribution and use in source and binary forms, with or without
6: * modification, are permitted provided that the following conditions
7: * are met:
8: * 1. Redistributions of source code must retain the above copyright
9: * notice, this list of conditions and the following disclaimer.
10: * 2. Redistributions in binary form must reproduce the above copyright
11: * notice in the documentation and/or other materials provided with
12: * the distribution.
13: *
14: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
15: * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE
18: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
20: * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
21: * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25: */
26:
27:
28: /*
29: * Prompting and other messages.
30: * There are three flavors of prompts, SHORT, MEDIUM and LONG,
31: * selected by the -m/-M options.
32: * There is also the "equals message", printed by the = command.
33: * A prompt is a message composed of various pieces, such as the
34: * name of the file being viewed, the percentage into the file, etc.
35: */
36:
37: #include "less.h"
38: #include "position.h"
39:
40: extern int pr_type;
41: extern int hit_eof;
42: extern int new_file;
43: extern int sc_width;
44: extern int so_s_width, so_e_width;
45: extern int linenums;
46: extern int sc_height;
47: extern int jump_sline;
48: extern IFILE curr_ifile;
49: #if EDITOR
50: extern char *editor;
51: #endif
52:
53: /*
54: * Prototypes for the three flavors of prompts.
55: * These strings are expanded by pr_expand().
56: */
57: static char s_proto[] =
58: "?n?f%f .?m(file %i of %m) ..?e(END) ?x- Next\\: %x..%t";
59: static char m_proto[] =
1.2 ! etheisen 60: "?f%f .?m(file %i of %m) .?e(END) ?x- Next\\: %x.:(?pB%pB\\%:byte %bB?s/%s..).%t";
1.1 etheisen 61: static char M_proto[] =
62: "?f%f .?n?m(file %i of %m) ..?ltline %lt?L/%L. :byte %bB?s/%s. .?e(END) ?x- Next\\: %x.:?pB%pB\\%..%t";
63: static char e_proto[] =
64: "?f%f .?m(file %i of %m) .?ltline %lt?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t";
65:
66: public char *prproto[3];
67: public char *eqproto = e_proto;
68:
69: static char message[250];
70: static char *mp;
71:
72: /*
73: * Initialize the prompt prototype strings.
74: */
75: public void
76: init_prompt()
77: {
78: prproto[0] = save(s_proto);
79: prproto[1] = save(m_proto);
80: prproto[2] = save(M_proto);
81: eqproto = save(e_proto);
82: }
83:
84: /*
85: * Set the message pointer to the end of the message string.
86: */
87: static void
88: setmp()
89: {
90: while (*mp != '\0')
91: mp++;
92: }
93:
94: /*
95: * Append a POSITION (as a decimal integer) to the end of the message.
96: */
97: static void
98: ap_pos(pos)
99: POSITION pos;
100: {
101: sprintf(mp, "%ld", (long)pos);
102: setmp();
103: }
104:
105: /*
106: * Append an integer to the end of the message.
107: */
108: static void
109: ap_int(n)
110: int n;
111: {
112: sprintf(mp, "%d", n);
113: setmp();
114: }
115:
116: /*
117: * Append a string to the end of the message.
118: */
119: static void
120: ap_str(s)
121: char *s;
122: {
123: strtcpy(mp, s, (unsigned int)(&message[sizeof(message)] - mp));
124: setmp();
125: }
126:
127: /*
128: * Append a question mark to the end of the message.
129: */
130: static void
131: ap_quest()
132: {
133: *mp++ = '?';
134: }
135:
136: /*
137: * Return the "current" byte offset in the file.
138: */
139: static POSITION
140: curr_byte(where)
141: int where;
142: {
143: POSITION pos;
144:
145: pos = position(where);
146: while (pos == NULL_POSITION && where >= 0 && where < sc_height)
147: pos = position(++where);
148: if (pos == NULL_POSITION)
149: pos = ch_length();
150: return (pos);
151: }
152:
153: /*
154: * Return the value of a prototype conditional.
155: * A prototype string may include conditionals which consist of a
156: * question mark followed by a single letter.
157: * Here we decode that letter and return the appropriate boolean value.
158: */
159: static int
160: cond(c, where)
161: char c;
162: int where;
163: {
164: switch (c)
165: {
166: case 'a': /* Anything in the message yet? */
167: return (mp > message);
168: case 'b': /* Current byte offset known? */
169: return (curr_byte(where) != NULL_POSITION);
170: case 'e': /* At end of file? */
171: return (hit_eof);
172: case 'f': /* Filename known? */
173: return (strcmp(get_filename(curr_ifile), "-") != 0);
174: case 'l': /* Line number known? */
175: return (linenums);
176: case 'L': /* Final line number known? */
177: return (linenums && ch_length() != NULL_POSITION);
178: case 'm': /* More than one file? */
179: return (nifile() > 1);
180: case 'n': /* First prompt in a new file? */
181: return (new_file);
182: case 'p': /* Percent into file known? */
183: return (curr_byte(where) != NULL_POSITION &&
184: ch_length() > 0);
185: case 's': /* Size of file known? */
186: case 'B':
187: return (ch_length() != NULL_POSITION);
188: case 'x': /* Is there a "next" file? */
189: return (next_ifile(curr_ifile) != NULL_IFILE);
190: }
191: return (0);
192: }
193:
194: /*
195: * Decode a "percent" prototype character.
196: * A prototype string may include various "percent" escapes;
197: * that is, a percent sign followed by a single letter.
198: * Here we decode that letter and take the appropriate action,
199: * usually by appending something to the message being built.
200: */
201: static void
202: protochar(c, where)
203: int c;
204: int where;
205: {
206: POSITION pos;
207: POSITION len;
208: int n;
209: IFILE h;
210:
211: switch (c)
212: {
213: case 'b': /* Current byte offset */
214: pos = curr_byte(where);
215: if (pos != NULL_POSITION)
216: ap_pos(pos);
217: else
218: ap_quest();
219: break;
220: #if EDITOR
221: case 'E': /* Editor name */
222: ap_str(editor);
223: break;
224: #endif
225: case 'f': /* File name */
226: ap_str(get_filename(curr_ifile));
227: break;
228: case 'i': /* Index into list of files */
229: ap_int(get_index(curr_ifile));
230: break;
231: case 'l': /* Current line number */
232: n = currline(where);
233: if (n != 0)
234: ap_int(n);
235: else
236: ap_quest();
237: break;
238: case 'L': /* Final line number */
239: len = ch_length();
240: if (len == NULL_POSITION || len == ch_zero() ||
241: (n = find_linenum(len)) <= 0)
242: ap_quest();
243: else
244: ap_int(n-1);
245: break;
246: case 'm': /* Number of files */
247: ap_int(nifile());
248: break;
249: case 'p': /* Percent into file */
250: pos = curr_byte(where);
251: len = ch_length();
252: if (pos != NULL_POSITION && len > 0)
253: ap_int(percentage(pos,len));
254: else
255: ap_quest();
256: break;
257: case 's': /* Size of file */
258: case 'B':
259: len = ch_length();
260: if (len != NULL_POSITION)
261: ap_pos(len);
262: else
263: ap_quest();
264: break;
265: case 't': /* Truncate trailing spaces in the message */
266: while (mp > message && mp[-1] == ' ')
267: mp--;
268: break;
269: case 'x': /* Name of next file */
270: h = next_ifile(curr_ifile);
271: if (h != NULL_IFILE)
272: ap_str(get_filename(h));
273: else
274: ap_quest();
275: break;
276: }
277: }
278:
279: /*
280: * Skip a false conditional.
281: * When a false condition is found (either a false IF or the ELSE part
282: * of a true IF), this routine scans the prototype string to decide
283: * where to resume parsing the string.
284: * We must keep track of nested IFs and skip them properly.
285: */
286: static char *
287: skipcond(p)
288: register char *p;
289: {
290: register int iflevel;
291:
292: /*
293: * We came in here after processing a ? or :,
294: * so we start nested one level deep.
295: */
296: iflevel = 1;
297:
298: for (;;) switch (*++p)
299: {
300: case '?':
301: /*
302: * Start of a nested IF.
303: */
304: iflevel++;
305: break;
306: case ':':
307: /*
308: * Else.
309: * If this matches the IF we came in here with,
310: * then we're done.
311: */
312: if (iflevel == 1)
313: return (p);
314: break;
315: case '.':
316: /*
317: * Endif.
318: * If this matches the IF we came in here with,
319: * then we're done.
320: */
321: if (--iflevel == 0)
322: return (p);
323: break;
324: case '\\':
325: /*
326: * Backslash escapes the next character.
327: */
328: ++p;
329: break;
330: case '\0':
331: /*
332: * Whoops. Hit end of string.
333: * This is a malformed conditional, but just treat it
334: * as if all active conditionals ends here.
335: */
336: return (p-1);
337: }
338: /*NOTREACHED*/
339: }
340:
341: static char *
342: wherechar(p, wp)
343: char *p;
344: int *wp;
345: {
346: switch (*p)
347: {
348: case 'b': case 'l': case 'p':
349: switch (*++p)
350: {
351: case 't': *wp = TOP; break;
352: case 'm': *wp = MIDDLE; break;
353: case 'b': *wp = BOTTOM; break;
354: case 'B': *wp = BOTTOM_PLUS_ONE; break;
355: case 'j': *wp = adjsline(jump_sline); break;
356: default: *wp = TOP; p--; break;
357: }
358: }
359: return (p);
360: }
361:
362: /*
363: * Construct a message based on a prototype string.
364: */
365: public char *
366: pr_expand(proto, maxwidth)
367: char *proto;
368: int maxwidth;
369: {
370: register char *p;
371: register int c;
372: int where;
373:
374: mp = message;
375:
376: if (*proto == '\0')
377: return ("");
378:
379: for (p = proto; *p != '\0'; p++)
380: {
381: switch (*p)
382: {
383: default: /* Just put the character in the message */
384: *mp++ = *p;
385: break;
386: case '\\': /* Backslash escapes the next character */
387: p++;
388: *mp++ = *p;
389: break;
390: case '?': /* Conditional (IF) */
391: if ((c = *++p) == '\0')
392: --p;
393: else
394: {
395: p = wherechar(p, &where);
396: if (!cond(c, where))
397: p = skipcond(p);
398: }
399: break;
400: case ':': /* ELSE */
401: p = skipcond(p);
402: break;
403: case '.': /* ENDIF */
404: break;
405: case '%': /* Percent escape */
406: if ((c = *++p) == '\0')
407: --p;
408: else
409: {
410: p = wherechar(p, &where);
411: protochar(c, where);
412: }
413: break;
414: }
415: }
416:
417: new_file = 0;
418: if (mp == message)
419: return (NULL);
420: *mp = '\0';
421: if (maxwidth > 0 && mp >= message + maxwidth)
422: {
423: /*
424: * Message is too long.
425: * Return just the final portion of it.
426: */
427: return (mp - maxwidth);
428: }
429: return (message);
430: }
431:
432: /*
433: * Return a message suitable for printing by the "=" command.
434: */
435: public char *
436: eq_message()
437: {
438: return (pr_expand(eqproto, 0));
439: }
440:
441: /*
442: * Return a prompt.
443: * This depends on the prompt type (SHORT, MEDIUM, LONG), etc.
444: * If we can't come up with an appropriate prompt, return NULL
445: * and the caller will prompt with a colon.
446: */
447: public char *
448: pr_string()
449: {
450: return (pr_expand(prproto[pr_type], sc_width-so_s_width-so_e_width-2));
451: }