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