distribution by Mark Nudleman
[unix-history] / usr / src / usr.bin / more / command.c
CommitLineData
bfe13c81
KB
1/*
2 * Copyright (c) 1988 Mark Nudleman
3 * Copyright (c) 1988 Regents of the University of California.
4 * All rights reserved.
5 *
6 * This code is derived from software contributed to Berkeley by
7 * Mark Nudleman.
8 *
9 * Redistribution and use in source and binary forms are permitted
10 * provided that the above copyright notice and this paragraph are
11 * duplicated in all such forms and that any documentation,
12 * advertising materials, and other materials related to such
13 * distribution and use acknowledge that the software was developed
14 * by the University of California, Berkeley. The name of the
15 * University may not be used to endorse or promote products derived
16 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
19 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20 */
21
22#ifndef lint
23static char sccsid[] = "@(#)command.c 5.1 (Berkeley) %G%";
24#endif /* not lint */
25
26/*
27 * User-level command processor.
28 */
29
30#include "less.h"
31#include "position.h"
32#include "cmd.h"
33
34#define NO_MCA 0
35#define MCA_DONE 1
36#define MCA_MORE 2
37
38extern int erase_char, kill_char;
39extern int ispipe;
40extern int sigs;
41extern int quit_at_eof;
42extern int hit_eof;
43extern int sc_width;
44extern int sc_height;
45extern int sc_window;
46extern int curr_ac;
47extern int ac;
48extern int quitting;
49extern int scroll;
50extern char *first_cmd;
51extern char *every_first_cmd;
52extern char version[];
53extern char *current_file;
54#if EDITOR
55extern char *editor;
56#endif
57extern int screen_trashed; /* The screen has been overwritten */
58
59static char cmdbuf[120]; /* Buffer for holding a multi-char command */
60#if SHELL_ESCAPE
61static char *shellcmd = NULL; /* For holding last shell command for "!!" */
62#endif
63static char *cp; /* Pointer into cmdbuf */
64static int cmd_col; /* Current column of the multi-char command */
65static int mca; /* The multicharacter command (action) */
66static int last_mca; /* The previous mca */
67static int number; /* The number typed by the user */
68static int wsearch; /* Search for matches (1) or non-matches (0) */
69
70/*
71 * Reset command buffer (to empty).
72 */
73cmd_reset()
74{
75 cp = cmdbuf;
76}
77
78/*
79 * Backspace in command buffer.
80 */
81 static int
82cmd_erase()
83{
84 if (cp == cmdbuf)
85 /*
86 * Backspace past beginning of the string:
87 * this usually means abort the command.
88 */
89 return (1);
90
91 if (control_char(*--cp))
92 {
93 /*
94 * Erase an extra character, for the carat.
95 */
96 backspace();
97 cmd_col--;
98 }
99 backspace();
100 cmd_col--;
101 return (0);
102}
103
104/*
105 * Set up the display to start a new multi-character command.
106 */
107start_mca(action, prompt)
108 int action;
109 char *prompt;
110{
111 lower_left();
112 clear_eol();
113 putstr(prompt);
114 cmd_col = strlen(prompt);
115 mca = action;
116}
117
118/*
119 * Process a single character of a multi-character command, such as
120 * a number, or the pattern of a search command.
121 */
122 static int
123cmd_char(c)
124 int c;
125{
126 if (c == erase_char)
127 {
128 if (cmd_erase())
129 return (1);
130 } else if (c == kill_char)
131 {
132 /* {{ Could do this faster, but who cares? }} */
133 while (cmd_erase() == 0)
134 ;
135 } else if (cp >= &cmdbuf[sizeof(cmdbuf)-1])
136 {
137 /*
138 * No room in the command buffer.
139 */
140 bell();
141 } else if (cmd_col >= sc_width-3)
142 {
143 /*
144 * No room on the screen.
145 * {{ Could get fancy here; maybe shift the displayed
146 * line and make room for more chars, like ksh. }}
147 */
148 bell();
149 } else
150 {
151 /*
152 * Append the character to the string.
153 */
154 *cp++ = c;
155 if (control_char(c))
156 {
157 putchr('^');
158 cmd_col++;
159 c = carat_char(c);
160 }
161 putchr(c);
162 cmd_col++;
163 }
164 return (0);
165}
166
167/*
168 * Return the number currently in the command buffer.
169 */
170 static int
171cmd_int()
172{
173 *cp = '\0';
174 cp = cmdbuf;
175 return (atoi(cmdbuf));
176}
177
178/*
179 * Move the cursor to lower left before executing a command.
180 * This looks nicer if the command takes a long time before
181 * updating the screen.
182 */
183 static void
184cmd_exec()
185{
186 lower_left();
187 flush();
188}
189
190/*
191 * Display the appropriate prompt.
192 */
193 static void
194prompt()
195{
196 register char *p;
197
198 if (first_cmd != NULL && *first_cmd != '\0')
199 {
200 /*
201 * No prompt necessary if commands are from first_cmd
202 * rather than from the user.
203 */
204 return;
205 }
206
207 /*
208 * If nothing is displayed yet, display starting from line 1.
209 */
210 if (position(TOP) == NULL_POSITION)
211 jump_back(1);
212 else if (screen_trashed)
213 repaint();
214
215 /*
216 * If the -E flag is set and we've hit EOF on the last file, quit.
217 */
218 if (quit_at_eof == 2 && hit_eof && curr_ac + 1 >= ac)
219 quit();
220
221 /*
222 * Select the proper prompt and display it.
223 */
224 lower_left();
225 clear_eol();
226 p = pr_string();
227 if (p == NULL)
228 putchr(':');
229 else
230 {
231 so_enter();
232 putstr(p);
233 so_exit();
234 }
235}
236
237/*
238 * Get command character.
239 * The character normally comes from the keyboard,
240 * but may come from the "first_cmd" string.
241 */
242 static int
243getcc()
244{
245 if (first_cmd == NULL)
246 return (getchr());
247
248 if (*first_cmd == '\0')
249 {
250 /*
251 * Reached end of first_cmd input.
252 */
253 first_cmd = NULL;
254 if (cp > cmdbuf && position(TOP) == NULL_POSITION)
255 {
256 /*
257 * Command is incomplete, so try to complete it.
258 * There are only two cases:
259 * 1. We have "/string" but no newline. Add the \n.
260 * 2. We have a number but no command. Treat as #g.
261 * (This is all pretty hokey.)
262 */
263 if (mca != A_DIGIT)
264 /* Not a number; must be search string */
265 return ('\n');
266 else
267 /* A number; append a 'g' */
268 return ('g');
269 }
270 return (getchr());
271 }
272 return (*first_cmd++);
273}
274
275/*
276 * Execute a multicharacter command.
277 */
278 static void
279exec_mca()
280{
281 register char *p;
282 register int n;
283
284 *cp = '\0';
285 cmd_exec();
286 switch (mca)
287 {
288 case A_F_SEARCH:
289 search(1, cmdbuf, number, wsearch);
290 break;
291 case A_B_SEARCH:
292 search(0, cmdbuf, number, wsearch);
293 break;
294 case A_FIRSTCMD:
295 /*
296 * Skip leading spaces or + signs in the string.
297 */
298 for (p = cmdbuf; *p == '+' || *p == ' '; p++)
299 ;
300 if (every_first_cmd != NULL)
301 free(every_first_cmd);
302 if (*p == '\0')
303 every_first_cmd = NULL;
304 else
305 every_first_cmd = save(p);
306 break;
307 case A_TOGGLE_OPTION:
308 toggle_option(cmdbuf, 1);
309 break;
310 case A_EXAMINE:
311 /*
312 * Ignore leading spaces in the filename.
313 */
314 for (p = cmdbuf; *p == ' '; p++)
315 ;
316 edit(glob(p));
317 break;
318#if SHELL_ESCAPE
319 case A_SHELL:
320 /*
321 * !! just uses whatever is in shellcmd.
322 * Otherwise, copy cmdbuf to shellcmd,
323 * replacing any '%' with the current
324 * file name.
325 */
326 if (*cmdbuf != '!')
327 {
328 register char *fr, *to;
329
330 /*
331 * Make one pass to see how big a buffer we
332 * need to allocate for the expanded shell cmd.
333 */
334 for (fr = cmdbuf; *fr != '\0'; fr++)
335 if (*fr == '%')
336 n += strlen(current_file);
337 else
338 n++;
339
340 if (shellcmd != NULL)
341 free(shellcmd);
342 shellcmd = calloc(n+1, sizeof(char));
343 if (shellcmd == NULL)
344 {
345 error("cannot allocate memory");
346 break;
347 }
348
349 /*
350 * Now copy the shell cmd, expanding any "%"
351 * into the current filename.
352 */
353 to = shellcmd;
354 for (fr = cmdbuf; *fr != '\0'; fr++)
355 {
356 if (*fr != '%')
357 *to++ = *fr;
358 else
359 {
360 strcpy(to, current_file);
361 to += strlen(to);
362 }
363 }
364 *to = '\0';
365 }
366
367 if (shellcmd == NULL)
368 lsystem("");
369 else
370 lsystem(shellcmd);
371 error("!done");
372 break;
373#endif
374 }
375}
376
377/*
378 * Add a character to a multi-character command.
379 */
380 static int
381mca_char(c)
382 int c;
383{
384 switch (mca)
385 {
386 case 0:
387 /*
388 * Not in a multicharacter command.
389 */
390 return (NO_MCA);
391
392 case A_PREFIX:
393 /*
394 * In the prefix of a command.
395 */
396 return (NO_MCA);
397
398 case A_DIGIT:
399 /*
400 * Entering digits of a number.
401 * Terminated by a non-digit.
402 */
403 if ((c < '0' || c > '9') &&
404 c != erase_char && c != kill_char)
405 {
406 /*
407 * Not part of the number.
408 * Treat as a normal command character.
409 */
410 number = cmd_int();
411 mca = 0;
412 return (NO_MCA);
413 }
414 break;
415
416 case A_TOGGLE_OPTION:
417 /*
418 * Special case for the TOGGLE_OPTION command.
419 * if the option letter which was entered is a
420 * single-char option, execute the command immediately,
421 * so he doesn't have to hit RETURN.
422 */
423 if (cp == cmdbuf && c != erase_char && c != kill_char &&
424 single_char_option(c))
425 {
426 cmdbuf[0] = c;
427 cmdbuf[1] = '\0';
428 toggle_option(cmdbuf, 1);
429 return (MCA_DONE);
430 }
431 break;
432 }
433
434 /*
435 * Any other multicharacter command
436 * is terminated by a newline.
437 */
438 if (c == '\n' || c == '\r')
439 {
440 /*
441 * Execute the command.
442 */
443 exec_mca();
444 return (MCA_DONE);
445 }
446 /*
447 * Append the char to the command buffer.
448 */
449 if (cmd_char(c))
450 /*
451 * Abort the multi-char command.
452 */
453 return (MCA_DONE);
454 /*
455 * Need another character.
456 */
457 return (MCA_MORE);
458}
459
460/*
461 * Main command processor.
462 * Accept and execute commands until a quit command, then return.
463 */
464 public void
465commands()
466{
467 register int c;
468 register int action;
469
470 last_mca = 0;
471 scroll = (sc_height + 1) / 2;
472
473 for (;;)
474 {
475 mca = 0;
476 number = 0;
477
478 /*
479 * See if any signals need processing.
480 */
481 if (sigs)
482 {
483 psignals();
484 if (quitting)
485 quit();
486 }
487
488 /*
489 * Display prompt and accept a character.
490 */
491 cmd_reset();
492 prompt();
493 noprefix();
494 c = getcc();
495
496 again:
497 if (sigs)
498 continue;
499
500 /*
501 * If we are in a multicharacter command, call mca_char.
502 * Otherwise we call cmd_decode to determine the
503 * action to be performed.
504 */
505 if (mca)
506 switch (mca_char(c))
507 {
508 case MCA_MORE:
509 /*
510 * Need another character.
511 */
512 c = getcc();
513 goto again;
514 case MCA_DONE:
515 /*
516 * Command has been handled by mca_char.
517 * Start clean with a prompt.
518 */
519 continue;
520 case NO_MCA:
521 /*
522 * Not a multi-char command
523 * (at least, not anymore).
524 */
525 break;
526 }
527
528 /*
529 * Decode the command character and decide what to do.
530 */
531 switch (action = cmd_decode(c))
532 {
533 case A_DIGIT:
534 /*
535 * First digit of a number.
536 */
537 start_mca(A_DIGIT, ":");
538 goto again;
539
540 case A_F_SCREEN:
541 /*
542 * Forward one screen.
543 */
544 if (number <= 0)
545 number = sc_window;
546 if (number <= 0)
547 number = sc_height - 1;
548 cmd_exec();
549 forward(number, 1);
550 break;
551
552 case A_B_SCREEN:
553 /*
554 * Backward one screen.
555 */
556 if (number <= 0)
557 number = sc_window;
558 if (number <= 0)
559 number = sc_height - 1;
560 cmd_exec();
561 backward(number, 1);
562 break;
563
564 case A_F_LINE:
565 /*
566 * Forward N (default 1) line.
567 */
568 if (number <= 0)
569 number = 1;
570 cmd_exec();
571 forward(number, 0);
572 break;
573
574 case A_B_LINE:
575 /*
576 * Backward N (default 1) line.
577 */
578 if (number <= 0)
579 number = 1;
580 cmd_exec();
581 backward(number, 0);
582 break;
583
584 case A_F_SCROLL:
585 /*
586 * Forward N lines
587 * (default same as last 'd' or 'u' command).
588 */
589 if (number > 0)
590 scroll = number;
591 cmd_exec();
592 forward(scroll, 0);
593 break;
594
595 case A_B_SCROLL:
596 /*
597 * Forward N lines
598 * (default same as last 'd' or 'u' command).
599 */
600 if (number > 0)
601 scroll = number;
602 cmd_exec();
603 backward(scroll, 0);
604 break;
605
606 case A_FREPAINT:
607 /*
608 * Flush buffers, then repaint screen.
609 * Don't flush the buffers on a pipe!
610 */
611 if (!ispipe)
612 {
613 ch_init(0, 0);
614 clr_linenum();
615 }
616 /* FALLTHRU */
617 case A_REPAINT:
618 /*
619 * Repaint screen.
620 */
621 cmd_exec();
622 repaint();
623 break;
624
625 case A_GOLINE:
626 /*
627 * Go to line N, default beginning of file.
628 */
629 if (number <= 0)
630 number = 1;
631 cmd_exec();
632 jump_back(number);
633 break;
634
635 case A_PERCENT:
636 /*
637 * Go to a specified percentage into the file.
638 */
639 if (number < 0)
640 number = 0;
641 if (number > 100)
642 number = 100;
643 cmd_exec();
644 jump_percent(number);
645 break;
646
647 case A_GOEND:
648 /*
649 * Go to line N, default end of file.
650 */
651 cmd_exec();
652 if (number <= 0)
653 jump_forw();
654 else
655 jump_back(number);
656 break;
657
658 case A_STAT:
659 /*
660 * Print file name, etc.
661 */
662 cmd_exec();
663 error(eq_message());
664 break;
665
666 case A_VERSION:
667 /*
668 * Print version number, without the "@(#)".
669 */
670 cmd_exec();
671 error(version+4);
672 break;
673
674 case A_QUIT:
675 /*
676 * Exit.
677 */
678 quit();
679
680 case A_F_SEARCH:
681 case A_B_SEARCH:
682 /*
683 * Search for a pattern.
684 * Accept chars of the pattern until \n.
685 */
686 if (number <= 0)
687 number = 1;
688 start_mca(action, (action==A_F_SEARCH) ? "/" : "?");
689 last_mca = mca;
690 wsearch = 1;
691 c = getcc();
692 if (c == '!')
693 {
694 /*
695 * Invert the sense of the search.
696 * Set wsearch to 0 and get a new
697 * character for the start of the pattern.
698 */
699 start_mca(action,
700 (action==A_F_SEARCH) ? "!/" : "!?");
701 wsearch = 0;
702 c = getcc();
703 }
704 goto again;
705
706 case A_AGAIN_SEARCH:
707 /*
708 * Repeat previous search.
709 */
710 if (number <= 0)
711 number = 1;
712 if (wsearch)
713 start_mca(last_mca,
714 (last_mca==A_F_SEARCH) ? "/" : "?");
715 else
716 start_mca(last_mca,
717 (last_mca==A_F_SEARCH) ? "!/" : "!?");
718 cmd_exec();
719 search(mca==A_F_SEARCH, (char *)NULL, number, wsearch);
720 break;
721
722 case A_HELP:
723 /*
724 * Help.
725 */
726 lower_left();
727 clear_eol();
728 putstr("help");
729 cmd_exec();
730 help();
731 break;
732
733 case A_EXAMINE:
734 /*
735 * Edit a new file. Get the filename.
736 */
737 cmd_reset();
738 start_mca(A_EXAMINE, "Examine: ");
739 c = getcc();
740 goto again;
741
742 case A_VISUAL:
743 /*
744 * Invoke an editor on the input file.
745 */
746#if EDITOR
747 if (ispipe)
748 {
749 error("Cannot edit standard input");
750 break;
751 }
752 /*
753 * Try to pass the line number to the editor.
754 */
755 cmd_exec();
756 c = currline(MIDDLE);
757 if (c == 0)
758 sprintf(cmdbuf, "%s %s",
759 editor, current_file);
760 else
761 sprintf(cmdbuf, "%s +%d %s",
762 editor, c, current_file);
763 lsystem(cmdbuf);
764 ch_init(0, 0);
765 clr_linenum();
766 break;
767#else
768 error("Command not available");
769 break;
770#endif
771
772 case A_NEXT_FILE:
773 /*
774 * Examine next file.
775 */
776 if (number <= 0)
777 number = 1;
778 next_file(number);
779 break;
780
781 case A_PREV_FILE:
782 /*
783 * Examine previous file.
784 */
785 if (number <= 0)
786 number = 1;
787 prev_file(number);
788 break;
789
790 case A_TOGGLE_OPTION:
791 /*
792 * Toggle a flag setting.
793 */
794 cmd_reset();
795 start_mca(A_TOGGLE_OPTION, "-");
796 c = getcc();
797 goto again;
798
799 case A_DISP_OPTION:
800 /*
801 * Report a flag setting.
802 */
803 cmd_reset();
804 start_mca(A_DISP_OPTION, "_");
805 c = getcc();
806 if (c == erase_char || c == kill_char)
807 break;
808 cmdbuf[0] = c;
809 cmdbuf[1] = '\0';
810 toggle_option(cmdbuf, 0);
811 break;
812
813 case A_FIRSTCMD:
814 /*
815 * Set an initial command for new files.
816 */
817 cmd_reset();
818 start_mca(A_FIRSTCMD, "+");
819 c = getcc();
820 goto again;
821
822 case A_SHELL:
823 /*
824 * Shell escape.
825 */
826#if SHELL_ESCAPE
827 cmd_reset();
828 start_mca(A_SHELL, "!");
829 c = getcc();
830 goto again;
831#else
832 error("Command not available");
833 break;
834#endif
835
836 case A_SETMARK:
837 /*
838 * Set a mark.
839 */
840 lower_left();
841 clear_eol();
842 start_mca(A_SETMARK, "mark: ");
843 c = getcc();
844 if (c == erase_char || c == kill_char)
845 break;
846 setmark(c);
847 break;
848
849 case A_GOMARK:
850 /*
851 * Go to a mark.
852 */
853 lower_left();
854 clear_eol();
855 start_mca(A_GOMARK, "goto mark: ");
856 c = getcc();
857 if (c == erase_char || c == kill_char)
858 break;
859 gomark(c);
860 break;
861
862 case A_PREFIX:
863 /*
864 * The command is incomplete (more chars are needed).
865 * Display the current char so the user knows
866 * what's going on and get another character.
867 */
868 if (mca != A_PREFIX)
869 start_mca(A_PREFIX, "& ");
870 if (control_char(c))
871 {
872 putchr('^');
873 c = carat_char(c);
874 }
875 putchr(c);
876 c = getcc();
877 goto again;
878
879 default:
880 bell();
881 break;
882 }
883 }
884}