xref: /freebsd/contrib/nvi/cl/cl_funcs.c (revision aa0a1e58)
1 /*-
2  * Copyright (c) 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1993, 1994, 1995, 1996
5  *	Keith Bostic.  All rights reserved.
6  *
7  * See the LICENSE file for redistribution information.
8  */
9 
10 #include "config.h"
11 
12 #ifndef lint
13 static const char sccsid[] = "@(#)cl_funcs.c	10.50 (Berkeley) 9/24/96";
14 #endif /* not lint */
15 
16 #include <sys/types.h>
17 #include <sys/queue.h>
18 #include <sys/time.h>
19 
20 #include <bitstring.h>
21 #include <ctype.h>
22 #include <curses.h>
23 #include <signal.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <termios.h>
28 #include <unistd.h>
29 
30 #include "../common/common.h"
31 #include "../vi/vi.h"
32 #include "cl.h"
33 
34 /*
35  * cl_addstr --
36  *	Add len bytes from the string at the cursor, advancing the cursor.
37  *
38  * PUBLIC: int cl_addstr __P((SCR *, const char *, size_t));
39  */
40 int
41 cl_addstr(sp, str, len)
42 	SCR *sp;
43 	const char *str;
44 	size_t len;
45 {
46 	CL_PRIVATE *clp;
47 	size_t oldy, oldx;
48 	int iv;
49 
50 	clp = CLP(sp);
51 
52 	/*
53 	 * If ex isn't in control, it's the last line of the screen and
54 	 * it's a split screen, use inverse video.
55 	 */
56 	iv = 0;
57 	getyx(stdscr, oldy, oldx);
58 	if (!F_ISSET(sp, SC_SCR_EXWROTE) &&
59 	    oldy == RLNO(sp, LASTLINE(sp)) && IS_SPLIT(sp)) {
60 		iv = 1;
61 		(void)standout();
62 	}
63 
64 	if (addnstr(str, len) == ERR)
65 		return (1);
66 
67 	if (iv)
68 		(void)standend();
69 	return (0);
70 }
71 
72 /*
73  * cl_attr --
74  *	Toggle a screen attribute on/off.
75  *
76  * PUBLIC: int cl_attr __P((SCR *, scr_attr_t, int));
77  */
78 int
79 cl_attr(sp, attribute, on)
80 	SCR *sp;
81 	scr_attr_t attribute;
82 	int on;
83 {
84 	CL_PRIVATE *clp;
85 
86 	clp = CLP(sp);
87 
88 	switch (attribute) {
89 	case SA_ALTERNATE:
90 	/*
91 	 * !!!
92 	 * There's a major layering violation here.  The problem is that the
93 	 * X11 xterm screen has what's known as an "alternate" screen.  Some
94 	 * xterm termcap/terminfo entries include sequences to switch to/from
95 	 * that alternate screen as part of the ti/te (smcup/rmcup) strings.
96 	 * Vi runs in the alternate screen, so that you are returned to the
97 	 * same screen contents on exit from vi that you had when you entered
98 	 * vi.  Further, when you run :shell, or :!date or similar ex commands,
99 	 * you also see the original screen contents.  This wasn't deliberate
100 	 * on vi's part, it's just that it historically sent terminal init/end
101 	 * sequences at those times, and the addition of the alternate screen
102 	 * sequences to the strings changed the behavior of vi.  The problem
103 	 * caused by this is that we don't want to switch back to the alternate
104 	 * screen while getting a new command from the user, when the user is
105 	 * continuing to enter ex commands, e.g.:
106 	 *
107 	 *	:!date				<<< switch to original screen
108 	 *	[Hit return to continue]	<<< prompt user to continue
109 	 *	:command			<<< get command from user
110 	 *
111 	 * Note that the :command input is a true vi input mode, e.g., input
112 	 * maps and abbreviations are being done.  So, we need to be able to
113 	 * switch back into the vi screen mode, without flashing the screen.
114 	 *
115 	 * To make matters worse, the curses initscr() and endwin() calls will
116 	 * do this automatically -- so, this attribute isn't as controlled by
117 	 * the higher level screen as closely as one might like.
118 	 */
119 	if (on) {
120 		if (clp->ti_te != TI_SENT) {
121 			clp->ti_te = TI_SENT;
122 			if (clp->smcup == NULL)
123 				(void)cl_getcap(sp, "smcup", &clp->smcup);
124 			if (clp->smcup != NULL)
125 				(void)tputs(clp->smcup, 1, cl_putchar);
126 		}
127 	} else
128 		if (clp->ti_te != TE_SENT) {
129 			clp->ti_te = TE_SENT;
130 			if (clp->rmcup == NULL)
131 				(void)cl_getcap(sp, "rmcup", &clp->rmcup);
132 			if (clp->rmcup != NULL)
133 				(void)tputs(clp->rmcup, 1, cl_putchar);
134 			(void)fflush(stdout);
135 		}
136 		(void)fflush(stdout);
137 		break;
138 	case SA_INVERSE:
139 		if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) {
140 			if (clp->smso == NULL)
141 				return (1);
142 			if (on)
143 				(void)tputs(clp->smso, 1, cl_putchar);
144 			else
145 				(void)tputs(clp->rmso, 1, cl_putchar);
146 			(void)fflush(stdout);
147 		} else {
148 			if (on)
149 				(void)standout();
150 			else
151 				(void)standend();
152 		}
153 		break;
154 	default:
155 		abort();
156 	}
157 	return (0);
158 }
159 
160 /*
161  * cl_baud --
162  *	Return the baud rate.
163  *
164  * PUBLIC: int cl_baud __P((SCR *, u_long *));
165  */
166 int
167 cl_baud(sp, ratep)
168 	SCR *sp;
169 	u_long *ratep;
170 {
171 	CL_PRIVATE *clp;
172 
173 	/*
174 	 * XXX
175 	 * There's no portable way to get a "baud rate" -- cfgetospeed(3)
176 	 * returns the value associated with some #define, which we may
177 	 * never have heard of, or which may be a purely local speed.  Vi
178 	 * only cares if it's SLOW (w300), slow (w1200) or fast (w9600).
179 	 * Try and detect the slow ones, and default to fast.
180 	 */
181 	clp = CLP(sp);
182 	switch (cfgetospeed(&clp->orig)) {
183 	case B50:
184 	case B75:
185 	case B110:
186 	case B134:
187 	case B150:
188 	case B200:
189 	case B300:
190 	case B600:
191 		*ratep = 600;
192 		break;
193 	case B1200:
194 		*ratep = 1200;
195 		break;
196 	default:
197 		*ratep = 9600;
198 		break;
199 	}
200 	return (0);
201 }
202 
203 /*
204  * cl_bell --
205  *	Ring the bell/flash the screen.
206  *
207  * PUBLIC: int cl_bell __P((SCR *));
208  */
209 int
210 cl_bell(sp)
211 	SCR *sp;
212 {
213 	if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE))
214 		(void)write(STDOUT_FILENO, "\07", 1);		/* \a */
215 	else {
216 		/*
217 		 * Vi has an edit option which determines if the terminal
218 		 * should be beeped or the screen flashed.
219 		 */
220 		if (O_ISSET(sp, O_FLASH))
221 			(void)flash();
222 		else
223 			(void)beep();
224 	}
225 	return (0);
226 }
227 
228 /*
229  * cl_clrtoeol --
230  *	Clear from the current cursor to the end of the line.
231  *
232  * PUBLIC: int cl_clrtoeol __P((SCR *));
233  */
234 int
235 cl_clrtoeol(sp)
236 	SCR *sp;
237 {
238 	return (clrtoeol() == ERR);
239 }
240 
241 /*
242  * cl_cursor --
243  *	Return the current cursor position.
244  *
245  * PUBLIC: int cl_cursor __P((SCR *, size_t *, size_t *));
246  */
247 int
248 cl_cursor(sp, yp, xp)
249 	SCR *sp;
250 	size_t *yp, *xp;
251 {
252 	/*
253 	 * The curses screen support splits a single underlying curses screen
254 	 * into multiple screens to support split screen semantics.  For this
255 	 * reason the returned value must be adjusted to be relative to the
256 	 * current screen, and not absolute.  Screens that implement the split
257 	 * using physically distinct screens won't need this hack.
258 	 */
259 	getyx(stdscr, *yp, *xp);
260 	*yp -= sp->woff;
261 	return (0);
262 }
263 
264 /*
265  * cl_deleteln --
266  *	Delete the current line, scrolling all lines below it.
267  *
268  * PUBLIC: int cl_deleteln __P((SCR *));
269  */
270 int
271 cl_deleteln(sp)
272 	SCR *sp;
273 {
274 	CHAR_T ch;
275 	CL_PRIVATE *clp;
276 	size_t col, lno, spcnt, oldy, oldx;
277 
278 	clp = CLP(sp);
279 
280 	/*
281 	 * This clause is required because the curses screen uses reverse
282 	 * video to delimit split screens.  If the screen does not do this,
283 	 * this code won't be necessary.
284 	 *
285 	 * If the bottom line was in reverse video, rewrite it in normal
286 	 * video before it's scrolled.
287 	 *
288 	 * Check for the existence of a chgat function; XSI requires it, but
289 	 * historic implementations of System V curses don't.   If it's not
290 	 * a #define, we'll fall back to doing it by hand, which is slow but
291 	 * acceptable.
292 	 *
293 	 * By hand means walking through the line, retrieving and rewriting
294 	 * each character.  Curses has no EOL marker, so track strings of
295 	 * spaces, and copy the trailing spaces only if there's a non-space
296 	 * character following.
297 	 */
298 	if (!F_ISSET(sp, SC_SCR_EXWROTE) && IS_SPLIT(sp)) {
299 		getyx(stdscr, oldy, oldx);
300 #ifdef mvchgat
301 		mvchgat(RLNO(sp, LASTLINE(sp)), 0, -1, A_NORMAL, 0, NULL);
302 #else
303 		for (lno = RLNO(sp, LASTLINE(sp)), col = spcnt = 0;;) {
304 			(void)move(lno, col);
305 			ch = winch(stdscr);
306 			if (isblank(ch))
307 				++spcnt;
308 			else {
309 				(void)move(lno, col - spcnt);
310 				for (; spcnt > 0; --spcnt)
311 					(void)addch(' ');
312 				(void)addch(ch);
313 			}
314 			if (++col >= sp->cols)
315 				break;
316 		}
317 #endif
318 		(void)move(oldy, oldx);
319 	}
320 
321 	/*
322 	 * The bottom line is expected to be blank after this operation,
323 	 * and other screens must support that semantic.
324 	 */
325 	return (deleteln() == ERR);
326 }
327 
328 /*
329  * cl_ex_adjust --
330  *	Adjust the screen for ex.  This routine is purely for standalone
331  *	ex programs.  All special purpose, all special case.
332  *
333  * PUBLIC: int cl_ex_adjust __P((SCR *, exadj_t));
334  */
335 int
336 cl_ex_adjust(sp, action)
337 	SCR *sp;
338 	exadj_t action;
339 {
340 	CL_PRIVATE *clp;
341 	int cnt;
342 
343 	clp = CLP(sp);
344 	switch (action) {
345 	case EX_TERM_SCROLL:
346 		/* Move the cursor up one line if that's possible. */
347 		if (clp->cuu1 != NULL)
348 			(void)tputs(clp->cuu1, 1, cl_putchar);
349 		else if (clp->cup != NULL)
350 			(void)tputs(tgoto(clp->cup,
351 			    0, LINES - 2), 1, cl_putchar);
352 		else
353 			return (0);
354 		/* FALLTHROUGH */
355 	case EX_TERM_CE:
356 		/* Clear the line. */
357 		if (clp->el != NULL) {
358 			(void)putchar('\r');
359 			(void)tputs(clp->el, 1, cl_putchar);
360 		} else {
361 			/*
362 			 * Historically, ex didn't erase the line, so, if the
363 			 * displayed line was only a single glyph, and <eof>
364 			 * was more than one glyph, the output would not fully
365 			 * overwrite the user's input.  To fix this, output
366 			 * the maxiumum character number of spaces.  Note,
367 			 * this won't help if the user entered extra prompt
368 			 * or <blank> characters before the command character.
369 			 * We'd have to do a lot of work to make that work, and
370 			 * it's almost certainly not worth the effort.
371 			 */
372 			for (cnt = 0; cnt < MAX_CHARACTER_COLUMNS; ++cnt)
373 				(void)putchar('\b');
374 			for (cnt = 0; cnt < MAX_CHARACTER_COLUMNS; ++cnt)
375 				(void)putchar(' ');
376 			(void)putchar('\r');
377 			(void)fflush(stdout);
378 		}
379 		break;
380 	default:
381 		abort();
382 	}
383 	return (0);
384 }
385 
386 /*
387  * cl_insertln --
388  *	Push down the current line, discarding the bottom line.
389  *
390  * PUBLIC: int cl_insertln __P((SCR *));
391  */
392 int
393 cl_insertln(sp)
394 	SCR *sp;
395 {
396 	/*
397 	 * The current line is expected to be blank after this operation,
398 	 * and the screen must support that semantic.
399 	 */
400 	return (insertln() == ERR);
401 }
402 
403 /*
404  * cl_keyval --
405  *	Return the value for a special key.
406  *
407  * PUBLIC: int cl_keyval __P((SCR *, scr_keyval_t, CHAR_T *, int *));
408  */
409 int
410 cl_keyval(sp, val, chp, dnep)
411 	SCR *sp;
412 	scr_keyval_t val;
413 	CHAR_T *chp;
414 	int *dnep;
415 {
416 	CL_PRIVATE *clp;
417 
418 	/*
419 	 * VEOF, VERASE and VKILL are required by POSIX 1003.1-1990,
420 	 * VWERASE is a 4BSD extension.
421 	 */
422 	clp = CLP(sp);
423 	switch (val) {
424 	case KEY_VEOF:
425 		*dnep = (*chp = clp->orig.c_cc[VEOF]) == _POSIX_VDISABLE;
426 		break;
427 	case KEY_VERASE:
428 		*dnep = (*chp = clp->orig.c_cc[VERASE]) == _POSIX_VDISABLE;
429 		break;
430 	case KEY_VKILL:
431 		*dnep = (*chp = clp->orig.c_cc[VKILL]) == _POSIX_VDISABLE;
432 		break;
433 #ifdef VWERASE
434 	case KEY_VWERASE:
435 		*dnep = (*chp = clp->orig.c_cc[VWERASE]) == _POSIX_VDISABLE;
436 		break;
437 #endif
438 	default:
439 		*dnep = 1;
440 		break;
441 	}
442 	return (0);
443 }
444 
445 /*
446  * cl_move --
447  *	Move the cursor.
448  *
449  * PUBLIC: int cl_move __P((SCR *, size_t, size_t));
450  */
451 int
452 cl_move(sp, lno, cno)
453 	SCR *sp;
454 	size_t lno, cno;
455 {
456 	/* See the comment in cl_cursor. */
457 	if (move(RLNO(sp, lno), cno) == ERR) {
458 		msgq(sp, M_ERR,
459 		    "Error: move: l(%u) c(%u) o(%u)", lno, cno, sp->woff);
460 		return (1);
461 	}
462 	return (0);
463 }
464 
465 /*
466  * cl_refresh --
467  *	Refresh the screen.
468  *
469  * PUBLIC: int cl_refresh __P((SCR *, int));
470  */
471 int
472 cl_refresh(sp, repaint)
473 	SCR *sp;
474 	int repaint;
475 {
476 	CL_PRIVATE *clp;
477 
478 	clp = CLP(sp);
479 
480 	/*
481 	 * If we received a killer signal, we're done, there's no point
482 	 * in refreshing the screen.
483 	 */
484 	if (clp->killersig)
485 		return (0);
486 
487 	/*
488 	 * If repaint is set, the editor is telling us that we don't know
489 	 * what's on the screen, so we have to repaint from scratch.
490 	 *
491 	 * In the curses library, doing wrefresh(curscr) is okay, but the
492 	 * screen flashes when we then apply the refresh() to bring it up
493 	 * to date.  So, use clearok().
494 	 */
495 	if (repaint)
496 		clearok(curscr, 1);
497 	return (refresh() == ERR);
498 }
499 
500 /*
501  * cl_rename --
502  *	Rename the file.
503  *
504  * PUBLIC: int cl_rename __P((SCR *, char *, int));
505  */
506 int
507 cl_rename(sp, name, on)
508 	SCR *sp;
509 	char *name;
510 	int on;
511 {
512 	GS *gp;
513 	CL_PRIVATE *clp;
514 	char *ttype;
515 
516 	gp = sp->gp;
517 	clp = CLP(sp);
518 
519 	ttype = OG_STR(gp, GO_TERM);
520 
521 	/*
522 	 * XXX
523 	 * We can only rename windows for xterm.
524 	 */
525 	if (on) {
526 		if (F_ISSET(clp, CL_RENAME_OK) &&
527 		    !strncmp(ttype, "xterm", sizeof("xterm") - 1)) {
528 			F_SET(clp, CL_RENAME);
529 			(void)printf(XTERM_RENAME, name);
530 			(void)fflush(stdout);
531 		}
532 	} else
533 		if (F_ISSET(clp, CL_RENAME)) {
534 			F_CLR(clp, CL_RENAME);
535 			(void)printf(XTERM_RENAME, ttype);
536 			(void)fflush(stdout);
537 		}
538 	return (0);
539 }
540 
541 /*
542  * cl_suspend --
543  *	Suspend a screen.
544  *
545  * PUBLIC: int cl_suspend __P((SCR *, int *));
546  */
547 int
548 cl_suspend(sp, allowedp)
549 	SCR *sp;
550 	int *allowedp;
551 {
552 	struct termios t;
553 	CL_PRIVATE *clp;
554 	GS *gp;
555 	size_t oldy, oldx;
556 	int changed;
557 
558 	gp = sp->gp;
559 	clp = CLP(sp);
560 	*allowedp = 1;
561 
562 	/*
563 	 * The ex implementation of this function isn't needed by screens not
564 	 * supporting ex commands that require full terminal canonical mode
565 	 * (e.g. :suspend).
566 	 *
567 	 * The vi implementation of this function isn't needed by screens not
568 	 * supporting vi process suspension, i.e. any screen that isn't backed
569 	 * by a UNIX shell.
570 	 *
571 	 * Setting allowedp to 0 will cause the editor to reject the command.
572 	 */
573 	if (F_ISSET(sp, SC_EX)) {
574 		/* Save the terminal settings, and restore the original ones. */
575 		if (F_ISSET(clp, CL_STDIN_TTY)) {
576 			(void)tcgetattr(STDIN_FILENO, &t);
577 			(void)tcsetattr(STDIN_FILENO,
578 			    TCSASOFT | TCSADRAIN, &clp->orig);
579 		}
580 
581 		/* Stop the process group. */
582 		(void)kill(0, SIGTSTP);
583 
584 		/* Time passes ... */
585 
586 		/* Restore terminal settings. */
587 		if (F_ISSET(clp, CL_STDIN_TTY))
588 			(void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &t);
589 		return (0);
590 	}
591 
592 	/*
593 	 * Move to the lower left-hand corner of the screen.
594 	 *
595 	 * XXX
596 	 * Not sure this is necessary in System V implementations, but it
597 	 * shouldn't hurt.
598 	 */
599 	getyx(stdscr, oldy, oldx);
600 	(void)move(LINES - 1, 0);
601 	(void)refresh();
602 
603 	/*
604 	 * Temporarily end the screen.  System V introduced a semantic where
605 	 * endwin() could be restarted.  We use it because restarting curses
606 	 * from scratch often fails in System V.  4BSD curses didn't support
607 	 * restarting after endwin(), so we have to do what clean up we can
608 	 * without calling it.
609 	 */
610 #ifdef HAVE_BSD_CURSES
611 	/* Save the terminal settings. */
612 	(void)tcgetattr(STDIN_FILENO, &t);
613 #endif
614 
615 	/* Restore the cursor keys to normal mode. */
616 	(void)keypad(stdscr, FALSE);
617 
618 	/* Restore the window name. */
619 	(void)cl_rename(sp, NULL, 0);
620 
621 #ifdef HAVE_BSD_CURSES
622 	(void)cl_attr(sp, SA_ALTERNATE, 0);
623 #else
624 	(void)endwin();
625 #endif
626 	/*
627 	 * XXX
628 	 * Restore the original terminal settings.  This is bad -- the
629 	 * reset can cause character loss from the tty queue.  However,
630 	 * we can't call endwin() in BSD curses implementations, and too
631 	 * many System V curses implementations don't get it right.
632 	 */
633 	(void)tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->orig);
634 
635 	/* Stop the process group. */
636 	(void)kill(0, SIGTSTP);
637 
638 	/* Time passes ... */
639 
640 	/*
641 	 * If we received a killer signal, we're done.  Leave everything
642 	 * unchanged.  In addition, the terminal has already been reset
643 	 * correctly, so leave it alone.
644 	 */
645 	if (clp->killersig) {
646 		F_CLR(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT);
647 		return (0);
648 	}
649 
650 #ifdef HAVE_BSD_CURSES
651 	/* Restore terminal settings. */
652 	if (F_ISSET(clp, CL_STDIN_TTY))
653 		(void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &t);
654 
655 	(void)cl_attr(sp, SA_ALTERNATE, 1);
656 #endif
657 
658 	/* Set the window name. */
659 	(void)cl_rename(sp, sp->frp->name, 1);
660 
661 	/* Put the cursor keys into application mode. */
662 	(void)keypad(stdscr, TRUE);
663 
664 	/* Refresh and repaint the screen. */
665 	(void)move(oldy, oldx);
666 	(void)cl_refresh(sp, 1);
667 
668 	/* If the screen changed size, set the SIGWINCH bit. */
669 	if (cl_ssize(sp, 1, NULL, NULL, &changed))
670 		return (1);
671 	if (changed)
672 		F_SET(CLP(sp), CL_SIGWINCH);
673 
674 	return (0);
675 }
676 
677 /*
678  * cl_usage --
679  *	Print out the curses usage messages.
680  *
681  * PUBLIC: void cl_usage __P((void));
682  */
683 void
684 cl_usage()
685 {
686 #define	USAGE "\
687 usage: ex [-eFRrSsv] [-c command] [-t tag] [-w size] [file ...]\n\
688 usage: vi [-eFlRrSv] [-c command] [-t tag] [-w size] [file ...]\n"
689 	(void)fprintf(stderr, "%s", USAGE);
690 #undef	USAGE
691 }
692 
693 #ifdef DEBUG
694 /*
695  * gdbrefresh --
696  *	Stub routine so can flush out curses screen changes using gdb.
697  */
698 int
699 gdbrefresh()
700 {
701 	refresh();
702 	return (0);		/* XXX Convince gdb to run it. */
703 }
704 #endif
705