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