GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/less/less/../prompt.c Lines: 71 181 39.2 %
Date: 2017-11-13 Branches: 37 138 26.8 %

Line Branch Exec Source
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
76
init_prompt(void)
77
{
78
16
	prproto[0] = estrdup(s_proto);
79
8
	prproto[1] = estrdup(less_is_more ? more_proto : m_proto);
80
8
	prproto[2] = estrdup(less_is_more ? more_M_proto : M_proto);
81
8
	eqproto = estrdup(e_proto);
82
8
	hproto = estrdup(h_proto);
83
8
	wproto = estrdup(w_proto);
84
8
}
85
86
/*
87
 * Append a string to the end of the message.
88
 */
89
static void
90
ap_str(char *s)
91
{
92
	int len;
93
94
248
	len = strlen(s);
95
124
	if (mp + len >= message + PROMPT_SIZE)
96
		len = message + PROMPT_SIZE - mp - 1;
97
124
	(void) strncpy(mp, s, len);
98
124
	mp += len;
99
124
	*mp = '\0';
100
124
}
101
102
/*
103
 * Append a character to the end of the message.
104
 */
105
static void
106
ap_char(char c)
107
{
108
232
	char buf[2];
109
110
116
	buf[0] = c;
111
116
	buf[1] = '\0';
112
116
	ap_str(buf);
113
116
}
114
115
/*
116
 * Append a off_t (as a decimal integer) to the end of the message.
117
 */
118
static void
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
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
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
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
171
cond(char c, int where)
172
{
173
	off_t len;
174
175




232
	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
41
		return (eof_displayed());
184
	case 'f':	/* Filename known? */
185
8
		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
24
		return (ntags() ? (ntags() > 1) : (nifile() > 1));
194
	case 'n':	/* First prompt in a new file? */
195
41
		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
18
		if (ntags())
206
			return (0);
207
18
		return (next_ifile(curr_ifile) != NULL);
208
	}
209
	return (0);
210
116
}
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
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




147
	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
8
		ap_str(get_filename(curr_ifile));
271
8
		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

238
		while (mp > message && mp[-1] == ' ')
330
23
			mp--;
331
41
		*mp = '\0';
332
41
		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
49
}
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 *
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
1652
	for (;;) {
368

1789
		switch (*++p) {
369
		case '?':
370
			/*
371
			 * Start of a nested IF.
372
			 */
373
89
			iflevel++;
374
89
			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
171
			if (--iflevel == 0)
391
82
				return (p);
392
			break;
393
		case '\\':
394
			/*
395
			 * Backslash escapes the next character.
396
			 */
397
41
			++p;
398
41
			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
82
}
409
410
/*
411
 * Decode a char that represents a position on the screen.
412
 */
413
static const char *
414
wherechar(const char *p, int *wp)
415
{
416

330
	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
165
	return (p);
428
}
429
430
/*
431
 * Construct a message based on a prototype string.
432
 */
433
char *
434
pr_expand(const char *proto, int maxwidth)
435
{
436
	const char *p;
437
	int c;
438
82
	int where;
439
440
41
	mp = message;
441
442
41
	if (*proto == '\0')
443
		return ("");
444
445
712
	for (p = proto; *p != '\0'; p++) {
446

596
		switch (*p) {
447
		default:	/* Just put the character in the message */
448
116
			ap_char(*p);
449
116
			break;
450
		case '\\':	/* Backslash escapes the next character */
451
			p++;
452
			ap_char(*p);
453
			break;
454
		case '?':	/* Conditional (IF) */
455
116
			if ((c = *++p) == '\0') {
456
				--p;
457
			} else {
458
116
				where = 0;
459
116
				p = wherechar(p, &where);
460
116
				if (!cond(c, where))
461
82
					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
49
			if ((c = *++p) == '\0') {
471
				--p;
472
			} else {
473
49
				where = 0;
474
49
				p = wherechar(p, &where);
475
49
				protochar(c, where);
476
			}
477
			break;
478
		}
479
	}
480
481
41
	if (*message == '\0')
482
18
		return ("");
483

46
	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
23
	return (message);
491
41
}
492
493
/*
494
 * Return a message suitable for printing by the "=" command.
495
 */
496
char *
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 *
509
prompt_string(void)
510
{
511
	char *prompt;
512
	int type;
513
514
82
	type = pr_type;
515
123
	prompt = pr_expand((ch_getflags() & CH_HELPFILE) ?
516
82
	    hproto : prproto[type], sc_width-so_s_width-so_e_width-2);
517
41
	new_file = 0;
518
41
	return (prompt);
519
}
520
521
/*
522
 * Return a message suitable for printing while waiting in the F command.
523
 */
524
char *
525
wait_message(void)
526
{
527
	return (pr_expand(wproto, sc_width-so_s_width-so_e_width-2));
528
}