prompt.c revision 161478
153813Simp/* $FreeBSD: head/contrib/less/prompt.c 161478 2006-08-20 15:50:51Z delphij $ */
2120330Simp/*
3100213Simp * Copyright (C) 1984-2004  Mark Nudelman
452506Simp *
552506Simp * You may distribute under the terms of either the GNU General Public
6140752Simp * License or the Less License, as specified in the README file.
752506Simp *
852506Simp * For more information about less, or for information on how to
952506Simp * contact the author, see the README file.
1052506Simp */
1152506Simp
1252506Simp
1352506Simp/*
1452506Simp * Prompting and other messages.
1552506Simp * There are three flavors of prompts, SHORT, MEDIUM and LONG,
1652506Simp * selected by the -m/-M options.
1752506Simp * There is also the "equals message", printed by the = command.
1852506Simp * A prompt is a message composed of various pieces, such as the
1952506Simp * name of the file being viewed, the percentage into the file, etc.
2052506Simp */
2152506Simp
2252506Simp#include "less.h"
2352506Simp#include "position.h"
2452506Simp
2552506Simpextern int pr_type;
2652506Simpextern int hit_eof;
2752506Simpextern int new_file;
2852506Simpextern int sc_width;
2952506Simpextern int so_s_width, so_e_width;
3052506Simpextern int linenums;
3152506Simpextern int hshift;
3252506Simpextern int sc_height;
3352506Simpextern int jump_sline;
3452506Simpextern IFILE curr_ifile;
3552506Simp#if EDITOR
3652506Simpextern char *editor;
3752506Simpextern char *editproto;
3852506Simp#endif
3952506Simp
4052506Simp/*
41140752Simp * Prototypes for the three flavors of prompts.
42140752Simp * These strings are expanded by pr_expand().
43140752Simp */
44140752Simpstatic constant char s_proto[] =
45140752Simp  "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x..%t";
46140752Simpstatic constant char m_proto[] =
47140752Simp  "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t";
48140752Simpstatic constant char M_proto[] =
49140752Simp  "?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";
50140752Simpstatic constant char e_proto[] =
51140752Simp  "?f%f .?m(%T %i of %m) .?ltlines %lt-%lb?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t";
52140752Simpstatic constant char h_proto[] =
53140752Simp  "HELP -- ?eEND -- Press g to see it again:Press RETURN for more., or q when done";
54140752Simpstatic constant char w_proto[] =
55140752Simp  "Waiting for data";
56140752Simp
57140752Simppublic char *prproto[3];
58140752Simppublic char constant *eqproto = e_proto;
59140752Simppublic char constant *hproto = h_proto;
60140752Simppublic char constant *wproto = w_proto;
61140752Simp
62140752Simpstatic char message[PROMPT_SIZE];
63140752Simpstatic char *mp;
64140752Simp
65140752Simp/*
66140752Simp * Initialize the prompt prototype strings.
67140752Simp */
6852506Simp	public void
69140749Simpinit_prompt()
70140749Simp{
71140749Simp	prproto[0] = save(s_proto);
72140749Simp	prproto[1] = save(m_proto);
73140749Simp	prproto[2] = save(M_proto);
7486269Simp	eqproto = save(e_proto);
7552506Simp	hproto = save(h_proto);
7652506Simp	wproto = save(w_proto);
77140793Simp}
78140793Simp
7958545Simp/*
8052506Simp * Append a string to the end of the message.
8165039Simp */
8265039Simp	static void
8352506Simpap_str(s)
84140793Simp	char *s;
85140793Simp{
8652506Simp	int len;
8752506Simp
8852506Simp	len = strlen(s);
8952506Simp	if (mp + len >= message + PROMPT_SIZE)
9058545Simp		len = message + PROMPT_SIZE - mp - 1;
9152506Simp	strncpy(mp, s, len);
9286455Simp	mp += len;
9379270Simp	*mp = '\0';
94107359Snon}
9552506Simp
9686269Simp/*
9786455Simp * Append a character to the end of the message.
98119225Simp */
9952506Simp	static void
100140749Simpap_char(c)
10186455Simp	char c;
10258545Simp{
103104854Simp	char buf[2];
10486269Simp
105104854Simp	buf[0] = c;
10652506Simp	buf[1] = '\0';
10786455Simp	ap_str(buf);
10852506Simp}
10986455Simp
11053813Simp/*
111100213Simp * Append a POSITION (as a decimal integer) to the end of the message.
11258545Simp */
11389945Simp	static void
11484514Simpap_pos(pos)
11558545Simp	POSITION pos;
116119234Simp{
11769138Speter	char buf[INT_STRLEN_BOUND(pos) + 2];
118118634Simp
11952506Simp	postoa(pos, buf);
12058545Simp	ap_str(buf);
121140837Simp}
122140793Simp
123140793Simp/*
124140793Simp * Append a line number to the end of the message.
12558545Simp */
12665039Simp 	static void
12792471Simpap_linenum(linenum)
128140793Simp	LINENUM linenum;
129116207Simp{
13084514Simp	char buf[INT_STRLEN_BOUND(linenum) + 2];
13179270Simp
132140793Simp	linenumtoa(linenum, buf);
13379270Simp	ap_str(buf);
134117438Simp}
135117602Simp
136118895Simp/*
137119240Simp * Append an integer to the end of the message.
138119240Simp */
139119240Simp	static void
140119240Simpap_int(num)
14193620Simp	int num;
14286455Simp{
143119240Simp	char buf[INT_STRLEN_BOUND(num) + 2];
144119240Simp
145119240Simp	inttoa(num, buf);
146119240Simp	ap_str(buf);
147119240Simp}
148140793Simp
149119240Simp/*
150119240Simp * Append a question mark to the end of the message.
151119240Simp */
152140792Simp	static void
153140792Simpap_quest()
154119240Simp{
155119240Simp	ap_str("?");
15686455Simp}
157104854Simp
15894461Simp/*
159140793Simp * Return the "current" byte offset in the file.
16086455Simp */
16186455Simp	static POSITION
16289945Simpcurr_byte(where)
16353813Simp	int where;
16471279Simp{
16571283Simp	POSITION pos;
166113667Ssanpei
16753813Simp	pos = position(where);
16852506Simp	while (pos == NULL_POSITION && where >= 0 && where < sc_height-1)
169140793Simp		pos = position(++where);
170140792Simp	if (pos == NULL_POSITION)
171107359Snon		pos = ch_length();
17271283Simp	return (pos);
17352506Simp}
17452506Simp
17586269Simp/*
17686269Simp * Return the value of a prototype conditional.
17752506Simp * A prototype string may include conditionals which consist of a
17853813Simp * question mark followed by a single letter.
17952506Simp * Here we decode that letter and return the appropriate boolean value.
18065039Simp */
18186269Simp	static int
18286269Simpcond(c, where)
18386269Simp	char c;
184135002Semax	int where;
18586269Simp{
18652506Simp	POSITION len;
18752506Simp
18852506Simp	switch (c)
18993893Simp	{
19086455Simp	case 'a':	/* Anything in the message yet? */
19184514Simp		return (mp > message);
19252506Simp	case 'b':	/* Current byte offset known? */
193104854Simp		return (curr_byte(where) != NULL_POSITION);
194104854Simp	case 'c':
19594461Simp		return (hshift != 0);
19686269Simp	case 'e':	/* At end of file? */
19786269Simp		return (hit_eof);
19886269Simp	case 'f':	/* Filename known? */
19986269Simp		return (strcmp(get_filename(curr_ifile), "-") != 0);
20086269Simp	case 'l':	/* Line number known? */
20186269Simp	case 'd':	/* Same as l */
20286269Simp		return (linenums);
20386269Simp	case 'L':	/* Final line number known? */
20486269Simp	case 'D':	/* Final page number known? */
205117614Simp		return (linenums && ch_length() != NULL_POSITION);
206117614Simp	case 'm':	/* More than one file? */
207117614Simp#if TAGS
20886269Simp		return (ntags() ? (ntags() > 1) : (nifile() > 1));
20986269Simp#else
21086269Simp		return (nifile() > 1);
211140793Simp#endif
212140793Simp	case 'n':	/* First prompt in a new file? */
213140793Simp#if TAGS
214140793Simp		return (ntags() ? 1 : new_file);
21586455Simp#else
21686455Simp		return (new_file);
21786455Simp#endif
218116207Simp	case 'p':	/* Percent into file (bytes) known? */
219116207Simp		return (curr_byte(where) != NULL_POSITION &&
220116207Simp				ch_length() > 0);
221117438Simp	case 'P':	/* Percent into file (lines) known? */
222117445Ssimokawa		return (currline(where) != 0 &&
223117438Simp				(len = ch_length()) > 0 &&
22486269Simp				find_linenum(len) != 0);
22586269Simp	case 's':	/* Size of file known? */
22686269Simp	case 'B':
22786269Simp		return (ch_length() != NULL_POSITION);
228104854Simp	case 'x':	/* Is there a "next" file? */
22986269Simp#if TAGS
23087757Simp		if (ntags())
23187757Simp			return (0);
23287757Simp#endif
23386455Simp		return (next_ifile(curr_ifile) != NULL_IFILE);
23486455Simp	}
23586455Simp	return (0);
236119231Simp}
237119231Simp
238119231Simp/*
239119231Simp * Decode a "percent" prototype character.
240119231Simp * A prototype string may include various "percent" escapes;
24186269Simp * that is, a percent sign followed by a single letter.
242117759Simp * Here we decode that letter and take the appropriate action,
24386269Simp * usually by appending something to the message being built.
244109455Sshiba */
245104854Simp	static void
24687044Simpprotochar(c, where, iseditproto)
24786269Simp	int c;
24865039Simp	int where;
24986269Simp	int iseditproto;
25065039Simp{
25165039Simp	POSITION pos;
25252506Simp	POSITION len;
25386455Simp	int n;
25452506Simp	LINENUM linenum;
25552506Simp	LINENUM last_linenum;
25686269Simp	IFILE h;
25786269Simp
25886269Simp#undef  PAGE_NUM
25953813Simp#define PAGE_NUM(linenum)  ((((linenum) - 1) / (sc_height - 1)) + 1)
26052506Simp
26152506Simp	switch (c)
26252506Simp	{
26352506Simp	case 'b':	/* Current byte offset */
26452506Simp		pos = curr_byte(where);
26552506Simp		if (pos != NULL_POSITION)
26652506Simp			ap_pos(pos);
26779270Simp		else
26879270Simp			ap_quest();
26979270Simp		break;
270119225Simp	case 'c':
271119225Simp		ap_int(hshift);
272119225Simp		break;
273119225Simp	case 'd':	/* Current page number */
27486455Simp		linenum = currline(where);
27586455Simp		if (linenum > 0 && sc_height > 1)
27686455Simp			ap_linenum(PAGE_NUM(linenum));
27789945Simp		else
27889945Simp			ap_quest();
27989945Simp		break;
28071279Simp	case 'D':	/* Final page number */
28171279Simp		/* Find the page number of the last byte in the file (len-1). */
28271279Simp		len = ch_length();
28386269Simp		if (len == NULL_POSITION)
284100213Simp			ap_quest();
28571279Simp		else if (len == 0)
28686269Simp			/* An empty file has no pages. */
28786269Simp			ap_linenum(0);
28886269Simp		else
28989945Simp		{
29089945Simp			linenum = find_linenum(len - 1);
29189945Simp			if (linenum <= 0)
29286269Simp				ap_quest();
29386269Simp			else
29486269Simp				ap_linenum(PAGE_NUM(linenum));
29552506Simp		}
296139963Simp		break;
29752506Simp#if EDITOR
29886269Simp	case 'E':	/* Editor name */
29953813Simp		ap_str(editor);
300120330Simp		break;
30153813Simp#endif
302100213Simp	case 'f':	/* File name */
303100213Simp		ap_str(get_filename(curr_ifile));
304100213Simp		break;
30592471Simp	case 'i':	/* Index into list of files */
30692471Simp#if TAGS
30792471Simp		if (ntags())
30852506Simp			ap_int(curr_tag());
30971279Simp		else
31065039Simp#endif
31165039Simp			ap_int(get_index(curr_ifile));
31265039Simp		break;
31365039Simp	case 'l':	/* Current line number */
31487044Simp		linenum = currline(where);
31565039Simp		if (linenum != 0)
31652506Simp			ap_linenum(linenum);
31765039Simp		else
31889103Simp			ap_quest();
31953813Simp		break;
32052506Simp	case 'L':	/* Final line number */
32165039Simp		len = ch_length();
32286269Simp		if (len == NULL_POSITION || len == ch_zero() ||
32365039Simp		    (linenum = find_linenum(len)) <= 0)
324121960Simp			ap_quest();
325129164Simp		else
32665039Simp			ap_linenum(linenum-1);
327119213Simp		break;
328127422Simp	case 'm':	/* Number of files */
329140520Simp#if TAGS
330140520Simp		n = ntags();
331140520Simp		if (n)
332119213Simp			ap_int(n);
33352506Simp		else
33479270Simp#endif
335117764Simp			ap_int(nifile());
33652506Simp		break;
33786269Simp	case 'p':	/* Percent into file (bytes) */
33886269Simp		pos = curr_byte(where);
33965039Simp		len = ch_length();
34086269Simp		if (pos != NULL_POSITION && len > 0)
34186269Simp			ap_int(percentage(pos,len));
34286269Simp		else
34358545Simp			ap_quest();
34471279Simp		break;
34558545Simp	case 'P':	/* Percent into file (lines) */
34652506Simp		linenum = currline(where);
34786269Simp		if (linenum == 0 ||
34858545Simp		    (len = ch_length()) == NULL_POSITION || len == ch_zero() ||
34952506Simp		    (last_linenum = find_linenum(len)) <= 0)
35052506Simp			ap_quest();
35152506Simp		else
35286269Simp			ap_int(percentage(linenum, last_linenum));
35386269Simp		break;
35486269Simp	case 's':	/* Size of file */
35586269Simp	case 'B':
35686269Simp		len = ch_length();
357107359Snon		if (len != NULL_POSITION)
35886269Simp			ap_pos(len);
359140749Simp		else
360140749Simp			ap_quest();
361140749Simp		break;
36252506Simp	case 't':	/* Truncate trailing spaces in the message */
36386269Simp		while (mp > message && mp[-1] == ' ')
36452506Simp			mp--;
36553813Simp		break;
36652506Simp	case 'T':	/* Type of list */
36752506Simp#if TAGS
36858545Simp		if (ntags())
369140515Simp			ap_str("tag");
37058545Simp		else
37158545Simp#endif
372118895Simp			ap_str("file");
373118895Simp		break;
374118895Simp	case 'x':	/* Name of next file */
37586269Simp		h = next_ifile(curr_ifile);
37686269Simp		if (h != NULL_IFILE)
37786269Simp			ap_str(get_filename(h));
37886455Simp		else
37986269Simp			ap_quest();
38086269Simp		break;
38186269Simp	}
38286455Simp}
38386455Simp
38486455Simp/*
385104831Simp * Skip a false conditional.
386106891Simp * When a false condition is found (either a false IF or the ELSE part
387106891Simp * of a true IF), this routine scans the prototype string to decide
38886455Simp * where to resume parsing the string.
38986455Simp * We must keep track of nested IFs and skip them properly.
39086269Simp */
39186455Simp	static char *
392104854Simpskipcond(p)
39386455Simp	register char *p;
39493620Simp{
39586455Simp	register int iflevel;
39679270Simp
39779270Simp	/*
39879270Simp	 * We came in here after processing a ? or :,
399140792Simp	 * so we start nested one level deep.
400140792Simp	 */
401140792Simp	iflevel = 1;
40258545Simp
403100213Simp	for (;;) switch (*++p)
40458545Simp	{
40558545Simp	case '?':
40693620Simp		/*
40793620Simp		 * Start of a nested IF.
40893620Simp		 */
40993620Simp		iflevel++;
41065039Simp		break;
411140515Simp	case ':':
41265039Simp		/*
413121586Simp		 * Else.
414121584Simp		 * If this matches the IF we came in here with,
41565039Simp		 * then we're done.
416104854Simp		 */
417104854Simp		if (iflevel == 1)
418104854Simp			return (p);
41958545Simp		break;
42058545Simp	case '.':
42165039Simp		/*
422107359Snon		 * Endif.
42386269Simp		 * If this matches the IF we came in here with,
424124789Sume		 * then we're done.
42586455Simp		 */
42658545Simp		if (--iflevel == 0)
427104831Simp			return (p);
428104854Simp		break;
429104831Simp	case '\\':
43086455Simp		/*
431120275Simp		 * Backslash escapes the next character.
43286455Simp		 */
43386455Simp		++p;
43493622Simp		break;
43586455Simp	case '\0':
43684514Simp		/*
43784514Simp		 * Whoops.  Hit end of string.
43884514Simp		 * This is a malformed conditional, but just treat it
439107359Snon		 * as if all active conditionals ends here.
440107359Snon		 */
441107359Snon		return (p-1);
44286455Simp	}
443120330Simp	/*NOTREACHED*/
44486455Simp}
44586455Simp
44686269Simp/*
447109103Sshiba * Decode a char that represents a position on the screen.
448109103Sshiba */
44952506Simp	static char *
450140837Simpwherechar(p, wp)
451140837Simp	char *p;
452140837Simp	int *wp;
45386269Simp{
45486269Simp	switch (*p)
45586269Simp	{
45686269Simp	case 'b': case 'd': case 'l': case 'p': case 'P':
45786269Simp		switch (*++p)
45886269Simp		{
45986269Simp		case 't':   *wp = TOP;			break;
46086269Simp		case 'm':   *wp = MIDDLE;		break;
46186269Simp		case 'b':   *wp = BOTTOM;		break;
46252506Simp		case 'B':   *wp = BOTTOM_PLUS_ONE;	break;
46352506Simp		case 'j':   *wp = adjsline(jump_sline);	break;
46452506Simp		default:    *wp = TOP;  p--;		break;
465118063Simp		}
466118063Simp	}
467118063Simp	return (p);
468117602Simp}
469117602Simp
470117602Simp/*
47152506Simp * Construct a message based on a prototype string.
47252506Simp */
47353813Simp	public char *
47453813Simppr_expand(proto, maxwidth)
47552506Simp	char *proto;
47686269Simp	int maxwidth;
47786269Simp{
47886269Simp	register char *p;
47986269Simp	register int c;
48052506Simp	int where;
48179270Simp
48289103Simp	mp = message;
48352506Simp
48452506Simp	if (*proto == '\0')
48586269Simp		return ("");
48671279Simp
487118063Simp	for (p = proto;  *p != '\0';  p++)
48852506Simp	{
48986269Simp		switch (*p)
49086269Simp		{
49186269Simp		default:	/* Just put the character in the message */
49252506Simp			ap_char(*p);
49386269Simp			break;
49452506Simp		case '\\':	/* Backslash escapes the next character */
49586269Simp			p++;
49652506Simp			ap_char(*p);
497104854Simp			break;
498124015Skato		case '?':	/* Conditional (IF) */
49952506Simp			if ((c = *++p) == '\0')
50086269Simp				--p;
50186269Simp			else
50286269Simp			{
50386269Simp				where = 0;
50486269Simp				p = wherechar(p, &where);
50586269Simp				if (!cond(c, where))
506107359Snon					p = skipcond(p);
507107359Snon			}
508107359Snon			break;
50965039Simp		case ':':	/* ELSE */
510120898Simp			p = skipcond(p);
51165039Simp			break;
512128064Srsm		case '.':	/* ENDIF */
513128064Srsm			break;
514128064Srsm		case '%':	/* Percent escape */
51586571Simp			if ((c = *++p) == '\0')
51686269Simp				--p;
51786269Simp			else
51879270Simp			{
51965039Simp				where = 0;
520128064Srsm				p = wherechar(p, &where);
521128064Srsm				protochar(c, where,
522128064Srsm#if EDITOR
52386269Simp					(proto == editproto));
524128064Srsm#else
525128064Srsm					0);
526128064Srsm#endif
527128064Srsm
528128064Srsm			}
52952506Simp			break;
53084514Simp		}
53184514Simp	}
53284514Simp
53386269Simp	if (mp == message)
53486269Simp		return ("");
53586269Simp	if (maxwidth > 0 && mp >= message + maxwidth)
53686269Simp	{
53752506Simp		/*
53893622Simp		 * Message is too long.
539103169Simp		 * Return just the final portion of it.
54086269Simp		 */
54186269Simp		return (mp - maxwidth);
54286269Simp	}
543113667Ssanpei	return (message);
54486269Simp}
545104854Simp
54686269Simp/*
54758545Simp * Return a message suitable for printing by the "=" command.
54858545Simp */
549120330Simp	public char *
550107359Snoneq_message()
551116481Simp{
55286281Simp	return (pr_expand(eqproto, 0));
55358545Simp}
554119215Simp
555107359Snon/*
55693620Simp * Return a prompt.
557118634Simp * This depends on the prompt type (SHORT, MEDIUM, LONG), etc.
55886269Simp * If we can't come up with an appropriate prompt, return NULL
55986392Simp * and the caller will prompt with a colon.
560107359Snon */
561114089Simp	public char *
56286269Simppr_string()
56386269Simp{
56486269Simp	char *prompt;
56586269Simp
56686269Simp	prompt = pr_expand((ch_getflags() & CH_HELPFILE) ?
56779270Simp				hproto : prproto[pr_type],
56886269Simp			sc_width-so_s_width-so_e_width-2);
569113318Simp	new_file = 0;
570107359Snon	return (prompt);
57186269Simp}
572110935Sshiba
57386580Simp/*
57452506Simp * Return a message suitable for printing while waiting in the F command.
57552506Simp */
576104854Simp	public char *
577109453Sshibawait_message()
57893622Simp{
579110175Sshiba	return (pr_expand(wproto, sc_width-so_s_width-so_e_width-2));
58086269Simp}
581104854Simp