import less(1)
[unleashed/tickless.git] / bin / less / prompt.c
blobbe343ba6d889d5c166a90470255c07f6cfa033d0
1 /*
2 * Copyright (C) 1984-2012 Mark Nudelman
3 * Modified for use with illumos by Garrett D'Amore.
4 * Copyright 2014 Garrett D'Amore <garrett@damore.org>
6 * You may distribute under the terms of either the GNU General Public
7 * License or the Less License, as specified in the README file.
9 * For more information, see the README file.
13 * Prompting and other messages.
14 * There are three flavors of prompts, SHORT, MEDIUM and LONG,
15 * selected by the -m/-M options.
16 * There is also the "equals message", printed by the = command.
17 * A prompt is a message composed of various pieces, such as the
18 * name of the file being viewed, the percentage into the file, etc.
21 #include "less.h"
22 #include "position.h"
24 extern int pr_type;
25 extern int new_file;
26 extern int sc_width;
27 extern int so_s_width, so_e_width;
28 extern int linenums;
29 extern int hshift;
30 extern int sc_height;
31 extern int jump_sline;
32 extern int less_is_more;
33 extern IFILE curr_ifile;
34 extern char *editor;
35 extern char *editproto;
38 * Prototypes for the three flavors of prompts.
39 * These strings are expanded by pr_expand().
41 static const char s_proto[] =
42 "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x..%t";
43 static const char m_proto[] =
44 "?n?f%f .?m(%T %i of %m) ..?e(END) "
45 "?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t";
46 static const char M_proto[] =
47 "?f%f .?n?m(%T %i of %m) ..?"
48 "ltlines %lt-%lb?L/%L. :byte %bB?s/%s. .?e(END)"
49 " ?x- Next\\: %x.:?pB%pB\\%..%t";
50 static const char e_proto[] =
51 "?f%f .?m(%T %i of %m) .?ltlines "
52 "%lt-%lb?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t";
53 static const char h_proto[] =
54 "HELP -- ?eEND -- Press g to see it again:"
55 "Press RETURN for more., or q when done";
56 static const char w_proto[] =
57 "Waiting for data";
58 static const char more_proto[] =
59 "%f (?eEND ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t)";
60 static const char more_M_proto[] =
61 "%f (?eEND ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t)"
62 "[Press space to continue, q to quit, h for help]";
64 char *prproto[3];
65 char const *eqproto = e_proto;
66 char const *hproto = h_proto;
67 char const *wproto = w_proto;
69 static char message[PROMPT_SIZE];
70 static char *mp;
73 * Initialize the prompt prototype strings.
75 void
76 init_prompt(void)
78 prproto[0] = estrdup(s_proto);
79 prproto[1] = estrdup(less_is_more ? more_proto : m_proto);
80 prproto[2] = estrdup(less_is_more ? more_M_proto : M_proto);
81 eqproto = estrdup(e_proto);
82 hproto = estrdup(h_proto);
83 wproto = estrdup(w_proto);
87 * Append a string to the end of the message.
89 static void
90 ap_str(char *s)
92 int len;
94 len = strlen(s);
95 if (mp + len >= message + PROMPT_SIZE)
96 len = message + PROMPT_SIZE - mp - 1;
97 (void) strncpy(mp, s, len);
98 mp += len;
99 *mp = '\0';
103 * Append a character to the end of the message.
105 static void
106 ap_char(char c)
108 char buf[2];
110 buf[0] = c;
111 buf[1] = '\0';
112 ap_str(buf);
116 * Append a off_t (as a decimal integer) to the end of the message.
118 static void
119 ap_pos(off_t pos)
121 char buf[23];
123 postoa(pos, buf, sizeof(buf));
124 ap_str(buf);
128 * Append an integer to the end of the message.
130 static void
131 ap_int(int num)
133 char buf[13];
135 inttoa(num, buf, sizeof buf);
136 ap_str(buf);
140 * Append a question mark to the end of the message.
142 static void
143 ap_quest(void)
145 ap_str("?");
149 * Return the "current" byte offset in the file.
151 static off_t
152 curr_byte(int where)
154 off_t pos;
156 pos = position(where);
157 while (pos == -1 && where >= 0 && where < sc_height-1)
158 pos = position(++where);
159 if (pos == -1)
160 pos = ch_length();
161 return (pos);
165 * Return the value of a prototype conditional.
166 * A prototype string may include conditionals which consist of a
167 * question mark followed by a single letter.
168 * Here we decode that letter and return the appropriate boolean value.
170 static int
171 cond(char c, int where)
173 off_t len;
175 switch (c) {
176 case 'a': /* Anything in the message yet? */
177 return (*message != '\0');
178 case 'b': /* Current byte offset known? */
179 return (curr_byte(where) != -1);
180 case 'c':
181 return (hshift != 0);
182 case 'e': /* At end of file? */
183 return (eof_displayed());
184 case 'f': /* Filename known? */
185 return (strcmp(get_filename(curr_ifile), "-") != 0);
186 case 'l': /* Line number known? */
187 case 'd': /* Same as l */
188 return (linenums);
189 case 'L': /* Final line number known? */
190 case 'D': /* Final page number known? */
191 return (linenums && ch_length() != -1);
192 case 'm': /* More than one file? */
193 return (ntags() ? (ntags() > 1) : (nifile() > 1));
194 case 'n': /* First prompt in a new file? */
195 return (ntags() ? 1 : new_file);
196 case 'p': /* Percent into file (bytes) known? */
197 return (curr_byte(where) != -1 && ch_length() > 0);
198 case 'P': /* Percent into file (lines) known? */
199 return (currline(where) != 0 &&
200 (len = ch_length()) > 0 && find_linenum(len) != 0);
201 case 's': /* Size of file known? */
202 case 'B':
203 return (ch_length() != -1);
204 case 'x': /* Is there a "next" file? */
205 if (ntags())
206 return (0);
207 return (next_ifile(curr_ifile) != NULL);
209 return (0);
213 * Decode a "percent" prototype character.
214 * A prototype string may include various "percent" escapes;
215 * that is, a percent sign followed by a single letter.
216 * Here we decode that letter and take the appropriate action,
217 * usually by appending something to the message being built.
219 static void
220 protochar(int c, int where)
222 off_t pos;
223 off_t len;
224 int n;
225 off_t linenum;
226 off_t last_linenum;
227 IFILE h;
229 #undef PAGE_NUM
230 #define PAGE_NUM(linenum) ((((linenum) - 1) / (sc_height - 1)) + 1)
232 switch (c) {
233 case 'b': /* Current byte offset */
234 pos = curr_byte(where);
235 if (pos != -1)
236 ap_pos(pos);
237 else
238 ap_quest();
239 break;
240 case 'c':
241 ap_int(hshift);
242 break;
243 case 'd': /* Current page number */
244 linenum = currline(where);
245 if (linenum > 0 && sc_height > 1)
246 ap_pos(PAGE_NUM(linenum));
247 else
248 ap_quest();
249 break;
250 case 'D': /* Final page number */
251 /* Find the page number of the last byte in the file (len-1). */
252 len = ch_length();
253 if (len == -1) {
254 ap_quest();
255 } else if (len == 0) {
256 /* An empty file has no pages. */
257 ap_pos(0);
258 } else {
259 linenum = find_linenum(len - 1);
260 if (linenum <= 0)
261 ap_quest();
262 else
263 ap_pos(PAGE_NUM(linenum));
265 break;
266 case 'E': /* Editor name */
267 ap_str(editor);
268 break;
269 case 'f': /* File name */
270 ap_str(get_filename(curr_ifile));
271 break;
272 case 'F': /* Last component of file name */
273 ap_str(last_component(get_filename(curr_ifile)));
274 break;
275 case 'i': /* Index into list of files */
276 if (ntags())
277 ap_int(curr_tag());
278 else
279 ap_int(get_index(curr_ifile));
280 break;
281 case 'l': /* Current line number */
282 linenum = currline(where);
283 if (linenum != 0)
284 ap_pos(linenum);
285 else
286 ap_quest();
287 break;
288 case 'L': /* Final line number */
289 len = ch_length();
290 if (len == -1 || len == ch_zero() ||
291 (linenum = find_linenum(len)) <= 0)
292 ap_quest();
293 else
294 ap_pos(linenum-1);
295 break;
296 case 'm': /* Number of files */
297 n = ntags();
298 if (n)
299 ap_int(n);
300 else
301 ap_int(nifile());
302 break;
303 case 'p': /* Percent into file (bytes) */
304 pos = curr_byte(where);
305 len = ch_length();
306 if (pos != -1 && len > 0)
307 ap_int(percentage(pos, len));
308 else
309 ap_quest();
310 break;
311 case 'P': /* Percent into file (lines) */
312 linenum = currline(where);
313 if (linenum == 0 ||
314 (len = ch_length()) == -1 || len == ch_zero() ||
315 (last_linenum = find_linenum(len)) <= 0)
316 ap_quest();
317 else
318 ap_int(percentage(linenum, last_linenum));
319 break;
320 case 's': /* Size of file */
321 case 'B':
322 len = ch_length();
323 if (len != -1)
324 ap_pos(len);
325 else
326 ap_quest();
327 break;
328 case 't': /* Truncate trailing spaces in the message */
329 while (mp > message && mp[-1] == ' ')
330 mp--;
331 *mp = '\0';
332 break;
333 case 'T': /* Type of list */
334 if (ntags())
335 ap_str("tag");
336 else
337 ap_str("file");
338 break;
339 case 'x': /* Name of next file */
340 h = next_ifile(curr_ifile);
341 if (h != NULL)
342 ap_str(get_filename(h));
343 else
344 ap_quest();
345 break;
350 * Skip a false conditional.
351 * When a false condition is found (either a false IF or the ELSE part
352 * of a true IF), this routine scans the prototype string to decide
353 * where to resume parsing the string.
354 * We must keep track of nested IFs and skip them properly.
356 static const char *
357 skipcond(const char *p)
359 int iflevel;
362 * We came in here after processing a ? or :,
363 * so we start nested one level deep.
365 iflevel = 1;
367 for (;;) {
368 switch (*++p) {
369 case '?':
371 * Start of a nested IF.
373 iflevel++;
374 break;
375 case ':':
377 * Else.
378 * If this matches the IF we came in here with,
379 * then we're done.
381 if (iflevel == 1)
382 return (p);
383 break;
384 case '.':
386 * Endif.
387 * If this matches the IF we came in here with,
388 * then we're done.
390 if (--iflevel == 0)
391 return (p);
392 break;
393 case '\\':
395 * Backslash escapes the next character.
397 ++p;
398 break;
399 case '\0':
401 * Whoops. Hit end of string.
402 * This is a malformed conditional, but just treat it
403 * as if all active conditionals ends here.
405 return (p-1);
411 * Decode a char that represents a position on the screen.
413 static const char *
414 wherechar(const char *p, int *wp)
416 switch (*p) {
417 case 'b': case 'd': case 'l': case 'p': case 'P':
418 switch (*++p) {
419 case 't': *wp = TOP; break;
420 case 'm': *wp = MIDDLE; break;
421 case 'b': *wp = BOTTOM; break;
422 case 'B': *wp = BOTTOM_PLUS_ONE; break;
423 case 'j': *wp = adjsline(jump_sline); break;
424 default: *wp = TOP; p--; break;
427 return (p);
431 * Construct a message based on a prototype string.
433 char *
434 pr_expand(const char *proto, int maxwidth)
436 const char *p;
437 int c;
438 int where;
440 mp = message;
442 if (*proto == '\0')
443 return ("");
445 for (p = proto; *p != '\0'; p++) {
446 switch (*p) {
447 default: /* Just put the character in the message */
448 ap_char(*p);
449 break;
450 case '\\': /* Backslash escapes the next character */
451 p++;
452 ap_char(*p);
453 break;
454 case '?': /* Conditional (IF) */
455 if ((c = *++p) == '\0') {
456 --p;
457 } else {
458 where = 0;
459 p = wherechar(p, &where);
460 if (!cond(c, where))
461 p = skipcond(p);
463 break;
464 case ':': /* ELSE */
465 p = skipcond(p);
466 break;
467 case '.': /* ENDIF */
468 break;
469 case '%': /* Percent escape */
470 if ((c = *++p) == '\0') {
471 --p;
472 } else {
473 where = 0;
474 p = wherechar(p, &where);
475 protochar(c, where);
477 break;
481 if (*message == '\0')
482 return ("");
483 if (maxwidth > 0 && mp >= message + maxwidth) {
485 * Message is too long.
486 * Return just the final portion of it.
488 return (mp - maxwidth);
490 return (message);
494 * Return a message suitable for printing by the "=" command.
496 char *
497 eq_message(void)
499 return (pr_expand(eqproto, 0));
503 * Return a prompt.
504 * This depends on the prompt type (SHORT, MEDIUM, LONG), etc.
505 * If we can't come up with an appropriate prompt, return NULL
506 * and the caller will prompt with a colon.
508 char *
509 prompt_string(void)
511 char *prompt;
512 int type;
514 type = pr_type;
515 prompt = pr_expand((ch_getflags() & CH_HELPFILE) ?
516 hproto : prproto[type], sc_width-so_s_width-so_e_width-2);
517 new_file = 0;
518 return (prompt);
522 * Return a message suitable for printing while waiting in the F command.
524 char *
525 wait_message(void)
527 return (pr_expand(wproto, sc_width-so_s_width-so_e_width-2));