xref: /openbsd-src/usr.bin/less/prompt.c (revision 62a742911104f98b9185b2c6b6007d9b1c36396c)
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[] =
60   "?f%f .?m(file %i of %m) .?e(END) ?x- Next\\: %x.:(?pB%pB\\%:byte %bB?s/%s..).%t";
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, "%qd", 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 }
452