1*838f5788Ssimonb /* $NetBSD: prompt.c,v 1.5 2023/10/06 05:49:49 simonb Exp $ */
220006a0bStron
320006a0bStron /*
4*838f5788Ssimonb * Copyright (C) 1984-2023 Mark Nudelman
520006a0bStron *
620006a0bStron * You may distribute under the terms of either the GNU General Public
720006a0bStron * License or the Less License, as specified in the README file.
820006a0bStron *
9ec18bca0Stron * For more information, see the README file.
1020006a0bStron */
1120006a0bStron
1220006a0bStron
1320006a0bStron /*
1420006a0bStron * Prompting and other messages.
1520006a0bStron * There are three flavors of prompts, SHORT, MEDIUM and LONG,
1620006a0bStron * selected by the -m/-M options.
1720006a0bStron * There is also the "equals message", printed by the = command.
1820006a0bStron * A prompt is a message composed of various pieces, such as the
1920006a0bStron * name of the file being viewed, the percentage into the file, etc.
2020006a0bStron */
2120006a0bStron
2220006a0bStron #include "less.h"
2320006a0bStron #include "position.h"
2420006a0bStron
2520006a0bStron extern int pr_type;
2620006a0bStron extern int new_file;
2720006a0bStron extern int sc_width;
2820006a0bStron extern int so_s_width, so_e_width;
2920006a0bStron extern int linenums;
3020006a0bStron extern int hshift;
3120006a0bStron extern int sc_height;
3220006a0bStron extern int jump_sline;
3320006a0bStron extern int less_is_more;
34*838f5788Ssimonb extern int header_lines;
3520006a0bStron extern IFILE curr_ifile;
3620006a0bStron #if EDITOR
3720006a0bStron extern char *editor;
3820006a0bStron extern char *editproto;
3920006a0bStron #endif
4020006a0bStron
4120006a0bStron /*
4220006a0bStron * Prototypes for the three flavors of prompts.
4320006a0bStron * These strings are expanded by pr_expand().
4420006a0bStron */
4520006a0bStron static constant char s_proto[] =
4620006a0bStron "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x..%t";
4720006a0bStron static constant char m_proto[] =
4820006a0bStron "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t";
4920006a0bStron static constant char M_proto[] =
5020006a0bStron "?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";
5120006a0bStron static constant char e_proto[] =
5220006a0bStron "?f%f .?m(%T %i of %m) .?ltlines %lt-%lb?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t";
5320006a0bStron static constant char h_proto[] =
5420006a0bStron "HELP -- ?eEND -- Press g to see it again:Press RETURN for more., or q when done";
5520006a0bStron static constant char w_proto[] =
5620006a0bStron "Waiting for data";
5720006a0bStron static constant char more_proto[] =
5820006a0bStron "--More--(?eEND ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t)";
5920006a0bStron
60824a88bbStron public char constant *prproto[3];
6120006a0bStron public char constant *eqproto = e_proto;
6220006a0bStron public char constant *hproto = h_proto;
6320006a0bStron public char constant *wproto = w_proto;
6420006a0bStron
6520006a0bStron static char message[PROMPT_SIZE];
6620006a0bStron static char *mp;
6720006a0bStron
6820006a0bStron /*
6920006a0bStron * Initialize the prompt prototype strings.
7020006a0bStron */
init_prompt(void)71*838f5788Ssimonb public void init_prompt(void)
7220006a0bStron {
7320006a0bStron prproto[0] = save(s_proto);
7420006a0bStron prproto[1] = save(less_is_more ? more_proto : m_proto);
7520006a0bStron prproto[2] = save(M_proto);
7620006a0bStron eqproto = save(e_proto);
7720006a0bStron hproto = save(h_proto);
7820006a0bStron wproto = save(w_proto);
7920006a0bStron }
8020006a0bStron
8120006a0bStron /*
8220006a0bStron * Append a string to the end of the message.
8320006a0bStron */
ap_str(char * s)84*838f5788Ssimonb static void ap_str(char *s)
8520006a0bStron {
8620006a0bStron int len;
8720006a0bStron
88*838f5788Ssimonb len = (int) strlen(s);
8920006a0bStron if (mp + len >= message + PROMPT_SIZE)
90*838f5788Ssimonb len = (int) (message + PROMPT_SIZE - mp - 1);
9120006a0bStron strncpy(mp, s, len);
9220006a0bStron mp += len;
9320006a0bStron *mp = '\0';
9420006a0bStron }
9520006a0bStron
9620006a0bStron /*
9720006a0bStron * Append a character to the end of the message.
9820006a0bStron */
ap_char(char c)99*838f5788Ssimonb static void ap_char(char c)
10020006a0bStron {
10120006a0bStron char buf[2];
10220006a0bStron
10320006a0bStron buf[0] = c;
10420006a0bStron buf[1] = '\0';
10520006a0bStron ap_str(buf);
10620006a0bStron }
10720006a0bStron
10820006a0bStron /*
10920006a0bStron * Append a POSITION (as a decimal integer) to the end of the message.
11020006a0bStron */
ap_pos(POSITION pos)111*838f5788Ssimonb static void ap_pos(POSITION pos)
11220006a0bStron {
11320006a0bStron char buf[INT_STRLEN_BOUND(pos) + 2];
11420006a0bStron
115*838f5788Ssimonb postoa(pos, buf, 10);
11620006a0bStron ap_str(buf);
11720006a0bStron }
11820006a0bStron
11920006a0bStron /*
12020006a0bStron * Append a line number to the end of the message.
12120006a0bStron */
ap_linenum(LINENUM linenum)122*838f5788Ssimonb static void ap_linenum(LINENUM linenum)
12320006a0bStron {
12420006a0bStron char buf[INT_STRLEN_BOUND(linenum) + 2];
12520006a0bStron
126*838f5788Ssimonb linenumtoa(linenum, buf, 10);
12720006a0bStron ap_str(buf);
12820006a0bStron }
12920006a0bStron
13020006a0bStron /*
13120006a0bStron * Append an integer to the end of the message.
13220006a0bStron */
ap_int(int num)133*838f5788Ssimonb static void ap_int(int num)
13420006a0bStron {
13520006a0bStron char buf[INT_STRLEN_BOUND(num) + 2];
13620006a0bStron
137*838f5788Ssimonb inttoa(num, buf, 10);
13820006a0bStron ap_str(buf);
13920006a0bStron }
14020006a0bStron
14120006a0bStron /*
14220006a0bStron * Append a question mark to the end of the message.
14320006a0bStron */
ap_quest(void)144*838f5788Ssimonb static void ap_quest(void)
14520006a0bStron {
14620006a0bStron ap_str("?");
14720006a0bStron }
14820006a0bStron
14920006a0bStron /*
15020006a0bStron * Return the "current" byte offset in the file.
15120006a0bStron */
curr_byte(int where)152*838f5788Ssimonb static POSITION curr_byte(int where)
15320006a0bStron {
15420006a0bStron POSITION pos;
15520006a0bStron
15620006a0bStron pos = position(where);
15720006a0bStron while (pos == NULL_POSITION && where >= 0 && where < sc_height-1)
15820006a0bStron pos = position(++where);
15920006a0bStron if (pos == NULL_POSITION)
16020006a0bStron pos = ch_length();
16120006a0bStron return (pos);
16220006a0bStron }
16320006a0bStron
16420006a0bStron /*
16520006a0bStron * Return the value of a prototype conditional.
16620006a0bStron * A prototype string may include conditionals which consist of a
16720006a0bStron * question mark followed by a single letter.
16820006a0bStron * Here we decode that letter and return the appropriate boolean value.
16920006a0bStron */
cond(char c,int where)170*838f5788Ssimonb static int cond(char c, int where)
17120006a0bStron {
17220006a0bStron POSITION len;
17320006a0bStron
17420006a0bStron switch (c)
17520006a0bStron {
17620006a0bStron case 'a': /* Anything in the message yet? */
17720006a0bStron return (mp > message);
17820006a0bStron case 'b': /* Current byte offset known? */
17920006a0bStron return (curr_byte(where) != NULL_POSITION);
18020006a0bStron case 'c':
18120006a0bStron return (hshift != 0);
18220006a0bStron case 'e': /* At end of file? */
18320006a0bStron return (eof_displayed());
18420006a0bStron case 'f': /* Filename known? */
185*838f5788Ssimonb case 'g':
18620006a0bStron return (strcmp(get_filename(curr_ifile), "-") != 0);
18720006a0bStron case 'l': /* Line number known? */
18820006a0bStron case 'd': /* Same as l */
189*838f5788Ssimonb if (!linenums)
190*838f5788Ssimonb return 0;
191*838f5788Ssimonb return (currline(where) != 0);
19220006a0bStron case 'L': /* Final line number known? */
19320006a0bStron case 'D': /* Final page number known? */
19420006a0bStron return (linenums && ch_length() != NULL_POSITION);
19520006a0bStron case 'm': /* More than one file? */
19620006a0bStron #if TAGS
19720006a0bStron return (ntags() ? (ntags() > 1) : (nifile() > 1));
19820006a0bStron #else
19920006a0bStron return (nifile() > 1);
20020006a0bStron #endif
20120006a0bStron case 'n': /* First prompt in a new file? */
20220006a0bStron #if TAGS
20320006a0bStron return (ntags() ? 1 : new_file);
20420006a0bStron #else
20520006a0bStron return (new_file);
20620006a0bStron #endif
20720006a0bStron case 'p': /* Percent into file (bytes) known? */
20820006a0bStron return (curr_byte(where) != NULL_POSITION &&
20920006a0bStron ch_length() > 0);
21020006a0bStron case 'P': /* Percent into file (lines) known? */
21120006a0bStron return (currline(where) != 0 &&
21220006a0bStron (len = ch_length()) > 0 &&
21320006a0bStron find_linenum(len) != 0);
21420006a0bStron case 's': /* Size of file known? */
21520006a0bStron case 'B':
21620006a0bStron return (ch_length() != NULL_POSITION);
21720006a0bStron case 'x': /* Is there a "next" file? */
21820006a0bStron #if TAGS
21920006a0bStron if (ntags())
22020006a0bStron return (0);
22120006a0bStron #endif
22220006a0bStron return (next_ifile(curr_ifile) != NULL_IFILE);
22320006a0bStron }
22420006a0bStron return (0);
22520006a0bStron }
22620006a0bStron
22720006a0bStron /*
22820006a0bStron * Decode a "percent" prototype character.
22920006a0bStron * A prototype string may include various "percent" escapes;
23020006a0bStron * that is, a percent sign followed by a single letter.
23120006a0bStron * Here we decode that letter and take the appropriate action,
23220006a0bStron * usually by appending something to the message being built.
23320006a0bStron */
protochar(int c,int where,int iseditproto)234*838f5788Ssimonb static void protochar(int c, int where, int iseditproto)
23520006a0bStron {
23620006a0bStron POSITION pos;
23720006a0bStron POSITION len;
23820006a0bStron int n;
23920006a0bStron LINENUM linenum;
24020006a0bStron LINENUM last_linenum;
24120006a0bStron IFILE h;
242*838f5788Ssimonb char *s;
24320006a0bStron
24420006a0bStron #undef PAGE_NUM
245*838f5788Ssimonb #define PAGE_NUM(linenum) ((((linenum) - 1) / (sc_height - header_lines - 1)) + 1)
24620006a0bStron
24720006a0bStron switch (c)
24820006a0bStron {
24920006a0bStron case 'b': /* Current byte offset */
25020006a0bStron pos = curr_byte(where);
25120006a0bStron if (pos != NULL_POSITION)
25220006a0bStron ap_pos(pos);
25320006a0bStron else
25420006a0bStron ap_quest();
25520006a0bStron break;
25620006a0bStron case 'c':
25720006a0bStron ap_int(hshift);
25820006a0bStron break;
25920006a0bStron case 'd': /* Current page number */
26020006a0bStron linenum = currline(where);
261*838f5788Ssimonb if (linenum > 0 && sc_height > header_lines + 1)
26220006a0bStron ap_linenum(PAGE_NUM(linenum));
26320006a0bStron else
26420006a0bStron ap_quest();
26520006a0bStron break;
26620006a0bStron case 'D': /* Final page number */
26720006a0bStron /* Find the page number of the last byte in the file (len-1). */
26820006a0bStron len = ch_length();
26920006a0bStron if (len == NULL_POSITION)
27020006a0bStron ap_quest();
27120006a0bStron else if (len == 0)
27220006a0bStron /* An empty file has no pages. */
27320006a0bStron ap_linenum(0);
27420006a0bStron else
27520006a0bStron {
27620006a0bStron linenum = find_linenum(len - 1);
27720006a0bStron if (linenum <= 0)
27820006a0bStron ap_quest();
27920006a0bStron else
28020006a0bStron ap_linenum(PAGE_NUM(linenum));
28120006a0bStron }
28220006a0bStron break;
28320006a0bStron #if EDITOR
28420006a0bStron case 'E': /* Editor name */
28520006a0bStron ap_str(editor);
28620006a0bStron break;
28720006a0bStron #endif
28820006a0bStron case 'f': /* File name */
28920006a0bStron ap_str(get_filename(curr_ifile));
29020006a0bStron break;
29120006a0bStron case 'F': /* Last component of file name */
29220006a0bStron ap_str(last_component(get_filename(curr_ifile)));
29320006a0bStron break;
294*838f5788Ssimonb case 'g': /* Shell-escaped file name */
295*838f5788Ssimonb s = shell_quote(get_filename(curr_ifile));
296*838f5788Ssimonb ap_str(s);
297*838f5788Ssimonb free(s);
298*838f5788Ssimonb break;
29920006a0bStron case 'i': /* Index into list of files */
30020006a0bStron #if TAGS
30120006a0bStron if (ntags())
30220006a0bStron ap_int(curr_tag());
30320006a0bStron else
30420006a0bStron #endif
30520006a0bStron ap_int(get_index(curr_ifile));
30620006a0bStron break;
30720006a0bStron case 'l': /* Current line number */
30820006a0bStron linenum = currline(where);
30920006a0bStron if (linenum != 0)
310*838f5788Ssimonb ap_linenum(vlinenum(linenum));
31120006a0bStron else
31220006a0bStron ap_quest();
31320006a0bStron break;
31420006a0bStron case 'L': /* Final line number */
31520006a0bStron len = ch_length();
31620006a0bStron if (len == NULL_POSITION || len == ch_zero() ||
31720006a0bStron (linenum = find_linenum(len)) <= 0)
31820006a0bStron ap_quest();
31920006a0bStron else
320*838f5788Ssimonb ap_linenum(vlinenum(linenum-1));
32120006a0bStron break;
32220006a0bStron case 'm': /* Number of files */
32320006a0bStron #if TAGS
32420006a0bStron n = ntags();
32520006a0bStron if (n)
32620006a0bStron ap_int(n);
32720006a0bStron else
32820006a0bStron #endif
32920006a0bStron ap_int(nifile());
33020006a0bStron break;
33120006a0bStron case 'p': /* Percent into file (bytes) */
33220006a0bStron pos = curr_byte(where);
33320006a0bStron len = ch_length();
33420006a0bStron if (pos != NULL_POSITION && len > 0)
33520006a0bStron ap_int(percentage(pos,len));
33620006a0bStron else
33720006a0bStron ap_quest();
33820006a0bStron break;
33920006a0bStron case 'P': /* Percent into file (lines) */
34020006a0bStron linenum = currline(where);
34120006a0bStron if (linenum == 0 ||
34220006a0bStron (len = ch_length()) == NULL_POSITION || len == ch_zero() ||
34320006a0bStron (last_linenum = find_linenum(len)) <= 0)
34420006a0bStron ap_quest();
34520006a0bStron else
34620006a0bStron ap_int(percentage(linenum, last_linenum));
34720006a0bStron break;
34820006a0bStron case 's': /* Size of file */
34920006a0bStron case 'B':
35020006a0bStron len = ch_length();
35120006a0bStron if (len != NULL_POSITION)
35220006a0bStron ap_pos(len);
35320006a0bStron else
35420006a0bStron ap_quest();
35520006a0bStron break;
35620006a0bStron case 't': /* Truncate trailing spaces in the message */
35720006a0bStron while (mp > message && mp[-1] == ' ')
35820006a0bStron mp--;
35920006a0bStron *mp = '\0';
36020006a0bStron break;
36120006a0bStron case 'T': /* Type of list */
36220006a0bStron #if TAGS
36320006a0bStron if (ntags())
36420006a0bStron ap_str("tag");
36520006a0bStron else
36620006a0bStron #endif
36720006a0bStron ap_str("file");
36820006a0bStron break;
36920006a0bStron case 'x': /* Name of next file */
37020006a0bStron h = next_ifile(curr_ifile);
37120006a0bStron if (h != NULL_IFILE)
37220006a0bStron ap_str(get_filename(h));
37320006a0bStron else
37420006a0bStron ap_quest();
37520006a0bStron break;
37620006a0bStron }
37720006a0bStron }
37820006a0bStron
37920006a0bStron /*
38020006a0bStron * Skip a false conditional.
38120006a0bStron * When a false condition is found (either a false IF or the ELSE part
38220006a0bStron * of a true IF), this routine scans the prototype string to decide
38320006a0bStron * where to resume parsing the string.
38420006a0bStron * We must keep track of nested IFs and skip them properly.
38520006a0bStron */
skipcond(constant char * p)386*838f5788Ssimonb static constant char * skipcond(constant char *p)
38720006a0bStron {
388*838f5788Ssimonb int iflevel;
38920006a0bStron
39020006a0bStron /*
39120006a0bStron * We came in here after processing a ? or :,
39220006a0bStron * so we start nested one level deep.
39320006a0bStron */
39420006a0bStron iflevel = 1;
39520006a0bStron
39620006a0bStron for (;;) switch (*++p)
39720006a0bStron {
39820006a0bStron case '?':
39920006a0bStron /*
40020006a0bStron * Start of a nested IF.
40120006a0bStron */
40220006a0bStron iflevel++;
40320006a0bStron break;
40420006a0bStron case ':':
40520006a0bStron /*
40620006a0bStron * Else.
40720006a0bStron * If this matches the IF we came in here with,
40820006a0bStron * then we're done.
40920006a0bStron */
41020006a0bStron if (iflevel == 1)
41120006a0bStron return (p);
41220006a0bStron break;
41320006a0bStron case '.':
41420006a0bStron /*
41520006a0bStron * Endif.
41620006a0bStron * If this matches the IF we came in here with,
41720006a0bStron * then we're done.
41820006a0bStron */
41920006a0bStron if (--iflevel == 0)
42020006a0bStron return (p);
42120006a0bStron break;
42220006a0bStron case '\\':
42320006a0bStron /*
42420006a0bStron * Backslash escapes the next character.
42520006a0bStron */
426*838f5788Ssimonb if (p[1] != '\0')
42720006a0bStron ++p;
42820006a0bStron break;
42920006a0bStron case '\0':
43020006a0bStron /*
43120006a0bStron * Whoops. Hit end of string.
43220006a0bStron * This is a malformed conditional, but just treat it
43320006a0bStron * as if all active conditionals ends here.
43420006a0bStron */
43520006a0bStron return (p-1);
43620006a0bStron }
43720006a0bStron /*NOTREACHED*/
43820006a0bStron }
43920006a0bStron
44020006a0bStron /*
44120006a0bStron * Decode a char that represents a position on the screen.
44220006a0bStron */
wherechar(char constant * p,int * wp)443*838f5788Ssimonb static constant char * wherechar(char constant *p, int *wp)
44420006a0bStron {
44520006a0bStron switch (*p)
44620006a0bStron {
44720006a0bStron case 'b': case 'd': case 'l': case 'p': case 'P':
44820006a0bStron switch (*++p)
44920006a0bStron {
45020006a0bStron case 't': *wp = TOP; break;
45120006a0bStron case 'm': *wp = MIDDLE; break;
45220006a0bStron case 'b': *wp = BOTTOM; break;
45320006a0bStron case 'B': *wp = BOTTOM_PLUS_ONE; break;
454*838f5788Ssimonb case 'j': *wp = sindex_from_sline(jump_sline); break;
45520006a0bStron default: *wp = TOP; p--; break;
45620006a0bStron }
45720006a0bStron }
45820006a0bStron return (p);
45920006a0bStron }
46020006a0bStron
46120006a0bStron /*
46220006a0bStron * Construct a message based on a prototype string.
46320006a0bStron */
pr_expand(constant char * proto)464*838f5788Ssimonb public char * pr_expand(constant char *proto)
46520006a0bStron {
466*838f5788Ssimonb constant char *p;
467*838f5788Ssimonb int c;
46820006a0bStron int where;
46920006a0bStron
47020006a0bStron mp = message;
47120006a0bStron
47220006a0bStron if (*proto == '\0')
47320006a0bStron return ("");
47420006a0bStron
47520006a0bStron for (p = proto; *p != '\0'; p++)
47620006a0bStron {
47720006a0bStron switch (*p)
47820006a0bStron {
47920006a0bStron default: /* Just put the character in the message */
48020006a0bStron ap_char(*p);
48120006a0bStron break;
48220006a0bStron case '\\': /* Backslash escapes the next character */
483*838f5788Ssimonb if (p[1] != '\0')
484*838f5788Ssimonb ap_char(*++p);
48520006a0bStron break;
48620006a0bStron case '?': /* Conditional (IF) */
48720006a0bStron if ((c = *++p) == '\0')
48820006a0bStron --p;
48920006a0bStron else
49020006a0bStron {
49120006a0bStron where = 0;
49220006a0bStron p = wherechar(p, &where);
49320006a0bStron if (!cond(c, where))
49420006a0bStron p = skipcond(p);
49520006a0bStron }
49620006a0bStron break;
49720006a0bStron case ':': /* ELSE */
49820006a0bStron p = skipcond(p);
49920006a0bStron break;
50020006a0bStron case '.': /* ENDIF */
50120006a0bStron break;
50220006a0bStron case '%': /* Percent escape */
50320006a0bStron if ((c = *++p) == '\0')
50420006a0bStron --p;
50520006a0bStron else
50620006a0bStron {
50720006a0bStron where = 0;
50820006a0bStron p = wherechar(p, &where);
50920006a0bStron protochar(c, where,
51020006a0bStron #if EDITOR
51120006a0bStron (proto == editproto));
51220006a0bStron #else
51320006a0bStron 0);
51420006a0bStron #endif
51520006a0bStron
51620006a0bStron }
51720006a0bStron break;
51820006a0bStron }
51920006a0bStron }
52020006a0bStron
52120006a0bStron if (mp == message)
52220006a0bStron return ("");
52320006a0bStron return (message);
52420006a0bStron }
52520006a0bStron
52620006a0bStron /*
52720006a0bStron * Return a message suitable for printing by the "=" command.
52820006a0bStron */
eq_message(void)529*838f5788Ssimonb public char * eq_message(void)
53020006a0bStron {
531*838f5788Ssimonb return (pr_expand(eqproto));
53220006a0bStron }
53320006a0bStron
53420006a0bStron /*
53520006a0bStron * Return a prompt.
53620006a0bStron * This depends on the prompt type (SHORT, MEDIUM, LONG), etc.
53720006a0bStron * If we can't come up with an appropriate prompt, return NULL
53820006a0bStron * and the caller will prompt with a colon.
53920006a0bStron */
pr_string(void)540*838f5788Ssimonb public char * pr_string(void)
54120006a0bStron {
54220006a0bStron char *prompt;
54320006a0bStron int type;
54420006a0bStron
54520006a0bStron type = (!less_is_more) ? pr_type : pr_type ? 0 : 1;
54620006a0bStron prompt = pr_expand((ch_getflags() & CH_HELPFILE) ?
547*838f5788Ssimonb hproto : prproto[type]);
54820006a0bStron new_file = 0;
54920006a0bStron return (prompt);
55020006a0bStron }
55120006a0bStron
55220006a0bStron /*
55320006a0bStron * Return a message suitable for printing while waiting in the F command.
55420006a0bStron */
wait_message(void)555*838f5788Ssimonb public char * wait_message(void)
55620006a0bStron {
557*838f5788Ssimonb return (pr_expand(wproto));
55820006a0bStron }
559