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