prompt.c revision 128348
160814Sps/* $FreeBSD: head/contrib/less/prompt.c 128348 2004-04-17 07:24:09Z tjr $ */ 260786Sps/* 3128348Stjr * Copyright (C) 1984-2002 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{ 117128348Stjr char buf[INT_STRLEN_BOUND(pos) + 2]; 118128348Stjr 119128348Stjr postoa(pos, buf); 120128348Stjr ap_str(buf); 12160786Sps} 12260786Sps 12360786Sps/* 124128348Stjr * Append a line number to the end of the message. 125128348Stjr */ 126128348Stjr static void 127128348Stjrap_linenum(linenum) 128128348Stjr LINENUM linenum; 129128348Stjr{ 130128348Stjr char buf[INT_STRLEN_BOUND(linenum) + 2]; 131128348Stjr 132128348Stjr linenumtoa(linenum, buf); 133128348Stjr ap_str(buf); 134128348Stjr} 135128348Stjr 136128348Stjr/* 13760786Sps * Append an integer to the end of the message. 13860786Sps */ 13960786Sps static void 140128348Stjrap_int(num) 141128348Stjr int num; 14260786Sps{ 143128348Stjr char buf[INT_STRLEN_BOUND(num) + 2]; 14460786Sps 145128348Stjr inttoa(num, buf); 14660786Sps ap_str(buf); 14760786Sps} 14860786Sps 14960786Sps/* 15060786Sps * Append a question mark to the end of the message. 15160786Sps */ 15260786Sps static void 15360786Spsap_quest() 15460786Sps{ 15560786Sps ap_str("?"); 15660786Sps} 15760786Sps 15860786Sps/* 15960786Sps * Return the "current" byte offset in the file. 16060786Sps */ 16160786Sps static POSITION 16260786Spscurr_byte(where) 16360786Sps int where; 16460786Sps{ 16560786Sps POSITION pos; 16660786Sps 16760786Sps pos = position(where); 16860786Sps while (pos == NULL_POSITION && where >= 0 && where < sc_height) 16960786Sps pos = position(++where); 17060786Sps if (pos == NULL_POSITION) 17160786Sps pos = ch_length(); 17260786Sps return (pos); 17360786Sps} 17460786Sps 17560786Sps/* 17660786Sps * Return the value of a prototype conditional. 17760786Sps * A prototype string may include conditionals which consist of a 17860786Sps * question mark followed by a single letter. 17960786Sps * Here we decode that letter and return the appropriate boolean value. 18060786Sps */ 18160786Sps static int 18260786Spscond(c, where) 18360786Sps char c; 18460786Sps int where; 18560786Sps{ 18660786Sps POSITION len; 18760786Sps 18860786Sps switch (c) 18960786Sps { 19060786Sps case 'a': /* Anything in the message yet? */ 19160786Sps return (mp > message); 19260786Sps case 'b': /* Current byte offset known? */ 19360786Sps return (curr_byte(where) != NULL_POSITION); 19460786Sps case 'c': 19560786Sps return (hshift != 0); 19660786Sps case 'e': /* At end of file? */ 19760786Sps return (hit_eof); 19860786Sps case 'f': /* Filename known? */ 19960786Sps return (strcmp(get_filename(curr_ifile), "-") != 0); 20060786Sps case 'l': /* Line number known? */ 20160786Sps case 'd': /* Same as l */ 20260786Sps return (linenums); 20360786Sps case 'L': /* Final line number known? */ 20460786Sps case 'D': /* Same as L */ 20560786Sps return (linenums && ch_length() != NULL_POSITION); 20660786Sps case 'm': /* More than one file? */ 207128348Stjr#if TAGS 20889022Sps return (ntags() ? (ntags() > 1) : (nifile() > 1)); 209128348Stjr#else 210128348Stjr return (nifile() > 1); 211128348Stjr#endif 21260786Sps case 'n': /* First prompt in a new file? */ 213128348Stjr#if TAGS 21489022Sps return (ntags() ? 1 : new_file); 215128348Stjr#else 216128348Stjr return (new_file); 217128348Stjr#endif 21860786Sps case 'p': /* Percent into file (bytes) known? */ 21960786Sps return (curr_byte(where) != NULL_POSITION && 22060786Sps ch_length() > 0); 22160786Sps case 'P': /* Percent into file (lines) known? */ 22260786Sps return (currline(where) != 0 && 22360786Sps (len = ch_length()) > 0 && 22460786Sps find_linenum(len) != 0); 22560786Sps case 's': /* Size of file known? */ 22660786Sps case 'B': 22760786Sps return (ch_length() != NULL_POSITION); 22860786Sps case 'x': /* Is there a "next" file? */ 229128348Stjr#if TAGS 23089022Sps if (ntags()) 23189022Sps return (0); 232128348Stjr#endif 23360786Sps return (next_ifile(curr_ifile) != NULL_IFILE); 23460786Sps } 23560786Sps return (0); 23660786Sps} 23760786Sps 23860786Sps/* 23960786Sps * Decode a "percent" prototype character. 24060786Sps * A prototype string may include various "percent" escapes; 24160786Sps * that is, a percent sign followed by a single letter. 24260786Sps * Here we decode that letter and take the appropriate action, 24360786Sps * usually by appending something to the message being built. 24460786Sps */ 24560786Sps static void 24660786Spsprotochar(c, where, iseditproto) 24760786Sps int c; 24860786Sps int where; 24960786Sps int iseditproto; 25060786Sps{ 25160786Sps POSITION pos; 25260786Sps POSITION len; 25360786Sps int n; 254128348Stjr LINENUM linenum; 255128348Stjr LINENUM last_linenum; 25660786Sps IFILE h; 25760786Sps 25860786Sps switch (c) 25960786Sps { 26060786Sps case 'b': /* Current byte offset */ 26160786Sps pos = curr_byte(where); 26260786Sps if (pos != NULL_POSITION) 26360786Sps ap_pos(pos); 26460786Sps else 26560786Sps ap_quest(); 26660786Sps break; 26760786Sps case 'c': 26860786Sps ap_int(hshift); 26960786Sps break; 27060786Sps case 'd': /* Current page number */ 271128348Stjr linenum = currline(where); 272128348Stjr if (linenum > 0 && sc_height > 1) 273128348Stjr ap_linenum(((linenum - 1) / (sc_height - 1)) + 1); 27460786Sps else 27560786Sps ap_quest(); 27660786Sps break; 27760786Sps case 'D': /* Last page number */ 27860786Sps len = ch_length(); 27960786Sps if (len == NULL_POSITION || len == ch_zero() || 280128348Stjr (linenum = find_linenum(len)) <= 0) 28160786Sps ap_quest(); 28260786Sps else 283128348Stjr ap_linenum(((linenum - 1) / (sc_height - 1)) + 1); 28460786Sps break; 28560786Sps#if EDITOR 28660786Sps case 'E': /* Editor name */ 28760786Sps ap_str(editor); 28860786Sps break; 28960786Sps#endif 29060786Sps case 'f': /* File name */ 291128348Stjr ap_str(get_filename(curr_ifile)); 29260786Sps break; 29360786Sps case 'i': /* Index into list of files */ 294128348Stjr#if TAGS 29589022Sps if (ntags()) 29689022Sps ap_int(curr_tag()); 29789022Sps else 298128348Stjr#endif 29989022Sps ap_int(get_index(curr_ifile)); 30060786Sps break; 30160786Sps case 'l': /* Current line number */ 302128348Stjr linenum = currline(where); 303128348Stjr if (linenum != 0) 304128348Stjr ap_linenum(linenum); 30560786Sps else 30660786Sps ap_quest(); 30760786Sps break; 30860786Sps case 'L': /* Final line number */ 30960786Sps len = ch_length(); 31060786Sps if (len == NULL_POSITION || len == ch_zero() || 311128348Stjr (linenum = find_linenum(len)) <= 0) 31260786Sps ap_quest(); 31360786Sps else 314128348Stjr ap_linenum(linenum-1); 31560786Sps break; 31660786Sps case 'm': /* Number of files */ 317128348Stjr#if TAGS 31889022Sps n = ntags(); 31989022Sps if (n) 32089022Sps ap_int(n); 32189022Sps else 322128348Stjr#endif 32389022Sps ap_int(nifile()); 32460786Sps break; 32560786Sps case 'p': /* Percent into file (bytes) */ 32660786Sps pos = curr_byte(where); 32760786Sps len = ch_length(); 32860786Sps if (pos != NULL_POSITION && len > 0) 32960786Sps ap_int(percentage(pos,len)); 33060786Sps else 33160786Sps ap_quest(); 33260786Sps break; 33360786Sps case 'P': /* Percent into file (lines) */ 334128348Stjr linenum = currline(where); 335128348Stjr if (linenum == 0 || 33660786Sps (len = ch_length()) == NULL_POSITION || len == ch_zero() || 337128348Stjr (last_linenum = find_linenum(len)) <= 0) 33860786Sps ap_quest(); 33960786Sps else 340128348Stjr ap_int(percentage(linenum, last_linenum)); 34160786Sps break; 34260786Sps case 's': /* Size of file */ 34360786Sps case 'B': 34460786Sps len = ch_length(); 34560786Sps if (len != NULL_POSITION) 34660786Sps ap_pos(len); 34760786Sps else 34860786Sps ap_quest(); 34960786Sps break; 35060786Sps case 't': /* Truncate trailing spaces in the message */ 35160786Sps while (mp > message && mp[-1] == ' ') 35260786Sps mp--; 35360786Sps break; 35489022Sps case 'T': /* Type of list */ 355128348Stjr#if TAGS 35689022Sps if (ntags()) 35789022Sps ap_str("tag"); 35889022Sps else 359128348Stjr#endif 36089022Sps ap_str("file"); 36189022Sps break; 36260786Sps case 'x': /* Name of next file */ 36360786Sps h = next_ifile(curr_ifile); 36460786Sps if (h != NULL_IFILE) 365128348Stjr ap_str(get_filename(h)); 366128348Stjr else 36760786Sps ap_quest(); 36860786Sps break; 36960786Sps } 37060786Sps} 37160786Sps 37260786Sps/* 37360786Sps * Skip a false conditional. 37460786Sps * When a false condition is found (either a false IF or the ELSE part 37560786Sps * of a true IF), this routine scans the prototype string to decide 37660786Sps * where to resume parsing the string. 37760786Sps * We must keep track of nested IFs and skip them properly. 37860786Sps */ 37960786Sps static char * 38060786Spsskipcond(p) 38160786Sps register char *p; 38260786Sps{ 38360786Sps register int iflevel; 38460786Sps 38560786Sps /* 38660786Sps * We came in here after processing a ? or :, 38760786Sps * so we start nested one level deep. 38860786Sps */ 38960786Sps iflevel = 1; 39060786Sps 39160786Sps for (;;) switch (*++p) 39260786Sps { 39360786Sps case '?': 39460786Sps /* 39560786Sps * Start of a nested IF. 39660786Sps */ 39760786Sps iflevel++; 39860786Sps break; 39960786Sps case ':': 40060786Sps /* 40160786Sps * Else. 40260786Sps * If this matches the IF we came in here with, 40360786Sps * then we're done. 40460786Sps */ 40560786Sps if (iflevel == 1) 40660786Sps return (p); 40760786Sps break; 40860786Sps case '.': 40960786Sps /* 41060786Sps * Endif. 41160786Sps * If this matches the IF we came in here with, 41260786Sps * then we're done. 41360786Sps */ 41460786Sps if (--iflevel == 0) 41560786Sps return (p); 41660786Sps break; 41760786Sps case '\\': 41860786Sps /* 41960786Sps * Backslash escapes the next character. 42060786Sps */ 42160786Sps ++p; 42260786Sps break; 42360786Sps case '\0': 42460786Sps /* 42560786Sps * Whoops. Hit end of string. 42660786Sps * This is a malformed conditional, but just treat it 42760786Sps * as if all active conditionals ends here. 42860786Sps */ 42960786Sps return (p-1); 43060786Sps } 43160786Sps /*NOTREACHED*/ 43260786Sps} 43360786Sps 43460786Sps/* 43560786Sps * Decode a char that represents a position on the screen. 43660786Sps */ 43760786Sps static char * 43860786Spswherechar(p, wp) 43960786Sps char *p; 44060786Sps int *wp; 44160786Sps{ 44260786Sps switch (*p) 44360786Sps { 44460786Sps case 'b': case 'd': case 'l': case 'p': case 'P': 44560786Sps switch (*++p) 44660786Sps { 44760786Sps case 't': *wp = TOP; break; 44860786Sps case 'm': *wp = MIDDLE; break; 44960786Sps case 'b': *wp = BOTTOM; break; 45060786Sps case 'B': *wp = BOTTOM_PLUS_ONE; break; 45160786Sps case 'j': *wp = adjsline(jump_sline); break; 45260786Sps default: *wp = TOP; p--; break; 45360786Sps } 45460786Sps } 45560786Sps return (p); 45660786Sps} 45760786Sps 45860786Sps/* 45960786Sps * Construct a message based on a prototype string. 46060786Sps */ 46160786Sps public char * 46260786Spspr_expand(proto, maxwidth) 46360786Sps char *proto; 46460786Sps int maxwidth; 46560786Sps{ 46660786Sps register char *p; 46760786Sps register int c; 46860786Sps int where; 46960786Sps 47060786Sps mp = message; 47160786Sps 47260786Sps if (*proto == '\0') 47360786Sps return (""); 47460786Sps 47560786Sps for (p = proto; *p != '\0'; p++) 47660786Sps { 47760786Sps switch (*p) 47860786Sps { 47960786Sps default: /* Just put the character in the message */ 48060786Sps ap_char(*p); 48160786Sps break; 48260786Sps case '\\': /* Backslash escapes the next character */ 48360786Sps p++; 48460786Sps ap_char(*p); 48560786Sps break; 48660786Sps case '?': /* Conditional (IF) */ 48760786Sps if ((c = *++p) == '\0') 48860786Sps --p; 48960786Sps else 49060786Sps { 49160786Sps where = 0; 49260786Sps p = wherechar(p, &where); 49360786Sps if (!cond(c, where)) 49460786Sps p = skipcond(p); 49560786Sps } 49660786Sps break; 49760786Sps case ':': /* ELSE */ 49860786Sps p = skipcond(p); 49960786Sps break; 50060786Sps case '.': /* ENDIF */ 50160786Sps break; 50260786Sps case '%': /* Percent escape */ 50360786Sps if ((c = *++p) == '\0') 50460786Sps --p; 50560786Sps else 50660786Sps { 50760786Sps where = 0; 50860786Sps p = wherechar(p, &where); 50960786Sps protochar(c, where, 51060786Sps#if EDITOR 51160786Sps (proto == editproto)); 51260786Sps#else 51360786Sps 0); 51460786Sps#endif 51560786Sps 51660786Sps } 51760786Sps break; 51860786Sps } 51960786Sps } 52060786Sps 52160786Sps if (mp == message) 52260786Sps return (NULL); 52360786Sps if (maxwidth > 0 && mp >= message + maxwidth) 52460786Sps { 52560786Sps /* 52660786Sps * Message is too long. 52760786Sps * Return just the final portion of it. 52860786Sps */ 52960786Sps return (mp - maxwidth); 53060786Sps } 53160786Sps return (message); 53260786Sps} 53360786Sps 53460786Sps/* 53560786Sps * Return a message suitable for printing by the "=" command. 53660786Sps */ 53760786Sps public char * 53860786Spseq_message() 53960786Sps{ 54060786Sps return (pr_expand(eqproto, 0)); 54160786Sps} 54260786Sps 54360786Sps/* 54460786Sps * Return a prompt. 54560786Sps * This depends on the prompt type (SHORT, MEDIUM, LONG), etc. 54660786Sps * If we can't come up with an appropriate prompt, return NULL 54760786Sps * and the caller will prompt with a colon. 54860786Sps */ 54960786Sps public char * 55060786Spspr_string() 55160786Sps{ 55289022Sps char *prompt; 55389022Sps 55489022Sps prompt = pr_expand((ch_getflags() & CH_HELPFILE) ? 55589022Sps hproto : prproto[pr_type], 55689022Sps sc_width-so_s_width-so_e_width-2); 55789022Sps new_file = 0; 55889022Sps return (prompt); 55960786Sps} 56089022Sps 56189022Sps/* 56289022Sps * Return a message suitable for printing while waiting in the F command. 56389022Sps */ 56489022Sps public char * 56589022Spswait_message() 56689022Sps{ 56789022Sps return (pr_expand(wproto, sc_width-so_s_width-so_e_width-2)); 56889022Sps} 569