Skip to content

Commit

Permalink
edit.c: disable multiline editing on invalid $TERM
Browse files Browse the repository at this point in the history
Reproducer: set TERM to an unknown terminal or one that does not
support cursor movement, and watch multiline editing go haywire.
Multiline editing should be auto-disabled under such conditions.

src/cmd/ksh93/edit/edit.c:
- Rename CURSOR_UP and ERASE_EOS to use lowercase names as they
  are not macros.
- get_tput(): Tweaks. Forget pre-existing escape sequences when
  tput(1) currently cannot provide them.
- ed_setup(): If e_multiline is on, and getting the tput(1) escape
  sequences upon a $TERM change fails, then disable e_multiline.
  To make this work correctly, we have to move this whole block
  to immediately before the code that adjusts the window size for
  single-line editing.
- ed_read(): Tweak and correct the code for redrawing the
  command-line on SIGWINCH in multiline mode. Using e_cur (the
  current position) as a factor in determining the number of lines
  to move up was incorrect, as that is the current position, not
  the total line length; for that, we need e_peol. (re: bb4f23e)

src/cmd/ksh93/features/cmds:
- Modernise a couple of old Bourne shell usages. POSIX shells can
  remove initial and final " with parameter expansion operators.

src/cmd/ksh93/include/edit.h:
- Remove two global vars that are unused after the above ed_setup()
  change; a locally scoped static variable is better here.

src/cmd/ksh93/tests/pty.sh:
- Four tests started failing because they depend on the specific
  behaviour of the multiline editor, which is now disabled because
  the pty tests are run with TERM=dumb so tput(1) will not return
  any escape sequences. Use TERM=vt100 for those (the DEC VT100 was
  pretty much the original ANSI escape sequence terminal, so it
  should be known about by just about every operating system).
  • Loading branch information
McDutchie committed Apr 10, 2023
1 parent 53bd0cc commit 5b0f235
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 42 deletions.
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ Uppercase BUG_* IDs are shell bug IDs as used by the Modernish shell library.

- Fixed command line redrawing on resizing the window with --multiline off.

- Multiline editing is now automatically disabled if and when the TERM
environment variable is unset, not exported, or set to a terminal type
that does not support the necessary operations. It is automatically
reenabled when the TERM variable is corrected and --multiline is on.

2023-04-08:

- Fixed a termcap(5) detection bug that broke multiline editing on FreeBSD.
Expand Down
80 changes: 50 additions & 30 deletions src/cmd/ksh93/edit/edit.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
#include "edit.h"
#include "shlex.h"

static char *CURSOR_UP = Empty; /* move cursor up one line */
static char *ERASE_EOS = Empty; /* erase to end of screen */
static char *cursor_up; /* move cursor up one line */
static char *erase_eos; /* erase to end of screen */
#if _tput_terminfo
#define E_MULTILINE ep->e_multiline
#define TPUT_CURSOR_UP "cuu1"
Expand Down Expand Up @@ -415,7 +415,7 @@ void ed_ringbell(void)

#if SHOPT_ESH || SHOPT_VSH

#ifdef _pth_tput
#if defined(_pth_tput) && (_tput_terminfo || _tput_termcap)
/*
* Get or update a tput (terminfo or termcap) capability string.
*/
Expand All @@ -427,19 +427,27 @@ static void get_tput(char *tp, char **cpp)
sh_offoption(SH_RESTRICTED);
sh_offoption(SH_VERBOSE);
sh_offoption(SH_XTRACE);
sfprintf(sh.strbuf,".sh.value=$(" _pth_tput " %s 2>/dev/null)",tp);
sfprintf(sh.strbuf,".sh.value=${ " _pth_tput " %s 2>/dev/null;}",tp);
sh_trap(sfstruse(sh.strbuf),0);
if((cp = nv_getval(SH_VALNOD)) && (!*cpp || strcmp(cp,*cpp)!=0))
{
if(*cpp && *cpp!=Empty)
if(*cpp)
free(*cpp);
*cpp = *cp ? sh_strdup(cp) : Empty;
*cpp = *cp ? sh_strdup(cp) : NULL;
}
else
{
if(*cpp)
free(*cpp);
*cpp = NULL;
}
nv_unset(SH_VALNOD);
sh.options = o;
sigrelease(SIGINT);
}
#endif /* _pth_tput */
#else
#define get_tput(tp,cpp) /* empty */
#endif /* defined(_pth_tput) && (_tput_terminfo || _tput_termcap) */

