1 /* 2 * Copyright (c) 1988 Mark Nudleman 3 * Copyright (c) 1988 Regents of the University of California. 4 * All rights reserved. 5 * 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 11 * by Mark Nudleman and the University of California, Berkeley. The 12 * name of Mark Nudleman or the 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 21 static char sccsid[] = "@(#)prompt.c 5.4 (Berkeley) 07/25/88"; 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 { 96 (void)sprintf(mp, "%ld", (long)pos); 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 { 107 (void)sprintf(mp, "%d", n); 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 { 308 switch (*p) 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 } 413