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