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