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>
5 *
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.
8 *
9 * For more information, see the README file.
10 */
11
12 /*
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.
19 */
20
21 #include "less.h"
22 #include "position.h"
23
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;
36
37 /*
38 * Prototypes for the three flavors of prompts.
39 * These strings are expanded by pr_expand().
40 */
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]";
63
64 char *prproto[3];
65 char const *eqproto = e_proto;
66 char const *hproto = h_proto;
67 char const *wproto = w_proto;
68
69 static char message[PROMPT_SIZE];
70 static char *mp;
71
72 /*
73 * Initialize the prompt prototype strings.
74 */
75 void
init_prompt(void)76 init_prompt(void)
77 {
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);
84 }
85
86 /*
87 * Append a string to the end of the message.
88 */
89 static void
ap_str(char * s)90 ap_str(char *s)
91 {
92 int len;
93
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';
100 }
101
102 /*
103 * Append a character to the end of the message.
104 */
105 static void
ap_char(char c)106 ap_char(char c)
107 {
108 char buf[2];
109
110 buf[0] = c;
111 buf[1] = '\0';
112 ap_str(buf);
113 }
114
115 /*
116 * Append a off_t (as a decimal integer) to the end of the message.
117 */
118 static void
ap_pos(off_t pos)119 ap_pos(off_t pos)
120 {
121 char buf[23];
122
123 postoa(pos, buf, sizeof(buf));
124 ap_str(buf);
125 }
126
127 /*
128 * Append an integer to the end of the message.
129 */
130 static void
ap_int(int num)131 ap_int(int num)
132 {
133 char buf[13];
134
135 inttoa(num, buf, sizeof buf);
136 ap_str(buf);
137 }
138
139 /*
140 * Append a question mark to the end of the message.
141 */
142 static void
ap_quest(void)143 ap_quest(void)
144 {
145 ap_str("?");
146 }
147
148 /*
149 * Return the "current" byte offset in the file.
150 */
151 static off_t
curr_byte(int where)152 curr_byte(int where)
153 {
154 off_t pos;
155
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);
162 }
163
164 /*
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.
169 */
170 static int
cond(char c,int where)171 cond(char c, int where)
172 {
173 off_t len;
174
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);
208 }
209 return (0);
210 }
211
212 /*
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.
218 */
219 static void
protochar(int c,int where)220 protochar(int c, int where)
221 {
222 off_t pos;
223 off_t len;
224 int n;
225 off_t linenum;
226 off_t last_linenum;
227 IFILE h;
228
229 #undef PAGE_NUM
230 #define PAGE_NUM(linenum) ((((linenum) - 1) / (sc_height - 1)) + 1)
231
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));
264 }
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;
346 }
347 }
348
349 /*
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.
355 */
356 static const char *
skipcond(const char * p)357 skipcond(const char *p)
358 {
359 int iflevel;
360
361 /*
362 * We came in here after processing a ? or :,
363 * so we start nested one level deep.
364 */
365 iflevel = 1;
366
367 for (;;) {
368 switch (*++p) {
369 case '?':
370 /*
371 * Start of a nested IF.
372 */
373 iflevel++;
374 break;
375 case ':':
376 /*
377 * Else.
378 * If this matches the IF we came in here with,
379 * then we're done.
380 */
381 if (iflevel == 1)
382 return (p);
383 break;
384 case '.':
385 /*
386 * Endif.
387 * If this matches the IF we came in here with,
388 * then we're done.
389 */
390 if (--iflevel == 0)
391 return (p);
392 break;
393 case '\\':
394 /*
395 * Backslash escapes the next character.
396 */
397 ++p;
398 break;
399 case '\0':
400 /*
401 * Whoops. Hit end of string.
402 * This is a malformed conditional, but just treat it
403 * as if all active conditionals ends here.
404 */
405 return (p-1);
406 }
407 }
408 }
409
410 /*
411 * Decode a char that represents a position on the screen.
412 */
413 static const char *
wherechar(const char * p,int * wp)414 wherechar(const char *p, int *wp)
415 {
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;
425 }
426 }
427 return (p);
428 }
429
430 /*
431 * Construct a message based on a prototype string.
432 */
433 char *
pr_expand(const char * proto,int maxwidth)434 pr_expand(const char *proto, int maxwidth)
435 {
436 const char *p;
437 int c;
438 int where;
439
440 mp = message;
441
442 if (*proto == '\0')
443 return ("");
444
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);
462 }
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);
476 }
477 break;
478 }
479 }
480
481 if (*message == '\0')
482 return ("");
483 if (maxwidth > 0 && mp >= message + maxwidth) {
484 /*
485 * Message is too long.
486 * Return just the final portion of it.
487 */
488 return (mp - maxwidth);
489 }
490 return (message);
491 }
492
493 /*
494 * Return a message suitable for printing by the "=" command.
495 */
496 char *
eq_message(void)497 eq_message(void)
498 {
499 return (pr_expand(eqproto, 0));
500 }
501
502 /*
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.
507 */
508 char *
prompt_string(void)509 prompt_string(void)
510 {
511 char *prompt;
512 int type;
513
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);
519 }
520
521 /*
522 * Return a message suitable for printing while waiting in the F command.
523 */
524 char *
wait_message(void)525 wait_message(void)
526 {
527 return (pr_expand(wproto, sc_width-so_s_width-so_e_width-2));
528 }
529