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