prompt.c revision 89022
160814Sps/* $FreeBSD: head/contrib/less/prompt.c 89022 2002-01-07 20:37:09Z ps $ */ 260786Sps/* 360786Sps * Copyright (C) 1984-2000 Mark Nudelman 460786Sps * 560786Sps * You may distribute under the terms of either the GNU General Public 660786Sps * License or the Less License, as specified in the README file. 760786Sps * 860786Sps * For more information about less, or for information on how to 960786Sps * contact the author, see the README file. 1060786Sps */ 1160786Sps 1260786Sps 1360786Sps/* 1460786Sps * Prompting and other messages. 1560786Sps * There are three flavors of prompts, SHORT, MEDIUM and LONG, 1660786Sps * selected by the -m/-M options. 1760786Sps * There is also the "equals message", printed by the = command. 1860786Sps * A prompt is a message composed of various pieces, such as the 1960786Sps * name of the file being viewed, the percentage into the file, etc. 2060786Sps */ 2160786Sps 2260786Sps#include "less.h" 2360786Sps#include "position.h" 2460786Sps 2560786Spsextern int pr_type; 2660786Spsextern int hit_eof; 2760786Spsextern int new_file; 2860786Spsextern int sc_width; 2960786Spsextern int so_s_width, so_e_width; 3060786Spsextern int linenums; 3160786Spsextern int hshift; 3260786Spsextern int sc_height; 3360786Spsextern int jump_sline; 3460786Spsextern IFILE curr_ifile; 3560786Sps#if EDITOR 3660786Spsextern char *editor; 3760786Spsextern char *editproto; 3860786Sps#endif 3960786Sps 4060786Sps/* 4160786Sps * Prototypes for the three flavors of prompts. 4260786Sps * These strings are expanded by pr_expand(). 4360786Sps */ 4460786Spsstatic constant char s_proto[] = 4589022Sps "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x..%t"; 4660786Spsstatic constant char m_proto[] = 4789022Sps "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t"; 4860786Spsstatic constant char M_proto[] = 4989022Sps "?f%f .?n?m(%T %i of %m) ..?ltlines %lt-%lb?L/%L. :byte %bB?s/%s. .?e(END) ?x- Next\\: %x.:?pB%pB\\%..%t"; 5060786Spsstatic constant char e_proto[] = 5189022Sps "?f%f .?m(%T %i of %m) .?ltlines %lt-%lb?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t"; 5260786Spsstatic constant char h_proto[] = 5360786Sps "HELP -- ?eEND -- Press g to see it again:Press RETURN for more., or q when done"; 5489022Spsstatic constant char w_proto[] = 5589022Sps "Waiting for data"; 5660786Sps 5760786Spspublic char *prproto[3]; 5860786Spspublic char constant *eqproto = e_proto; 5960786Spspublic char constant *hproto = h_proto; 6089022Spspublic char constant *wproto = w_proto; 6160786Sps 6260786Spsstatic char message[PROMPT_SIZE]; 6360786Spsstatic char *mp; 6460786Sps 6560786Sps/* 6660786Sps * Initialize the prompt prototype strings. 6760786Sps */ 6860786Sps public void 6960786Spsinit_prompt() 7060786Sps{ 7160786Sps prproto[0] = save(s_proto); 7260786Sps prproto[1] = save(m_proto); 7360786Sps prproto[2] = save(M_proto); 7460786Sps eqproto = save(e_proto); 7560786Sps hproto = save(h_proto); 7689022Sps wproto = save(w_proto); 7760786Sps} 7860786Sps 7960786Sps/* 8060786Sps * Append a string to the end of the message. 8160786Sps */ 8260786Sps static void 8360786Spsap_str(s) 8460786Sps char *s; 8560786Sps{ 8660786Sps int len; 8760786Sps 8860786Sps len = strlen(s); 8960786Sps if (mp + len >= message + PROMPT_SIZE) 9060786Sps len = message + PROMPT_SIZE - mp - 1; 9160786Sps strncpy(mp, s, len); 9260786Sps mp += len; 9360786Sps *mp = '\0'; 9460786Sps} 9560786Sps 9660786Sps/* 9760786Sps * Append a character to the end of the message. 9860786Sps */ 9960786Sps static void 10060786Spsap_char(c) 10160786Sps char c; 10260786Sps{ 10360786Sps char buf[2]; 10460786Sps 10560786Sps buf[0] = c; 10660786Sps buf[1] = '\0'; 10760786Sps ap_str(buf); 10860786Sps} 10960786Sps 11060786Sps/* 11160786Sps * Append a POSITION (as a decimal integer) to the end of the message. 11260786Sps */ 11360786Sps static void 11460786Spsap_pos(pos) 11560786Sps POSITION pos; 11660786Sps{ 11789022Sps char buf[INT_STRLEN_BOUND(pos) + 1]; 11889022Sps char *p = buf + sizeof(buf) - 1; 11989022Sps int neg = (pos < 0); 12089022Sps 12189022Sps if (neg) 12289022Sps pos = -pos; 12389022Sps *p = '\0'; 12489022Sps do 12589022Sps *--p = '0' + (pos % 10); 12689022Sps while ((pos /= 10) != 0); 12789022Sps if (neg) 12889022Sps *--p = '-'; 12989022Sps ap_str(p); 13060786Sps} 13160786Sps 13260786Sps/* 13360786Sps * Append an integer to the end of the message. 13460786Sps */ 13560786Sps static void 13660786Spsap_int(n) 13760786Sps int n; 13860786Sps{ 13989022Sps char buf[INT_STRLEN_BOUND(n) + 1]; 14060786Sps 14160786Sps sprintf(buf, "%d", n); 14260786Sps ap_str(buf); 14360786Sps} 14460786Sps 14560786Sps/* 14660786Sps * Append a question mark to the end of the message. 14760786Sps */ 14860786Sps static void 14960786Spsap_quest() 15060786Sps{ 15160786Sps ap_str("?"); 15260786Sps} 15360786Sps 15460786Sps/* 15560786Sps * Return the "current" byte offset in the file. 15660786Sps */ 15760786Sps static POSITION 15860786Spscurr_byte(where) 15960786Sps int where; 16060786Sps{ 16160786Sps POSITION pos; 16260786Sps 16360786Sps pos = position(where); 16460786Sps while (pos == NULL_POSITION && where >= 0 && where < sc_height) 16560786Sps pos = position(++where); 16660786Sps if (pos == NULL_POSITION) 16760786Sps pos = ch_length(); 16860786Sps return (pos); 16960786Sps} 17060786Sps 17160786Sps/* 17260786Sps * Return the value of a prototype conditional. 17360786Sps * A prototype string may include conditionals which consist of a 17460786Sps * question mark followed by a single letter. 17560786Sps * Here we decode that letter and return the appropriate boolean value. 17660786Sps */ 17760786Sps static int 17860786Spscond(c, where) 17960786Sps char c; 18060786Sps int where; 18160786Sps{ 18260786Sps POSITION len; 18360786Sps 18460786Sps switch (c) 18560786Sps { 18660786Sps case 'a': /* Anything in the message yet? */ 18760786Sps return (mp > message); 18860786Sps case 'b': /* Current byte offset known? */ 18960786Sps return (curr_byte(where) != NULL_POSITION); 19060786Sps case 'c': 19160786Sps return (hshift != 0); 19260786Sps case 'e': /* At end of file? */ 19360786Sps return (hit_eof); 19460786Sps case 'f': /* Filename known? */ 19560786Sps return (strcmp(get_filename(curr_ifile), "-") != 0); 19660786Sps case 'l': /* Line number known? */ 19760786Sps case 'd': /* Same as l */ 19860786Sps return (linenums); 19960786Sps case 'L': /* Final line number known? */ 20060786Sps case 'D': /* Same as L */ 20160786Sps return (linenums && ch_length() != NULL_POSITION); 20260786Sps case 'm': /* More than one file? */ 20389022Sps return (ntags() ? (ntags() > 1) : (nifile() > 1)); 20460786Sps case 'n': /* First prompt in a new file? */ 20589022Sps return (ntags() ? 1 : new_file); 20660786Sps case 'p': /* Percent into file (bytes) known? */ 20760786Sps return (curr_byte(where) != NULL_POSITION && 20860786Sps ch_length() > 0); 20960786Sps case 'P': /* Percent into file (lines) known? */ 21060786Sps return (currline(where) != 0 && 21160786Sps (len = ch_length()) > 0 && 21260786Sps find_linenum(len) != 0); 21360786Sps case 's': /* Size of file known? */ 21460786Sps case 'B': 21560786Sps return (ch_length() != NULL_POSITION); 21660786Sps case 'x': /* Is there a "next" file? */ 21789022Sps if (ntags()) 21889022Sps return (0); 21960786Sps return (next_ifile(curr_ifile) != NULL_IFILE); 22060786Sps } 22160786Sps return (0); 22260786Sps} 22360786Sps 22460786Sps/* 22560786Sps * Decode a "percent" prototype character. 22660786Sps * A prototype string may include various "percent" escapes; 22760786Sps * that is, a percent sign followed by a single letter. 22860786Sps * Here we decode that letter and take the appropriate action, 22960786Sps * usually by appending something to the message being built. 23060786Sps */ 23160786Sps static void 23260786Spsprotochar(c, where, iseditproto) 23360786Sps int c; 23460786Sps int where; 23560786Sps int iseditproto; 23660786Sps{ 23760786Sps POSITION pos; 23860786Sps POSITION len; 23960786Sps int n; 24060786Sps IFILE h; 24160786Sps char *s; 24260786Sps char *escs; 24360786Sps 24460786Sps switch (c) 24560786Sps { 24660786Sps case 'b': /* Current byte offset */ 24760786Sps pos = curr_byte(where); 24860786Sps if (pos != NULL_POSITION) 24960786Sps ap_pos(pos); 25060786Sps else 25160786Sps ap_quest(); 25260786Sps break; 25360786Sps case 'c': 25460786Sps ap_int(hshift); 25560786Sps break; 25660786Sps case 'd': /* Current page number */ 25760786Sps n = currline(where); 25860786Sps if (n > 0 && sc_height > 1) 25960786Sps ap_int(((n - 1) / (sc_height - 1)) + 1); 26060786Sps else 26160786Sps ap_quest(); 26260786Sps break; 26360786Sps case 'D': /* Last page number */ 26460786Sps len = ch_length(); 26560786Sps if (len == NULL_POSITION || len == ch_zero() || 26660786Sps (n = find_linenum(len)) <= 0) 26760786Sps ap_quest(); 26860786Sps else 26960786Sps ap_int(((n - 1) / (sc_height - 1)) + 1); 27060786Sps break; 27160786Sps#if EDITOR 27260786Sps case 'E': /* Editor name */ 27360786Sps ap_str(editor); 27460786Sps break; 27560786Sps#endif 27660786Sps case 'f': /* File name */ 27760786Sps s = unquote_file(get_filename(curr_ifile)); 27860786Sps /* 27960786Sps * If we are expanding editproto then we escape metachars. 28060786Sps * This allows us to run the editor on files with funny names. 28160786Sps */ 28260786Sps if (iseditproto && (escs = esc_metachars(s)) != NULL) 28360786Sps { 28460786Sps free(s); 28560786Sps s = escs; 28660786Sps } 28760786Sps ap_str(s); 28860786Sps free(s); 28960786Sps break; 29060786Sps case 'i': /* Index into list of files */ 29189022Sps if (ntags()) 29289022Sps ap_int(curr_tag()); 29389022Sps else 29489022Sps ap_int(get_index(curr_ifile)); 29560786Sps break; 29660786Sps case 'l': /* Current line number */ 29760786Sps n = currline(where); 29860786Sps if (n != 0) 29960786Sps ap_int(n); 30060786Sps else 30160786Sps ap_quest(); 30260786Sps break; 30360786Sps case 'L': /* Final line number */ 30460786Sps len = ch_length(); 30560786Sps if (len == NULL_POSITION || len == ch_zero() || 30660786Sps (n = find_linenum(len)) <= 0) 30760786Sps ap_quest(); 30860786Sps else 30960786Sps ap_int(n-1); 31060786Sps break; 31160786Sps case 'm': /* Number of files */ 31289022Sps n = ntags(); 31389022Sps if (n) 31489022Sps ap_int(n); 31589022Sps else 31689022Sps ap_int(nifile()); 31760786Sps break; 31860786Sps case 'p': /* Percent into file (bytes) */ 31960786Sps pos = curr_byte(where); 32060786Sps len = ch_length(); 32160786Sps if (pos != NULL_POSITION && len > 0) 32260786Sps ap_int(percentage(pos,len)); 32360786Sps else 32460786Sps ap_quest(); 32560786Sps break; 32660786Sps case 'P': /* Percent into file (lines) */ 32760786Sps pos = (POSITION) currline(where); 32860786Sps if (pos == 0 || 32960786Sps (len = ch_length()) == NULL_POSITION || len == ch_zero() || 33060786Sps (n = find_linenum(len)) <= 0) 33160786Sps ap_quest(); 33260786Sps else 33360786Sps ap_int(percentage(pos, (POSITION)n)); 33460786Sps break; 33560786Sps case 's': /* Size of file */ 33660786Sps case 'B': 33760786Sps len = ch_length(); 33860786Sps if (len != NULL_POSITION) 33960786Sps ap_pos(len); 34060786Sps else 34160786Sps ap_quest(); 34260786Sps break; 34360786Sps case 't': /* Truncate trailing spaces in the message */ 34460786Sps while (mp > message && mp[-1] == ' ') 34560786Sps mp--; 34660786Sps break; 34789022Sps case 'T': /* Type of list */ 34889022Sps if (ntags()) 34989022Sps ap_str("tag"); 35089022Sps else 35189022Sps ap_str("file"); 35289022Sps break; 35360786Sps case 'x': /* Name of next file */ 35460786Sps h = next_ifile(curr_ifile); 35560786Sps if (h != NULL_IFILE) 35660786Sps { 35760786Sps s = unquote_file(get_filename(h)); 35860786Sps ap_str(s); 35960786Sps free(s); 36060786Sps } else 36160786Sps ap_quest(); 36260786Sps break; 36360786Sps } 36460786Sps} 36560786Sps 36660786Sps/* 36760786Sps * Skip a false conditional. 36860786Sps * When a false condition is found (either a false IF or the ELSE part 36960786Sps * of a true IF), this routine scans the prototype string to decide 37060786Sps * where to resume parsing the string. 37160786Sps * We must keep track of nested IFs and skip them properly. 37260786Sps */ 37360786Sps static char * 37460786Spsskipcond(p) 37560786Sps register char *p; 37660786Sps{ 37760786Sps register int iflevel; 37860786Sps 37960786Sps /* 38060786Sps * We came in here after processing a ? or :, 38160786Sps * so we start nested one level deep. 38260786Sps */ 38360786Sps iflevel = 1; 38460786Sps 38560786Sps for (;;) switch (*++p) 38660786Sps { 38760786Sps case '?': 38860786Sps /* 38960786Sps * Start of a nested IF. 39060786Sps */ 39160786Sps iflevel++; 39260786Sps break; 39360786Sps case ':': 39460786Sps /* 39560786Sps * Else. 39660786Sps * If this matches the IF we came in here with, 39760786Sps * then we're done. 39860786Sps */ 39960786Sps if (iflevel == 1) 40060786Sps return (p); 40160786Sps break; 40260786Sps case '.': 40360786Sps /* 40460786Sps * Endif. 40560786Sps * If this matches the IF we came in here with, 40660786Sps * then we're done. 40760786Sps */ 40860786Sps if (--iflevel == 0) 40960786Sps return (p); 41060786Sps break; 41160786Sps case '\\': 41260786Sps /* 41360786Sps * Backslash escapes the next character. 41460786Sps */ 41560786Sps ++p; 41660786Sps break; 41760786Sps case '\0': 41860786Sps /* 41960786Sps * Whoops. Hit end of string. 42060786Sps * This is a malformed conditional, but just treat it 42160786Sps * as if all active conditionals ends here. 42260786Sps */ 42360786Sps return (p-1); 42460786Sps } 42560786Sps /*NOTREACHED*/ 42660786Sps} 42760786Sps 42860786Sps/* 42960786Sps * Decode a char that represents a position on the screen. 43060786Sps */ 43160786Sps static char * 43260786Spswherechar(p, wp) 43360786Sps char *p; 43460786Sps int *wp; 43560786Sps{ 43660786Sps switch (*p) 43760786Sps { 43860786Sps case 'b': case 'd': case 'l': case 'p': case 'P': 43960786Sps switch (*++p) 44060786Sps { 44160786Sps case 't': *wp = TOP; break; 44260786Sps case 'm': *wp = MIDDLE; break; 44360786Sps case 'b': *wp = BOTTOM; break; 44460786Sps case 'B': *wp = BOTTOM_PLUS_ONE; break; 44560786Sps case 'j': *wp = adjsline(jump_sline); break; 44660786Sps default: *wp = TOP; p--; break; 44760786Sps } 44860786Sps } 44960786Sps return (p); 45060786Sps} 45160786Sps 45260786Sps/* 45360786Sps * Construct a message based on a prototype string. 45460786Sps */ 45560786Sps public char * 45660786Spspr_expand(proto, maxwidth) 45760786Sps char *proto; 45860786Sps int maxwidth; 45960786Sps{ 46060786Sps register char *p; 46160786Sps register int c; 46260786Sps int where; 46360786Sps 46460786Sps mp = message; 46560786Sps 46660786Sps if (*proto == '\0') 46760786Sps return (""); 46860786Sps 46960786Sps for (p = proto; *p != '\0'; p++) 47060786Sps { 47160786Sps switch (*p) 47260786Sps { 47360786Sps default: /* Just put the character in the message */ 47460786Sps ap_char(*p); 47560786Sps break; 47660786Sps case '\\': /* Backslash escapes the next character */ 47760786Sps p++; 47860786Sps ap_char(*p); 47960786Sps break; 48060786Sps case '?': /* Conditional (IF) */ 48160786Sps if ((c = *++p) == '\0') 48260786Sps --p; 48360786Sps else 48460786Sps { 48560786Sps where = 0; 48660786Sps p = wherechar(p, &where); 48760786Sps if (!cond(c, where)) 48860786Sps p = skipcond(p); 48960786Sps } 49060786Sps break; 49160786Sps case ':': /* ELSE */ 49260786Sps p = skipcond(p); 49360786Sps break; 49460786Sps case '.': /* ENDIF */ 49560786Sps break; 49660786Sps case '%': /* Percent escape */ 49760786Sps if ((c = *++p) == '\0') 49860786Sps --p; 49960786Sps else 50060786Sps { 50160786Sps where = 0; 50260786Sps p = wherechar(p, &where); 50360786Sps protochar(c, where, 50460786Sps#if EDITOR 50560786Sps (proto == editproto)); 50660786Sps#else 50760786Sps 0); 50860786Sps#endif 50960786Sps 51060786Sps } 51160786Sps break; 51260786Sps } 51360786Sps } 51460786Sps 51560786Sps if (mp == message) 51660786Sps return (NULL); 51760786Sps if (maxwidth > 0 && mp >= message + maxwidth) 51860786Sps { 51960786Sps /* 52060786Sps * Message is too long. 52160786Sps * Return just the final portion of it. 52260786Sps */ 52360786Sps return (mp - maxwidth); 52460786Sps } 52560786Sps return (message); 52660786Sps} 52760786Sps 52860786Sps/* 52960786Sps * Return a message suitable for printing by the "=" command. 53060786Sps */ 53160786Sps public char * 53260786Spseq_message() 53360786Sps{ 53460786Sps return (pr_expand(eqproto, 0)); 53560786Sps} 53660786Sps 53760786Sps/* 53860786Sps * Return a prompt. 53960786Sps * This depends on the prompt type (SHORT, MEDIUM, LONG), etc. 54060786Sps * If we can't come up with an appropriate prompt, return NULL 54160786Sps * and the caller will prompt with a colon. 54260786Sps */ 54360786Sps public char * 54460786Spspr_string() 54560786Sps{ 54689022Sps char *prompt; 54789022Sps 54889022Sps prompt = pr_expand((ch_getflags() & CH_HELPFILE) ? 54989022Sps hproto : prproto[pr_type], 55089022Sps sc_width-so_s_width-so_e_width-2); 55189022Sps new_file = 0; 55289022Sps return (prompt); 55360786Sps} 55489022Sps 55589022Sps/* 55689022Sps * Return a message suitable for printing while waiting in the F command. 55789022Sps */ 55889022Sps public char * 55989022Spswait_message() 56089022Sps{ 56189022Sps return (pr_expand(wproto, sc_width-so_s_width-so_e_width-2)); 56289022Sps} 563