prompt.c revision 240121
11556Srgrimes/* $FreeBSD: stable/9/contrib/less/prompt.c 240121 2012-09-04 23:24:00Z delphij $ */ 21556Srgrimes/* 31556Srgrimes * Copyright (C) 1984-2012 Mark Nudelman 41556Srgrimes * 51556Srgrimes * You may distribute under the terms of either the GNU General Public 61556Srgrimes * License or the Less License, as specified in the README file. 71556Srgrimes * 81556Srgrimes * For more information, see the README file. 91556Srgrimes */ 101556Srgrimes 111556Srgrimes 121556Srgrimes/* 131556Srgrimes * Prompting and other messages. 141556Srgrimes * There are three flavors of prompts, SHORT, MEDIUM and LONG, 151556Srgrimes * selected by the -m/-M options. 161556Srgrimes * There is also the "equals message", printed by the = command. 171556Srgrimes * A prompt is a message composed of various pieces, such as the 181556Srgrimes * name of the file being viewed, the percentage into the file, etc. 191556Srgrimes */ 201556Srgrimes 211556Srgrimes#include "less.h" 221556Srgrimes#include "position.h" 231556Srgrimes 241556Srgrimesextern int pr_type; 251556Srgrimesextern int new_file; 261556Srgrimesextern int sc_width; 271556Srgrimesextern int so_s_width, so_e_width; 281556Srgrimesextern int linenums; 291556Srgrimesextern int hshift; 301556Srgrimesextern int sc_height; 311556Srgrimesextern int jump_sline; 321556Srgrimesextern int less_is_more; 331556Srgrimesextern IFILE curr_ifile; 3436150Scharnier#if EDITOR 3536150Scharnierextern char *editor; 3636150Scharnierextern char *editproto; 371556Srgrimes#endif 3899110Sobrien 3999110Sobrien/* 401556Srgrimes * Prototypes for the three flavors of prompts. 41213775Sjhb * These strings are expanded by pr_expand(). 42213775Sjhb */ 43213775Sjhbstatic constant char s_proto[] = 44213775Sjhb "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x..%t"; 45213775Sjhbstatic constant char m_proto[] = 46213775Sjhb "?n?f%f .?m(%T %i of %m) ..?e(END) ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t"; 4717987Speterstatic constant char M_proto[] = 48213775Sjhb "?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"; 4917987Speterstatic constant char e_proto[] = 50213925Sjilles "?f%f .?m(%T %i of %m) .?ltlines %lt-%lb?L/%L. .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t"; 51213775Sjhbstatic constant char h_proto[] = 5217987Speter "HELP -- ?eEND -- Press g to see it again:Press RETURN for more., or q when done"; 5317987Speterstatic constant char w_proto[] = 541556Srgrimes "Waiting for data"; 551556Srgrimesstatic constant char more_proto[] = 5617987Speter "--More--(?eEND ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t)"; 571556Srgrimes 581556Srgrimespublic char *prproto[3]; 5917987Speterpublic char constant *eqproto = e_proto; 6017987Speterpublic char constant *hproto = h_proto; 611556Srgrimespublic char constant *wproto = w_proto; 621556Srgrimes 631556Srgrimesstatic char message[PROMPT_SIZE]; 641556Srgrimesstatic char *mp; 651556Srgrimes 661556Srgrimes/* 671556Srgrimes * Initialize the prompt prototype strings. 681556Srgrimes */ 691556Srgrimes public void 701556Srgrimesinit_prompt() 711556Srgrimes{ 721556Srgrimes prproto[0] = save(s_proto); 731556Srgrimes prproto[1] = save(less_is_more ? more_proto : m_proto); 741556Srgrimes prproto[2] = save(M_proto); 75213760Sobrien eqproto = save(e_proto); 76213760Sobrien hproto = save(h_proto); 7728346Ssteve wproto = save(w_proto); 78209600Sjilles} 791556Srgrimes 80213760Sobrien/* 81213760Sobrien * Append a string to the end of the message. 821556Srgrimes */ 8338536Scracauer static void 8438950Scracauerap_str(s) 8538536Scracauer char *s; 8699762Stjr{ 871556Srgrimes int len; 8820425Ssteve 89213811Sobrien len = strlen(s); 9020425Ssteve if (mp + len >= message + PROMPT_SIZE) 91213811Sobrien len = message + PROMPT_SIZE - mp - 1; 92213811Sobrien strncpy(mp, s, len); 93213811Sobrien mp += len; 94213811Sobrien *mp = '\0'; 95213811Sobrien} 96213811Sobrien 97213811Sobrien/* 9897659Stjr * Append a character to the end of the message. 99213811Sobrien */ 100213811Sobrien static void 101213811Sobrienap_char(c) 10297659Stjr char c; 103213811Sobrien{ 1041556Srgrimes char buf[2]; 1051556Srgrimes 1061556Srgrimes buf[0] = c; 1071556Srgrimes buf[1] = '\0'; 1081556Srgrimes ap_str(buf); 1091556Srgrimes} 1101556Srgrimes 1111556Srgrimes/* 11220425Ssteve * Append a POSITION (as a decimal integer) to the end of the message. 1131556Srgrimes */ 11490111Simp static void 11517987Speterap_pos(pos) 11699762Stjr POSITION pos; 1171556Srgrimes{ 1181556Srgrimes char buf[INT_STRLEN_BOUND(pos) + 2]; 1191556Srgrimes 1201556Srgrimes postoa(pos, buf); 12199762Stjr ap_str(buf); 12299762Stjr} 12399762Stjr 12499762Stjr/* 12599762Stjr * Append a line number to the end of the message. 12699762Stjr */ 127109927Stjr static void 12899762Stjrap_linenum(linenum) 12999762Stjr LINENUM linenum; 130109927Stjr{ 131109927Stjr char buf[INT_STRLEN_BOUND(linenum) + 2]; 132109927Stjr 133109927Stjr linenumtoa(linenum, buf); 134109927Stjr ap_str(buf); 135109927Stjr} 136109927Stjr 137109927Stjr/* 138109927Stjr * Append an integer to the end of the message. 139109927Stjr */ 140109927Stjr static void 141109927Stjrap_int(num) 142109927Stjr int num; 143103223Snectar{ 14499762Stjr char buf[INT_STRLEN_BOUND(num) + 2]; 14599762Stjr 14699762Stjr inttoa(num, buf); 14799762Stjr ap_str(buf); 1481556Srgrimes} 14999762Stjr 15020425Ssteve/* 151199629Sjilles * Append a question mark to the end of the message. 1521556Srgrimes */ 1531556Srgrimes static void 1541556Srgrimesap_quest() 1551556Srgrimes{ 15617987Speter ap_str("?"); 15717987Speter} 15899762Stjr 1591556Srgrimes/* 1601556Srgrimes * Return the "current" byte offset in the file. 1611556Srgrimes */ 1621556Srgrimes static POSITION 1631556Srgrimescurr_byte(where) 1641556Srgrimes int where; 16517987Speter{ 16699762Stjr POSITION pos; 1671556Srgrimes 16817987Speter pos = position(where); 16999762Stjr while (pos == NULL_POSITION && where >= 0 && where < sc_height-1) 17099762Stjr pos = position(++where); 17199762Stjr if (pos == NULL_POSITION) 1721556Srgrimes pos = ch_length(); 1731556Srgrimes return (pos); 1741556Srgrimes} 1751556Srgrimes 1761556Srgrimes/* 1771556Srgrimes * Return the value of a prototype conditional. 17820425Ssteve * A prototype string may include conditionals which consist of a 1791556Srgrimes * question mark followed by a single letter. 1801556Srgrimes * Here we decode that letter and return the appropriate boolean value. 1811556Srgrimes */ 18228346Ssteve static int 18317987Spetercond(c, where) 1841556Srgrimes char c; 1851556Srgrimes int where; 1861556Srgrimes{ 187209600Sjilles POSITION len; 1881556Srgrimes 1891556Srgrimes switch (c) 1901556Srgrimes { 1911556Srgrimes case 'a': /* Anything in the message yet? */ 1921556Srgrimes return (mp > message); 1931556Srgrimes case 'b': /* Current byte offset known? */ 1941556Srgrimes return (curr_byte(where) != NULL_POSITION); 1951556Srgrimes case 'c': 1961556Srgrimes return (hshift != 0); 1971556Srgrimes case 'e': /* At end of file? */ 19817987Speter return (eof_displayed()); 19990111Simp case 'f': /* Filename known? */ 20017987Speter return (strcmp(get_filename(curr_ifile), "-") != 0); 2011556Srgrimes case 'l': /* Line number known? */ 202100308Stjr case 'd': /* Same as l */ 2031556Srgrimes return (linenums); 2041556Srgrimes case 'L': /* Final line number known? */ 2051556Srgrimes case 'D': /* Final page number known? */ 2061556Srgrimes return (linenums && ch_length() != NULL_POSITION); 2071556Srgrimes case 'm': /* More than one file? */ 20896933Stjr#if TAGS 20996933Stjr return (ntags() ? (ntags() > 1) : (nifile() > 1)); 21096933Stjr#else 2111556Srgrimes return (nifile() > 1); 21299762Stjr#endif 2131556Srgrimes case 'n': /* First prompt in a new file? */ 214100305Stjr#if TAGS 2151556Srgrimes return (ntags() ? 1 : new_file); 21645916Scracauer#else 2171556Srgrimes return (new_file); 2181556Srgrimes#endif 2191556Srgrimes case 'p': /* Percent into file (bytes) known? */ 2201556Srgrimes return (curr_byte(where) != NULL_POSITION && 2211556Srgrimes ch_length() > 0); 22217987Speter case 'P': /* Percent into file (lines) known? */ 22390111Simp return (currline(where) != 0 && 22417987Speter (len = ch_length()) > 0 && 22596933Stjr find_linenum(len) != 0); 2261556Srgrimes case 's': /* Size of file known? */ 2271556Srgrimes case 'B': 2281556Srgrimes return (ch_length() != NULL_POSITION); 2291556Srgrimes case 'x': /* Is there a "next" file? */ 2301556Srgrimes#if TAGS 2311556Srgrimes if (ntags()) 23296933Stjr return (0); 23396933Stjr#endif 2341556Srgrimes return (next_ifile(curr_ifile) != NULL_IFILE); 235100305Stjr } 236104275Smux return (0); 23796933Stjr} 23896933Stjr 23996933Stjr/* 2401556Srgrimes * Decode a "percent" prototype character. 2411556Srgrimes * A prototype string may include various "percent" escapes; 2421556Srgrimes * that is, a percent sign followed by a single letter. 2431556Srgrimes * Here we decode that letter and take the appropriate action, 2441556Srgrimes * usually by appending something to the message being built. 245213811Sobrien */ 24690111Simp static void 24717987Speterprotochar(c, where, iseditproto) 2481556Srgrimes int c; 2491556Srgrimes int where; 2501556Srgrimes int iseditproto; 2511556Srgrimes{ 2521556Srgrimes POSITION pos; 25397660Stjr POSITION len; 2541556Srgrimes int n; 2551556Srgrimes LINENUM linenum; 2561556Srgrimes LINENUM last_linenum; 25726104Ssteve IFILE h; 2581556Srgrimes 2591556Srgrimes#undef PAGE_NUM 2601556Srgrimes#define PAGE_NUM(linenum) ((((linenum) - 1) / (sc_height - 1)) + 1) 2611556Srgrimes 2621556Srgrimes switch (c) 2631556Srgrimes { 2641556Srgrimes case 'b': /* Current byte offset */ 2651556Srgrimes pos = curr_byte(where); 2661556Srgrimes if (pos != NULL_POSITION) 2671556Srgrimes ap_pos(pos); 26897669Stjr else 26917987Speter ap_quest(); 27097669Stjr break; 271163085Sstefanf case 'c': 27297669Stjr ap_int(hshift); 27397669Stjr break; 274100663Stjr case 'd': /* Current page number */ 275163085Sstefanf linenum = currline(where); 276163085Sstefanf if (linenum > 0 && sc_height > 1) 27797669Stjr ap_linenum(PAGE_NUM(linenum)); 27897669Stjr else 279163085Sstefanf ap_quest(); 28097669Stjr break; 281163085Sstefanf case 'D': /* Final page number */ 282163085Sstefanf /* Find the page number of the last byte in the file (len-1). */ 283163085Sstefanf len = ch_length(); 28497669Stjr if (len == NULL_POSITION) 285163085Sstefanf ap_quest(); 28697669Stjr else if (len == 0) 28797669Stjr /* An empty file has no pages. */ 28897669Stjr ap_linenum(0); 28997669Stjr else 29097669Stjr { 29197669Stjr linenum = find_linenum(len - 1); 29297669Stjr if (linenum <= 0) 29397669Stjr ap_quest(); 29497669Stjr else 29597669Stjr ap_linenum(PAGE_NUM(linenum)); 296163085Sstefanf } 29797669Stjr break; 29897669Stjr#if EDITOR 299163085Sstefanf case 'E': /* Editor name */ 30097669Stjr ap_str(editor); 30197669Stjr break; 3021556Srgrimes#endif 3031556Srgrimes case 'f': /* File name */ 304213811Sobrien ap_str(get_filename(curr_ifile)); 305163085Sstefanf break; 30697663Stjr case 'F': /* Last component of file name */ 30797663Stjr ap_str(last_component(get_filename(curr_ifile))); 30897663Stjr break; 30997669Stjr case 'i': /* Index into list of files */ 31097669Stjr#if TAGS 311163085Sstefanf if (ntags()) 31297669Stjr ap_int(curr_tag()); 3131556Srgrimes else 314163085Sstefanf#endif 31597663Stjr ap_int(get_index(curr_ifile)); 31697669Stjr break; 31797669Stjr case 'l': /* Current line number */ 31897669Stjr linenum = currline(where); 31997669Stjr if (linenum != 0) 32097669Stjr ap_linenum(linenum); 32197669Stjr else 32297669Stjr ap_quest(); 32397669Stjr break; 32497663Stjr case 'L': /* Final line number */ 325163085Sstefanf len = ch_length(); 326163085Sstefanf if (len == NULL_POSITION || len == ch_zero() || 327163085Sstefanf (linenum = find_linenum(len)) <= 0) 328163085Sstefanf ap_quest(); 32997669Stjr else 33097669Stjr ap_linenum(linenum-1); 331163085Sstefanf break; 33297669Stjr case 'm': /* Number of files */ 33397822Stjr#if TAGS 33497822Stjr n = ntags(); 33597819Stjr if (n) 33697669Stjr ap_int(n); 33797819Stjr else 33897669Stjr#endif 33997669Stjr ap_int(nifile()); 34097669Stjr break; 34197663Stjr case 'p': /* Percent into file (bytes) */ 34297669Stjr pos = curr_byte(where); 34397663Stjr len = ch_length(); 34497816Stjr if (pos != NULL_POSITION && len > 0) 34597663Stjr ap_int(percentage(pos,len)); 34697663Stjr else 347163085Sstefanf ap_quest(); 348100308Stjr break; 34997669Stjr case 'P': /* Percent into file (lines) */ 35097669Stjr linenum = currline(where); 35197669Stjr if (linenum == 0 || 35297663Stjr (len = ch_length()) == NULL_POSITION || len == ch_zero() || 35397819Stjr (last_linenum = find_linenum(len)) <= 0) 35497819Stjr ap_quest(); 35597819Stjr else 35697669Stjr ap_int(percentage(linenum, last_linenum)); 35797663Stjr break; 35897820Stjr case 's': /* Size of file */ 35997820Stjr case 'B': 36097820Stjr len = ch_length(); 36197820Stjr if (len != NULL_POSITION) 36297820Stjr ap_pos(len); 36397663Stjr else 36497663Stjr ap_quest(); 365155301Sschweikh break; 36697663Stjr case 't': /* Truncate trailing spaces in the message */ 36797663Stjr while (mp > message && mp[-1] == ' ') 36897663Stjr mp--; 36997663Stjr *mp = '\0'; 370125155Snjl break; 37197663Stjr case 'T': /* Type of list */ 37297663Stjr#if TAGS 37397663Stjr if (ntags()) 37497663Stjr ap_str("tag"); 37597663Stjr else 37697663Stjr#endif 37797663Stjr ap_str("file"); 37897663Stjr break; 37997663Stjr case 'x': /* Name of next file */ 38097663Stjr h = next_ifile(curr_ifile); 38197663Stjr if (h != NULL_IFILE) 38297663Stjr ap_str(get_filename(h)); 38397663Stjr else 38497663Stjr ap_quest(); 38597669Stjr break; 38697663Stjr } 38797663Stjr} 38897663Stjr 38997663Stjr/* 3901556Srgrimes * Skip a false conditional. 3911556Srgrimes * When a false condition is found (either a false IF or the ELSE part 3921556Srgrimes * of a true IF), this routine scans the prototype string to decide 3931556Srgrimes * where to resume parsing the string. 3941556Srgrimes * We must keep track of nested IFs and skip them properly. 3951556Srgrimes */ 3961556Srgrimes static constant char * 3971556Srgrimesskipcond(p) 3981556Srgrimes register constant char *p; 3991556Srgrimes{ 400163085Sstefanf register int iflevel; 40117987Speter 4021556Srgrimes /* 4031556Srgrimes * We came in here after processing a ? or :, 4041556Srgrimes * so we start nested one level deep. 4051556Srgrimes */ 406208489Sjilles iflevel = 1; 4071556Srgrimes 4081556Srgrimes for (;;) switch (*++p) 4091556Srgrimes { 4101556Srgrimes case '?': 4111556Srgrimes /* 4121556Srgrimes * Start of a nested IF. 4131556Srgrimes */ 4141556Srgrimes iflevel++; 4151556Srgrimes break; 416163085Sstefanf case ':': 4171556Srgrimes /* 418209600Sjilles * Else. 419209600Sjilles * If this matches the IF we came in here with, 420209600Sjilles * then we're done. 421209600Sjilles */ 422209600Sjilles if (iflevel == 1) 4231556Srgrimes return (p); 4241556Srgrimes break; 4251556Srgrimes case '.': 4261556Srgrimes /* 4271556Srgrimes * Endif. 4281556Srgrimes * If this matches the IF we came in here with, 4291556Srgrimes * then we're done. 4301556Srgrimes */ 4311556Srgrimes if (--iflevel == 0) 4321556Srgrimes return (p); 433213811Sobrien break; 43490111Simp case '\\': 43590111Simp /* 4361556Srgrimes * Backslash escapes the next character. 4371556Srgrimes */ 4381556Srgrimes ++p; 4391556Srgrimes break; 440209600Sjilles case '\0': 441209600Sjilles /* 4421556Srgrimes * Whoops. Hit end of string. 4431556Srgrimes * This is a malformed conditional, but just treat it 4441556Srgrimes * as if all active conditionals ends here. 4451556Srgrimes */ 4461556Srgrimes return (p-1); 4471556Srgrimes } 4481556Srgrimes /*NOTREACHED*/ 4491556Srgrimes} 45097659Stjr 4511556Srgrimes/* 4521556Srgrimes * Decode a char that represents a position on the screen. 4531556Srgrimes */ 4541556Srgrimes static constant char * 4551556Srgrimeswherechar(p, wp) 4561556Srgrimes char constant *p; 4571556Srgrimes int *wp; 45890111Simp{ 45917987Speter switch (*p) 4601556Srgrimes { 46126104Ssteve case 'b': case 'd': case 'l': case 'p': case 'P': 4621556Srgrimes switch (*++p) 4631556Srgrimes { 4641556Srgrimes case 't': *wp = TOP; break; 4651556Srgrimes case 'm': *wp = MIDDLE; break; 4661556Srgrimes case 'b': *wp = BOTTOM; break; 4671556Srgrimes case 'B': *wp = BOTTOM_PLUS_ONE; break; 4681556Srgrimes case 'j': *wp = adjsline(jump_sline); break; 46938536Scracauer default: *wp = TOP; p--; break; 47038536Scracauer } 47138536Scracauer } 47238536Scracauer return (p); 47338536Scracauer} 47438536Scracauer 47538521Scracauer/* 47638536Scracauer * Construct a message based on a prototype string. 4771556Srgrimes */ 4781556Srgrimes public char * 4791556Srgrimespr_expand(proto, maxwidth) 48026104Ssteve constant char *proto; 48126104Ssteve int maxwidth; 4821556Srgrimes{ 48326104Ssteve register constant char *p; 48426104Ssteve register int c; 4851556Srgrimes int where; 4861556Srgrimes 48726104Ssteve mp = message; 488209600Sjilles 4891556Srgrimes if (*proto == '\0') 490209600Sjilles return (""); 491209600Sjilles 492209600Sjilles for (p = proto; *p != '\0'; p++) 493209600Sjilles { 494209600Sjilles switch (*p) 49538536Scracauer { 49626104Ssteve default: /* Just put the character in the message */ 4971556Srgrimes ap_char(*p); 4981556Srgrimes break; 499209600Sjilles case '\\': /* Backslash escapes the next character */ 500209600Sjilles p++; 501209600Sjilles ap_char(*p); 502209600Sjilles break; 503209600Sjilles case '?': /* Conditional (IF) */ 504209600Sjilles if ((c = *++p) == '\0') 505209600Sjilles --p; 506209600Sjilles else 507209600Sjilles { 508209600Sjilles where = 0; 5091556Srgrimes p = wherechar(p, &where); 5101556Srgrimes if (!cond(c, where)) 51138536Scracauer p = skipcond(p); 5121556Srgrimes } 5131556Srgrimes break; 5141556Srgrimes case ':': /* ELSE */ 5151556Srgrimes p = skipcond(p); 5161556Srgrimes break; 5171556Srgrimes case '.': /* ENDIF */ 51838521Scracauer break; 51938521Scracauer case '%': /* Percent escape */ 52038521Scracauer if ((c = *++p) == '\0') 52138521Scracauer --p; 5221556Srgrimes else 5231556Srgrimes { 5241556Srgrimes where = 0; 5251556Srgrimes p = wherechar(p, &where); 52617987Speter protochar(c, where, 52790111Simp#if EDITOR 52817987Speter (proto == editproto)); 5291556Srgrimes#else 5301556Srgrimes 0); 5311556Srgrimes#endif 5321556Srgrimes 5331556Srgrimes } 534100308Stjr break; 5351556Srgrimes } 5361556Srgrimes } 5371556Srgrimes 5381556Srgrimes if (mp == message) 5391556Srgrimes return (""); 5401556Srgrimes if (maxwidth > 0 && mp >= message + maxwidth) 5411556Srgrimes { 5421556Srgrimes /* 5431556Srgrimes * Message is too long. 5441556Srgrimes * Return just the final portion of it. 5451556Srgrimes */ 546213811Sobrien return (mp - maxwidth); 54790111Simp } 54890111Simp return (message); 5491556Srgrimes} 55097688Stjr 551100308Stjr/* 5521556Srgrimes * Return a message suitable for printing by the "=" command. 5531556Srgrimes */ 5541556Srgrimes public char * 5551556Srgrimeseq_message() 55697659Stjr{ 5571556Srgrimes return (pr_expand(eqproto, 0)); 55897659Stjr} 5591556Srgrimes 5601556Srgrimes/* 5611556Srgrimes * Return a prompt. 5621556Srgrimes * This depends on the prompt type (SHORT, MEDIUM, LONG), etc. 5631556Srgrimes * If we can't come up with an appropriate prompt, return NULL 5641556Srgrimes * and the caller will prompt with a colon. 5651556Srgrimes */ 5661556Srgrimes public char * 5671556Srgrimespr_string() 5681556Srgrimes{ 5691556Srgrimes char *prompt; 5701556Srgrimes int type; 57197688Stjr 57297688Stjr type = (!less_is_more) ? pr_type : pr_type ? 0 : 1; 57397688Stjr prompt = pr_expand((ch_getflags() & CH_HELPFILE) ? 57497688Stjr hproto : prproto[type], 57597688Stjr sc_width-so_s_width-so_e_width-2); 57697688Stjr new_file = 0; 57797688Stjr return (prompt); 5781556Srgrimes} 57997688Stjr 58097688Stjr/* 58197688Stjr * Return a message suitable for printing while waiting in the F command. 58297688Stjr */ 58397688Stjr public char * 58497688Stjrwait_message() 58597688Stjr{ 58697688Stjr return (pr_expand(wproto, sc_width-so_s_width-so_e_width-2)); 58797688Stjr} 58897688Stjr