1/* Copyright (c) 1985 Ceriel J.H. Jacobs */
2
3#include "commands.h"
4#include "assert.h"
5#include "display.h"
6#include "getcomm.h"
7#include "getline.h"
8#include "help.h"
9#include "in_all.h"
10#include "keys.h"
11#include "machine.h"
12#include "main.h"
13#include "options.h"
14#include "output.h"
15#include "pattern.h"
16#include "process.h"
17#include "prompt.h"
18#include "term.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 if (cnt) {
391 if (cnt > (unsigned)maxpagesize)
392 return maxpagesize;
393 if (cnt < MINPAGESIZE)
394 return MINPAGESIZE;
395 return (int)cnt;
396 }
397 return pagesize;
398}
399
400static int
401do_forward(long cnt)
402{ /* Display next page */
403 int i;
404
405 i = page_size((unsigned)cnt);
406 if (status & EOFILE) {
407 /*
408 * May seem strange, but actually a visit to the next file
409 * has already been done here
410 */
411 (void)display(1L, 0, i, 1);
412 return 0;
413 }
414 (void)next_screen(i, 1);
415 return 0;
416}
417
418static int
419do_backward(long cnt)
420{
421 int i, temp;
422
423 i = page_size((unsigned)cnt);
424 if (!(status & START)) {
425 (void)prev_screen(i, 1);
426 return 0;
427 }
428 if (stdf < 0) {
429 (void)display(1L, 0, i, 1);
430 return 0;
431 }
432 /*
433 * The next part is a bit clumsy.
434 * We want to display the last page of the previous file (for which
435 * a visit has already been done), but the pagesize may temporarily
436 * be different because the command had a count
437 */
438 temp = pagesize;
439 pagesize = i;
440 do_lline(cnt);
441 pagesize = temp;
442 return 0;
443}
444
445static int
446do_firstline(long cnt)
447{ /* Go to start of input */
448 (void)cnt;
449 do_absolute(1L);
450 return 0;
451}
452
453static int
454do_lline(long cnt)
455{ /* Go to end of input */
456 int i = 0;
457 int j = pagesize - 1;
458
459 if ((cnt = to_lastline()) < 0) {
460 /*
461 * Interrupted by the user
462 */
463 return 0;
464 }
465 /*
466 * Display the page such that only the last line of the page is
467 * a "~", independant of the pagesize
468 */
469 while (!(display(cnt, i, j, 0) & EOFILE)) {
470 /*
471 * The last line could of course be very long ...
472 */
473 i += j;
474 }
475 (void)scrollb(j - scr_info.tail->cnt, 0);
476 redraw(0);
477 return 0;
478}
479
480static int
481do_lf(long cnt)
482{ /* Display next line, or go to line */
483
484 if (cnt) { /* Go to line */
485 do_absolute(cnt);
486 return 0;
487 }
488 (void)scrollf(1, 1);
489 return 0;
490}
491
492static int
493do_upline(long cnt)
494{ /* Display previous line, or go to line */
495
496 if (cnt) { /* Go to line */
497 do_absolute(cnt);
498 return 0;
499 }
500 (void)scrollb(1, 1);
501 return 0;
502}
503
504static int
505do_skiplines(long cnt)
506{ /* Skip lines forwards */
507
508 /* Should be interruptable ... */
509 (void)scrollf((int)(cnt + maxpagesize - 1), 0);
510 redraw(0);
511 return 0;
512}
513
514static int
515do_bskiplines(long cnt)
516{ /* Skip lines backwards */
517
518 /* Should be interruptable ... */
519 (void)scrollb((int)(cnt + pagesize - 1), 0);
520 redraw(0);
521 return 0;
522}
523
524static int
525do_fscreens(long cnt)
526{ /* Skip screens forwards */
527
528 do {
529 if ((next_screen(pagesize, 0) & EOFILE) || interrupt)
530 break;
531 } while (--cnt >= 0);
532 redraw(0);
533 return 0;
534}
535
536static int
537do_bscreens(long cnt)
538{ /* Skip screens backwards */
539
540 do {
541 if ((prev_screen(pagesize, 0) & START) || interrupt)
542 break;
543 } while (--cnt >= 0);
544 redraw(0);
545 return 0;
546}
547
548static int
549scro_size(unsigned cnt)
550{
551 if (cnt >= (unsigned)maxpagesize)
552 return maxpagesize;
553 if (cnt)
554 return (int)cnt;
555 return scrollsize;
556}
557
558static int
559do_f_scroll(long cnt)
560{ /* Scroll forwards */
561
562 (void)scrollf(scro_size((unsigned)cnt), 1);
563 return 0;
564}
565
566static int
567do_b_scroll(long cnt)
568{ /* Scroll backwards */
569
570 (void)scrollb(scro_size((unsigned)cnt), 1);
571 return 0;
572}
573
574static int
575do_previousfile(long cnt)
576{ /* Visit previous file */
577
578 if (nextfile(-(int)cnt)) {
579 error("No (Nth) previous file");
580 return 0;
581 }
582 redraw(0);
583 return 0;
584}
585
586static int
587do_nextfile(long cnt)
588{ /* Visit next file */
589
590 if (nextfile((int)cnt)) {
591 error("No (Nth) next file");
592 return 0;
593 }
594 redraw(0);
595 return 0;
596}
597
598static int
599do_quit(long cnt)
600{
601 (void)cnt;
602 return quit();
603}
604
605/*
606 * The next array is initialized, sorted on the first element of the structs,
607 * so that we can perform binary search
608 */
609struct commands commands[] = {
610 {"", 0, do_error, ""},
611 {"", 0, do_nocomm, ""},
612 {"bf", STICKY | NEEDS_COUNT, do_previousfile, "Visit previous file"},
613 {"bl", NEEDS_SCREEN | STICKY, do_upline, "Scroll one line up, or go to line"},
614 {"bot", STICKY, do_lline, "Go to last line of the input"},
615 {"bp", BACK | NEEDS_SCREEN | TOPREVFILE | STICKY, do_backward, "display previous page"},
616 {"bps",
617 SCREENSIZE_ADAPT | BACK | NEEDS_SCREEN | TOPREVFILE | STICKY,
618 do_backward,
619 "Display previous page, set pagesize"},
620 {"bs", BACK | NEEDS_SCREEN | STICKY, do_b_scroll, "Scroll backwards"},
621 {"bse", 0, do_bsearch, "Search backwards for pattern"},
622 {"bsl", BACK | NEEDS_SCREEN | STICKY | NEEDS_COUNT, do_bskiplines, "Skip lines backwards"},
623 {"bsp", BACK | NEEDS_SCREEN | STICKY | NEEDS_COUNT, do_bscreens, "Skip screens backwards"},
624 {"bss",
625 SCROLLSIZE_ADAPT | BACK | NEEDS_SCREEN | STICKY,
626 do_b_scroll,
627 "Scroll backwards, set scrollsize"},
628 {"chm", 0, do_chkm, "Switch to other keymap"},
629 {"exg", STICKY, exgmark, "Exchange current page with mark"},
630 {"ff", STICKY | NEEDS_COUNT, do_nextfile, "Visit next file"},
631 {"fl", NEEDS_SCREEN | STICKY, do_lf, "Scroll one line down, or go to line"},
632 {"fp", TONEXTFILE | AHEAD | STICKY, do_forward, "Display next page"},
633 {"fps",
634 SCREENSIZE_ADAPT | TONEXTFILE | AHEAD | STICKY,
635 do_forward,
636 "Display next page, set pagesize"},
637 {"fs", AHEAD | NEEDS_SCREEN | STICKY, do_f_scroll, "Scroll forwards"},
638 {"fse", 0, do_fsearch, "Search forwards for pattern"},
639 {"fsl", AHEAD | NEEDS_SCREEN | STICKY | NEEDS_COUNT, do_skiplines, "Skip lines forwards"},
640 {"fsp", AHEAD | NEEDS_SCREEN | STICKY | NEEDS_COUNT, do_fscreens, "Skip screens forwards"},
641 {"fss",
642 SCROLLSIZE_ADAPT | AHEAD | NEEDS_SCREEN | STICKY,
643 do_f_scroll,
644 "Scroll forwards, set scrollsize"},
645 {"hlp", 0, do_help, "Give description of all commands"},
646 {"mar", 0, setmark, "Set a mark on the current page"},
647 {"nse", STICKY, do_nsearch, "Repeat the last search"},
648 {"nsr", STICKY, do_rnsearch, "Repeat last search in other direction"},
649 {"pip", ESC, do_pipe, "pipe input into shell command"},
650 {"qui", 0, do_quit, "Exit from yap"},
651 {"red", 0, do_redraw, "Redraw screen"},
652 {"rep", 0, do_lcomm, "Repeat last command"},
653 {"shl", ESC, do_shell, "Execute a shell escape"},
654 {"tom", 0, tomark, "Go to mark"},
655 {"top", STICKY, do_firstline, "Go to the first line of the input"},
656 {"vis", 0, do_visit, "Visit a file"},
657 {"wrf", 0, do_writefile, "Write input to a file"},
658};
659
660/*
661 * Lookup string "s" in the commands array, and return index.
662 * return 0 if not found.
663 */
664
665int
666lookup(char *s)
667{
668 struct commands *l, *u, *m;
669
670 l = &commands[2];
671 u = &commands[sizeof(commands) / sizeof(*u) - 1];
672 do {
673 /*
674 * Perform binary search
675 */
676 m = l + (u - l) / 2;
677 if (strcmp(s, m->c_cmd) > 0)
678 l = m + 1;
679 else
680 u = m;
681 } while (l < u);
682 if (!strcmp(s, u->c_cmd))
683 return u - commands;
684 return 0;
685}
686
687/*ARGSUSED*/
688static int
689do_lcomm(long cnt)
690{ /* Repeat last command */
691 (void)cnt;
692
693 if (!lastcomm) {
694 error("No previous command");
695 return 0;
696 }
697 do_comm(lastcomm, lastcount);
698 return 0;
699}
700
701/*
702 * Execute a command, with optional count "count".
703 */
704
705void
706do_comm(int comm, long count)
707{
708 struct commands *pcomm;
709 int temp;
710 int flags;
711
712 pcomm = &commands[comm];
713 flags = pcomm->c_flags;
714
715 /*
716 * Check the command.
717 * If the last line of the file is displayed and the command goes
718 * forwards and does'nt have the ability to go to the next file, it
719 * is an error.
720 * If the first line of the file is displayed and the command goes
721 * backwards and does'nt have the ability to go to the previous file,
722 * it is an error.
723 * Also check wether we need the next or previous file. If so, get it.
724 */
725 if ((status & EOFILE) && (flags & AHEAD)) {
726 if (qflag || !(flags & TONEXTFILE))
727 return;
728 if (nextfile(1))
729 quit();
730 }
731 if ((status & START) && (flags & BACK)) {
732 if (qflag || !(flags & TOPREVFILE))
733 return;
734 if (nextfile(-1))
735 quit();
736 }
737 /*
738 * Does the command stick around for LASTCOMM?
739 */
740 if (flags & STICKY) {
741 lastcomm = comm;
742 lastcount = count;
743 }
744 if (!count) {
745 if (flags & NEEDS_COUNT)
746 count = 1;
747 } else {
748 /*
749 * Does the command adapt the screensize?
750 */
751 if (flags & SCREENSIZE_ADAPT) {
752 temp = maxpagesize;
753 if ((unsigned)count < (unsigned)temp) {
754 temp = (int)count;
755 }
756 if (temp < MINPAGESIZE) {
757 temp = MINPAGESIZE;
758 }
759 count = 0;
760 pagesize = temp;
761 }
762 /*
763 * Does the command adapt the scrollsize?
764 */
765 if (flags & SCROLLSIZE_ADAPT) {
766 temp = maxpagesize - 1;
767 if ((unsigned)count < (unsigned)temp) {
768 temp = (int)count;
769 }
770 scrollsize = temp;
771 count = 0;
772 }
773 }
774 /*
775 * Now execute the command.
776 */
777 (*(pcomm->c_func))(count);
778}