Annotation of src/usr.bin/less/command.c, Revision 1.8
1.1 etheisen 1: /*
1.6 millert 2: * Copyright (C) 1984-2002 Mark Nudelman
1.1 etheisen 3: *
1.6 millert 4: * You may distribute under the terms of either the GNU General Public
5: * License or the Less License, as specified in the README file.
1.1 etheisen 6: *
1.6 millert 7: * For more information about less, or for information on how to
8: * contact the author, see the README file.
1.1 etheisen 9: */
10:
11:
12: /*
13: * User-level command processor.
14: */
15:
16: #include "less.h"
1.6 millert 17: #if MSDOS_COMPILER==WIN32C
18: #include <windows.h>
19: #endif
1.1 etheisen 20: #include "position.h"
21: #include "option.h"
22: #include "cmd.h"
23:
24: extern int erase_char, kill_char;
25: extern int sigs;
26: extern int quit_at_eof;
1.6 millert 27: extern int quit_if_one_screen;
28: extern int squished;
1.1 etheisen 29: extern int hit_eof;
30: extern int sc_width;
31: extern int sc_height;
32: extern int swindow;
33: extern int jump_sline;
34: extern int quitting;
35: extern int wscroll;
1.7 millert 36: extern int nohelp;
1.1 etheisen 37: extern int top_scroll;
38: extern int ignore_eoi;
1.6 millert 39: extern int secure;
40: extern int hshift;
41: extern int show_attn;
1.1 etheisen 42: extern char *every_first_cmd;
43: extern char *curr_altfilename;
44: extern char version[];
45: extern struct scrpos initial_scrpos;
46: extern IFILE curr_ifile;
1.6 millert 47: extern void constant *ml_search;
48: extern void constant *ml_examine;
1.1 etheisen 49: #if SHELL_ESCAPE || PIPEC
1.6 millert 50: extern void constant *ml_shell;
1.1 etheisen 51: #endif
52: #if EDITOR
53: extern char *editor;
54: extern char *editproto;
55: #endif
56: extern int screen_trashed; /* The screen has been overwritten */
1.6 millert 57: extern int shift_count;
1.2 etheisen 58: extern int be_helpful;
59:
1.6 millert 60: static char ungot[UNGOT_SIZE];
1.1 etheisen 61: static char *ungotp = NULL;
62: #if SHELL_ESCAPE
63: static char *shellcmd = NULL; /* For holding last shell command for "!!" */
64: #endif
65: static int mca; /* The multicharacter command (action) */
66: static int search_type; /* The previous type of search */
1.6 millert 67: static LINENUM number; /* The number typed by the user */
1.1 etheisen 68: static char optchar;
69: static int optflag;
1.6 millert 70: static int optgetname;
71: static POSITION bottompos;
72: static char *help_prompt;
1.1 etheisen 73: #if PIPEC
74: static char pipec;
75: #endif
76:
77: static void multi_search();
78:
79: /*
80: * Move the cursor to lower left before executing a command.
81: * This looks nicer if the command takes a long time before
82: * updating the screen.
83: */
84: static void
85: cmd_exec()
86: {
1.6 millert 87: clear_attn();
1.1 etheisen 88: lower_left();
89: flush();
90: }
91:
92: /*
93: * Set up the display to start a new multi-character command.
94: */
95: static void
1.6 millert 96: start_mca(action, prompt, mlist, cmdflags)
1.1 etheisen 97: int action;
98: char *prompt;
99: void *mlist;
1.6 millert 100: int cmdflags;
1.1 etheisen 101: {
102: mca = action;
1.6 millert 103: clear_cmd();
1.1 etheisen 104: cmd_putstr(prompt);
1.6 millert 105: set_mlist(mlist, cmdflags);
1.1 etheisen 106: }
107:
108: public int
109: in_mca()
110: {
111: return (mca != 0 && mca != A_PREFIX);
112: }
113:
114: /*
115: * Set up the display to start a new search command.
116: */
117: static void
118: mca_search()
119: {
120: if (search_type & SRCH_FORW)
121: mca = A_F_SEARCH;
122: else
123: mca = A_B_SEARCH;
124:
1.6 millert 125: clear_cmd();
1.1 etheisen 126:
1.6 millert 127: if (search_type & SRCH_NO_MATCH)
128: cmd_putstr("Non-match ");
1.1 etheisen 129: if (search_type & SRCH_FIRST_FILE)
1.6 millert 130: cmd_putstr("First-file ");
1.1 etheisen 131: if (search_type & SRCH_PAST_EOF)
1.6 millert 132: cmd_putstr("EOF-ignore ");
133: if (search_type & SRCH_NO_MOVE)
134: cmd_putstr("Keep-pos ");
135: if (search_type & SRCH_NO_REGEX)
136: cmd_putstr("Regex-off ");
1.1 etheisen 137:
138: if (search_type & SRCH_FORW)
139: cmd_putstr("/");
140: else
141: cmd_putstr("?");
1.6 millert 142: set_mlist(ml_search, 0);
143: }
144:
145: /*
146: * Set up the display to start a new toggle-option command.
147: */
148: static void
149: mca_opt_toggle()
150: {
151: int no_prompt;
152: int flag;
153: char *dash;
154:
155: no_prompt = (optflag & OPT_NO_PROMPT);
156: flag = (optflag & ~OPT_NO_PROMPT);
157: dash = (flag == OPT_NO_TOGGLE) ? "_" : "-";
158:
159: mca = A_OPT_TOGGLE;
160: clear_cmd();
161: cmd_putstr(dash);
1.8 ! millert 162: #if GNU_OPTIONS
1.6 millert 163: if (optgetname)
164: cmd_putstr(dash);
1.8 ! millert 165: #endif
1.6 millert 166: if (no_prompt)
167: cmd_putstr("(P)");
168: switch (flag)
169: {
170: case OPT_UNSET:
171: cmd_putstr("+");
172: break;
173: case OPT_SET:
174: cmd_putstr("!");
175: break;
176: }
177: set_mlist(NULL, 0);
1.1 etheisen 178: }
179:
180: /*
181: * Execute a multicharacter command.
182: */
183: static void
184: exec_mca()
185: {
1.6 millert 186: register char *cbuf;
1.1 etheisen 187:
188: cmd_exec();
189: cbuf = get_cmdbuf();
190:
191: switch (mca)
192: {
193: case A_F_SEARCH:
194: case A_B_SEARCH:
1.6 millert 195: multi_search(cbuf, (int) number);
1.1 etheisen 196: break;
197: case A_FIRSTCMD:
198: /*
199: * Skip leading spaces or + signs in the string.
200: */
201: while (*cbuf == '+' || *cbuf == ' ')
202: cbuf++;
203: if (every_first_cmd != NULL)
204: free(every_first_cmd);
205: if (*cbuf == '\0')
206: every_first_cmd = NULL;
207: else
208: every_first_cmd = save(cbuf);
209: break;
210: case A_OPT_TOGGLE:
211: toggle_option(optchar, cbuf, optflag);
212: optchar = '\0';
213: break;
214: case A_F_BRACKET:
1.6 millert 215: match_brac(cbuf[0], cbuf[1], 1, (int) number);
1.1 etheisen 216: break;
217: case A_B_BRACKET:
1.6 millert 218: match_brac(cbuf[1], cbuf[0], 0, (int) number);
1.1 etheisen 219: break;
220: #if EXAMINE
221: case A_EXAMINE:
1.6 millert 222: if (secure)
223: break;
1.1 etheisen 224: edit_list(cbuf);
1.6 millert 225: #if TAGS
226: /* If tag structure is loaded then clean it up. */
227: cleantags();
228: #endif
1.1 etheisen 229: break;
230: #endif
231: #if SHELL_ESCAPE
232: case A_SHELL:
233: /*
234: * !! just uses whatever is in shellcmd.
235: * Otherwise, copy cmdbuf to shellcmd,
236: * expanding any special characters ("%" or "#").
237: */
238: if (*cbuf != '!')
239: {
240: if (shellcmd != NULL)
241: free(shellcmd);
242: shellcmd = fexpand(cbuf);
243: }
244:
1.6 millert 245: if (secure)
246: break;
1.1 etheisen 247: if (shellcmd == NULL)
1.6 millert 248: lsystem("", "!done");
1.1 etheisen 249: else
1.6 millert 250: lsystem(shellcmd, "!done");
1.1 etheisen 251: break;
252: #endif
253: #if PIPEC
254: case A_PIPE:
1.6 millert 255: if (secure)
256: break;
1.1 etheisen 257: (void) pipe_mark(pipec, cbuf);
258: error("|done", NULL_PARG);
259: break;
260: #endif
261: }
262: }
263:
264: /*
265: * Add a character to a multi-character command.
266: */
267: static int
268: mca_char(c)
269: int c;
270: {
271: char *p;
272: int flag;
273: char buf[3];
1.6 millert 274: PARG parg;
1.1 etheisen 275:
276: switch (mca)
277: {
278: case 0:
279: /*
280: * Not in a multicharacter command.
281: */
282: return (NO_MCA);
283:
284: case A_PREFIX:
285: /*
286: * In the prefix of a command.
287: * This not considered a multichar command
288: * (even tho it uses cmdbuf, etc.).
289: * It is handled in the commands() switch.
290: */
291: return (NO_MCA);
292:
293: case A_DIGIT:
294: /*
295: * Entering digits of a number.
296: * Terminated by a non-digit.
297: */
298: if ((c < '0' || c > '9') &&
1.6 millert 299: editchar(c, EC_PEEK|EC_NOHISTORY|EC_NOCOMPLETE|EC_NORIGHTLEFT) == A_INVALID)
1.1 etheisen 300: {
301: /*
302: * Not part of the number.
303: * Treat as a normal command character.
304: */
305: number = cmd_int();
306: mca = 0;
307: cmd_accept();
308: return (NO_MCA);
309: }
310: break;
311:
312: case A_OPT_TOGGLE:
313: /*
314: * Special case for the TOGGLE_OPTION command.
315: * If the option letter which was entered is a
316: * single-char option, execute the command immediately,
317: * so user doesn't have to hit RETURN.
318: * If the first char is + or -, this indicates
319: * OPT_UNSET or OPT_SET respectively, instead of OPT_TOGGLE.
1.6 millert 320: * "--" begins inputting a long option name.
1.1 etheisen 321: */
1.6 millert 322: if (optchar == '\0' && len_cmdbuf() == 0)
323: {
324: flag = (optflag & ~OPT_NO_PROMPT);
1.8 ! millert 325: #if GNU_OPTIONS
1.6 millert 326: if (flag == OPT_NO_TOGGLE)
327: {
328: switch (c)
329: {
330: case '_':
331: /* "__" = long option name. */
332: optgetname = TRUE;
333: mca_opt_toggle();
334: return (MCA_MORE);
335: }
336: } else
1.8 ! millert 337: #endif
1.6 millert 338: {
339: switch (c)
340: {
341: case '+':
342: /* "-+" = UNSET. */
343: optflag = (flag == OPT_UNSET) ?
344: OPT_TOGGLE : OPT_UNSET;
345: mca_opt_toggle();
346: return (MCA_MORE);
347: case '!':
348: /* "-!" = SET */
349: optflag = (flag == OPT_SET) ?
350: OPT_TOGGLE : OPT_SET;
351: mca_opt_toggle();
352: return (MCA_MORE);
353: case CONTROL('P'):
354: optflag ^= OPT_NO_PROMPT;
355: mca_opt_toggle();
356: return (MCA_MORE);
1.8 ! millert 357: #if GNU_OPTIONS
1.6 millert 358: case '-':
359: /* "--" = long option name. */
360: optgetname = TRUE;
361: mca_opt_toggle();
362: return (MCA_MORE);
1.8 ! millert 363: #endif
1.6 millert 364: }
365: }
366: }
1.8 ! millert 367: #if GNU_OPTIONS
1.6 millert 368: if (optgetname)
369: {
1.1 etheisen 370: /*
1.6 millert 371: * We're getting a long option name.
372: * See if we've matched an option name yet.
373: * If so, display the complete name and stop
374: * accepting chars until user hits RETURN.
375: */
376: struct loption *o;
377: char *oname;
378: int lc;
379:
380: if (c == '\n' || c == '\r')
381: {
382: /*
383: * When the user hits RETURN, make sure
384: * we've matched an option name, then
385: * pretend he just entered the equivalent
386: * option letter.
387: */
388: if (optchar == '\0')
389: {
390: parg.p_string = get_cmdbuf();
391: error("There is no --%s option", &parg);
392: return (MCA_DONE);
393: }
394: optgetname = FALSE;
395: cmd_reset();
396: c = optchar;
397: } else
1.1 etheisen 398: {
1.6 millert 399: if (optchar != '\0')
400: {
401: /*
402: * Already have a match for the name.
403: * Don't accept anything but erase/kill.
404: */
405: if (c == erase_char || c == kill_char)
406: return (MCA_DONE);
407: return (MCA_MORE);
408: }
409: /*
410: * Add char to cmd buffer and try to match
411: * the option name.
412: */
413: if (cmd_char(c) == CC_QUIT)
414: return (MCA_DONE);
415: p = get_cmdbuf();
416: lc = islower(p[0]);
417: o = findopt_name(&p, &oname, NULL);
418: if (o != NULL)
419: {
420: /*
421: * Got a match.
422: * Remember the option letter and
423: * display the full option name.
424: */
425: optchar = o->oletter;
426: if (!lc && islower(optchar))
427: optchar = toupper(optchar);
428: cmd_reset();
429: mca_opt_toggle();
430: for (p = oname; *p != '\0'; p++)
431: {
432: c = *p;
433: if (!lc && islower(c))
434: c = toupper(c);
435: if (cmd_char(c) != CC_OK)
436: return (MCA_DONE);
437: }
438: }
439: return (MCA_MORE);
1.1 etheisen 440: }
1.6 millert 441: } else
1.8 ! millert 442: #endif
1.6 millert 443: {
444: if (c == erase_char || c == kill_char)
445: break;
446: if (optchar != '\0')
447: /* We already have the option letter. */
448: break;
1.1 etheisen 449: }
1.6 millert 450:
451: optchar = c;
452: if ((optflag & ~OPT_NO_PROMPT) != OPT_TOGGLE ||
453: single_char_option(c))
1.1 etheisen 454: {
1.6 millert 455: toggle_option(c, "", optflag);
456: return (MCA_DONE);
1.1 etheisen 457: }
458: /*
459: * Display a prompt appropriate for the option letter.
460: */
461: if ((p = opt_prompt(c)) == NULL)
462: {
463: buf[0] = '-';
464: buf[1] = c;
465: buf[2] = '\0';
466: p = buf;
467: }
1.6 millert 468: start_mca(A_OPT_TOGGLE, p, (void*)NULL, 0);
1.1 etheisen 469: return (MCA_MORE);
470:
471: case A_F_SEARCH:
472: case A_B_SEARCH:
473: /*
474: * Special case for search commands.
475: * Certain characters as the first char of
476: * the pattern have special meaning:
1.6 millert 477: * ! Toggle the NO_MATCH flag
1.1 etheisen 478: * * Toggle the PAST_EOF flag
479: * @ Toggle the FIRST_FILE flag
480: */
481: if (len_cmdbuf() > 0)
482: /*
483: * Only works for the first char of the pattern.
484: */
485: break;
486:
487: flag = 0;
488: switch (c)
489: {
1.6 millert 490: case CONTROL('E'): /* ignore END of file */
491: case '*':
492: flag = SRCH_PAST_EOF;
1.1 etheisen 493: break;
1.6 millert 494: case CONTROL('F'): /* FIRST file */
1.1 etheisen 495: case '@':
496: flag = SRCH_FIRST_FILE;
497: break;
1.6 millert 498: case CONTROL('K'): /* KEEP position */
499: flag = SRCH_NO_MOVE;
500: break;
501: case CONTROL('R'): /* Don't use REGULAR EXPRESSIONS */
502: flag = SRCH_NO_REGEX;
503: break;
504: case CONTROL('N'): /* NOT match */
505: case '!':
506: flag = SRCH_NO_MATCH;
1.1 etheisen 507: break;
508: }
509: if (flag != 0)
510: {
511: search_type ^= flag;
512: mca_search();
513: return (MCA_MORE);
514: }
515: break;
516: }
517:
518: /*
519: * Any other multicharacter command
520: * is terminated by a newline.
521: */
522: if (c == '\n' || c == '\r')
523: {
524: /*
525: * Execute the command.
526: */
527: exec_mca();
528: return (MCA_DONE);
529: }
1.6 millert 530:
1.1 etheisen 531: /*
532: * Append the char to the command buffer.
533: */
534: if (cmd_char(c) == CC_QUIT)
535: /*
536: * Abort the multi-char command.
537: */
538: return (MCA_DONE);
539:
540: if ((mca == A_F_BRACKET || mca == A_B_BRACKET) && len_cmdbuf() >= 2)
541: {
542: /*
543: * Special case for the bracket-matching commands.
544: * Execute the command after getting exactly two
545: * characters from the user.
546: */
547: exec_mca();
548: return (MCA_DONE);
549: }
550:
551: /*
552: * Need another character.
553: */
554: return (MCA_MORE);
555: }
556:
557: /*
1.6 millert 558: * Make sure the screen is displayed.
1.1 etheisen 559: */
560: static void
1.6 millert 561: make_display()
1.1 etheisen 562: {
563: /*
564: * If nothing is displayed yet, display starting from initial_scrpos.
565: */
566: if (empty_screen())
567: {
568: if (initial_scrpos.pos == NULL_POSITION)
569: /*
570: * {{ Maybe this should be:
571: * jump_loc(ch_zero(), jump_sline);
572: * but this behavior seems rather unexpected
573: * on the first screen. }}
574: */
575: jump_loc(ch_zero(), 1);
576: else
577: jump_loc(initial_scrpos.pos, initial_scrpos.ln);
578: } else if (screen_trashed)
579: {
580: int save_top_scroll;
581: save_top_scroll = top_scroll;
582: top_scroll = 1;
583: repaint();
584: top_scroll = save_top_scroll;
585: }
1.6 millert 586: }
587:
588: /*
589: * Display the appropriate prompt.
590: */
591: static void
592: prompt()
593: {
594: register char *p;
595:
596: if (ungotp != NULL && ungotp > ungot)
597: {
598: /*
599: * No prompt necessary if commands are from
600: * ungotten chars rather than from the user.
601: */
602: return;
603: }
604:
605: /*
606: * Make sure the screen is displayed.
607: */
608: make_display();
609: bottompos = position(BOTTOM_PLUS_ONE);
1.1 etheisen 610:
611: /*
612: * If the -E flag is set and we've hit EOF on the last file, quit.
613: */
1.6 millert 614: if ((quit_at_eof == OPT_ONPLUS || quit_if_one_screen) &&
1.7 millert 615: hit_eof && next_ifile(curr_ifile) == NULL_IFILE)
1.1 etheisen 616: quit(QUIT_OK);
1.6 millert 617: quit_if_one_screen = FALSE;
618: #if 0 /* This doesn't work well because some "te"s clear the screen. */
619: /*
620: * If the -e flag is set and we've hit EOF on the last file,
621: * and the file is squished (shorter than the screen), quit.
622: */
623: if (quit_at_eof && squished &&
624: next_ifile(curr_ifile) == NULL_IFILE)
625: quit(QUIT_OK);
626: #endif
1.1 etheisen 627:
628: /*
629: * Select the proper prompt and display it.
630: */
1.6 millert 631: clear_cmd();
632: p = help_prompt ? help_prompt : pr_string();
633: if (p == NULL)
634: putchr(':');
635: else
636: {
1.1 etheisen 637: so_enter();
1.6 millert 638: putstr(p);
639: if (be_helpful && !help_prompt && strlen(p) + 40 < sc_width)
640: putstr(" [Press space to continue, 'q' to quit.]");
1.1 etheisen 641: so_exit();
642: }
1.6 millert 643: help_prompt = NULL;
1.1 etheisen 644: }
645:
1.6 millert 646: /*
647: * Display the less version message.
648: */
1.1 etheisen 649: public void
650: dispversion()
651: {
652: PARG parg;
653:
654: parg.p_string = version;
1.6 millert 655: error("less %s", &parg);
1.1 etheisen 656: }
657:
658: /*
659: * Get command character.
660: * The character normally comes from the keyboard,
661: * but may come from ungotten characters
662: * (characters previously given to ungetcc or ungetsc).
663: */
664: public int
665: getcc()
666: {
667: if (ungotp == NULL)
668: /*
669: * Normal case: no ungotten chars, so get one from the user.
670: */
671: return (getchr());
672:
673: if (ungotp > ungot)
674: /*
675: * Return the next ungotten char.
676: */
677: return (*--ungotp);
678:
679: /*
680: * We have just run out of ungotten chars.
681: */
682: ungotp = NULL;
683: if (len_cmdbuf() == 0 || !empty_screen())
684: return (getchr());
685: /*
686: * Command is incomplete, so try to complete it.
687: */
688: switch (mca)
689: {
690: case A_DIGIT:
691: /*
692: * We have a number but no command. Treat as #g.
693: */
694: return ('g');
695:
696: case A_F_SEARCH:
697: case A_B_SEARCH:
698: /*
699: * We have "/string" but no newline. Add the \n.
700: */
701: return ('\n');
702:
703: default:
704: /*
705: * Some other incomplete command. Let user complete it.
706: */
707: return (getchr());
708: }
709: }
710:
711: /*
712: * "Unget" a command character.
713: * The next getcc() will return this character.
714: */
715: public void
716: ungetcc(c)
717: int c;
718: {
719: if (ungotp == NULL)
720: ungotp = ungot;
721: if (ungotp >= ungot + sizeof(ungot))
722: {
723: error("ungetcc overflow", NULL_PARG);
724: quit(QUIT_ERROR);
725: }
726: *ungotp++ = c;
727: }
728:
729: /*
730: * Unget a whole string of command characters.
731: * The next sequence of getcc()'s will return this string.
732: */
733: public void
734: ungetsc(s)
735: char *s;
736: {
1.6 millert 737: register char *p;
1.1 etheisen 738:
739: for (p = s + strlen(s) - 1; p >= s; p--)
740: ungetcc(*p);
741: }
742:
743: /*
744: * Search for a pattern, possibly in multiple files.
745: * If SRCH_FIRST_FILE is set, begin searching at the first file.
746: * If SRCH_PAST_EOF is set, continue the search thru multiple files.
747: */
748: static void
749: multi_search(pattern, n)
750: char *pattern;
751: int n;
752: {
1.6 millert 753: register int nomore;
1.1 etheisen 754: IFILE save_ifile;
755: int changed_file;
756:
757: changed_file = 0;
1.6 millert 758: save_ifile = save_curr_ifile();
1.1 etheisen 759:
760: if (search_type & SRCH_FIRST_FILE)
761: {
762: /*
763: * Start at the first (or last) file
764: * in the command line list.
765: */
766: if (search_type & SRCH_FORW)
767: nomore = edit_first();
768: else
769: nomore = edit_last();
770: if (nomore)
1.6 millert 771: {
772: unsave_ifile(save_ifile);
1.1 etheisen 773: return;
1.6 millert 774: }
1.1 etheisen 775: changed_file = 1;
776: search_type &= ~SRCH_FIRST_FILE;
777: }
778:
779: for (;;)
780: {
1.6 millert 781: n = search(search_type, pattern, n);
782: /*
783: * The SRCH_NO_MOVE flag doesn't "stick": it gets cleared
784: * after being used once. This allows "n" to work after
785: * using a /@@ search.
786: */
787: search_type &= ~SRCH_NO_MOVE;
788: if (n == 0)
789: {
1.1 etheisen 790: /*
791: * Found it.
792: */
1.6 millert 793: unsave_ifile(save_ifile);
1.1 etheisen 794: return;
1.6 millert 795: }
1.1 etheisen 796:
797: if (n < 0)
798: /*
799: * Some kind of error in the search.
800: * Error message has been printed by search().
801: */
802: break;
803:
804: if ((search_type & SRCH_PAST_EOF) == 0)
805: /*
806: * We didn't find a match, but we're
807: * supposed to search only one file.
808: */
809: break;
810: /*
811: * Move on to the next file.
812: */
813: if (search_type & SRCH_FORW)
814: nomore = edit_next(1);
815: else
816: nomore = edit_prev(1);
817: if (nomore)
818: break;
819: changed_file = 1;
820: }
821:
822: /*
823: * Didn't find it.
824: * Print an error message if we haven't already.
825: */
826: if (n > 0)
827: error("Pattern not found", NULL_PARG);
828:
829: if (changed_file)
830: {
831: /*
832: * Restore the file we were originally viewing.
833: */
1.6 millert 834: reedit_ifile(save_ifile);
1.1 etheisen 835: }
836: }
837:
838: /*
839: * Main command processor.
840: * Accept and execute commands until a quit command.
841: */
842: public void
843: commands()
844: {
1.6 millert 845: register int c;
846: register int action;
847: register char *cbuf;
848: int newaction;
1.1 etheisen 849: int save_search_type;
1.6 millert 850: char *extra;
1.1 etheisen 851: char tbuf[2];
852: PARG parg;
1.6 millert 853: IFILE old_ifile;
854: IFILE new_ifile;
855: char *tagfile;
1.1 etheisen 856:
857: search_type = SRCH_FORW;
858: wscroll = (sc_height + 1) / 2;
1.6 millert 859: newaction = A_NOACTION;
1.1 etheisen 860:
861: for (;;)
862: {
863: mca = 0;
864: cmd_accept();
865: number = 0;
866: optchar = '\0';
867:
868: /*
869: * See if any signals need processing.
870: */
871: if (sigs)
872: {
873: psignals();
874: if (quitting)
875: quit(QUIT_SAVED_STATUS);
876: }
1.6 millert 877:
878: /*
879: * See if window size changed, for systems that don't
880: * generate SIGWINCH.
881: */
882: check_winch();
883:
1.1 etheisen 884: /*
885: * Display prompt and accept a character.
886: */
887: cmd_reset();
888: prompt();
889: if (sigs)
890: continue;
1.6 millert 891: if (newaction == A_NOACTION)
892: c = getcc();
1.1 etheisen 893:
894: again:
895: if (sigs)
896: continue;
897:
1.6 millert 898: if (newaction != A_NOACTION)
899: {
900: action = newaction;
901: newaction = A_NOACTION;
902: } else
903: {
904: /*
905: * If we are in a multicharacter command, call mca_char.
906: * Otherwise we call fcmd_decode to determine the
907: * action to be performed.
908: */
909: if (mca)
910: switch (mca_char(c))
911: {
912: case MCA_MORE:
913: /*
914: * Need another character.
915: */
916: c = getcc();
917: goto again;
918: case MCA_DONE:
919: /*
920: * Command has been handled by mca_char.
921: * Start clean with a prompt.
922: */
923: continue;
924: case NO_MCA:
925: /*
926: * Not a multi-char command
927: * (at least, not anymore).
928: */
929: break;
930: }
931:
932: /*
933: * Decode the command character and decide what to do.
934: */
935: if (mca)
1.1 etheisen 936: {
937: /*
1.6 millert 938: * We're in a multichar command.
939: * Add the character to the command buffer
940: * and display it on the screen.
941: * If the user backspaces past the start
942: * of the line, abort the command.
1.1 etheisen 943: */
1.6 millert 944: if (cmd_char(c) == CC_QUIT || len_cmdbuf() == 0)
945: continue;
946: cbuf = get_cmdbuf();
947: } else
948: {
1.1 etheisen 949: /*
1.6 millert 950: * Don't use cmd_char if we're starting fresh
951: * at the beginning of a command, because we
952: * don't want to echo the command until we know
953: * it is a multichar command. We also don't
954: * want erase_char/kill_char to be treated
955: * as line editing characters.
1.1 etheisen 956: */
1.6 millert 957: tbuf[0] = c;
958: tbuf[1] = '\0';
959: cbuf = tbuf;
1.1 etheisen 960: }
1.6 millert 961: extra = NULL;
962: action = fcmd_decode(cbuf, &extra);
1.1 etheisen 963: /*
1.6 millert 964: * If an "extra" string was returned,
965: * process it as a string of command characters.
966: */
967: if (extra != NULL)
968: ungetsc(extra);
1.1 etheisen 969: }
970: /*
971: * Clear the cmdbuf string.
972: * (But not if we're in the prefix of a command,
973: * because the partial command string is kept there.)
974: */
975: if (action != A_PREFIX)
976: cmd_reset();
977:
978: switch (action)
979: {
980: case A_DIGIT:
981: /*
982: * First digit of a number.
983: */
1.6 millert 984: start_mca(A_DIGIT, ":", (void*)NULL, CF_QUIT_ON_ERASE);
1.1 etheisen 985: goto again;
986:
987: case A_F_WINDOW:
988: /*
989: * Forward one window (and set the window size).
990: */
991: if (number > 0)
1.6 millert 992: swindow = (int) number;
1.1 etheisen 993: /* FALLTHRU */
994: case A_F_SCREEN:
995: /*
996: * Forward one screen.
997: */
998: if (number <= 0)
999: number = get_swindow();
1000: cmd_exec();
1.6 millert 1001: if (show_attn)
1002: set_attnpos(bottompos);
1003: forward((int) number, 0, 1);
1.1 etheisen 1004: break;
1005:
1006: case A_B_WINDOW:
1007: /*
1008: * Backward one window (and set the window size).
1009: */
1010: if (number > 0)
1.6 millert 1011: swindow = (int) number;
1.1 etheisen 1012: /* FALLTHRU */
1013: case A_B_SCREEN:
1014: /*
1015: * Backward one screen.
1016: */
1017: if (number <= 0)
1018: number = get_swindow();
1019: cmd_exec();
1.6 millert 1020: backward((int) number, 0, 1);
1.1 etheisen 1021: break;
1022:
1023: case A_F_LINE:
1024: /*
1025: * Forward N (default 1) line.
1026: */
1027: if (number <= 0)
1028: number = 1;
1029: cmd_exec();
1.6 millert 1030: if (show_attn == OPT_ONPLUS && number > 1)
1031: set_attnpos(bottompos);
1032: forward((int) number, 0, 0);
1.1 etheisen 1033: break;
1034:
1035: case A_B_LINE:
1036: /*
1037: * Backward N (default 1) line.
1038: */
1039: if (number <= 0)
1040: number = 1;
1041: cmd_exec();
1.6 millert 1042: backward((int) number, 0, 0);
1.1 etheisen 1043: break;
1044:
1045: case A_FF_LINE:
1046: /*
1047: * Force forward N (default 1) line.
1048: */
1049: if (number <= 0)
1050: number = 1;
1051: cmd_exec();
1.6 millert 1052: if (show_attn == OPT_ONPLUS && number > 1)
1053: set_attnpos(bottompos);
1054: forward((int) number, 1, 0);
1.1 etheisen 1055: break;
1056:
1057: case A_BF_LINE:
1058: /*
1059: * Force backward N (default 1) line.
1060: */
1061: if (number <= 0)
1062: number = 1;
1063: cmd_exec();
1.6 millert 1064: backward((int) number, 1, 0);
1.1 etheisen 1065: break;
1066:
1.6 millert 1067: case A_FF_SCREEN:
1068: /*
1069: * Force forward one screen.
1070: */
1071: if (number <= 0)
1072: number = get_swindow();
1073: cmd_exec();
1074: if (show_attn == OPT_ONPLUS)
1075: set_attnpos(bottompos);
1076: forward((int) number, 1, 0);
1077: break;
1078:
1.1 etheisen 1079: case A_F_FOREVER:
1080: /*
1081: * Forward forever, ignoring EOF.
1082: */
1083: cmd_exec();
1084: jump_forw();
1085: ignore_eoi = 1;
1086: hit_eof = 0;
1.6 millert 1087: while (!sigs)
1.1 etheisen 1088: forward(1, 0, 0);
1089: ignore_eoi = 0;
1.6 millert 1090: /*
1091: * This gets us back in "F mode" after processing
1092: * a non-abort signal (e.g. window-change).
1093: */
1094: if (sigs && !ABORT_SIGS())
1095: newaction = A_F_FOREVER;
1.1 etheisen 1096: break;
1097:
1098: case A_F_SCROLL:
1099: /*
1100: * Forward N lines
1101: * (default same as last 'd' or 'u' command).
1102: */
1103: if (number > 0)
1.6 millert 1104: wscroll = (int) number;
1.1 etheisen 1105: cmd_exec();
1.6 millert 1106: if (show_attn == OPT_ONPLUS)
1107: set_attnpos(bottompos);
1.1 etheisen 1108: forward(wscroll, 0, 0);
1109: break;
1110:
1111: case A_B_SCROLL:
1112: /*
1113: * Forward N lines
1114: * (default same as last 'd' or 'u' command).
1115: */
1116: if (number > 0)
1.6 millert 1117: wscroll = (int) number;
1.1 etheisen 1118: cmd_exec();
1119: backward(wscroll, 0, 0);
1120: break;
1121:
1122: case A_FREPAINT:
1123: /*
1124: * Flush buffers, then repaint screen.
1125: * Don't flush the buffers on a pipe!
1126: */
1127: if (ch_getflags() & CH_CANSEEK)
1128: {
1129: ch_flush();
1130: clr_linenum();
1.6 millert 1131: #if HILITE_SEARCH
1132: clr_hilite();
1133: #endif
1.1 etheisen 1134: }
1135: /* FALLTHRU */
1136: case A_REPAINT:
1137: /*
1138: * Repaint screen.
1139: */
1140: cmd_exec();
1141: repaint();
1142: break;
1143:
1144: case A_GOLINE:
1145: /*
1146: * Go to line N, default beginning of file.
1147: */
1148: if (number <= 0)
1149: number = 1;
1150: cmd_exec();
1151: jump_back(number);
1152: break;
1153:
1154: case A_PERCENT:
1155: /*
1156: * Go to a specified percentage into the file.
1157: */
1158: if (number < 0)
1159: number = 0;
1160: if (number > 100)
1161: number = 100;
1162: cmd_exec();
1.6 millert 1163: jump_percent((int) number);
1.1 etheisen 1164: break;
1165:
1166: case A_GOEND:
1167: /*
1168: * Go to line N, default end of file.
1169: */
1170: cmd_exec();
1171: if (number <= 0)
1172: jump_forw();
1173: else
1174: jump_back(number);
1175: break;
1176:
1177: case A_GOPOS:
1178: /*
1179: * Go to a specified byte position in the file.
1180: */
1181: cmd_exec();
1182: if (number < 0)
1183: number = 0;
1.6 millert 1184: jump_line_loc((POSITION) number, jump_sline);
1.1 etheisen 1185: break;
1186:
1187: case A_STAT:
1188: /*
1189: * Print file name, etc.
1190: */
1191: cmd_exec();
1192: parg.p_string = eq_message();
1193: error("%s", &parg);
1194: break;
1.6 millert 1195:
1.1 etheisen 1196: case A_VERSION:
1197: /*
1198: * Print version number, without the "@(#)".
1199: */
1200: cmd_exec();
1201: dispversion();
1202: break;
1203:
1204: case A_QUIT:
1205: /*
1206: * Exit.
1207: */
1.6 millert 1208: if (extra != NULL)
1209: quit(*extra);
1.1 etheisen 1210: quit(QUIT_OK);
1.6 millert 1211: break;
1.1 etheisen 1212:
1213: /*
1214: * Define abbreviation for a commonly used sequence below.
1215: */
1216: #define DO_SEARCH() if (number <= 0) number = 1; \
1217: mca_search(); \
1218: cmd_exec(); \
1.6 millert 1219: multi_search((char *)NULL, (int) number);
1.1 etheisen 1220:
1221:
1222: case A_F_SEARCH:
1223: /*
1224: * Search forward for a pattern.
1225: * Get the first char of the pattern.
1226: */
1227: search_type = SRCH_FORW;
1228: if (number <= 0)
1229: number = 1;
1230: mca_search();
1231: c = getcc();
1232: goto again;
1233:
1234: case A_B_SEARCH:
1235: /*
1236: * Search backward for a pattern.
1237: * Get the first char of the pattern.
1238: */
1239: search_type = SRCH_BACK;
1240: if (number <= 0)
1241: number = 1;
1242: mca_search();
1243: c = getcc();
1244: goto again;
1245:
1246: case A_AGAIN_SEARCH:
1247: /*
1248: * Repeat previous search.
1249: */
1250: DO_SEARCH();
1251: break;
1252:
1253: case A_T_AGAIN_SEARCH:
1254: /*
1255: * Repeat previous search, multiple files.
1256: */
1257: search_type |= SRCH_PAST_EOF;
1258: DO_SEARCH();
1259: break;
1260:
1261: case A_REVERSE_SEARCH:
1262: /*
1263: * Repeat previous search, in reverse direction.
1264: */
1265: save_search_type = search_type;
1266: search_type = SRCH_REVERSE(search_type);
1267: DO_SEARCH();
1268: search_type = save_search_type;
1269: break;
1270:
1271: case A_T_REVERSE_SEARCH:
1272: /*
1273: * Repeat previous search,
1274: * multiple files in reverse direction.
1275: */
1276: save_search_type = search_type;
1277: search_type = SRCH_REVERSE(search_type);
1278: search_type |= SRCH_PAST_EOF;
1279: DO_SEARCH();
1280: search_type = save_search_type;
1281: break;
1282:
1283: case A_UNDO_SEARCH:
1284: undo_search();
1285: break;
1286:
1287: case A_HELP:
1288: /*
1289: * Help.
1290: */
1.7 millert 1291: if (nohelp)
1292: {
1293: bell();
1.1 etheisen 1294: break;
1.7 millert 1295: }
1296: clear_bot();
1297: putstr(" help");
1.1 etheisen 1298: cmd_exec();
1.7 millert 1299: help(0);
1.1 etheisen 1300: break;
1301:
1302: case A_EXAMINE:
1303: #if EXAMINE
1304: /*
1305: * Edit a new file. Get the filename.
1306: */
1.6 millert 1307: if (secure)
1308: {
1309: error("Command not available", NULL_PARG);
1310: break;
1311: }
1312: start_mca(A_EXAMINE, "Examine: ", ml_examine, 0);
1.1 etheisen 1313: c = getcc();
1314: goto again;
1315: #else
1316: error("Command not available", NULL_PARG);
1317: break;
1318: #endif
1319:
1320: case A_VISUAL:
1321: /*
1322: * Invoke an editor on the input file.
1323: */
1324: #if EDITOR
1.6 millert 1325: if (secure)
1326: {
1327: error("Command not available", NULL_PARG);
1328: break;
1329: }
1.1 etheisen 1330: if (strcmp(get_filename(curr_ifile), "-") == 0)
1331: {
1332: error("Cannot edit standard input", NULL_PARG);
1333: break;
1334: }
1335: if (curr_altfilename != NULL)
1336: {
1337: error("Cannot edit file processed with LESSOPEN",
1338: NULL_PARG);
1339: break;
1340: }
1.6 millert 1341: start_mca(A_SHELL, "!", ml_shell, 0);
1.1 etheisen 1342: /*
1343: * Expand the editor prototype string
1344: * and pass it to the system to execute.
1.6 millert 1345: * (Make sure the screen is displayed so the
1346: * expansion of "+%lm" works.)
1.1 etheisen 1347: */
1.6 millert 1348: make_display();
1.1 etheisen 1349: cmd_exec();
1.6 millert 1350: lsystem(pr_expand(editproto, 0), (char*)NULL);
1.1 etheisen 1351: break;
1352: #else
1353: error("Command not available", NULL_PARG);
1354: break;
1355: #endif
1356:
1357: case A_NEXT_FILE:
1358: /*
1359: * Examine next file.
1360: */
1.6 millert 1361: #if TAGS
1362: if (ntags())
1363: {
1364: error("No next file", NULL_PARG);
1365: break;
1366: }
1367: #endif
1.1 etheisen 1368: if (number <= 0)
1369: number = 1;
1.6 millert 1370: if (edit_next((int) number))
1.1 etheisen 1371: {
1.7 millert 1372: if (quit_at_eof && hit_eof)
1.1 etheisen 1373: quit(QUIT_OK);
1374: parg.p_string = (number > 1) ? "(N-th) " : "";
1375: error("No %snext file", &parg);
1376: }
1377: break;
1378:
1379: case A_PREV_FILE:
1380: /*
1381: * Examine previous file.
1382: */
1.6 millert 1383: #if TAGS
1384: if (ntags())
1385: {
1386: error("No previous file", NULL_PARG);
1387: break;
1388: }
1389: #endif
1.1 etheisen 1390: if (number <= 0)
1391: number = 1;
1.6 millert 1392: if (edit_prev((int) number))
1.1 etheisen 1393: {
1394: parg.p_string = (number > 1) ? "(N-th) " : "";
1395: error("No %sprevious file", &parg);
1396: }
1397: break;
1398:
1.6 millert 1399: case A_NEXT_TAG:
1400: #if TAGS
1401: if (number <= 0)
1402: number = 1;
1403: tagfile = nexttag((int) number);
1404: if (tagfile == NULL)
1405: {
1406: error("No next tag", NULL_PARG);
1407: break;
1408: }
1409: if (edit(tagfile) == 0)
1410: {
1411: POSITION pos = tagsearch();
1412: if (pos != NULL_POSITION)
1413: jump_loc(pos, jump_sline);
1414: }
1415: #else
1416: error("Command not available", NULL_PARG);
1417: #endif
1418: break;
1419:
1420: case A_PREV_TAG:
1421: #if TAGS
1422: if (number <= 0)
1423: number = 1;
1424: tagfile = prevtag((int) number);
1425: if (tagfile == NULL)
1426: {
1427: error("No previous tag", NULL_PARG);
1428: break;
1429: }
1430: if (edit(tagfile) == 0)
1431: {
1432: POSITION pos = tagsearch();
1433: if (pos != NULL_POSITION)
1434: jump_loc(pos, jump_sline);
1435: }
1436: #else
1437: error("Command not available", NULL_PARG);
1438: #endif
1439: break;
1440:
1.1 etheisen 1441: case A_INDEX_FILE:
1442: /*
1443: * Examine a particular file.
1444: */
1445: if (number <= 0)
1446: number = 1;
1.6 millert 1447: if (edit_index((int) number))
1.1 etheisen 1448: error("No such file", NULL_PARG);
1449: break;
1450:
1.6 millert 1451: case A_REMOVE_FILE:
1452: old_ifile = curr_ifile;
1453: new_ifile = getoff_ifile(curr_ifile);
1454: if (new_ifile == NULL_IFILE)
1455: {
1456: bell();
1457: break;
1458: }
1459: if (edit_ifile(new_ifile) != 0)
1460: {
1461: reedit_ifile(old_ifile);
1462: break;
1463: }
1464: del_ifile(old_ifile);
1465: break;
1466:
1.1 etheisen 1467: case A_OPT_TOGGLE:
1468: optflag = OPT_TOGGLE;
1.6 millert 1469: optgetname = FALSE;
1470: mca_opt_toggle();
1.1 etheisen 1471: c = getcc();
1472: goto again;
1473:
1474: case A_DISP_OPTION:
1475: /*
1476: * Report a flag setting.
1477: */
1.6 millert 1478: optflag = OPT_NO_TOGGLE;
1479: optgetname = FALSE;
1480: mca_opt_toggle();
1.1 etheisen 1481: c = getcc();
1.6 millert 1482: goto again;
1.1 etheisen 1483:
1484: case A_FIRSTCMD:
1485: /*
1486: * Set an initial command for new files.
1487: */
1.6 millert 1488: start_mca(A_FIRSTCMD, "+", (void*)NULL, 0);
1.1 etheisen 1489: c = getcc();
1490: goto again;
1491:
1492: case A_SHELL:
1493: /*
1494: * Shell escape.
1495: */
1496: #if SHELL_ESCAPE
1.6 millert 1497: if (secure)
1498: {
1499: error("Command not available", NULL_PARG);
1500: break;
1501: }
1502: start_mca(A_SHELL, "!", ml_shell, 0);
1.1 etheisen 1503: c = getcc();
1504: goto again;
1505: #else
1506: error("Command not available", NULL_PARG);
1507: break;
1508: #endif
1509:
1510: case A_SETMARK:
1511: /*
1512: * Set a mark.
1513: */
1.6 millert 1514: start_mca(A_SETMARK, "mark: ", (void*)NULL, 0);
1.1 etheisen 1515: c = getcc();
1516: if (c == erase_char || c == kill_char ||
1517: c == '\n' || c == '\r')
1518: break;
1519: setmark(c);
1520: break;
1521:
1522: case A_GOMARK:
1523: /*
1524: * Go to a mark.
1525: */
1.6 millert 1526: start_mca(A_GOMARK, "goto mark: ", (void*)NULL, 0);
1.1 etheisen 1527: c = getcc();
1528: if (c == erase_char || c == kill_char ||
1529: c == '\n' || c == '\r')
1530: break;
1531: gomark(c);
1532: break;
1533:
1534: case A_PIPE:
1535: #if PIPEC
1.6 millert 1536: if (secure)
1537: {
1538: error("Command not available", NULL_PARG);
1539: break;
1540: }
1541: start_mca(A_PIPE, "|mark: ", (void*)NULL, 0);
1.1 etheisen 1542: c = getcc();
1543: if (c == erase_char || c == kill_char)
1544: break;
1545: if (c == '\n' || c == '\r')
1546: c = '.';
1547: if (badmark(c))
1548: break;
1549: pipec = c;
1.6 millert 1550: start_mca(A_PIPE, "!", ml_shell, 0);
1.1 etheisen 1551: c = getcc();
1552: goto again;
1553: #else
1554: error("Command not available", NULL_PARG);
1555: break;
1556: #endif
1557:
1558: case A_B_BRACKET:
1559: case A_F_BRACKET:
1.6 millert 1560: start_mca(action, "Brackets: ", (void*)NULL, 0);
1.1 etheisen 1561: c = getcc();
1562: goto again;
1563:
1.6 millert 1564: case A_LSHIFT:
1565: if (number > 0)
1566: shift_count = number;
1567: else
1568: number = (shift_count > 0) ?
1569: shift_count : sc_width / 2;
1570: if (number > hshift)
1571: number = hshift;
1572: hshift -= number;
1573: screen_trashed = 1;
1574: break;
1575:
1576: case A_RSHIFT:
1577: if (number > 0)
1578: shift_count = number;
1579: else
1580: number = (shift_count > 0) ?
1581: shift_count : sc_width / 2;
1582: hshift += number;
1583: screen_trashed = 1;
1584: break;
1585:
1.1 etheisen 1586: case A_PREFIX:
1587: /*
1588: * The command is incomplete (more chars are needed).
1589: * Display the current char, so the user knows
1590: * what's going on, and get another character.
1591: */
1592: if (mca != A_PREFIX)
1593: {
1594: cmd_reset();
1.6 millert 1595: start_mca(A_PREFIX, " ", (void*)NULL,
1596: CF_QUIT_ON_ERASE);
1.1 etheisen 1597: (void) cmd_char(c);
1598: }
1599: c = getcc();
1600: goto again;
1601:
1602: case A_NOACTION:
1603: break;
1604:
1605: default:
1.2 etheisen 1606: if (be_helpful)
1.6 millert 1607: help_prompt = "[Press 'h' for instructions.]";
1.2 etheisen 1608: else
1609: bell();
1.1 etheisen 1610: break;
1611: }
1612: }
1613: }