Home | History | Annotate | Line # | Download | only in more
command.c revision 1.3
      1 /*	$NetBSD: command.c,v 1.3 1998/01/09 08:03:25 perry Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1988 Mark Nudleman
      5  * Copyright (c) 1988, 1993
      6  *	The Regents of the University of California.  All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  * 1. Redistributions of source code must retain the above copyright
     12  *    notice, this list of conditions and the following disclaimer.
     13  * 2. Redistributions in binary form must reproduce the above copyright
     14  *    notice, this list of conditions and the following disclaimer in the
     15  *    documentation and/or other materials provided with the distribution.
     16  * 3. All advertising materials mentioning features or use of this software
     17  *    must display the following acknowledgement:
     18  *	This product includes software developed by the University of
     19  *	California, Berkeley and its contributors.
     20  * 4. Neither the name of the University nor the names of its contributors
     21  *    may be used to endorse or promote products derived from this software
     22  *    without specific prior written permission.
     23  *
     24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     34  * SUCH DAMAGE.
     35  */
     36 
     37 #ifndef lint
     38 static char sccsid[] = "@(#)command.c	8.1 (Berkeley) 6/6/93";
     39 #endif /* not lint */
     40 
     41 #include <sys/param.h>
     42 #include <stdio.h>
     43 #include <string.h>
     44 #include <ctype.h>
     45 #include <less.h>
     46 #include "pathnames.h"
     47 
     48 #define	NO_MCA		0
     49 #define	MCA_DONE	1
     50 #define	MCA_MORE	2
     51 
     52 extern int erase_char, kill_char, werase_char;
     53 extern int ispipe;
     54 extern int sigs;
     55 extern int quit_at_eof;
     56 extern int hit_eof;
     57 extern int sc_width;
     58 extern int sc_height;
     59 extern int sc_window;
     60 extern int curr_ac;
     61 extern int ac;
     62 extern int quitting;
     63 extern int scroll;
     64 extern int screen_trashed;	/* The screen has been overwritten */
     65 
     66 static char cmdbuf[120];	/* Buffer for holding a multi-char command */
     67 static char *cp;		/* Pointer into cmdbuf */
     68 static int cmd_col;		/* Current column of the multi-char command */
     69 static int longprompt;		/* if stat command instead of prompt */
     70 static int mca;			/* The multicharacter command (action) */
     71 static int last_mca;		/* The previous mca */
     72 static int number;		/* The number typed by the user */
     73 static int wsearch;		/* Search for matches (1) or non-matches (0) */
     74 
     75 #define	CMD_RESET	cp = cmdbuf	/* reset command buffer to empty */
     76 #define	CMD_EXEC	lower_left(); flush()
     77 
     78 /* backspace in command buffer. */
     79 static
     80 cmd_erase()
     81 {
     82 	/*
     83 	 * backspace past beginning of the string: this usually means
     84 	 * abort the command.
     85 	 */
     86 	if (cp == cmdbuf)
     87 		return(1);
     88 
     89 	/* erase an extra character, for the carat. */
     90 	if (CONTROL_CHAR(*--cp)) {
     91 		backspace();
     92 		--cmd_col;
     93 	}
     94 
     95 	backspace();
     96 	--cmd_col;
     97 	return(0);
     98 }
     99 
    100 /* set up the display to start a new multi-character command. */
    101 start_mca(action, prompt)
    102 	int action;
    103 	char *prompt;
    104 {
    105 	lower_left();
    106 	clear_eol();
    107 	putstr(prompt);
    108 	cmd_col = strlen(prompt);
    109 	mca = action;
    110 }
    111 
    112 /*
    113  * process a single character of a multi-character command, such as
    114  * a number, or the pattern of a search command.
    115  */
    116 static
    117 cmd_char(c)
    118 	int c;
    119 {
    120 	if (c == erase_char)
    121 		return(cmd_erase());
    122 	/* in this order, in case werase == erase_char */
    123 	if (c == werase_char) {
    124 		if (cp > cmdbuf) {
    125 			while (isspace(cp[-1]) && !cmd_erase());
    126 			while (!isspace(cp[-1]) && !cmd_erase());
    127 			while (isspace(cp[-1]) && !cmd_erase());
    128 		}
    129 		return(cp == cmdbuf);
    130 	}
    131 	if (c == kill_char) {
    132 		while (!cmd_erase());
    133 		return(1);
    134 	}
    135 	/*
    136 	 * No room in the command buffer, or no room on the screen;
    137 	 * {{ Could get fancy here; maybe shift the displayed line
    138 	 * and make room for more chars, like ksh. }}
    139 	 */
    140 	if (cp >= &cmdbuf[sizeof(cmdbuf)-1] || cmd_col >= sc_width-3)
    141 		bell();
    142 	else {
    143 		*cp++ = c;
    144 		if (CONTROL_CHAR(c)) {
    145 			putchr('^');
    146 			cmd_col++;
    147 			c = CARAT_CHAR(c);
    148 		}
    149 		putchr(c);
    150 		cmd_col++;
    151 	}
    152 	return(0);
    153 }
    154 
    155 prompt()
    156 {
    157 	extern int linenums, short_file;
    158 	extern char *current_name, *firstsearch, *next_name;
    159 	off_t len, pos, ch_length(), position(), forw_line();
    160 	char pbuf[40];
    161 
    162 	/*
    163 	 * if nothing is displayed yet, display starting from line 1;
    164 	 * if search string provided, go there instead.
    165 	 */
    166 	if (position(TOP) == NULL_POSITION) {
    167 		if (forw_line((off_t)0) == NULL_POSITION)
    168 			return(0);
    169 		if (!firstsearch || !search(1, firstsearch, 1, 1))
    170 			jump_back(1);
    171 	}
    172 	else if (screen_trashed)
    173 		repaint();
    174 
    175 	/* if no -e flag and we've hit EOF on the last file, quit. */
    176 	if ((!quit_at_eof || short_file) && hit_eof && curr_ac + 1 >= ac)
    177 		quit();
    178 
    179 	/* select the proper prompt and display it. */
    180 	lower_left();
    181 	clear_eol();
    182 	if (longprompt) {
    183 		so_enter();
    184 		putstr(current_name);
    185 		putstr(":");
    186 		if (!ispipe) {
    187 			(void)sprintf(pbuf, " file %d/%d", curr_ac + 1, ac);
    188 			putstr(pbuf);
    189 		}
    190 		if (linenums) {
    191 			(void)sprintf(pbuf, " line %d", currline(BOTTOM));
    192 			putstr(pbuf);
    193 		}
    194 		if ((pos = position(BOTTOM)) != NULL_POSITION) {
    195 			(void)sprintf(pbuf, " byte %qd", pos);
    196 			putstr(pbuf);
    197 			if (!ispipe && (len = ch_length())) {
    198 				(void)sprintf(pbuf, "/%qd pct %qd%%",
    199 				    len, ((100 * pos) / len));
    200 				putstr(pbuf);
    201 			}
    202 		}
    203 		so_exit();
    204 		longprompt = 0;
    205 	}
    206 	else {
    207 		so_enter();
    208 		putstr(current_name);
    209 		if (hit_eof)
    210 			if (next_name) {
    211 				putstr(": END (next file: ");
    212 				putstr(next_name);
    213 				putstr(")");
    214 			}
    215 			else
    216 				putstr(": END");
    217 		else if (!ispipe &&
    218 		    (pos = position(BOTTOM)) != NULL_POSITION &&
    219 		    (len = ch_length())) {
    220 			(void)sprintf(pbuf, " (%qd%%)", ((100 * pos) / len));
    221 			putstr(pbuf);
    222 		}
    223 		so_exit();
    224 	}
    225 	return(1);
    226 }
    227 
    228 /* get command character. */
    229 static
    230 getcc()
    231 {
    232 	extern int cmdstack;
    233 	int ch;
    234 	off_t position();
    235 
    236 	/* left over from error() routine. */
    237 	if (cmdstack) {
    238 		ch = cmdstack;
    239 		cmdstack = NULL;
    240 		return(ch);
    241 	}
    242 	if (cp > cmdbuf && position(TOP) == NULL_POSITION) {
    243 		/*
    244 		 * Command is incomplete, so try to complete it.
    245 		 * There are only two cases:
    246 		 * 1. We have "/string" but no newline.  Add the \n.
    247 		 * 2. We have a number but no command.  Treat as #g.
    248 		 * (This is all pretty hokey.)
    249 		 */
    250 		if (mca != A_DIGIT)
    251 			/* Not a number; must be search string */
    252 			return('\n');
    253 		else
    254 			/* A number; append a 'g' */
    255 			return('g');
    256 	}
    257 	return(getchr());
    258 }
    259 
    260 /* execute a multicharacter command. */
    261 static
    262 exec_mca()
    263 {
    264 	extern int file;
    265 	register char *p;
    266 	char *glob();
    267 
    268 	*cp = '\0';
    269 	CMD_EXEC;
    270 	switch (mca) {
    271 	case A_F_SEARCH:
    272 		(void)search(1, cmdbuf, number, wsearch);
    273 		break;
    274 	case A_B_SEARCH:
    275 		(void)search(0, cmdbuf, number, wsearch);
    276 		break;
    277 	case A_EXAMINE:
    278 		for (p = cmdbuf; isspace(*p); ++p);
    279 		(void)edit(glob(p));
    280 		break;
    281 	}
    282 }
    283 
    284 /* add a character to a multi-character command. */
    285 static
    286 mca_char(c)
    287 	int c;
    288 {
    289 	switch (mca) {
    290 	case 0:			/* not in a multicharacter command. */
    291 	case A_PREFIX:		/* in the prefix of a command. */
    292 		return(NO_MCA);
    293 	case A_DIGIT:
    294 		/*
    295 		 * Entering digits of a number.
    296 		 * Terminated by a non-digit.
    297 		 */
    298 		if (!isascii(c) || !isdigit(c) &&
    299 		    c != erase_char && c != kill_char && c != werase_char) {
    300 			/*
    301 			 * Not part of the number.
    302 			 * Treat as a normal command character.
    303 			 */
    304 			*cp = '\0';
    305 			number = atoi(cmdbuf);
    306 			CMD_RESET;
    307 			mca = 0;
    308 			return(NO_MCA);
    309 		}
    310 		break;
    311 	}
    312 
    313 	/*
    314 	 * Any other multicharacter command
    315 	 * is terminated by a newline.
    316 	 */
    317 	if (c == '\n' || c == '\r') {
    318 		exec_mca();
    319 		return(MCA_DONE);
    320 	}
    321 
    322 	/* append the char to the command buffer. */
    323 	if (cmd_char(c))
    324 		return(MCA_DONE);
    325 
    326 	return(MCA_MORE);
    327 }
    328 
    329 /*
    330  * Main command processor.
    331  * Accept and execute commands until a quit command, then return.
    332  */
    333 commands()
    334 {
    335 	register int c;
    336 	register int action;
    337 
    338 	last_mca = 0;
    339 	scroll = (sc_height + 1) / 2;
    340 
    341 	for (;;) {
    342 		mca = 0;
    343 		number = 0;
    344 
    345 		/*
    346 		 * See if any signals need processing.
    347 		 */
    348 		if (sigs) {
    349 			psignals();
    350 			if (quitting)
    351 				quit();
    352 		}
    353 		/*
    354 		 * Display prompt and accept a character.
    355 		 */
    356 		CMD_RESET;
    357 		if (!prompt()) {
    358 			next_file(1);
    359 			continue;
    360 		}
    361 		noprefix();
    362 		c = getcc();
    363 
    364 again:		if (sigs)
    365 			continue;
    366 
    367 		/*
    368 		 * If we are in a multicharacter command, call mca_char.
    369 		 * Otherwise we call cmd_decode to determine the
    370 		 * action to be performed.
    371 		 */
    372 		if (mca)
    373 			switch (mca_char(c)) {
    374 			case MCA_MORE:
    375 				/*
    376 				 * Need another character.
    377 				 */
    378 				c = getcc();
    379 				goto again;
    380 			case MCA_DONE:
    381 				/*
    382 				 * Command has been handled by mca_char.
    383 				 * Start clean with a prompt.
    384 				 */
    385 				continue;
    386 			case NO_MCA:
    387 				/*
    388 				 * Not a multi-char command
    389 				 * (at least, not anymore).
    390 				 */
    391 				break;
    392 			}
    393 
    394 		/* decode the command character and decide what to do. */
    395 		switch (action = cmd_decode(c)) {
    396 		case A_DIGIT:		/* first digit of a number */
    397 			start_mca(A_DIGIT, ":");
    398 			goto again;
    399 		case A_F_SCREEN:	/* forward one screen */
    400 			CMD_EXEC;
    401 			if (number <= 0 && (number = sc_window) <= 0)
    402 				number = sc_height - 1;
    403 			forward(number, 1);
    404 			break;
    405 		case A_B_SCREEN:	/* backward one screen */
    406 			CMD_EXEC;
    407 			if (number <= 0 && (number = sc_window) <= 0)
    408 				number = sc_height - 1;
    409 			backward(number, 1);
    410 			break;
    411 		case A_F_LINE:		/* forward N (default 1) line */
    412 			CMD_EXEC;
    413 			forward(number <= 0 ? 1 : number, 0);
    414 			break;
    415 		case A_B_LINE:		/* backward N (default 1) line */
    416 			CMD_EXEC;
    417 			backward(number <= 0 ? 1 : number, 0);
    418 			break;
    419 		case A_F_SCROLL:	/* forward N lines */
    420 			CMD_EXEC;
    421 			if (number > 0)
    422 				scroll = number;
    423 			forward(scroll, 0);
    424 			break;
    425 		case A_B_SCROLL:	/* backward N lines */
    426 			CMD_EXEC;
    427 			if (number > 0)
    428 				scroll = number;
    429 			backward(scroll, 0);
    430 			break;
    431 		case A_FREPAINT:	/* flush buffers and repaint */
    432 			if (!ispipe) {
    433 				ch_init(0, 0);
    434 				clr_linenum();
    435 			}
    436 			/* FALLTHROUGH */
    437 		case A_REPAINT:		/* repaint the screen */
    438 			CMD_EXEC;
    439 			repaint();
    440 			break;
    441 		case A_GOLINE:		/* go to line N, default 1 */
    442 			CMD_EXEC;
    443 			if (number <= 0)
    444 				number = 1;
    445 			jump_back(number);
    446 			break;
    447 		case A_PERCENT:		/* go to percent of file */
    448 			CMD_EXEC;
    449 			if (number < 0)
    450 				number = 0;
    451 			else if (number > 100)
    452 				number = 100;
    453 			jump_percent(number);
    454 			break;
    455 		case A_GOEND:		/* go to line N, default end */
    456 			CMD_EXEC;
    457 			if (number <= 0)
    458 				jump_forw();
    459 			else
    460 				jump_back(number);
    461 			break;
    462 		case A_STAT:		/* print file name, etc. */
    463 			longprompt = 1;
    464 			continue;
    465 		case A_QUIT:		/* exit */
    466 			quit();
    467 		case A_F_SEARCH:	/* search for a pattern */
    468 		case A_B_SEARCH:
    469 			if (number <= 0)
    470 				number = 1;
    471 			start_mca(action, (action==A_F_SEARCH) ? "/" : "?");
    472 			last_mca = mca;
    473 			wsearch = 1;
    474 			c = getcc();
    475 			if (c == '!') {
    476 				/*
    477 				 * Invert the sense of the search; set wsearch
    478 				 * to 0 and get a new character for the start
    479 				 * of the pattern.
    480 				 */
    481 				start_mca(action,
    482 				    (action == A_F_SEARCH) ? "!/" : "!?");
    483 				wsearch = 0;
    484 				c = getcc();
    485 			}
    486 			goto again;
    487 		case A_AGAIN_SEARCH:		/* repeat previous search */
    488 			if (number <= 0)
    489 				number = 1;
    490 			if (wsearch)
    491 				start_mca(last_mca,
    492 				    (last_mca == A_F_SEARCH) ? "/" : "?");
    493 			else
    494 				start_mca(last_mca,
    495 				    (last_mca == A_F_SEARCH) ? "!/" : "!?");
    496 			CMD_EXEC;
    497 			(void)search(mca == A_F_SEARCH, (char *)NULL,
    498 			    number, wsearch);
    499 			break;
    500 		case A_HELP:			/* help */
    501 			lower_left();
    502 			clear_eol();
    503 			putstr("help");
    504 			CMD_EXEC;
    505 			help();
    506 			break;
    507 		case A_FILE_LIST:		/* show list of file names */
    508 			CMD_EXEC;
    509 			showlist();
    510 			repaint();
    511 			break;
    512 		case A_EXAMINE:			/* edit a new file */
    513 			CMD_RESET;
    514 			start_mca(A_EXAMINE, "Examine: ");
    515 			c = getcc();
    516 			goto again;
    517 		case A_VISUAL:			/* invoke the editor */
    518 			if (ispipe) {
    519 				error("Cannot edit standard input");
    520 				break;
    521 			}
    522 			CMD_EXEC;
    523 			editfile();
    524 			ch_init(0, 0);
    525 			clr_linenum();
    526 			break;
    527 		case A_NEXT_FILE:		/* examine next file */
    528 			if (number <= 0)
    529 				number = 1;
    530 			next_file(number);
    531 			break;
    532 		case A_PREV_FILE:		/* examine previous file */
    533 			if (number <= 0)
    534 				number = 1;
    535 			prev_file(number);
    536 			break;
    537 		case A_SETMARK:			/* set a mark */
    538 			lower_left();
    539 			clear_eol();
    540 			start_mca(A_SETMARK, "mark: ");
    541 			c = getcc();
    542 			if (c == erase_char || c == kill_char)
    543 				break;
    544 			setmark(c);
    545 			break;
    546 		case A_GOMARK:			/* go to mark */
    547 			lower_left();
    548 			clear_eol();
    549 			start_mca(A_GOMARK, "goto mark: ");
    550 			c = getcc();
    551 			if (c == erase_char || c == kill_char)
    552 				break;
    553 			gomark(c);
    554 			break;
    555 		case A_PREFIX:
    556 			/*
    557 			 * The command is incomplete (more chars are needed).
    558 			 * Display the current char so the user knows what's
    559 			 * going on and get another character.
    560 			 */
    561 			if (mca != A_PREFIX)
    562 				start_mca(A_PREFIX, "");
    563 			if (CONTROL_CHAR(c)) {
    564 				putchr('^');
    565 				c = CARAT_CHAR(c);
    566 			}
    567 			putchr(c);
    568 			c = getcc();
    569 			goto again;
    570 		default:
    571 			bell();
    572 			break;
    573 		}
    574 	}
    575 }
    576 
    577 editfile()
    578 {
    579 	extern char *current_file;
    580 	static int dolinenumber;
    581 	static char *editor;
    582 	int c;
    583 	char buf[MAXPATHLEN * 2 + 20], *getenv();
    584 
    585 	if (editor == NULL) {
    586 		editor = getenv("EDITOR");
    587 		/* pass the line number to vi */
    588 		if (editor == NULL || *editor == '\0') {
    589 			editor = _PATH_VI;
    590 			dolinenumber = 1;
    591 		}
    592 		else
    593 			dolinenumber = 0;
    594 	}
    595 	if (dolinenumber && (c = currline(MIDDLE)))
    596 		(void)sprintf(buf, "%s +%d %s", editor, c, current_file);
    597 	else
    598 		(void)sprintf(buf, "%s %s", editor, current_file);
    599 	lsystem(buf);
    600 }
    601 
    602 showlist()
    603 {
    604 	extern int sc_width;
    605 	extern char **av;
    606 	register int indx, width;
    607 	int len;
    608 	char *p;
    609 
    610 	if (ac <= 0) {
    611 		error("No files provided as arguments.");
    612 		return;
    613 	}
    614 	for (width = indx = 0; indx < ac;) {
    615 		p = strcmp(av[indx], "-") ? av[indx] : "stdin";
    616 		len = strlen(p) + 1;
    617 		if (curr_ac == indx)
    618 			len += 2;
    619 		if (width + len + 1 >= sc_width) {
    620 			if (!width) {
    621 				if (curr_ac == indx)
    622 					putchr('[');
    623 				putstr(p);
    624 				if (curr_ac == indx)
    625 					putchr(']');
    626 				++indx;
    627 			}
    628 			width = 0;
    629 			putchr('\n');
    630 			continue;
    631 		}
    632 		if (width)
    633 			putchr(' ');
    634 		if (curr_ac == indx)
    635 			putchr('[');
    636 		putstr(p);
    637 		if (curr_ac == indx)
    638 			putchr(']');
    639 		width += len;
    640 		++indx;
    641 	}
    642 	putchr('\n');
    643 	error((char *)NULL);
    644 }
    645