1 /*
2 * Copyright (c) 1988 Mark Nudleman
3 * Portions copyright (c) 1999 T. Michael Vanderhoek
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. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by the University of
18 * California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #ifndef lint
37 static char sccsid[] = "@(#)command.c 8.1 (Berkeley) 6/6/93";
38 #endif /* not lint */
39
40 #ifndef lint
41 static const char rcsid[] =
42 "$FreeBSD: src/usr.bin/more/command.c,v 1.17 2000/01/08 18:11:05 hoek Exp $";
43 #endif /* not lint */
44
45 /*
46 * Functions for interacting with the user directly printing hello
47 * messages or reading from the terminal. All of these functions deal
48 * specifically with the prompt line, and only the prompt line.
49 */
50
51 #include <sys/param.h>
52
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <assert.h>
56 #include <ctype.h>
57 #include <stdarg.h>
58 #include <string.h>
59 #include <unistd.h>
60
61 #include "less.h"
62 #include "pathnames.h"
63
64 extern int erase_char, kill_char, werase_char;
65 extern int sigs;
66 extern int quit_at_eof;
67 extern int hit_eof;
68 extern int horiz_off;
69 extern int sc_width;
70 extern int bo_width;
71 extern int be_width;
72 extern int so_width;
73 extern int se_width;
74 extern int curr_ac;
75 extern int ac;
76 extern char **av;
77 extern int screen_trashed; /* The screen has been overwritten */
78
79 static int cmd_col; /* Current screen column when accepting input */
80
81 static cmd_char(), cmd_erase(), getcc();
82 void error(char *);
83
84
85 /*****************************************************************************
86 *
87 * Functions for reading-in user input.
88 *
89 */
90
91 static int biggetinputhack_f;
92
93 /* biggetinputhack()
94 *
95 * Performs as advertised.
96 */
biggetinputhack()97 biggetinputhack()
98 {
99 biggetinputhack_f = 1;
100 }
101
102 /*
103 * Read a line of input from the terminal. Reads at most bufsiz - 1 characters
104 * and places them in buffer buf. They are NUL-terminated. Prints the
105 * temporary prompt prompt. Returns true if the user aborted the input and
106 * returns false otherwise.
107 */
108 int
getinput(prompt,buf,bufsiz)109 getinput(prompt, buf, bufsiz)
110 const char *prompt;
111 char *buf;
112 int bufsiz;
113 {
114 extern bo_width, be_width;
115 char *bufcur;
116 int c;
117
118 prmpt(prompt);
119
120 bufcur = buf;
121 for (;;) {
122 c = getcc();
123 if (c == '\n') {
124 *bufcur = '\0';
125 return 0;
126 }
127 if (c == READ_INTR ||
128 cmd_char(c, buf, &bufcur, buf + bufsiz - 1)) {
129 /* input cancelled */
130 if (bufsiz) *buf = '\0';
131 return 1;
132 }
133 if (biggetinputhack_f) {
134 biggetinputhack_f = 0;
135 *bufcur = '\0';
136 return 0;
137 }
138 }
139 }
140
141 /*
142 * Process a single character of a multi-character input, such as
143 * a number, or the pattern of a search command. Returns true if the user
144 * has cancelled the multi-character input, false otherwise and attempts
145 * to add it to buf (not exceeding bufsize). Prints the character on the
146 * terminal output. The bufcur should initially equal bufbeg. After that
147 * it does not need to be touched or modified by the user, but may be expected
148 * to point at the future position of the next character.
149 */
150 static int
cmd_char(c,bufbeg,bufcur,bufend)151 cmd_char(c, bufbeg, bufcur, bufend)
152 int c; /* The character to process */
153 char *bufbeg; /* The buffer to add the character to */
154 char **bufcur; /* The position at which to add the character */
155 char *bufend; /* One after the last address available in the buffer.
156 * No character will be placed into *bufend. */
157 {
158 if (c == erase_char)
159 return(cmd_erase(bufbeg, bufcur));
160 /* in this order, in case werase == erase_char */
161 if (c == werase_char) {
162 if (*bufcur > bufbeg) {
163 while (isspace((*bufcur)[-1]) &&
164 !cmd_erase(bufbeg, bufcur)) ;
165 while (!isspace((*bufcur)[-1]) &&
166 !cmd_erase(bufbeg, bufcur)) ;
167 while (isspace((*bufcur)[-1]) &&
168 !cmd_erase(bufbeg, bufcur)) ;
169 }
170 return *bufcur == bufbeg;
171 }
172 if (c == kill_char) {
173 while (!cmd_erase(bufbeg, bufcur));
174 return 1;
175 }
176
177 /*
178 * No room in the command buffer, or no room on the screen;
179 * XXX If there is no room on the screen, we should just let the
180 * screen scroll down and set screen_trashed=1 appropriately, or
181 * alternatively, scroll the prompt line horizontally.
182 */
183 assert (*bufcur <= bufend);
184 if (*bufcur == bufend || cmd_col >= sc_width - 3)
185 bell();
186 else {
187 *(*bufcur)++ = c;
188 if (CONTROL_CHAR(c)) {
189 putchr('^');
190 cmd_col++;
191 c &= ~0200;
192 c = CARAT_CHAR(c);
193 }
194 putchr(c);
195 cmd_col++;
196 }
197 return 0;
198 }
199
200 /*
201 * Helper function to cmd_char(). Backs-up one character from bufcur in the
202 * buffer passed, and prints a backspace on the screen. Returns true if the
203 * we backspaced past bufbegin (ie. the input is being aborted), and false
204 * otherwise. The bufcur is expected to point to the future location of the
205 * next character in the buffer, and is modified appropriately.
206 */
207 static
cmd_erase(bufbegin,bufcur)208 cmd_erase(bufbegin, bufcur)
209 char *bufbegin;
210 char **bufcur;
211 {
212 int c;
213
214 /*
215 * XXX Could add code to detect a backspace that is backing us over
216 * the beginning of a line and onto the previous line. The backspace
217 * would not be printed for some terminals (eg. hardcopy) in that
218 * case.
219 */
220
221 /*
222 * backspace past beginning of the string: this usually means
223 * abort the input.
224 */
225 if (*bufcur == bufbegin)
226 return 1;
227
228 (*bufcur)--;
229
230 /* If erasing a control-char, erase an extra character for the carat. */
231 c = **bufcur;
232 if (CONTROL_CHAR(c)) {
233 backspace();
234 cmd_col--;
235 }
236
237 backspace();
238 cmd_col--;
239
240 return 0;
241 }
242
243 static int ungotcc;
244
245 /*
246 * Get command character from the terminal.
247 */
248 static
getcc()249 getcc()
250 {
251 int ch;
252 off_t position();
253
254 /* left over from error() routine. */
255 if (ungotcc) {
256 ch = ungotcc;
257 ungotcc = 0;
258 return(ch);
259 }
260
261 return(getchr());
262 }
263
264 /*
265 * Same as ungetc(), but works for people who don't like to use streams.
266 */
ungetcc(c)267 ungetcc(c)
268 int c;
269 {
270 ungotcc = c;
271 }
272
273
274 /*****************************************************************************
275 *
276 * prompts
277 *
278 */
279
280 static int longprompt;
281
282 /*
283 * Prints prmpt where the prompt would normally appear. This is different
284 * from changing the current prompt --- this is more like printing a
285 * unimportant notice or error. The prmpt line will be printed in bold (if
286 * possible). Will in the future print only the last sc_width - 1 - bo_width
287 * characters (to prevent newline).
288 */
prmpt(prmpt)289 prmpt(prmpt)
290 const char *prmpt;
291 {
292 lower_left();
293 clear_eol();
294 bo_enter();
295 putxstr(prmpt);
296 bo_exit();
297 flush();
298 cmd_col = strlen(prmpt) + bo_width + be_width;
299 }
300
301 /*
302 * Print the main prompt that signals we are ready for user commands. This
303 * also magically positions the current file where it should be (either by
304 * calling repaint() if screen_trashed or by searching for a search
305 * string that was specified through option.c on the more(1) command line).
306 * Additional magic will randomly call the quit() function.
307 *
308 * This is really intended to do a lot of the work of commands(). It has
309 * little purpose outside of commands().
310 */
prompt()311 prompt()
312 {
313 extern int linenums, short_file, ispipe;
314 extern char *current_name, *firstsearch, *next_name;
315 off_t len, pos, ch_length(), position(), forw_line();
316 char pbuf[40];
317
318 /*
319 * if nothing is displayed yet, display starting from line 1;
320 * if search string provided, go there instead.
321 */
322 if (position(TOP) == NULL_POSITION) {
323 #if 0
324 /* This code causes "more zero-byte-file /etc/termcap" to skip straight
325 * to the /etc/termcap file ... that is undesireable. There are only a few
326 * instances where these two lines perform something useful. */
327 if (forw_line((off_t)0) == NULL_POSITION)
328 return 0 ;
329 #endif
330 if (!firstsearch || !search(1, firstsearch, 1, 1))
331 jump_back(1);
332 }
333 else if (screen_trashed)
334 repaint();
335
336 /* if no -e flag and we've hit EOF on the last file, quit. */
337 if (!quit_at_eof && hit_eof && curr_ac + 1 >= ac)
338 quit();
339
340 /* select the proper prompt and display it. */
341 lower_left();
342 clear_eol();
343 pbuf[sizeof(pbuf) - 1] = '\0';
344 if (longprompt) {
345 /*
346 * Get the current line/pos from the BOTTOM of the screen
347 * even though that's potentially confusing for the user
348 * when switching between wraplines=true and a valid horiz_off
349 * (with wraplines=false). In exchange, it is sometimes
350 * easier for the user to tell when a file is relatively
351 * short vs. long.
352 */
353 so_enter();
354 putstr(current_name);
355 putstr(":");
356 if (!ispipe) {
357 (void)snprintf(pbuf, sizeof(pbuf) - 1,
358 " file %d/%d", curr_ac + 1, ac);
359 putstr(pbuf);
360 }
361 if (linenums) {
362 (void)snprintf(pbuf, sizeof(pbuf) - 1,
363 " line %d", currline(BOTTOM));
364 putstr(pbuf);
365 }
366 (void)snprintf(pbuf, sizeof(pbuf) - 1, " col %d", horiz_off);
367 putstr(pbuf);
368 if ((pos = position(BOTTOM)) != NULL_POSITION) {
369 (void)snprintf(pbuf, sizeof(pbuf) - 1,
370 " byte %qd", pos);
371 putstr(pbuf);
372 if (!ispipe && (len = ch_length())) {
373 (void)snprintf(pbuf, sizeof(pbuf) - 1,
374 "/%qd pct %qd%%", len, ((100 * pos) / len));
375 putstr(pbuf);
376 }
377 }
378 so_exit();
379 }
380 else {
381 so_enter();
382 putstr(current_name);
383 if (hit_eof)
384 if (next_name) {
385 putstr(": END (next file: ");
386 putstr(next_name);
387 putstr(")");
388 }
389 else
390 putstr(": END");
391 else if (!ispipe &&
392 (pos = position(BOTTOM)) != NULL_POSITION &&
393 (len = ch_length())) {
394 (void)snprintf(pbuf, sizeof(pbuf) - 1,
395 " (%qd%%)", ((100 * pos) / len));
396 putstr(pbuf);
397 }
398 so_exit();
399 }
400
401 /*
402 * XXX This isn't correct, but until we get around to reworking
403 * the whole prompt stuff the way we want it to be, this hack
404 * is necessary to prevent input from being blocked if getinput()
405 * is called and the user enters an input that fills the cmd
406 * buffer (or reaches the far rightside end of the screen).
407 */
408 cmd_col = 0;
409
410 return 1;
411 }
412
413 /*
414 * Sets the current prompt. Currently it sets the current prompt to the
415 * long prompt.
416 */
statprompt(nostatprompt)417 statprompt(nostatprompt)
418 int nostatprompt; /* Turn off the stat prompt? (off by default...) */
419 {
420 if (nostatprompt)
421 longprompt = 0;
422 else
423 longprompt = 1;
424 }
425
426
427 /*****************************************************************************
428 *
429 * Errors, next-of-kin to prompts.
430 *
431 */
432
433 /*
434 * Shortcut function that may be used when setting the current erreur
435 * and erreur string at the same time. The function name is chosen to be
436 * symetric with the SETERR() macro in less.h. This could be written as
437 * macro, too, but we'd need to use a GNU C extension.
438 */
SETERRSTR(enum error e,const char * s,...)439 SETERRSTR(enum error e, const char *s, ...)
440 {
441 va_list args;
442
443 erreur = e;
444 if (errstr) free(errstr);
445 errstr = NULL;
446 va_start(args, s);
447 vasprintf(&errstr, s, args);
448 va_end(args);
449 }
450
451 /*
452 * Prints an error message and clears the current error.
453 */
454 void
handle_error()455 handle_error()
456 {
457 if (erreur == E_OK)
458 return;
459
460 bell();
461 if (errstr)
462 error(errstr);
463 else
464 error(deferr[erreur]);
465 erreur = E_OK;
466 errstr = NULL;
467 }
468
469 /*
470 * Clears any error messages and pretends they never occurred.
471 */
472 void
clear_error()473 clear_error()
474 {
475 erreur = E_OK;
476 if (errstr) free(errstr);
477 errstr = NULL;
478 }
479
480 int errmsgs;
481 static char return_to_continue[] = "(press RETURN)";
482
483 /*
484 * Output a message in the lower left corner of the screen
485 * and wait for carriage return.
486 */
487 /* static */
488 void
error(s)489 error(s)
490 char *s;
491 {
492 extern int any_display;
493 int ch;
494
495 errmsgs++;
496 if (!any_display) {
497 /*
498 * Nothing has been displayed yet. Output this message on
499 * error output (file descriptor 2) and don't wait for a
500 * keystroke to continue.
501 *
502 * This has the desirable effect of producing all error
503 * messages on error output if standard output is directed
504 * to a file. It also does the same if we never produce
505 * any real output; for example, if the input file(s) cannot
506 * be opened. If we do eventually produce output, code in
507 * edit() makes sure these messages can be seen before they
508 * are overwritten or scrolled away.
509 */
510 (void)write(2, s, strlen(s));
511 (void)write(2, "\n", 1);
512 return;
513 }
514
515 lower_left();
516 clear_eol();
517 so_enter();
518 if (s) {
519 putstr(s);
520 putstr(" ");
521 }
522 putstr(return_to_continue);
523 so_exit();
524
525 if ((ch = getchr()) != '\n') {
526 /* XXX hardcoded */
527 if (ch == 'q')
528 quit();
529 ungotcc = ch;
530 }
531 lower_left();
532
533 if ((s==NULL)?0:(strlen(s)) + sizeof(return_to_continue) +
534 so_width + se_width + 1 > sc_width) {
535 /*
536 * Printing the message has probably scrolled the screen.
537 * {{ Unless the terminal doesn't have auto margins,
538 * in which case we just hammered on the right margin. }}
539 */
540 /* XXX Should probably just set screen_trashed=1, but I'm
541 * not going to touch that until all the places that call
542 * error() have been checked, or until error() is staticized. */
543 repaint();
544 }
545 flush();
546 }
547
548
549 /****************************************************************************
550 *
551 * The main command processor.
552 *
553 * (Well, it deals with things on the prompt line, doesn't it?)
554 *
555 */
556
557 /*
558 * Main command processor.
559 *
560 * Accept and execute commands until a quit command, then return.
561 */
562 void
commands()563 commands()
564 {
565 enum runmacro runmacro();
566 enum runmacro rmret;
567 long numberN;
568 enum { NOTGOTTEN=0, GOTTEN=1, GETTING } Nstate; /* ie. numberNstate */
569 int c;
570 char inbuf[20], *incur = inbuf;
571 *inbuf = '\0';
572
573 Nstate = GETTING;
574 for (;;) {
575 /*
576 * See if any signals need processing.
577 */
578 if (sigs)
579 psignals();
580
581 /*
582 * Display prompt and generally get setup. Don't display the
583 * prompt if we are already in the middle of accepting a
584 * set of characters.
585 */
586 if (!*inbuf && !prompt()) {
587 next_file(1);
588 continue;
589 }
590
591 c = getcc();
592
593 /* Check sigs here --- getcc() may have given us READ_INTR */
594 if (sigs) {
595 /* terminate any current macro */
596 *inbuf = '\0';
597 incur = inbuf;
598
599 continue; /* process the sigs */
600 }
601
602 if (Nstate == GETTING && !isdigit(c)
603 && c != erase_char && c != werase_char && c != kill_char) {
604 /*
605 * Mark the end of an input number N, if any.
606 */
607
608 if (!*inbuf) {
609 /* We never actually got an input number */
610 Nstate = NOTGOTTEN;
611 } else {
612 numberN = atol(inbuf);
613 Nstate = GOTTEN;
614 }
615 *inbuf = '\0';
616 incur = inbuf;
617 }
618 (void) cmd_char(c, inbuf, &incur, inbuf + sizeof(inbuf) - 1);
619 *incur = '\0';
620 if (*inbuf)
621 prmpt(inbuf);
622 else
623 Nstate = GETTING; /* abort command */
624
625 if (Nstate == GETTING) {
626 /* Still reading in the number N ... don't want to
627 * try running the macro expander. */
628 continue;
629 } else {
630 /* Try expanding the macro */
631 switch (runmacro(inbuf, numberN, Nstate)) {
632 case TOOMACRO:
633 break;
634 case BADMACRO: case NOMACRO: case BADCOMMAND:
635 handle_error();
636 /* fallthrough */
637 case OK:
638 /* recock */
639 *inbuf = '\0';
640 incur = inbuf;
641 Nstate = GETTING;
642 break;
643 }
644 }
645 } /* for (;;) */
646 }
647
648
649 /*****************************************************************************
650 *
651 * Misc functions that belong in ncommand.c but are here for historical
652 * and for copyright reasons.
653 *
654 */
655
656 void
editfile()657 editfile()
658 {
659 off_t position();
660 extern char *current_file;
661 static int dolinenumber;
662 static char *editor;
663 char *base;
664 int linenumber;
665 char buf[MAXPATHLEN * 2 + 20], *getenv();
666
667 if (editor == NULL) {
668 editor = getenv("EDITOR");
669
670 /* default editor is vi */
671 if (editor == NULL || *editor == '\0')
672 editor = _PATH_VI;
673
674 /* check last component in case of full path */
675 base = strrchr(editor, '/');
676 if (!base)
677 base = editor;
678 else
679 base++;
680
681 /* emacs also accepts vi-style +nnnn */
682 if (strncmp(base, "vi", 2) == 0 || strcmp(base, "emacs") == 0)
683 dolinenumber = 1;
684 else
685 dolinenumber = 0;
686 }
687 /*
688 * XXX Can't just use currline(MIDDLE) since that might be NULL_POSITION
689 * if we are editting a short file or some kind of search positioned
690 * us near the last line. It's not clear what currline() should do
691 * in those circumstances, but as of this writing, it doesn't do
692 * anything reasonable from our perspective. The currline(MIDDLE)
693 * never had the desired results for an editfile() after a search()
694 * anyways. Note, though, that when vi(1) starts its editting, it
695 * positions the focus line in the middle of the screen, not the top.
696 *
697 * I think what is needed is some kind of setfocus() and getfocus()
698 * function. This could put the focussed line in the middle, top,
699 * or wherever as per the user's wishes, and allow things like us
700 * to getfocus() the correct file-position/line-number. A search would
701 * then search forward (or backward) from the current focus position,
702 * etc.
703 *
704 * currline() doesn't belong.
705 */
706 if (position(MIDDLE) == NULL_POSITION)
707 linenumber = currline(TOP);
708 else
709 linenumber = currline(MIDDLE);
710 if (dolinenumber && linenumber)
711 (void)snprintf(buf, sizeof(buf),
712 "%s +%d %s", editor, linenumber, current_file);
713 else
714 (void)snprintf(buf, sizeof(buf), "%s %s", editor, current_file);
715 lsystem(buf);
716 }
717
718 void
showlist()719 showlist()
720 {
721 extern int sc_width;
722 register int indx, width;
723 int len;
724 char *p;
725
726 if (ac <= 0) {
727 error("No files provided as arguments.");
728 return;
729 }
730 for (width = indx = 0; indx < ac;) {
731 p = strcmp(av[indx], "-") ? av[indx] : "stdin";
732 len = strlen(p) + 1;
733 if (curr_ac == indx)
734 len += 2;
735 if (width + len + 1 >= sc_width) {
736 if (!width) {
737 if (curr_ac == indx)
738 putchr('[');
739 putstr(p);
740 if (curr_ac == indx)
741 putchr(']');
742 ++indx;
743 }
744 width = 0;
745 putchr('\n');
746 continue;
747 }
748 if (width)
749 putchr(' ');
750 if (curr_ac == indx)
751 putchr('[');
752 putstr(p);
753 if (curr_ac == indx)
754 putchr(']');
755 width += len;
756 ++indx;
757 }
758 putchr('\n');
759 error((char *)NULL);
760 }
761