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