main
commands.c
1/* Copyright (c) 1985 Ceriel J.H. Jacobs */
2
3#include "in_all.h"
4#include "commands.h"
5#include "output.h"
6#include "process.h"
7#include "help.h"
8#include "term.h"
9#include "prompt.h"
10#include "getline.h"
11#include "getcomm.h"
12#include "pattern.h"
13#include "display.h"
14#include "options.h"
15#include "machine.h"
16#include "keys.h"
17#include "main.h"
18#include "assert.h"
19
20static long lastcount; /* Save last count for '.' command */
21static int lastcomm; /* Save last command for '.' command */
22static int searchdir; /* Direction of last search */
23
24static int do_nocomm(long cnt);
25static void do_search(char *str, long cnt, int dir);
26static int do_fsearch(long cnt);
27static int do_bsearch(long cnt);
28static int n_or_rn_search(long cnt, int dir);
29static int do_nsearch(long cnt);
30static int do_rnsearch(long cnt);
31static int shell_command(int esc_ch, long cnt);
32static int do_shell(long cnt);
33static int do_pipe(long cnt);
34static int do_writefile(long cnt);
35static int do_absolute(long cnt);
36static int do_visit(long cnt);
37static int do_error(long cnt);
38static int prev_screen(int sz, int really);
39static int next_screen(int sz, int really);
40static int do_redraw(long cnt);
41static int page_size(unsigned cnt);
42static int do_forward(long cnt);
43static int do_backward(long cnt);
44static int do_firstline(long cnt);
45static int do_lline(long cnt);
46static int do_lf(long cnt);
47static int do_upline(long cnt);
48static int do_skiplines(long cnt);
49static int do_bskiplines(long cnt);
50static int do_fscreens(long cnt);
51static int do_bscreens(long cnt);
52static int scro_size(unsigned cnt);
53static int do_f_scroll(long cnt);
54static int do_b_scroll(long cnt);
55static int do_previousfile(long cnt);
56static int do_nextfile(long cnt);
57static int do_lcomm(long cnt);
58static int do_quit(long cnt);
59
60static int
61do_nocomm(long cnt)
62{ /* Do nothing */
63 (void)cnt;
64 return 0;
65}
66
67int
68do_chkm(long cnt)
69{ /* Change key map */
70 struct keymap *p;
71 (void)cnt;
72
73 if (!(p = othermap)) {
74 error("No other keymap");
75 return 0;
76 }
77 othermap = currmap;
78 currmap = p;
79 return 0;
80}
81
82/*
83 * Perform searches
84 */
85
86static void
87do_search(char *str, long cnt, int dir)
88{
89 char *p;
90 long lineno;
91
92 if (str) {
93 /*
94 * We have to get a pattern, which we have to prompt for
95 * with the string "str".
96 */
97 if ((p = readline(str)) == 0) {
98 /*
99 * User cancelled command
100 */
101 return;
102 }
103 if ((p = re_comp(p))) {
104 /*
105 * There was an error in the pattern
106 */
107 error(p);
108 return;
109 }
110 searchdir = dir;
111 }
112 if (dir < 0)
113 lineno = scr_info.firstline;
114 else
115 lineno = scr_info.lastline;
116 for (;;) {
117 p = 0;
118 if ((lineno += dir) > 0)
119 p = getline(lineno, 0);
120 if (interrupt)
121 return;
122 if (!p) { /* End of file reached */
123 error("pattern not found");
124 return;
125 }
126 if (re_exec(p) && --cnt <= 0) {
127 /*
128 * We found the pattern, and we found it often enough.
129 * Pity that we still don't know where the match is.
130 * We only know the linenumber. So, we just hope the
131 * following will at least bring it on the screen ...
132 */
133 (void)display(lineno, 0, pagesize, 0);
134 (void)scrollb(2, 0);
135 redraw(0);
136 return;
137 }
138 }
139 /* NOTREACHED */
140}
141
142static int
143do_fsearch(long cnt)
144{ /* Forward search */
145 do_search("/", cnt, 1);
146 return 0;
147}
148
149static int
150do_bsearch(long cnt)
151{ /* Backward search */
152 do_search("?", cnt, -1);
153 return 0;
154}
155
156/*
157 * Repeat last search in direction "dir"
158 */
159
160static int
161n_or_rn_search(long cnt, int dir)
162{
163 char *p;
164
165 if (dir == 1) {
166 p = "/\r";
167 } else if (dir == -1) {
168 p = "?\r";
169 } else {
170 error("No previous pattern");
171 return 0;
172 }
173 if (!stupid)
174 clrbline();
175 putline(p);
176 flush();
177 do_search((char *)0, cnt, dir);
178 return 0;
179}
180
181static int
182do_nsearch(long cnt)
183{ /* Repeat search in same direction */
184 n_or_rn_search(cnt, searchdir);
185 return 0;
186}
187
188static int
189do_rnsearch(long cnt)
190{ /* Repeat search in opposite direction */
191 n_or_rn_search(cnt, -searchdir);
192 return 0;
193}
194
195static int
196shell_command(int esc_ch, long cnt)
197{
198 char *p;
199 static char buf[2];
200
201 buf[0] = esc_ch;
202 buf[1] = '\0';
203 if ((p = readline(buf)) != 0) {
204 shellescape(p, esc_ch);
205 if (cnt >= 0 && !hardcopy) {
206 p = startcomm;
207 startcomm = 0;
208 ret_to_continue();
209 putline(TI);
210 if (!p) {
211 /*
212 * Avoid double redraw.
213 * After a "startcomm", a redraw will
214 * take place anyway.
215 */
216 redraw(1);
217 }
218 }
219 }
220 return 0;
221}
222
223static int
224do_shell(long cnt)
225{ /* Execute a shell escape */
226 return shell_command('!', cnt);
227}
228
229static int
230do_pipe(long cnt)
231{ /* Execute a shell escape */
232 return shell_command('|', cnt);
233}
234
235static int
236do_writefile(long cnt)
237{ /* Write input to a file */
238 char *p;
239 int fd;
240 (void)cnt;
241
242 if ((p = readline("Filename: ")) == 0 || !*p) {
243 /*
244 * No file name given
245 */
246 return 0;
247 }
248 if ((fd = open(p, O_CREAT | O_EXCL | O_WRONLY, 0644)) < 0) {
249 if (errno == EEXIST) {
250 error("File exists");
251 return 0;
252 }
253 error("Could not open file");
254 return 0;
255 }
256 wrt_fd(fd);
257 (void)close(fd);
258 return 0;
259}
260
261void
262wrt_fd(int fd)
263{
264 long l = 1;
265 char *p = getline(l, 0), *pbuf;
266 char buf[1024];
267
268 while (p) {
269 pbuf = buf;
270 while (p && pbuf < &buf[1024]) {
271 if (!*p) {
272 *pbuf++ = '\n';
273 p = getline(++l, 0);
274 } else
275 *pbuf++ = *p++;
276 }
277 if (write(fd, buf, pbuf - buf) < 0) {
278 error("Write failed");
279 break;
280 }
281 }
282}
283
284static int
285do_absolute(long cnt)
286{ /* Go to linenumber "cnt" */
287
288 if (!getline(cnt, 0)) { /* Not there or interrupt */
289 if (!interrupt) {
290 /*
291 * User did'nt give an interrupt, so the line number
292 * was too high. Go to the last line.
293 */
294 do_lline(cnt);
295 }
296 return 0;
297 }
298 (void)display(cnt, 0, pagesize, 1);
299 return 0;
300}
301
302static int
303do_visit(long cnt)
304{ /* Visit a file */
305 char *p;
306 static char fn[128]; /* Keep file name */
307 (void)cnt;
308
309 if ((p = readline("Filename: ")) == 0) {
310 return 0;
311 }
312 if (*p) {
313 (void)strcpy(fn, p);
314 visitfile(fn);
315 } else {
316 /*
317 * User typed a return. Visit the current file
318 */
319 if (!(p = filenames[filecount])) {
320 error("No current file");
321 return 0;
322 }
323 visitfile(p);
324 }
325 (void)display(1L, 0, pagesize, 1);
326 return 0;
327}
328
329static int
330do_error(long cnt)
331{ /* Called when user types wrong key sequence */
332 (void)cnt;
333 error(currmap->k_help);
334 return 0;
335}
336
337/*
338 * Interface routine for displaying previous screen,
339 * depending on cflag.
340 */
341
342static int
343prev_screen(int sz, int really)
344{
345 int retval;
346
347 retval = scrollb(sz - 1, really && cflag);
348 if (really && !cflag) {
349 /*
350 * The previous call did not display anything, but at least we
351 * know where to start
352 */
353 return display(scr_info.firstline, scr_info.nf, sz, 1);
354 }
355 return retval;
356}
357
358/*
359 * Interface routine for displaying the next screen,
360 * dependent on cflag.
361 */
362
363static int
364next_screen(int sz, int really)
365{
366 int t;
367 struct scr_info *p = &scr_info;
368
369 if (cflag) {
370 return scrollf(sz - 1, really);
371 }
372 t = p->tail->cnt - 1;
373 if (p->lastline == p->firstline) {
374 t += p->nf;
375 }
376 return display(p->lastline, t, sz, really);
377}
378
379static int
380do_redraw(long cnt)
381{
382 (void)cnt;
383 redraw(1);
384 return 0;
385}
386
387static int
388page_size(unsigned cnt)
389{
390
391 if (cnt) {
392 if (cnt > (unsigned)maxpagesize)
393 return maxpagesize;
394 if (cnt < MINPAGESIZE)
395 return MINPAGESIZE;
396 return (int)cnt;
397 }
398 return pagesize;
399}
400
401static int
402do_forward(long cnt)
403{ /* Display next page */
404 int i;
405
406 i = page_size((unsigned)cnt);
407 if (status & EOFILE) {
408 /*
409 * May seem strange, but actually a visit to the next file
410 * has already been done here
411 */
412 (void)display(1L, 0, i, 1);
413 return 0;
414 }
415 (void)next_screen(i, 1);
416 return 0;
417}
418
419static int
420do_backward(long cnt)
421{
422 int i, temp;
423
424 i = page_size((unsigned)cnt);
425 if (!(status & START)) {
426 (void)prev_screen(i, 1);
427 return 0;
428 }
429 if (stdf < 0) {
430 (void)display(1L, 0, i, 1);
431 return 0;
432 }
433 /*
434 * The next part is a bit clumsy.
435 * We want to display the last page of the previous file (for which
436 * a visit has already been done), but the pagesize may temporarily
437 * be different because the command had a count
438 */
439 temp = pagesize;
440 pagesize = i;
441 do_lline(cnt);
442 pagesize = temp;
443 return 0;
444}
445
446static int
447do_firstline(long cnt)
448{ /* Go to start of input */
449 (void)cnt;
450 do_absolute(1L);
451 return 0;
452}
453
454static int
455do_lline(long cnt)
456{ /* Go to end of input */
457 int i = 0;
458 int j = pagesize - 1;
459
460 if ((cnt = to_lastline()) < 0) {
461 /*
462 * Interrupted by the user
463 */
464 return 0;
465 }
466 /*
467 * Display the page such that only the last line of the page is
468 * a "~", independant of the pagesize
469 */
470 while (!(display(cnt, i, j, 0) & EOFILE)) {
471 /*
472 * The last line could of course be very long ...
473 */
474 i += j;
475 }
476 (void)scrollb(j - scr_info.tail->cnt, 0);
477 redraw(0);
478 return 0;
479}
480
481static int
482do_lf(long cnt)
483{ /* Display next line, or go to line */
484
485 if (cnt) { /* Go to line */
486 do_absolute(cnt);
487 return 0;
488 }
489 (void)scrollf(1, 1);
490 return 0;
491}
492
493static int
494do_upline(long cnt)
495{ /* Display previous line, or go to line */
496
497 if (cnt) { /* Go to line */
498 do_absolute(cnt);
499 return 0;
500 }
501 (void)scrollb(1, 1);
502 return 0;
503}
504
505static int
506do_skiplines(long cnt)
507{ /* Skip lines forwards */
508
509 /* Should be interruptable ... */
510 (void)scrollf((int)(cnt + maxpagesize - 1), 0);
511 redraw(0);
512 return 0;
513}
514
515static int
516do_bskiplines(long cnt)
517{ /* Skip lines backwards */
518
519 /* Should be interruptable ... */
520 (void)scrollb((int)(cnt + pagesize - 1), 0);
521 redraw(0);
522 return 0;
523}
524
525static int
526do_fscreens(long cnt)
527{ /* Skip screens forwards */
528
529 do {
530 if ((next_screen(pagesize, 0) & EOFILE) || interrupt)
531 break;
532 } while (--cnt >= 0);
533 redraw(0);
534 return 0;
535}
536
537static int
538do_bscreens(long cnt)
539{ /* Skip screens backwards */
540
541 do {
542 if ((prev_screen(pagesize, 0) & START) || interrupt)
543 break;
544 } while (--cnt >= 0);
545 redraw(0);
546 return 0;
547}
548
549static int
550scro_size(unsigned cnt)
551{
552
553 if (cnt >= (unsigned)maxpagesize)
554 return maxpagesize;
555 if (cnt)
556 return (int)cnt;
557 return scrollsize;
558}
559
560static int
561do_f_scroll(long cnt)
562{ /* Scroll forwards */
563
564 (void)scrollf(scro_size((unsigned)cnt), 1);
565 return 0;
566}
567
568static int
569do_b_scroll(long cnt)
570{ /* Scroll backwards */
571
572 (void)scrollb(scro_size((unsigned)cnt), 1);
573 return 0;
574}
575
576static int
577do_previousfile(long cnt)
578{ /* Visit previous file */
579
580 if (nextfile(-(int)cnt)) {
581 error("No (Nth) previous file");
582 return 0;
583 }
584 redraw(0);
585 return 0;
586}
587
588static int
589do_nextfile(long cnt)
590{ /* Visit next file */
591
592 if (nextfile((int)cnt)) {
593 error("No (Nth) next file");
594 return 0;
595 }
596 redraw(0);
597 return 0;
598}
599
600static int
601do_quit(long cnt)
602{
603 (void)cnt;
604 return quit();
605}
606
607/*
608 * The next array is initialized, sorted on the first element of the structs,
609 * so that we can perform binary search
610 */
611struct commands commands[] = {
612 {"", 0, do_error, ""},
613 {"", 0, do_nocomm, ""},
614 {"bf", STICKY | NEEDS_COUNT,
615 do_previousfile, "Visit previous file"},
616 {"bl", NEEDS_SCREEN | STICKY,
617 do_upline, "Scroll one line up, or go to line"},
618 {"bot", STICKY,
619 do_lline, "Go to last line of the input"},
620 {"bp", BACK | NEEDS_SCREEN | TOPREVFILE | STICKY,
621 do_backward, "display previous page"},
622 {"bps", SCREENSIZE_ADAPT | BACK | NEEDS_SCREEN | TOPREVFILE | STICKY,
623 do_backward, "Display previous page, set pagesize"},
624 {"bs", BACK | NEEDS_SCREEN | STICKY,
625 do_b_scroll, "Scroll backwards"},
626 {"bse", 0, do_bsearch, "Search backwards for pattern"},
627 {"bsl", BACK | NEEDS_SCREEN | STICKY | NEEDS_COUNT,
628 do_bskiplines, "Skip lines backwards"},
629 {"bsp", BACK | NEEDS_SCREEN | STICKY | NEEDS_COUNT,
630 do_bscreens, "Skip screens backwards"},
631 {"bss", SCROLLSIZE_ADAPT | BACK | NEEDS_SCREEN | STICKY,
632 do_b_scroll, "Scroll backwards, set scrollsize"},
633 {"chm", 0, do_chkm, "Switch to other keymap"},
634 {"exg", STICKY, exgmark, "Exchange current page with mark"},
635 {"ff", STICKY | NEEDS_COUNT,
636 do_nextfile, "Visit next file"},
637 {"fl", NEEDS_SCREEN | STICKY,
638 do_lf, "Scroll one line down, or go to line"},
639 {"fp", TONEXTFILE | AHEAD | STICKY,
640 do_forward, "Display next page"},
641 {"fps", SCREENSIZE_ADAPT | TONEXTFILE | AHEAD | STICKY,
642 do_forward, "Display next page, set pagesize"},
643 {"fs", AHEAD | NEEDS_SCREEN | STICKY,
644 do_f_scroll, "Scroll forwards"},
645 {"fse", 0, do_fsearch, "Search forwards for pattern"},
646 {"fsl", AHEAD | NEEDS_SCREEN | STICKY | NEEDS_COUNT,
647 do_skiplines, "Skip lines forwards"},
648 {"fsp", AHEAD | NEEDS_SCREEN | STICKY | NEEDS_COUNT,
649 do_fscreens, "Skip screens forwards"},
650 {"fss", SCROLLSIZE_ADAPT | AHEAD | NEEDS_SCREEN | STICKY,
651 do_f_scroll, "Scroll forwards, set scrollsize"},
652 {"hlp", 0, do_help, "Give description of all commands"},
653 {"mar", 0, setmark, "Set a mark on the current page"},
654 {"nse", STICKY, do_nsearch, "Repeat the last search"},
655 {"nsr", STICKY, do_rnsearch, "Repeat last search in other direction"},
656 {"pip", ESC, do_pipe, "pipe input into shell command"},
657 {"qui", 0, do_quit, "Exit from yap"},
658 {"red", 0, do_redraw, "Redraw screen"},
659 {"rep", 0, do_lcomm, "Repeat last command"},
660 {"shl", ESC, do_shell, "Execute a shell escape"},
661 {"tom", 0, tomark, "Go to mark"},
662 {"top", STICKY, do_firstline, "Go to the first line of the input"},
663 {"vis", 0, do_visit, "Visit a file"},
664 {"wrf", 0, do_writefile, "Write input to a file"},
665};
666
667/*
668 * Lookup string "s" in the commands array, and return index.
669 * return 0 if not found.
670 */
671
672int
673lookup(char *s)
674{
675 struct commands *l, *u, *m;
676
677 l = &commands[2];
678 u = &commands[sizeof(commands) / sizeof(*u) - 1];
679 do {
680 /*
681 * Perform binary search
682 */
683 m = l + (u - l) / 2;
684 if (strcmp(s, m->c_cmd) > 0)
685 l = m + 1;
686 else
687 u = m;
688 } while (l < u);
689 if (!strcmp(s, u->c_cmd))
690 return u - commands;
691 return 0;
692}
693
694/*ARGSUSED*/
695static int
696do_lcomm(long cnt)
697{ /* Repeat last command */
698 (void)cnt;
699
700 if (!lastcomm) {
701 error("No previous command");
702 return 0;
703 }
704 do_comm(lastcomm, lastcount);
705 return 0;
706}
707
708/*
709 * Execute a command, with optional count "count".
710 */
711
712void
713do_comm(int comm, long count)
714{
715 struct commands *pcomm;
716 int temp;
717 int flags;
718
719 pcomm = &commands[comm];
720 flags = pcomm->c_flags;
721
722 /*
723 * Check the command.
724 * If the last line of the file is displayed and the command goes
725 * forwards and does'nt have the ability to go to the next file, it
726 * is an error.
727 * If the first line of the file is displayed and the command goes
728 * backwards and does'nt have the ability to go to the previous file,
729 * it is an error.
730 * Also check wether we need the next or previous file. If so, get it.
731 */
732 if ((status & EOFILE) && (flags & AHEAD)) {
733 if (qflag || !(flags & TONEXTFILE))
734 return;
735 if (nextfile(1))
736 quit();
737 }
738 if ((status & START) && (flags & BACK)) {
739 if (qflag || !(flags & TOPREVFILE))
740 return;
741 if (nextfile(-1))
742 quit();
743 }
744 /*
745 * Does the command stick around for LASTCOMM?
746 */
747 if (flags & STICKY) {
748 lastcomm = comm;
749 lastcount = count;
750 }
751 if (!count) {
752 if (flags & NEEDS_COUNT)
753 count = 1;
754 } else {
755 /*
756 * Does the command adapt the screensize?
757 */
758 if (flags & SCREENSIZE_ADAPT) {
759 temp = maxpagesize;
760 if ((unsigned)count < (unsigned)temp) {
761 temp = (int)count;
762 }
763 if (temp < MINPAGESIZE) {
764 temp = MINPAGESIZE;
765 }
766 count = 0;
767 pagesize = temp;
768 }
769 /*
770 * Does the command adapt the scrollsize?
771 */
772 if (flags & SCROLLSIZE_ADAPT) {
773 temp = maxpagesize - 1;
774 if ((unsigned)count < (unsigned)temp) {
775 temp = (int)count;
776 }
777 scrollsize = temp;
778 count = 0;
779 }
780 }
781 /*
782 * Now execute the command.
783 */
784 (*(pcomm->c_func))(count);
785}