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