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}