xref: /openbsd-src/usr.bin/less/prompt.c (revision 3a3fbb3f2e2521ab7c4a56b7ff7462ebd9095ec5)
1 /*	$OpenBSD: prompt.c,v 1.5 2001/11/19 19:02:14 mpech Exp $	*/
2 
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[] =
62   "?f%f .?m(file %i of %m) .?e(END) ?x- Next\\: %x.:(?pB%pB\\%:byte %bB?s/%s..).%t";
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 {
103 	sprintf(mp, "%qd", pos);
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)
290 	char *p;
291 {
292 	int iflevel;
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 {
372 	char *p;
373 	int c;
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 }
454