386BSD 0.1 development
[unix-history] / usr / othersrc / public / less-177 / prompt.c
CommitLineData
77e25d77
WJ
1/*
2 * Prompting and other messages.
3 * There are three flavors of prompts, SHORT, MEDIUM and LONG,
4 * selected by the -m/-M options.
5 * There is also the "equals message", printed by the = command.
6 * A prompt is a message composed of various pieces, such as the
7 * name of the file being viewed, the percentage into the file, etc.
8 */
9
10#include "less.h"
11#include "position.h"
12
13extern int pr_type;
14extern int hit_eof;
15extern int new_file;
16extern int sc_width;
17extern int so_s_width, so_e_width;
18extern int linenums;
19extern int sc_height;
20extern int jump_sline;
21extern IFILE curr_ifile;
22#if EDITOR
23extern char *editor;
24#endif
25
26/*
27 * Prototypes for the three flavors of prompts.
28 * These strings are expanded by pr_expand().
29 */
30static char s_proto[] =
31 "?n?f%f .?m(file %i of %m) ..?e(END) ?x- Next\\: %x..%t";
32static char m_proto[] =
33 "?n?f%f .?m(file %i of %m) ..?e(END) ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t";
34static char M_proto[] =
35 "?f%f .?n?m(file %i of %m) ..?ltline %lt?L/%L. :byte %bB?s/%s. .?e(END) ?x- Next\\: %x.:?pB%pB\\%..%t";
36static char e_proto[] =
37 "?f%f .?m(file %i of %m) .?ltline %lt?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t";
38
39public char *prproto[3];
40public char *eqproto = e_proto;
41
42static char message[250];
43static char *mp;
44
45/*
46 * Initialize the prompt prototype strings.
47 */
48 public void
49init_prompt()
50{
51 prproto[0] = save(s_proto);
52 prproto[1] = save(m_proto);
53 prproto[2] = save(M_proto);
54 eqproto = save(e_proto);
55}
56
57/*
58 * Set the message pointer to the end of the message string.
59 */
60 static void
61setmp()
62{
63 while (*mp != '\0')
64 mp++;
65}
66
67/*
68 * Append a POSITION (as a decimal integer) to the end of the message.
69 */
70 static void
71ap_pos(pos)
72 POSITION pos;
73{
74 sprintf(mp, "%ld", (long)pos);
75 setmp();
76}
77
78/*
79 * Append an integer to the end of the message.
80 */
81 static void
82ap_int(n)
83 int n;
84{
85 sprintf(mp, "%d", n);
86 setmp();
87}
88
89/*
90 * Append a string to the end of the message.
91 */
92 static void
93ap_str(s)
94 char *s;
95{
96 strtcpy(mp, s, (unsigned int)(&message[sizeof(message)] - mp));
97 setmp();
98}
99
100/*
101 * Append a question mark to the end of the message.
102 */
103 static void
104ap_quest()
105{
106 *mp++ = '?';
107}
108
109/*
110 * Return the "current" byte offset in the file.
111 */
112 static POSITION
113curr_byte(where)
114 int where;
115{
116 POSITION pos;
117
118 pos = position(where);
119 while (pos == NULL_POSITION && where >= 0 && where < sc_height)
120 pos = position(++where);
121 if (pos == NULL_POSITION)
122 pos = ch_length();
123 return (pos);
124}
125
126/*
127 * Return the value of a prototype conditional.
128 * A prototype string may include conditionals which consist of a
129 * question mark followed by a single letter.
130 * Here we decode that letter and return the appropriate boolean value.
131 */
132 static int
133cond(c, where)
134 char c;
135 int where;
136{
137 switch (c)
138 {
139 case 'a': /* Anything in the message yet? */
140 return (mp > message);
141 case 'b': /* Current byte offset known? */
142 return (curr_byte(where) != NULL_POSITION);
143 case 'e': /* At end of file? */
144 return (hit_eof);
145 case 'f': /* Filename known? */
146 return (strcmp(get_filename(curr_ifile), "-") != 0);
147 case 'l': /* Line number known? */
148 return (linenums);
149 case 'L': /* Final line number known? */
150 return (linenums && ch_length() != NULL_POSITION);
151 case 'm': /* More than one file? */
152 return (nifile() > 1);
153 case 'n': /* First prompt in a new file? */
154 return (new_file);
155 case 'p': /* Percent into file known? */
156 return (curr_byte(where) != NULL_POSITION &&
157 ch_length() > 0);
158 case 's': /* Size of file known? */
159 case 'B':
160 return (ch_length() != NULL_POSITION);
161 case 'x': /* Is there a "next" file? */
162 return (next_ifile(curr_ifile) != NULL_IFILE);
163 }
164 return (0);
165}
166
167/*
168 * Decode a "percent" prototype character.
169 * A prototype string may include various "percent" escapes;
170 * that is, a percent sign followed by a single letter.
171 * Here we decode that letter and take the appropriate action,
172 * usually by appending something to the message being built.
173 */
174 static void
175protochar(c, where)
176 int c;
177 int where;
178{
179 POSITION pos;
180 POSITION len;
181 int n;
182 IFILE h;
183
184 switch (c)
185 {
186 case 'b': /* Current byte offset */
187 pos = curr_byte(where);
188 if (pos != NULL_POSITION)
189 ap_pos(pos);
190 else
191 ap_quest();
192 break;
193#if EDITOR
194 case 'E': /* Editor name */
195 ap_str(editor);
196 break;
197#endif
198 case 'f': /* File name */
199 ap_str(get_filename(curr_ifile));
200 break;
201 case 'i': /* Index into list of files */
202 ap_int(get_index(curr_ifile));
203 break;
204 case 'l': /* Current line number */
205 n = currline(where);
206 if (n != 0)
207 ap_int(n);
208 else
209 ap_quest();
210 break;
211 case 'L': /* Final line number */
212 len = ch_length();
213 if (len == NULL_POSITION || len == ch_zero() ||
214 (n = find_linenum(len)) <= 0)
215 ap_quest();
216 else
217 ap_int(n-1);
218 break;
219 case 'm': /* Number of files */
220 ap_int(nifile());
221 break;
222 case 'p': /* Percent into file */
223 pos = curr_byte(where);
224 len = ch_length();
225 if (pos != NULL_POSITION && len > 0)
226 /*
227 * {{ This calculation may overflow! }}
228 */
229 ap_int((int)(100*pos / len));
230 else
231 ap_quest();
232 break;
233 case 's': /* Size of file */
234 case 'B':
235 len = ch_length();
236 if (len != NULL_POSITION)
237 ap_pos(len);
238 else
239 ap_quest();
240 break;
241 case 't': /* Truncate trailing spaces in the message */
242 while (mp > message && mp[-1] == ' ')
243 mp--;
244 break;
245 case 'x': /* Name of next file */
246 h = next_ifile(curr_ifile);
247 if (h != NULL_IFILE)
248 ap_str(get_filename(h));
249 else
250 ap_quest();
251 break;
252 }
253}
254
255/*
256 * Skip a false conditional.
257 * When a false condition is found (either a false IF or the ELSE part
258 * of a true IF), this routine scans the prototype string to decide
259 * where to resume parsing the string.
260 * We must keep track of nested IFs and skip them properly.
261 */
262 static char *
263skipcond(p)
264 register char *p;
265{
266 register int iflevel;
267
268 /*
269 * We came in here after processing a ? or :,
270 * so we start nested one level deep.
271 */
272 iflevel = 1;
273
274 for (;;) switch (*++p)
275 {
276 case '?':
277 /*
278 * Start of a nested IF.
279 */
280 iflevel++;
281 break;
282 case ':':
283 /*
284 * Else.
285 * If this matches the IF we came in here with,
286 * then we're done.
287 */
288 if (iflevel == 1)
289 return (p);
290 break;
291 case '.':
292 /*
293 * Endif.
294 * If this matches the IF we came in here with,
295 * then we're done.
296 */
297 if (--iflevel == 0)
298 return (p);
299 break;
300 case '\\':
301 /*
302 * Backslash escapes the next character.
303 */
304 ++p;
305 break;
306 case '\0':
307 /*
308 * Whoops. Hit end of string.
309 * This is a malformed conditional, but just treat it
310 * as if all active conditionals ends here.
311 */
312 return (p-1);
313 }
314 /*NOTREACHED*/
315}
316
317 static char *
318wherechar(p, wp)
319 char *p;
320 int *wp;
321{
322 switch (*p)
323 {
324 case 'b': case 'l': case 'p':
325 switch (*++p)
326 {
327 case 't': *wp = TOP; break;
328 case 'm': *wp = MIDDLE; break;
329 case 'b': *wp = BOTTOM; break;
330 case 'B': *wp = BOTTOM_PLUS_ONE; break;
331 case 'j': *wp = adjsline(jump_sline); break;
332 default: *wp = TOP; p--; break;
333 }
334 }
335 return (p);
336}
337
338/*
339 * Construct a message based on a prototype string.
340 */
341 public char *
342pr_expand(proto, maxwidth)
343 char *proto;
344 int maxwidth;
345{
346 register char *p;
347 register int c;
348 int where;
349
350 mp = message;
351
352 if (*proto == '\0')
353 return ("");
354
355 for (p = proto; *p != '\0'; p++)
356 {
357 switch (*p)
358 {
359 default: /* Just put the character in the message */
360 *mp++ = *p;
361 break;
362 case '\\': /* Backslash escapes the next character */
363 p++;
364 *mp++ = *p;
365 break;
366 case '?': /* Conditional (IF) */
367 if ((c = *++p) == '\0')
368 --p;
369 else
370 {
371 p = wherechar(p, &where);
372 if (!cond(c, where))
373 p = skipcond(p);
374 }
375 break;
376 case ':': /* ELSE */
377 p = skipcond(p);
378 break;
379 case '.': /* ENDIF */
380 break;
381 case '%': /* Percent escape */
382 if ((c = *++p) == '\0')
383 --p;
384 else
385 {
386 p = wherechar(p, &where);
387 protochar(c, where);
388 }
389 break;
390 }
391 }
392
393 new_file = 0;
394 if (mp == message)
395 return (NULL);
396 *mp = '\0';
397 if (maxwidth > 0 && mp >= message + maxwidth)
398 {
399 /*
400 * Message is too long.
401 * Return just the final portion of it.
402 */
403 return (mp - maxwidth);
404 }
405 return (message);
406}
407
408/*
409 * Return a message suitable for printing by the "=" command.
410 */
411 public char *
412eq_message()
413{
414 return (pr_expand(eqproto, 0));
415}
416
417/*
418 * Return a prompt.
419 * This depends on the prompt type (SHORT, MEDIUM, LONG), etc.
420 * If we can't come up with an appropriate prompt, return NULL
421 * and the caller will prompt with a colon.
422 */
423 public char *
424pr_string()
425{
426 return (pr_expand(prproto[pr_type], sc_width-so_s_width-so_e_width-2));
427}