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