160814Sps/* $FreeBSD$ */
260786Sps/*
3240121Sdelphij * Copyright (C) 1984-2012  Mark Nudelman
460786Sps *
560786Sps * You may distribute under the terms of either the GNU General Public
660786Sps * License or the Less License, as specified in the README file.
760786Sps *
8240121Sdelphij * For more information, see the README file.
960786Sps */
1060786Sps
1160786Sps
1260786Sps/*
1360786Sps * Prompting and other messages.
1460786Sps * There are three flavors of prompts, SHORT, MEDIUM and LONG,
1560786Sps * selected by the -m/-M options.
1660786Sps * There is also the "equals message", printed by the = command.
1760786Sps * A prompt is a message composed of various pieces, such as the
1860786Sps * name of the file being viewed, the percentage into the file, etc.
1960786Sps */
2060786Sps
2160786Sps#include "less.h"
2260786Sps#include "position.h"
2360786Sps
2460786Spsextern int pr_type;
2560786Spsextern int new_file;
2660786Spsextern int sc_width;
2760786Spsextern int so_s_width, so_e_width;
2860786Spsextern int linenums;
2960786Spsextern int hshift;
3060786Spsextern int sc_height;
3160786Spsextern int jump_sline;
32170259Sdelphijextern int less_is_more;
3360786Spsextern IFILE curr_ifile;
3460786Sps#if EDITOR
3560786Spsextern char *editor;
3660786Spsextern char *editproto;
3760786Sps#endif
3860786Sps
3960786Sps/*
4060786Sps * Prototypes for the three flavors of prompts.
4160786Sps * These strings are expanded by pr_expand().
4260786Sps */
4360786Spsstatic constant char s_proto[] =
4489022Sps  "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x..%t";
4560786Spsstatic constant char m_proto[] =
4689022Sps  "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t";
4760786Spsstatic constant char M_proto[] =
4889022Sps  "?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";
4960786Spsstatic constant char e_proto[] =
5089022Sps  "?f%f .?m(%T %i of %m) .?ltlines %lt-%lb?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t";
5160786Spsstatic constant char h_proto[] =
5260786Sps  "HELP -- ?eEND -- Press g to see it again:Press RETURN for more., or q when done";
5389022Spsstatic constant char w_proto[] =
5489022Sps  "Waiting for data";
55170259Sdelphijstatic constant char more_proto[] =
56170259Sdelphij  "--More--(?eEND ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t)";
5760786Sps
5860786Spspublic char *prproto[3];
5960786Spspublic char constant *eqproto = e_proto;
6060786Spspublic char constant *hproto = h_proto;
6189022Spspublic char constant *wproto = w_proto;
6260786Sps
6360786Spsstatic char message[PROMPT_SIZE];
6460786Spsstatic char *mp;
6560786Sps
6660786Sps/*
6760786Sps * Initialize the prompt prototype strings.
6860786Sps */
6960786Sps	public void
7060786Spsinit_prompt()
7160786Sps{
7260786Sps	prproto[0] = save(s_proto);
73170259Sdelphij	prproto[1] = save(less_is_more ? more_proto : m_proto);
7460786Sps	prproto[2] = save(M_proto);
7560786Sps	eqproto = save(e_proto);
7660786Sps	hproto = save(h_proto);
7789022Sps	wproto = save(w_proto);
7860786Sps}
7960786Sps
8060786Sps/*
8160786Sps * Append a string to the end of the message.
8260786Sps */
8360786Sps	static void
8460786Spsap_str(s)
8560786Sps	char *s;
8660786Sps{
8760786Sps	int len;
8860786Sps
8960786Sps	len = strlen(s);
9060786Sps	if (mp + len >= message + PROMPT_SIZE)
9160786Sps		len = message + PROMPT_SIZE - mp - 1;
9260786Sps	strncpy(mp, s, len);
9360786Sps	mp += len;
9460786Sps	*mp = '\0';
9560786Sps}
9660786Sps
9760786Sps/*
9860786Sps * Append a character to the end of the message.
9960786Sps */
10060786Sps	static void
10160786Spsap_char(c)
10260786Sps	char c;
10360786Sps{
10460786Sps	char buf[2];
10560786Sps
10660786Sps	buf[0] = c;
10760786Sps	buf[1] = '\0';
10860786Sps	ap_str(buf);
10960786Sps}
11060786Sps
11160786Sps/*
11260786Sps * Append a POSITION (as a decimal integer) to the end of the message.
11360786Sps */
11460786Sps	static void
11560786Spsap_pos(pos)
11660786Sps	POSITION pos;
11760786Sps{
118128348Stjr	char buf[INT_STRLEN_BOUND(pos) + 2];
119128348Stjr
120128348Stjr	postoa(pos, buf);
121128348Stjr	ap_str(buf);
12260786Sps}
12360786Sps
12460786Sps/*
125128348Stjr * Append a line number to the end of the message.
126128348Stjr */
127128348Stjr 	static void
128128348Stjrap_linenum(linenum)
129128348Stjr	LINENUM linenum;
130128348Stjr{
131128348Stjr	char buf[INT_STRLEN_BOUND(linenum) + 2];
132128348Stjr
133128348Stjr	linenumtoa(linenum, buf);
134128348Stjr	ap_str(buf);
135128348Stjr}
136128348Stjr
137128348Stjr/*
13860786Sps * Append an integer to the end of the message.
13960786Sps */
14060786Sps	static void
141128348Stjrap_int(num)
142128348Stjr	int num;
14360786Sps{
144128348Stjr	char buf[INT_STRLEN_BOUND(num) + 2];
14560786Sps
146128348Stjr	inttoa(num, buf);
14760786Sps	ap_str(buf);
14860786Sps}
14960786Sps
15060786Sps/*
15160786Sps * Append a question mark to the end of the message.
15260786Sps */
15360786Sps	static void
15460786Spsap_quest()
15560786Sps{
15660786Sps	ap_str("?");
15760786Sps}
15860786Sps
15960786Sps/*
16060786Sps * Return the "current" byte offset in the file.
16160786Sps */
16260786Sps	static POSITION
16360786Spscurr_byte(where)
16460786Sps	int where;
16560786Sps{
16660786Sps	POSITION pos;
16760786Sps
16860786Sps	pos = position(where);
169161478Sdelphij	while (pos == NULL_POSITION && where >= 0 && where < sc_height-1)
17060786Sps		pos = position(++where);
17160786Sps	if (pos == NULL_POSITION)
17260786Sps		pos = ch_length();
17360786Sps	return (pos);
17460786Sps}
17560786Sps
17660786Sps/*
17760786Sps * Return the value of a prototype conditional.
17860786Sps * A prototype string may include conditionals which consist of a
17960786Sps * question mark followed by a single letter.
18060786Sps * Here we decode that letter and return the appropriate boolean value.
18160786Sps */
18260786Sps	static int
18360786Spscond(c, where)
18460786Sps	char c;
18560786Sps	int where;
18660786Sps{
18760786Sps	POSITION len;
18860786Sps
18960786Sps	switch (c)
19060786Sps	{
19160786Sps	case 'a':	/* Anything in the message yet? */
19260786Sps		return (mp > message);
19360786Sps	case 'b':	/* Current byte offset known? */
19460786Sps		return (curr_byte(where) != NULL_POSITION);
19560786Sps	case 'c':
19660786Sps		return (hshift != 0);
19760786Sps	case 'e':	/* At end of file? */
198191930Sdelphij		return (eof_displayed());
19960786Sps	case 'f':	/* Filename known? */
20060786Sps		return (strcmp(get_filename(curr_ifile), "-") != 0);
20160786Sps	case 'l':	/* Line number known? */
20260786Sps	case 'd':	/* Same as l */
20360786Sps		return (linenums);
20460786Sps	case 'L':	/* Final line number known? */
205161478Sdelphij	case 'D':	/* Final page number known? */
20660786Sps		return (linenums && ch_length() != NULL_POSITION);
20760786Sps	case 'm':	/* More than one file? */
208128348Stjr#if TAGS
20989022Sps		return (ntags() ? (ntags() > 1) : (nifile() > 1));
210128348Stjr#else
211128348Stjr		return (nifile() > 1);
212128348Stjr#endif
21360786Sps	case 'n':	/* First prompt in a new file? */
214128348Stjr#if TAGS
21589022Sps		return (ntags() ? 1 : new_file);
216128348Stjr#else
217128348Stjr		return (new_file);
218128348Stjr#endif
21960786Sps	case 'p':	/* Percent into file (bytes) known? */
22060786Sps		return (curr_byte(where) != NULL_POSITION &&
22160786Sps				ch_length() > 0);
22260786Sps	case 'P':	/* Percent into file (lines) known? */
22360786Sps		return (currline(where) != 0 &&
22460786Sps				(len = ch_length()) > 0 &&
22560786Sps				find_linenum(len) != 0);
22660786Sps	case 's':	/* Size of file known? */
22760786Sps	case 'B':
22860786Sps		return (ch_length() != NULL_POSITION);
22960786Sps	case 'x':	/* Is there a "next" file? */
230128348Stjr#if TAGS
23189022Sps		if (ntags())
23289022Sps			return (0);
233128348Stjr#endif
23460786Sps		return (next_ifile(curr_ifile) != NULL_IFILE);
23560786Sps	}
23660786Sps	return (0);
23760786Sps}
23860786Sps
23960786Sps/*
24060786Sps * Decode a "percent" prototype character.
24160786Sps * A prototype string may include various "percent" escapes;
24260786Sps * that is, a percent sign followed by a single letter.
24360786Sps * Here we decode that letter and take the appropriate action,
24460786Sps * usually by appending something to the message being built.
24560786Sps */
24660786Sps	static void
24760786Spsprotochar(c, where, iseditproto)
24860786Sps	int c;
24960786Sps	int where;
25060786Sps	int iseditproto;
25160786Sps{
25260786Sps	POSITION pos;
25360786Sps	POSITION len;
25460786Sps	int n;
255128348Stjr	LINENUM linenum;
256128348Stjr	LINENUM last_linenum;
25760786Sps	IFILE h;
25860786Sps
259161478Sdelphij#undef  PAGE_NUM
260161478Sdelphij#define PAGE_NUM(linenum)  ((((linenum) - 1) / (sc_height - 1)) + 1)
261161478Sdelphij
26260786Sps	switch (c)
26360786Sps	{
26460786Sps	case 'b':	/* Current byte offset */
26560786Sps		pos = curr_byte(where);
26660786Sps		if (pos != NULL_POSITION)
26760786Sps			ap_pos(pos);
26860786Sps		else
26960786Sps			ap_quest();
27060786Sps		break;
27160786Sps	case 'c':
27260786Sps		ap_int(hshift);
27360786Sps		break;
27460786Sps	case 'd':	/* Current page number */
275128348Stjr		linenum = currline(where);
276128348Stjr		if (linenum > 0 && sc_height > 1)
277161478Sdelphij			ap_linenum(PAGE_NUM(linenum));
27860786Sps		else
27960786Sps			ap_quest();
28060786Sps		break;
281161478Sdelphij	case 'D':	/* Final page number */
282161478Sdelphij		/* Find the page number of the last byte in the file (len-1). */
28360786Sps		len = ch_length();
284161478Sdelphij		if (len == NULL_POSITION)
28560786Sps			ap_quest();
286161478Sdelphij		else if (len == 0)
287161478Sdelphij			/* An empty file has no pages. */
288161478Sdelphij			ap_linenum(0);
28960786Sps		else
290161478Sdelphij		{
291161478Sdelphij			linenum = find_linenum(len - 1);
292161478Sdelphij			if (linenum <= 0)
293161478Sdelphij				ap_quest();
294161478Sdelphij			else
295161478Sdelphij				ap_linenum(PAGE_NUM(linenum));
296161478Sdelphij		}
29760786Sps		break;
29860786Sps#if EDITOR
29960786Sps	case 'E':	/* Editor name */
30060786Sps		ap_str(editor);
30160786Sps		break;
30260786Sps#endif
30360786Sps	case 'f':	/* File name */
304128348Stjr		ap_str(get_filename(curr_ifile));
30560786Sps		break;
306221715Sdelphij	case 'F':	/* Last component of file name */
307221715Sdelphij		ap_str(last_component(get_filename(curr_ifile)));
308221715Sdelphij		break;
30960786Sps	case 'i':	/* Index into list of files */
310128348Stjr#if TAGS
31189022Sps		if (ntags())
31289022Sps			ap_int(curr_tag());
31389022Sps		else
314128348Stjr#endif
31589022Sps			ap_int(get_index(curr_ifile));
31660786Sps		break;
31760786Sps	case 'l':	/* Current line number */
318128348Stjr		linenum = currline(where);
319128348Stjr		if (linenum != 0)
320128348Stjr			ap_linenum(linenum);
32160786Sps		else
32260786Sps			ap_quest();
32360786Sps		break;
32460786Sps	case 'L':	/* Final line number */
32560786Sps		len = ch_length();
32660786Sps		if (len == NULL_POSITION || len == ch_zero() ||
327128348Stjr		    (linenum = find_linenum(len)) <= 0)
32860786Sps			ap_quest();
32960786Sps		else
330128348Stjr			ap_linenum(linenum-1);
33160786Sps		break;
33260786Sps	case 'm':	/* Number of files */
333128348Stjr#if TAGS
33489022Sps		n = ntags();
33589022Sps		if (n)
33689022Sps			ap_int(n);
33789022Sps		else
338128348Stjr#endif
33989022Sps			ap_int(nifile());
34060786Sps		break;
34160786Sps	case 'p':	/* Percent into file (bytes) */
34260786Sps		pos = curr_byte(where);
34360786Sps		len = ch_length();
34460786Sps		if (pos != NULL_POSITION && len > 0)
34560786Sps			ap_int(percentage(pos,len));
34660786Sps		else
34760786Sps			ap_quest();
34860786Sps		break;
34960786Sps	case 'P':	/* Percent into file (lines) */
350128348Stjr		linenum = currline(where);
351128348Stjr		if (linenum == 0 ||
35260786Sps		    (len = ch_length()) == NULL_POSITION || len == ch_zero() ||
353128348Stjr		    (last_linenum = find_linenum(len)) <= 0)
35460786Sps			ap_quest();
35560786Sps		else
356128348Stjr			ap_int(percentage(linenum, last_linenum));
35760786Sps		break;
35860786Sps	case 's':	/* Size of file */
35960786Sps	case 'B':
36060786Sps		len = ch_length();
36160786Sps		if (len != NULL_POSITION)
36260786Sps			ap_pos(len);
36360786Sps		else
36460786Sps			ap_quest();
36560786Sps		break;
36660786Sps	case 't':	/* Truncate trailing spaces in the message */
36760786Sps		while (mp > message && mp[-1] == ' ')
36860786Sps			mp--;
369221715Sdelphij		*mp = '\0';
37060786Sps		break;
37189022Sps	case 'T':	/* Type of list */
372128348Stjr#if TAGS
37389022Sps		if (ntags())
37489022Sps			ap_str("tag");
37589022Sps		else
376128348Stjr#endif
37789022Sps			ap_str("file");
37889022Sps		break;
37960786Sps	case 'x':	/* Name of next file */
38060786Sps		h = next_ifile(curr_ifile);
38160786Sps		if (h != NULL_IFILE)
382128348Stjr			ap_str(get_filename(h));
383128348Stjr		else
38460786Sps			ap_quest();
38560786Sps		break;
38660786Sps	}
38760786Sps}
38860786Sps
38960786Sps/*
39060786Sps * Skip a false conditional.
39160786Sps * When a false condition is found (either a false IF or the ELSE part
39260786Sps * of a true IF), this routine scans the prototype string to decide
39360786Sps * where to resume parsing the string.
39460786Sps * We must keep track of nested IFs and skip them properly.
39560786Sps */
396240121Sdelphij	static constant char *
39760786Spsskipcond(p)
398240121Sdelphij	register constant char *p;
39960786Sps{
40060786Sps	register int iflevel;
40160786Sps
40260786Sps	/*
40360786Sps	 * We came in here after processing a ? or :,
40460786Sps	 * so we start nested one level deep.
40560786Sps	 */
40660786Sps	iflevel = 1;
40760786Sps
40860786Sps	for (;;) switch (*++p)
40960786Sps	{
41060786Sps	case '?':
41160786Sps		/*
41260786Sps		 * Start of a nested IF.
41360786Sps		 */
41460786Sps		iflevel++;
41560786Sps		break;
41660786Sps	case ':':
41760786Sps		/*
41860786Sps		 * Else.
41960786Sps		 * If this matches the IF we came in here with,
42060786Sps		 * then we're done.
42160786Sps		 */
42260786Sps		if (iflevel == 1)
42360786Sps			return (p);
42460786Sps		break;
42560786Sps	case '.':
42660786Sps		/*
42760786Sps		 * Endif.
42860786Sps		 * If this matches the IF we came in here with,
42960786Sps		 * then we're done.
43060786Sps		 */
43160786Sps		if (--iflevel == 0)
43260786Sps			return (p);
43360786Sps		break;
43460786Sps	case '\\':
43560786Sps		/*
43660786Sps		 * Backslash escapes the next character.
43760786Sps		 */
43860786Sps		++p;
43960786Sps		break;
44060786Sps	case '\0':
44160786Sps		/*
44260786Sps		 * Whoops.  Hit end of string.
44360786Sps		 * This is a malformed conditional, but just treat it
44460786Sps		 * as if all active conditionals ends here.
44560786Sps		 */
44660786Sps		return (p-1);
44760786Sps	}
44860786Sps	/*NOTREACHED*/
44960786Sps}
45060786Sps
45160786Sps/*
45260786Sps * Decode a char that represents a position on the screen.
45360786Sps */
454240121Sdelphij	static constant char *
45560786Spswherechar(p, wp)
456229811Sdim	char constant *p;
45760786Sps	int *wp;
45860786Sps{
45960786Sps	switch (*p)
46060786Sps	{
46160786Sps	case 'b': case 'd': case 'l': case 'p': case 'P':
46260786Sps		switch (*++p)
46360786Sps		{
46460786Sps		case 't':   *wp = TOP;			break;
46560786Sps		case 'm':   *wp = MIDDLE;		break;
46660786Sps		case 'b':   *wp = BOTTOM;		break;
46760786Sps		case 'B':   *wp = BOTTOM_PLUS_ONE;	break;
46860786Sps		case 'j':   *wp = adjsline(jump_sline);	break;
46960786Sps		default:    *wp = TOP;  p--;		break;
47060786Sps		}
47160786Sps	}
47260786Sps	return (p);
47360786Sps}
47460786Sps
47560786Sps/*
47660786Sps * Construct a message based on a prototype string.
47760786Sps */
47860786Sps	public char *
47960786Spspr_expand(proto, maxwidth)
480240121Sdelphij	constant char *proto;
48160786Sps	int maxwidth;
48260786Sps{
483240121Sdelphij	register constant char *p;
48460786Sps	register int c;
48560786Sps	int where;
48660786Sps
48760786Sps	mp = message;
48860786Sps
48960786Sps	if (*proto == '\0')
49060786Sps		return ("");
49160786Sps
49260786Sps	for (p = proto;  *p != '\0';  p++)
49360786Sps	{
49460786Sps		switch (*p)
49560786Sps		{
49660786Sps		default:	/* Just put the character in the message */
49760786Sps			ap_char(*p);
49860786Sps			break;
49960786Sps		case '\\':	/* Backslash escapes the next character */
50060786Sps			p++;
50160786Sps			ap_char(*p);
50260786Sps			break;
50360786Sps		case '?':	/* Conditional (IF) */
50460786Sps			if ((c = *++p) == '\0')
50560786Sps				--p;
50660786Sps			else
50760786Sps			{
50860786Sps				where = 0;
50960786Sps				p = wherechar(p, &where);
51060786Sps				if (!cond(c, where))
51160786Sps					p = skipcond(p);
51260786Sps			}
51360786Sps			break;
51460786Sps		case ':':	/* ELSE */
51560786Sps			p = skipcond(p);
51660786Sps			break;
51760786Sps		case '.':	/* ENDIF */
51860786Sps			break;
51960786Sps		case '%':	/* Percent escape */
52060786Sps			if ((c = *++p) == '\0')
52160786Sps				--p;
52260786Sps			else
52360786Sps			{
52460786Sps				where = 0;
52560786Sps				p = wherechar(p, &where);
52660786Sps				protochar(c, where,
52760786Sps#if EDITOR
52860786Sps					(proto == editproto));
52960786Sps#else
53060786Sps					0);
53160786Sps#endif
53260786Sps
53360786Sps			}
53460786Sps			break;
53560786Sps		}
53660786Sps	}
53760786Sps
53860786Sps	if (mp == message)
539161478Sdelphij		return ("");
54060786Sps	if (maxwidth > 0 && mp >= message + maxwidth)
54160786Sps	{
54260786Sps		/*
54360786Sps		 * Message is too long.
54460786Sps		 * Return just the final portion of it.
54560786Sps		 */
54660786Sps		return (mp - maxwidth);
54760786Sps	}
54860786Sps	return (message);
54960786Sps}
55060786Sps
55160786Sps/*
55260786Sps * Return a message suitable for printing by the "=" command.
55360786Sps */
55460786Sps	public char *
55560786Spseq_message()
55660786Sps{
55760786Sps	return (pr_expand(eqproto, 0));
55860786Sps}
55960786Sps
56060786Sps/*
56160786Sps * Return a prompt.
56260786Sps * This depends on the prompt type (SHORT, MEDIUM, LONG), etc.
56360786Sps * If we can't come up with an appropriate prompt, return NULL
56460786Sps * and the caller will prompt with a colon.
56560786Sps */
56660786Sps	public char *
56760786Spspr_string()
56860786Sps{
56989022Sps	char *prompt;
570170259Sdelphij	int type;
57189022Sps
572170259Sdelphij	type = (!less_is_more) ? pr_type : pr_type ? 0 : 1;
57389022Sps	prompt = pr_expand((ch_getflags() & CH_HELPFILE) ?
574170259Sdelphij				hproto : prproto[type],
57589022Sps			sc_width-so_s_width-so_e_width-2);
57689022Sps	new_file = 0;
57789022Sps	return (prompt);
57860786Sps}
57989022Sps
58089022Sps/*
58189022Sps * Return a message suitable for printing while waiting in the F command.
58289022Sps */
58389022Sps	public char *
58489022Spswait_message()
58589022Sps{
58689022Sps	return (pr_expand(wproto, sc_width-so_s_width-so_e_width-2));
58789022Sps}
588