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