* Copyright (c) 1991, 1993, 1994
* The Regents of the University of California. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
static char sccsid
[] = "@(#)term.c 9.6 (Berkeley) 12/2/94";
* DON'T INCLUDE <curses.h> HERE, IT BREAKS OSF1 V2.0 WHERE IT
* CHANGES THE VALUES OF VERASE/VKILL/VWERASE TO INCORRECT ONES.
* If we're reading less than 20 characters, up the size of the tty buffer.
* This shouldn't ever happen, other than the first time through, but it's
* possible if a map is large enough.
#define term_read_grow(sp) \
(sp)->gp->i_nelem - ((sp)->gp->i_cnt + (sp)->gp->i_next) >= 20 ?\
0 : __term_read_grow(sp, 64)
static int keycmp
__P((const void *, const void *));
static void term_key_set
__P((GS
*, int, int));
static int __term_read_grow
__P((SCR
*, int));
* Historic vi always used:
* ^D: autoindent deletion
* ^H: last character deletion
* ^Q: quote the next character (if not used in flow control).
* ^V: quote the next character
* regardless of the user's choices for these characters. The user's erase
* and kill characters worked in addition to these characters. Nvi wires
* down the above characters, but in addition permits the VEOF, VERASE, VKILL
* and VWERASE characters described by the user's termios structure.
* Ex was not consistent with this scheme, as it historically ran in tty
* cooked mode. This meant that the scroll command and autoindent erase
* characters were mapped to the user's EOF character, and the character
* and word deletion characters were the user's tty character and word
* deletion characters. This implementation makes it all consistent, as
* described above for vi.
* THIS REQUIRES THAT ALL SCREENS SHARE A SPECIAL KEY SET.
{K_BACKSLASH
, '\\'}, /* \ */
{K_CNTRLD
, '\004'}, /* ^D */
{K_CNTRLR
, '\022'}, /* ^R */
{K_CNTRLT
, '\024'}, /* ^T */
{K_CNTRLZ
, '\032'}, /* ^Z */
{K_ESCAPE
, '\033'}, /* ^[ */
{K_FORMFEED
, '\f'}, /* \f */
{K_HEXCHAR
, '\030'}, /* ^X */
{K_RIGHTBRACE
, '}'}, /* } */
{K_RIGHTPAREN
, ')'}, /* ) */
{K_VERASE
, '\b'}, /* \b */
{K_VKILL
, '\025'}, /* ^U */
{K_VLNEXT
, '\021'}, /* ^Q */
{K_VLNEXT
, '\026'}, /* ^V */
{K_VWERASE
, '\027'}, /* ^W */
{K_NOTUSED
, 0}, /* VEOF, VERASE, VKILL, VWERASE */
static int nkeylist
= (sizeof(keylist
) / sizeof(keylist
[0])) - 4;
* Initialize the special key lookup table.
* 8-bit only, for now. Recompilation should get you any
* 8-bit character set, as long as nul isn't a character.
(void)setlocale(LC_ALL
, "");
term_key_set(gp
, VEOF
, K_CNTRLD
);
term_key_set(gp
, VERASE
, K_VERASE
);
term_key_set(gp
, VKILL
, K_VKILL
);
term_key_set(gp
, VWERASE
, K_VWERASE
);
/* Sort the special key list. */
qsort(keylist
, nkeylist
, sizeof(keylist
[0]), keycmp
);
/* Initialize the fast lookup table. */
for (gp
->max_special
= 0, kp
= keylist
, cnt
= nkeylist
; cnt
--; ++kp
) {
if (gp
->max_special
< kp
->value
)
gp
->max_special
= kp
->value
;
if (kp
->ch
<= MAX_FAST_KEY
)
gp
->special_key
[kp
->ch
] = kp
->value
;
/* Find a non-printable character to use as a message separator. */
for (ch
= 1; ch
<= MAX_CHAR_T
; ++ch
)
msgq(sp
, M_ERR
, "090|No non-printable character found");
* Set keys found in the termios structure.
* VEOF, VERASE and VKILL are required by POSIX 1003.1-1990, VWERASE is
* a 4.4BSD extension. We've left four open slots in the keylist table,
* if these values exist, put them into place. Note, they may reset (or
* duplicate) values already in the table, so we check for that first.
term_key_set(gp
, name
, val
)
if (!F_ISSET(gp
, G_TERMIOS_SET
))
if ((ch
= gp
->original_termios
.c_cc
[name
]) == _POSIX_VDISABLE
)
/* Check for duplication. */
for (kp
= keylist
; kp
->value
!= K_NOTUSED
; ++kp
)
if (kp
->value
== K_NOTUSED
) {
keylist
[nkeylist
].ch
= ch
;
keylist
[nkeylist
].value
= val
;
* Build the fast-lookup key display array.
for (gp
= sp
->gp
, ch
= 0; ch
<= MAX_FAST_KEY
; ++ch
)
for (p
= gp
->cname
[ch
].name
, t
= __key_name(sp
, ch
),
len
= gp
->cname
[ch
].len
= sp
->clen
; len
--;)
* Return the length of the string that will display the key.
* This routine is the backup for the KEY_LEN() macro.
(void)__key_name(sp
, ch
);
* Return the string that will display the key. This routine
* is the backup for the KEY_NAME() macro.
static const CHAR_T hexdigit
[] = "0123456789abcdef";
static const CHAR_T octdigit
[] = "01234567";
* Historical (ARPA standard) mappings. Printable characters are left
* alone. Control characters less than '\177' are represented as '^'
* followed by the character offset from the '@' character in the ASCII
* map. '\177' is represented as '^' followed by '?'.
* The following code depends on the current locale being identical to
* the ASCII map from '\100' to '\076' (\076 since that's the largest
* character for which we can offset from '@' and get something that's
* a printable character in ASCII. I'm told that this is a reasonable
* This code will only work with CHAR_T's that are multiples of 8-bit
* NB: There's an assumption here that all printable characters take
* up a single column on the screen. This is not always correct.
} else if (ch
<= '\076' && iscntrl(ch
)) {
sp
->cname
[1] = ch
== '\177' ? '?' : '@' + ch
;
} else if (O_ISSET(sp
, O_OCTAL
)) {
#define BITS (sizeof(CHAR_T) * 8)
#define SHIFT (BITS - BITS % 3)
#define TOPMASK (BITS % 3 == 2 ? 3 : 1) << (BITS - BITS % 3)
sp
->cname
[1] = octdigit
[(ch
& TOPMASK
) >> SHIFT
];
for (len
= 2, mask
= 7 << (SHIFT
- 3),
cnt
= BITS
/ 3; cnt
-- > 0; mask
>>= 3, shift
-= 3)
sp
->cname
[len
++] = octdigit
[(ch
& mask
) >> shift
];
for (len
= 2, chp
= (u_int8_t
*)&ch
,
cnt
= sizeof(CHAR_T
); cnt
-- > 0; ++chp
) {
sp
->cname
[len
++] = hexdigit
[(*chp
& 0xf0) >> 4];
sp
->cname
[len
++] = hexdigit
[*chp
& 0x0f];
sp
->cname
[sp
->clen
= len
] = '\0';
* Push keys onto the front of a buffer.
* There is a single input buffer in ex/vi. Characters are read onto the
* end of the buffer by the terminal input routines, and pushed onto the
* front of the buffer by various other functions in ex/vi. Each key has
* an associated flag value, which indicates if it has already been quoted,
* if it is the result of a mapping or an abbreviation, as well as a count
* of the number of times it has been mapped.
term_push(sp
, s
, nchars
, flags
)
CHAR_T
*s
; /* Characters. */
size_t nchars
; /* Number of chars. */
u_int flags
; /* CH_* flags. */
/* If we have room, stuff the keys into the buffer. */
if (nchars
<= gp
->i_next
||
(gp
->i_ch
!= NULL
&& gp
->i_cnt
== 0 && nchars
<= gp
->i_nelem
)) {
MEMMOVE(gp
->i_ch
+ gp
->i_next
, s
, nchars
);
MEMSET(gp
->i_chf
+ gp
->i_next
, flags
, nchars
);
* If there are currently characters in the queue, shift them up,
* leaving some extra room. Get enough space plus a little extra.
#define TERM_PUSH_SHIFT 30
total
= gp
->i_cnt
+ gp
->i_next
+ nchars
+ TERM_PUSH_SHIFT
;
if (total
>= gp
->i_nelem
&& __term_read_grow(sp
, MAX(total
, 64)))
MEMMOVE(gp
->i_ch
+ TERM_PUSH_SHIFT
+ nchars
,
gp
->i_ch
+ gp
->i_next
, gp
->i_cnt
);
MEMMOVE(gp
->i_chf
+ TERM_PUSH_SHIFT
+ nchars
,
gp
->i_chf
+ gp
->i_next
, gp
->i_cnt
);
/* Put the new characters into the queue. */
gp
->i_next
= TERM_PUSH_SHIFT
;
MEMMOVE(gp
->i_ch
+ TERM_PUSH_SHIFT
, s
, nchars
);
MEMSET(gp
->i_chf
+ TERM_PUSH_SHIFT
, flags
, nchars
);
* Remove characters from the queue, simultaneously clearing the flag
#define QREM_HEAD(len) { \
size_t __off = gp->i_next; \
MEMSET(gp->i_chf + __off, 0, len); \
if ((gp->i_cnt -= len) == 0) \
#define QREM_TAIL(len) { \
size_t __off = gp->i_next + gp->i_cnt - 1; \
MEMSET(gp->i_chf + __off, 0, len); \
if ((gp->i_cnt -= len) == 0) \
* The flag TXT_MAPNODIGIT probably needs some explanation. First, the idea
* of mapping keys is that one or more keystrokes act like a function key.
* What's going on is that vi is reading a number, and the character following
* the number may or may not be mapped (TXT_MAPCOMMAND). For example, if the
* user is entering the z command, a valid command is "z40+", and we don't want
* to map the '+', i.e. if '+' is mapped to "xxx", we don't want to change it
* into "z40xxx". However, if the user enters "35x", we want to put all of the
* characters through the mapping code.
* Historical practice is a bit muddled here. (Surprise!) It always permitted
* mapping digits as long as they weren't the first character of the map, e.g.
* ":map ^A1 xxx" was okay. It also permitted the mapping of the digits 1-9
* (the digit 0 was a special case as it doesn't indicate the start of a count)
* as the first character of the map, but then ignored those mappings. While
* it's probably stupid to map digits, vi isn't your mother.
* The way this works is that the TXT_MAPNODIGIT causes term_key to return the
* end-of-digit without "looking" at the next character, i.e. leaving it as the
* user entered it. Presumably, the next term_key call will tell us how the
* There is one more complication. Users might map keys to digits, and, as
* it's described above, the commands:
* would return the keys "d2<end-of-digits>1G", when the user probably wanted
* "d21<end-of-digits>G". So, if a map starts off with a digit we continue as
* before, otherwise, we pretend we haven't mapped the character, and return
* Now that that's out of the way, let's talk about Energizer Bunny macros.
* It's easy to create macros that expand to a loop, e.g. map x 3x. It's
* fairly easy to detect this example, because it's all internal to term_key.
* If we're expanding a macro and it gets big enough, at some point we can
* assume it's looping and kill it. The examples that are tough are the ones
* where the parser is involved, e.g. map x "ayyx"byy. We do an expansion
* on 'x', and get "ayyx"byy. We then return the first 4 characters, and then
* find the looping macro again. There is no way that we can detect this
* without doing a full parse of the command, because the character that might
* cause the loop (in this case 'x') may be a literal character, e.g. the map
* map x "ayy"xyy"byy is perfectly legal and won't cause a loop.
* Historic vi tried to detect looping macros by disallowing obvious cases in
* the map command, maps that that ended with the same letter as they started
* (which wrongly disallowed "map x 'x"), and detecting macros that expanded
* too many times before keys were returned to the command parser. It didn't
* get many (most?) of the tricky cases right, however, and it was certainly
* possible to create macros that ran forever. And, even if it did figure out
* what was going on, the user was usually tossed into ex mode. Finally, any
* changes made before vi realized that the macro was recursing were left in
* place. We recover gracefully, but the only recourse the user has in an
* infinite macro loop is to interrupt.
* It is historic practice that mapping characters to themselves as the first
* part of the mapped string was legal, and did not cause infinite loops, i.e.
* ":map! { {^M^T" and ":map n nz." were known to work. The initial, matching
* characters were returned instead of being remapped.
* The final issue is recovery. It would be possible to undo all of the work
* that was done by the macro if we entered a record into the log so that we
* knew when the macro started, and, in fact, this might be worth doing at some
* point. Given that this might make the log grow unacceptably (consider that
* cursor keys are done with maps), for now we leave any changes made in place.
int init_nomap
, ispartial
, nr
;
/* If we've been interrupted, return an error. */
* If the queue is empty, read more keys in. Since no timeout is
* requested, s_key_read will either return an error or will read
* some number of characters.
loop
: if (gp
->i_cnt
== 0) {
if ((rval
= sp
->s_key_read(sp
, &nr
, NULL
)) != INP_OK
)
TRACE(sp
, "read: {%.*s}\n", gp
->i_cnt
, &gp
->i_ch
[gp
->i_next
]);
* If there's something on the mode line that we wanted
* the user to see, presume they've seen it as they just
/* If the key is mappable and should be mapped, look it up. */
if (!(gp
->i_chf
[gp
->i_next
] & CH_NOMAP
) &&
LF_ISSET(TXT_MAPCOMMAND
| TXT_MAPINPUT
)) {
newmap
: ch
= gp
->i_ch
[gp
->i_next
];
if (ch
< MAX_BIT_SEQ
&& !bit_test(gp
->seqb
, ch
))
remap
: qp
= seq_find(sp
, NULL
, &gp
->i_ch
[gp
->i_next
], gp
->i_cnt
,
LF_ISSET(TXT_MAPCOMMAND
) ? SEQ_COMMAND
: SEQ_INPUT
,
/* If we've been interrupted, return an error. */
* If get a partial match, read more characters and retry
* the map. If no characters read, return the characters
* <escape> characters are a problem. Input mode ends with
* an <escape>, and cursor keys start with one, so there's
* an ugly pause at the end of an input session. If it's an
* <escape>, check for followon characters, but timeout after
* a 20th of a second. This will lose if users create maps
* that use <escape> as the first character, and that aren't
* entered as a single keystroke.
if (O_ISSET(sp
, O_TIMEOUT
)) {
gp
->i_ch
[gp
->i_next
]) == K_ESCAPE
) {
t
.tv_sec
= O_VAL(sp
, O_KEYTIME
) / 10;
O_KEYTIME
) % 10) * 100000L;
if ((rval
= sp
->s_key_read(sp
, &nr
, tp
)) != INP_OK
)
/* If no map, return the character. */
* If looking for the end of a digit string, and the first
* character of the map is it, pretend we haven't seen the
if (LF_ISSET(TXT_MAPNODIGIT
) &&
qp
->output
!= NULL
&& !isdigit(qp
->output
[0]))
/* Find out if the initial segments are identical. */
!memcmp(&gp
->i_ch
[gp
->i_next
], qp
->output
, qp
->ilen
);
/* Delete the mapped characters from the queue. */
/* If keys mapped to nothing, go get more. */
/* If remapping characters, push the character on the queue. */
if (O_ISSET(sp
, O_REMAP
)) {
if (term_push(sp
, qp
->output
+ qp
->ilen
,
qp
->olen
- qp
->ilen
, CH_MAPPED
))
qp
->output
, qp
->ilen
, CH_NOMAP
| CH_MAPPED
))
qp
->output
, qp
->olen
, CH_MAPPED
))
/* Else, push the characters on the queue and return one. */
if (term_push(sp
, qp
->output
, qp
->olen
, CH_MAPPED
| CH_NOMAP
))
nomap
: ch
= gp
->i_ch
[gp
->i_next
];
if (LF_ISSET(TXT_MAPNODIGIT
) && !isdigit(ch
)) {
not_digit_ch
: chp
->ch
= CH_NOT_DIGIT
;
/* Fill in the return information. */
TRACE(sp
, "rtrn: {%s}\n", KEY_NAME(sp
, ch
));
chp
->flags
= gp
->i_chf
[gp
->i_next
];
chp
->value
= KEY_VAL(sp
, ch
);
/* Delete the character from the queue. */
* Flush any flagged keys.
term_flush(sp
, msg
, flags
)
if (!gp
->i_cnt
|| !(gp
->i_chf
[gp
->i_next
] & flags
))
} while (gp
->i_cnt
&& gp
->i_chf
[gp
->i_next
] & flags
);
msgq(sp
, M_ERR
, "091|%s: keys flushed", msg
);
#ifdef MAKE_THE_USER_ENTER_A_KEY
Earlier versions of nvi required that a user enter a key when waiting
for something to happen
, e
.g
. when getting a key to acknowledge that
the user has seen one
or more error messages
. Historic vi just used
the next character regardless
, and users complained
. This routine is
left in place in
case we ever need it again
.
static enum input term_key_queue
__P((SCR
*));
static enum input term_user_key
__P((SCR
*, CH
*));
* Get the next key, but require the user enter one.
* Read any keys the user has waiting. Make the race
* condition as short as possible.
if ((rval
= term_key_queue(sp
)) != INP_OK
)
/* Wait and read another key. */
if ((rval
= sp
->s_key_read(sp
, &nr
, NULL
)) != INP_OK
)
/* Fill in the return information. */
chp
->ch
= gp
->i_ch
[gp
->i_next
+ (gp
->i_cnt
- 1)];
chp
->value
= KEY_VAL(sp
, chp
->ch
);
* Read the keys off of the terminal queue until it's empty.
if ((rval
= sp
->s_key_read(sp
, &nr
, &t
)) != INP_OK
)
* Fill in the value for a key. This routine is the backup
* for the KEY_VAL() macro.
kp
= bsearch(&k
, keylist
, nkeylist
, sizeof(keylist
[0]), keycmp
);
return (kp
== NULL
? K_NOTUSED
: kp
->value
);
* Grow the terminal queue. This routine is the backup for
* the term_read_grow() macro.
__term_read_grow(sp
, add
)
new_nelem
= gp
->i_nelem
+ add
;
olen
= gp
->i_nelem
* sizeof(gp
->i_ch
[0]);
BINC_RET(sp
, gp
->i_ch
, olen
, new_nelem
* sizeof(gp
->i_ch
[0]));
olen
= gp
->i_nelem
* sizeof(gp
->i_chf
[0]);
BINC_RET(sp
, gp
->i_chf
, olen
, new_nelem
* sizeof(gp
->i_chf
[0]));
gp
->i_nelem
= olen
/ sizeof(gp
->i_chf
[0]);
return (((KEYLIST
*)ap
)->ch
- ((KEYLIST
*)bp
)->ch
);
term_window(sp
, sigwinch
)
* Get the screen rows and columns. If the values are wrong, it's
* not a big deal -- as soon as the user sets them explicitly the
* environment will be set and the screen package will use the new
if (ioctl(STDERR_FILENO
, TIOCGWINSZ
, &win
) != -1) {
/* If here because of suspend or a signal, only trust TIOCGWINSZ. */
* Somebody didn't get TIOCGWINSZ right, or has suspend
* without window resizing support. The user just lost,
* but there's nothing we can do.
if (row
== 0 || col
== 0)
* SunOS systems deliver SIGWINCH when windows are uncovered
* as well as when they change size. In addition, we call
* here when continuing after being suspended since the window
* may have changed size. Since we don't want to background
* all of the screens just because the window was uncovered,
* ignore the signal if there's no change.
if (row
== O_VAL(sp
, O_LINES
) && col
== O_VAL(sp
, O_COLUMNS
))
* If TIOCGWINSZ failed, or had entries of 0, try termcap. This
* routine is called before any termcap or terminal information
* has been set up. If there's no TERM environmental variable set,
* let it go, at least ex can run.
if (row
== 0 || col
== 0) {
if ((s
= getenv("TERM")) == NULL
)
if ((rval
= tigetnum("lines")) < 0)
msgq(sp
, M_SYSERR
, "tigetnum: lines");
if ((rval
= tigetnum("cols")) < 0)
msgq(sp
, M_SYSERR
, "tigetnum: cols");
switch (tgetent(buf
, s
)) {
msgq(sp
, M_SYSERR
, "tgetent: %s", s
);
p
= msg_print(sp
, s
, &nf
);
msgq(sp
, M_ERR
, "096|%s: unknown terminal type", p
);
if ((rval
= tgetnum("li")) < 0) {
p
= msg_print(sp
, s
, &nf
);
"097|no \"li\" terminal capability for %s", p
);
if ((rval
= tgetnum("co")) < 0) {
p
= msg_print(sp
, s
, &nf
);
"098|no \"co\" terminal capability for %s", p
);
/* If nothing else, well, it's probably a VT100. */
/* POSIX 1003.2 requires the environment to override. */
if ((s
= getenv("LINES")) != NULL
)
row
= strtol(s
, NULL
, 10);
if ((s
= getenv("COLUMNS")) != NULL
)
col
= strtol(s
, NULL
, 10);
/* Use the options code to accomplish the change. */
a
.len
= snprintf(buf
, sizeof(buf
), "lines=%u", row
);
if (opts_set(sp
, argv
, 1, NULL
))
a
.len
= snprintf(buf
, sizeof(buf
), "columns=%u", col
);
if (opts_set(sp
, argv
, 1, NULL
))