Upgrade vi(1). 1/2
[dragonfly.git] / contrib / nvi2 / cl / cl_term.c
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 #include <sys/types.h>
13 #include <sys/ioctl.h>
14 #include <sys/queue.h>
15 #include <sys/stat.h>
16
17 #include <bitstring.h>
18 #include <errno.h>
19 #include <limits.h>
20 #include <signal.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #ifdef HAVE_TERM_H
25 #include <term.h>
26 #endif
27 #include <termios.h>
28 #include <unistd.h>
29
30 #include "../common/common.h"
31 #include "cl.h"
32
33 static int cl_pfmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t);
34 static size_t atoz_or(const char *, size_t);
35
36 /*
37  * XXX
38  * THIS REQUIRES THAT ALL SCREENS SHARE A TERMINAL TYPE.
39  */
40 typedef struct _tklist {
41         char    *ts;                    /* Key's termcap string. */
42         char    *output;                /* Corresponding vi command. */
43         char    *name;                  /* Name. */
44         u_char   value;                 /* Special value (for lookup). */
45 } TKLIST;
46 static TKLIST const c_tklist[] = {      /* Command mappings. */
47         {"kil1",        "O",    "insert line"},
48         {"kdch1",       "x",    "delete character"},
49         {"kcud1",       "j",    "cursor down"},
50         {"kel",         "D",    "delete to eol"},
51         {"kind",     "\004",    "scroll down"},                 /* ^D */
52         {"kll",         "$",    "go to eol"},
53         {"kend",        "$",    "go to eol"},
54         {"khome",       "^",    "go to sol"},
55         {"kich1",       "i",    "insert at cursor"},
56         {"kdl1",       "dd",    "delete line"},
57         {"kcub1",       "h",    "cursor left"},
58         {"knp",      "\006",    "page down"},                   /* ^F */
59         {"kpp",      "\002",    "page up"},                     /* ^B */
60         {"kri",      "\025",    "scroll up"},                   /* ^U */
61         {"ked",        "dG",    "delete to end of screen"},
62         {"kcuf1",       "l",    "cursor right"},
63         {"kcuu1",       "k",    "cursor up"},
64         {NULL},
65 };
66 static TKLIST const m1_tklist[] = {     /* Input mappings (lookup). */
67         {NULL},
68 };
69 static TKLIST const m2_tklist[] = {     /* Input mappings (set or delete). */
70         {"kcud1",  "\033ja",    "cursor down"},                 /* ^[ja */
71         {"kcub1",  "\033ha",    "cursor left"},                 /* ^[ha */
72         {"kcuu1",  "\033ka",    "cursor up"},                   /* ^[ka */
73         {"kcuf1",  "\033la",    "cursor right"},                /* ^[la */
74         {NULL},
75 };
76
77 /*
78  * cl_term_init --
79  *      Initialize the special keys defined by the termcap/terminfo entry.
80  *
81  * PUBLIC: int cl_term_init(SCR *);
82  */
83 int
84 cl_term_init(SCR *sp)
85 {
86         KEYLIST *kp;
87         SEQ *qp;
88         TKLIST const *tkp;
89         char *t;
90         CHAR_T name[60];
91         CHAR_T output[5];
92         CHAR_T ts[20];
93         CHAR_T *wp;
94         size_t wlen;
95
96         /* Command mappings. */
97         for (tkp = c_tklist; tkp->name != NULL; ++tkp) {
98                 if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1)
99                         continue;
100                 CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen);
101                 MEMCPY(name, wp, wlen);
102                 CHAR2INT(sp, t, strlen(t), wp, wlen);
103                 MEMCPY(ts, wp, wlen);
104                 CHAR2INT(sp, tkp->output, strlen(tkp->output), wp, wlen);
105                 MEMCPY(output, wp, wlen);
106                 if (seq_set(sp, name, strlen(tkp->name), ts, strlen(t),
107                     output, strlen(tkp->output), SEQ_COMMAND,
108                     SEQ_NOOVERWRITE | SEQ_SCREEN))
109                         return (1);
110         }
111
112         /* Input mappings needing to be looked up. */
113         for (tkp = m1_tklist; tkp->name != NULL; ++tkp) {
114                 if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1)
115                         continue;
116                 for (kp = keylist;; ++kp)
117                         if (kp->value == tkp->value)
118                                 break;
119                 if (kp == NULL)
120                         continue;
121                 CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen);
122                 MEMCPY(name, wp, wlen);
123                 CHAR2INT(sp, t, strlen(t), wp, wlen);
124                 MEMCPY(ts, wp, wlen);
125                 output[0] = (UCHAR_T)kp->ch;
126                 if (seq_set(sp, name, strlen(tkp->name), ts, strlen(t),
127                     output, 1, SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN))
128                         return (1);
129         }
130
131         /* Input mappings that are already set or are text deletions. */
132         for (tkp = m2_tklist; tkp->name != NULL; ++tkp) {
133                 if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1)
134                         continue;
135                 /*
136                  * !!!
137                  * Some terminals' <cursor_left> keys send single <backspace>
138                  * characters.  This is okay in command mapping, but not okay
139                  * in input mapping.  That combination is the only one we'll
140                  * ever see, hopefully, so kluge it here for now.
141                  */
142                 if (!strcmp(t, "\b"))
143                         continue;
144                 if (tkp->output == NULL) {
145                         CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen);
146                         MEMCPY(name, wp, wlen);
147                         CHAR2INT(sp, t, strlen(t), wp, wlen);
148                         MEMCPY(ts, wp, wlen);
149                         if (seq_set(sp, name, strlen(tkp->name),
150                             ts, strlen(t), NULL, 0,
151                             SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN))
152                                 return (1);
153                 } else {
154                         CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen);
155                         MEMCPY(name, wp, wlen);
156                         CHAR2INT(sp, t, strlen(t), wp, wlen);
157                         MEMCPY(ts, wp, wlen);
158                         CHAR2INT(sp, tkp->output, strlen(tkp->output), wp, wlen);
159                         MEMCPY(output, wp, wlen);
160                         if (seq_set(sp, name, strlen(tkp->name),
161                             ts, strlen(t), output, strlen(tkp->output),
162                             SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN))
163                                 return (1);
164                 }
165         }
166
167         /*
168          * Rework any function key mappings that were set before the
169          * screen was initialized.
170          */
171         SLIST_FOREACH(qp, sp->gp->seqq, q)
172                 if (F_ISSET(qp, SEQ_FUNCMAP))
173                         (void)cl_pfmap(sp, qp->stype,
174                             qp->input, qp->ilen, qp->output, qp->olen);
175         return (0);
176 }
177
178 /*
179  * cl_term_end --
180  *      End the special keys defined by the termcap/terminfo entry.
181  *
182  * PUBLIC: int cl_term_end(GS *);
183  */
184 int
185 cl_term_end(GS *gp)
186 {
187         SEQ *qp, *nqp, *pre_qp = NULL;
188
189         /* Delete screen specific mappings. */
190         SLIST_FOREACH_MUTABLE(qp, gp->seqq, q, nqp)
191                 if (F_ISSET(qp, SEQ_SCREEN)) {
192                         if (qp == SLIST_FIRST(gp->seqq))
193                                 SLIST_REMOVE_HEAD(gp->seqq, q);
194                         else
195                                 SLIST_REMOVE_AFTER(pre_qp, q);
196                         (void)seq_free(qp);
197                 } else
198                         pre_qp = qp;
199         return (0);
200 }
201
202 /*
203  * cl_fmap --
204  *      Map a function key.
205  *
206  * PUBLIC: int cl_fmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t);
207  */
208 int
209 cl_fmap(SCR *sp, seq_t stype, CHAR_T *from, size_t flen, CHAR_T *to, size_t tlen)
210 {
211         /* Ignore until the screen is running, do the real work then. */
212         if (F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_SCR_VI))
213                 return (0);
214         if (F_ISSET(sp, SC_EX) && !F_ISSET(sp, SC_SCR_EX))
215                 return (0);
216
217         return (cl_pfmap(sp, stype, from, flen, to, tlen));
218 }
219
220 /*
221  * cl_pfmap --
222  *      Map a function key (private version).
223  */
224 static int
225 cl_pfmap(SCR *sp, seq_t stype, CHAR_T *from, size_t flen, CHAR_T *to, size_t tlen)
226 {
227         size_t nlen;
228         char *p;
229         char name[64];
230         CHAR_T keyname[64];
231         CHAR_T ts[20];
232         CHAR_T *wp;
233         size_t wlen;
234
235         (void)snprintf(name, sizeof(name), "kf%d", 
236                         (int)STRTOL(from+1,NULL,10));
237         if ((p = tigetstr(name)) == NULL ||
238             p == (char *)-1 || strlen(p) == 0)
239                 p = NULL;
240         if (p == NULL) {
241                 msgq_wstr(sp, M_ERR, from, "233|This terminal has no %s key");
242                 return (1);
243         }
244
245         nlen = SPRINTF(keyname,
246             SIZE(keyname), L("function key %d"), 
247                         (int)STRTOL(from+1,NULL,10));
248         CHAR2INT(sp, p, strlen(p), wp, wlen);
249         MEMCPY(ts, wp, wlen);
250         return (seq_set(sp, keyname, nlen,
251             ts, strlen(p), to, tlen, stype, SEQ_NOOVERWRITE | SEQ_SCREEN));
252 }
253
254 /*
255  * cl_optchange --
256  *      Curses screen specific "option changed" routine.
257  *
258  * PUBLIC: int cl_optchange(SCR *, int, char *, u_long *);
259  */
260 int
261 cl_optchange(SCR *sp, int opt, char *str, u_long *valp)
262 {
263         CL_PRIVATE *clp;
264
265         clp = CLP(sp);
266
267         switch (opt) {
268         case O_TERM:
269                 F_CLR(sp, SC_SCR_EX | SC_SCR_VI);
270                 /* FALLTHROUGH */
271         case O_COLUMNS:
272         case O_LINES:
273                 /*
274                  * Changing the terminal type requires that we reinitialize
275                  * curses, while resizing does not.
276                  */
277                 F_SET(sp->gp, G_SRESTART);
278                 break;
279         case O_MESG:
280                 (void)cl_omesg(sp, clp, *valp);
281                 break;
282         case O_WINDOWNAME:
283                 if (*valp) {
284                         F_SET(clp, CL_RENAME_OK);
285
286                         /*
287                          * If the screen is live, i.e. we're not reading the
288                          * .exrc file, update the window.
289                          */
290                         if (sp->frp != NULL && sp->frp->name != NULL)
291                                 (void)cl_rename(sp, sp->frp->name, 1);
292                 } else {
293                         F_CLR(clp, CL_RENAME_OK);
294
295                         (void)cl_rename(sp, NULL, 0);
296                 }
297                 break;
298         }
299         return (0);
300 }
301
302 /*
303  * cl_omesg --
304  *      Turn the tty write permission on or off.
305  *
306  * PUBLIC: int cl_omesg(SCR *, CL_PRIVATE *, int);
307  */
308 int
309 cl_omesg(SCR *sp, CL_PRIVATE *clp, int on)
310 {
311         struct stat sb;
312         char *tty;
313
314         /* Find the tty, get the current permissions. */
315         if ((tty = ttyname(STDERR_FILENO)) == NULL) {
316                 if (sp != NULL)
317                         msgq(sp, M_SYSERR, "stderr");
318                 return (1);
319         }
320         if (stat(tty, &sb) < 0) {
321                 if (sp != NULL)
322                         msgq(sp, M_SYSERR, "%s", tty);
323                 return (1);
324         }
325
326         /* Save the original status if it's unknown. */
327         if (clp->tgw == TGW_UNKNOWN)
328                 clp->tgw = sb.st_mode & S_IWGRP ? TGW_SET : TGW_UNSET;
329
330         /* Toggle the permissions. */
331         if (on) {
332                 if (chmod(tty, sb.st_mode | S_IWGRP) < 0) {
333                         if (sp != NULL)
334                                 msgq(sp, M_SYSERR,
335                                     "046|messages not turned on: %s", tty);
336                         return (1);
337                 }
338         } else
339                 if (chmod(tty, sb.st_mode & ~S_IWGRP) < 0) {
340                         if (sp != NULL)
341                                 msgq(sp, M_SYSERR,
342                                     "045|messages not turned off: %s", tty);
343                         return (1);
344                 }
345         return (0);
346 }
347
348 /*
349  * cl_ssize --
350  *      Return the terminal size.
351  *
352  * PUBLIC: int cl_ssize(SCR *, int, size_t *, size_t *, int *);
353  */
354 int
355 cl_ssize(SCR *sp, int sigwinch, size_t *rowp, size_t *colp, int *changedp)
356 {
357         struct winsize win;
358         size_t col, row;
359         int rval;
360         char *p;
361
362         /* Assume it's changed. */
363         if (changedp != NULL)
364                 *changedp = 1;
365
366         /*
367          * !!!
368          * sp may be NULL.
369          *
370          * Get the screen rows and columns.  If the values are wrong, it's
371          * not a big deal -- as soon as the user sets them explicitly the
372          * environment will be set and the screen package will use the new
373          * values.
374          *
375          * Try TIOCGWINSZ.
376          */
377         row = col = 0;
378         if (ioctl(STDERR_FILENO, TIOCGWINSZ, &win) != -1) {
379                 row = win.ws_row;
380                 col = win.ws_col;
381         }
382         /* If here because of suspend or a signal, only trust TIOCGWINSZ. */
383         if (sigwinch) {
384                 /*
385                  * Somebody didn't get TIOCGWINSZ right, or has suspend
386                  * without window resizing support.  The user just lost,
387                  * but there's nothing we can do.
388                  */
389                 if (row == 0 || col == 0) {
390                         if (changedp != NULL)
391                                 *changedp = 0;
392                         return (0);
393                 }
394
395                 /*
396                  * SunOS systems deliver SIGWINCH when windows are uncovered
397                  * as well as when they change size.  In addition, we call
398                  * here when continuing after being suspended since the window
399                  * may have changed size.  Since we don't want to background
400                  * all of the screens just because the window was uncovered,
401                  * ignore the signal if there's no change.
402                  */
403                 if (sp != NULL &&
404                     row == O_VAL(sp, O_LINES) && col == O_VAL(sp, O_COLUMNS)) {
405                         if (changedp != NULL)
406                                 *changedp = 0;
407                         return (0);
408                 }
409
410                 if (rowp != NULL)
411                         *rowp = row;
412                 if (colp != NULL)
413                         *colp = col;
414                 return (0);
415         }
416
417         /*
418          * !!!
419          * If TIOCGWINSZ failed, or had entries of 0, try termcap.  This
420          * routine is called before any termcap or terminal information
421          * has been set up.  If there's no TERM environmental variable set,
422          * let it go, at least ex can run.
423          */
424         if (row == 0 || col == 0) {
425                 if ((p = getenv("TERM")) == NULL)
426                         goto noterm;
427                 if (row == 0)
428                         if ((rval = tigetnum("lines")) < 0)
429                                 msgq(sp, M_SYSERR, "tigetnum: lines");
430                         else
431                                 row = rval;
432                 if (col == 0)
433                         if ((rval = tigetnum("cols")) < 0)
434                                 msgq(sp, M_SYSERR, "tigetnum: cols");
435                         else
436                                 col = rval;
437         }
438
439         /* If nothing else, well, it's probably a VT100. */
440 noterm: if (row == 0)
441                 row = 24;
442         if (col == 0)
443                 col = 80;
444
445         /*
446          * !!!
447          * POSIX 1003.2 requires the environment to override everything.
448          * Often, people can get nvi to stop messing up their screen by
449          * deleting the LINES and COLUMNS environment variables from their
450          * dot-files.
451          */
452         if ((p = getenv("LINES")) != NULL)
453                 row = atoz_or(p, row);
454         if ((p = getenv("COLUMNS")) != NULL)
455                 col = atoz_or(p, col);
456
457         if (rowp != NULL)
458                 *rowp = row;
459         if (colp != NULL)
460                 *colp = col;
461         return (0);
462 }
463
464 /*
465  * atoz_or --
466  *      Parse non-zero positive decimal with a fallback.
467  */
468 static size_t
469 atoz_or(const char *s, size_t y)
470 {
471         char *ep;
472         long x = strtol(s, &ep, 10);
473
474         if (*ep == '\0' && (0 < x && x < INT_MAX))
475                 return (size_t)x;
476         else
477                 return y;
478 }
479
480 /*
481  * cl_putchar --
482  *      Function version of putchar, for tputs.
483  *
484  * PUBLIC: int cl_putchar(int);
485  */
486 int
487 cl_putchar(int ch)
488 {
489         return (putchar(ch));
490 }