/* ED_SETUP( max_prompt_size )
*
Expand Down Expand Up @@ -620,6 +628,28 @@ void ed_setup(Edit_t *ep, int fd, int reedit)
if(pp-ep->e_prompt > qlen)
ep->e_plen = pp - ep->e_prompt - qlen;
*pp = 0;
if(E_MULTILINE)
{
static char *oldterm;
Namval_t *np = nv_search("TERM",sh.var_tree,0);
char *term = NULL;
if(nv_isattr(np,NV_EXPORT))
term = nv_getval(np);
if(!term)
term = "";
if(!oldterm || strcmp(term,oldterm))
{
get_tput(TPUT_CURSOR_UP,&cursor_up);
get_tput(TPUT_ERASE_EOS,&erase_eos);
if(oldterm)
free(oldterm);
oldterm = sh_strdup(term);
}
if(cursor_up && erase_eos)
ep->e_wsize = MAXLINE - (ep->e_plen + 1);
else
ep->e_multiline = 0;
}
if(!E_MULTILINE && (ep->e_wsize -= ep->e_plen) < 7)
{
int shift = 7-ep->e_wsize;
Expand Down Expand Up @@ -648,21 +678,6 @@ void ed_setup(Edit_t *ep, int fd, int reedit)
sfset(sfstderr,SF_READ,1);
sfwrite(sfstderr,ep->e_outptr,0);
ep->e_eol = reedit;
if(E_MULTILINE)
{
#ifdef _pth_tput
char *term;
if(!ep->e_term)
ep->e_term = nv_search("TERM",sh.var_tree,0);
if(ep->e_term && (term=nv_getval(ep->e_term)) && strlen(term)<sizeof(ep->e_termname) && strcmp(term,ep->e_termname))
{
get_tput(TPUT_CURSOR_UP,&CURSOR_UP);
get_tput(TPUT_ERASE_EOS,&ERASE_EOS);
strcopy(ep->e_termname,term);
}
#endif /* _pth_tput */
ep->e_wsize = MAXLINE - (ep->e_plen+1);
}
if(ep->e_default && (pp = nv_getval(ep->e_default)))
{
n = strlen(pp);
Expand Down Expand Up @@ -730,20 +745,25 @@ int ed_read(void *context, int fd, char *buff, int size, int reedit)
int n, newsize;
char *cp;
sh_winsize(NULL,&newsize);
ed_putchar(ep,'\r');
/*
* Try to move cursor to start of first line and pray it works... it's very
* failure-prone if the window size changed, especially on modern terminals
* that break the whole terminal abstraction by rewrapping lines themselves :(
*/
if(E_MULTILINE)
{
n = (ep->e_plen + ep->e_cur) / newsize;
while(n--)
ed_putstring(ep,CURSOR_UP);
n = (ep->e_plen + ep->e_peol) / ep->e_winsz;
while(n-- > 0)
ed_putstring(ep,cursor_up);
/* clear the current command line */
ed_putstring(ep,erase_eos);
}
else
{
ed_nputchar(ep,newsize-1,' ');
ed_putchar(ep,'\r');
}
ed_putchar(ep,'\r');
/* clear the current command line */
ed_putstring(ep,ERASE_EOS);
ed_flush(ep);
/* show any buffered 'set -b' job notification(s) */
if(sh.notifybuf && (cp = sfstruse(sh.notifybuf)) && *cp)
Expand Down Expand Up @@ -1127,7 +1147,7 @@ int ed_setcursor(Edit_t *ep,genchar *physical,int old,int new,int first)
{
int n,pline,plen=ep->e_plen;
for(;ep->e_curpos.line > newpos.line; ep->e_curpos.line--)
ed_putstring(ep,CURSOR_UP);
ed_putstring(ep,cursor_up);
pline = plen/(ep->e_winsz+1);
if(newpos.line <= pline)
plen -= pline*(ep->e_winsz+1);
Expand All @@ -1150,7 +1170,7 @@ int ed_setcursor(Edit_t *ep,genchar *physical,int old,int new,int first)
ed_putchar(ep,physical[m++]);
}
ed_nputchar(ep,n,' ');
ed_putstring(ep,CURSOR_UP);
ed_putstring(ep,cursor_up);
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/cmd/ksh93/features/cmds
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ tput_terminfo note{ does tput support terminfo codes }end run{
export TERM=xterm
case ${_pth_tput-} in
\"/*/tput\")
tput=`echo "${_pth_tput}" | sed 's/^"//; s/"$//'`
tput=${_pth_tput#\"}
tput=${tput%\"}
if "$tput" ed >/dev/null 2>&1 &&
"$tput" cuu1 >/dev/null 2>&1
then echo '#define _tput_terminfo 1 /* tput supports terminfo codes */'
Expand All @@ -25,7 +26,8 @@ tput_termcap note{ does tput support termcap codes }end run{
export TERM=xterm
case ${_pth_tput-} in
\"/*/tput\")
tput=`echo "${_pth_tput}" | sed 's/^"//; s/"$//'`
tput=${_pth_tput#\"}
tput=${tput%\"}
if "$tput" cd >/dev/null 2>&1 &&
"$tput" up >/dev/null 2>&1
then echo '#define _tput_termcap 1 /* tput supports termcap codes */'
Expand Down
2 changes: 0 additions & 2 deletions src/cmd/ksh93/include/edit.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,6 @@ typedef struct edit
int e_winsz; /* columns in window */
Edpos_t e_curpos; /* cursor line and column */
Namval_t *e_default; /* variable containing default value */
Namval_t *e_term; /* TERM variable */
char e_termname[80]; /* terminal name */
#if SHOPT_EDPREDICT
Histmatch_t **hlist;
Histmatch_t *hfirst;
Expand Down
20 changes: 16 additions & 4 deletions src/cmd/ksh93/sh.1
Original file line number Diff line number Diff line change
Expand Up @@ -7779,11 +7779,23 @@ Same as
.B multiline
The built-in editors will use multiple lines on the screen for lines
that are longer than the width of the screen. This may not work
for all terminals. The option is forced off on systems with neither
.B terminfo (5)
for all terminals.
The shell uses the system's
.BR tput (1)
command to obtain the terminal escape codes for the necessary operations.
Multi-line editing is disabled if this fails.
On most systems, setting the
.B TERM
variable to your terminal's type and exporting it corrects this situation.
The
.B multiline
option is permanently forced off on systems whose
.BR tput (1)
command supports neither
.BR terminfo (5)
nor
.B termcap (5)
support.
.BR termcap (5)
capability names.
.TP 8
.B noclobber
Same as
Expand Down
12 changes: 8 additions & 4 deletions src/cmd/ksh93/tests/pty.sh
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,8 @@ w :\E_
r ^:test-2: : One\\ "Two Three"\$'Four Five'\.mp3\r\n$
!

((SHOPT_VSH)) && tst $LINENO <<"!"
# needs non-dumb terminal for multiline editing
((SHOPT_VSH)) && TERM=vt100 tst $LINENO <<"!"
L crash when entering comment into history file (vi mode)
# https://github.com/att/ast/issues/798
Expand Down Expand Up @@ -857,7 +858,8 @@ w $'`/dev\t
r ^:test-5: \$'`/dev[[:blank:]]*\r\n$
!

((SHOPT_ESH)) && VISUAL=emacs tst $LINENO <<"!"
# needs non-dumb terminal for multiline editing
((SHOPT_ESH)) && VISUAL=emacs TERM=vt100 tst $LINENO <<"!"
L emacs: keys with repeat parameters repeat extra steps
# https://github.com/ksh93/ksh/issues/292
Expand Down Expand Up @@ -1083,7 +1085,8 @@ w p\E[AT
u CORRECT
!

((SHOPT_ESH)) && mkdir -p fullcomplete/foe && VISUAL=emacs tst $LINENO <<"!"
# needs non-dumb terminal for multiline editing
((SHOPT_ESH)) && mkdir -p fullcomplete/foe && VISUAL=emacs TERM=vt100 tst $LINENO <<"!"
L full-word completion in emacs mode
# https://github.com/ksh93/ksh/pull/580
Expand All @@ -1099,7 +1102,8 @@ w true fullcomplete/foi\cb*
r ^:test-3: true fullcomplete/foi\r\n
!

((SHOPT_VSH)) && mkdir -p fullcomplete/fov && VISUAL=vi tst $LINENO <<"!"
# needs non-dumb terminal for multiline editing
((SHOPT_VSH)) && mkdir -p fullcomplete/fov && VISUAL=vi TERM=vt100 tst $LINENO <<"!"
L full-word completion in vi mode
# https://github.com/ksh93/ksh/pull/580
Expand Down

0 comments on commit 5b0f235

Please sign in to comment.