1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-02-13 11:42:21 +00:00

set -b/--notify: do not mess up the command line

This commit makes three interrelated changes.

First, the code for erasing the command line before redrawing it
upon a window size change is simplified and modernised. Instead of
erasing the line with lots of spaces, it now uses the sequence
obtained from 'tput ed' (usually ESC, '[', 'J') to "erase to the
end of screen". This avoids messing up the detection and automatic
redrawing of wrapped lines on terminals such as Apple Terminal.app.

Second, the -b/--notify option is made more usable. When it is on
and either the vi or emacs/gmacs line editor is in use, 'Done' and
similar notifications are now buffered and trigger a command line
redraw as if the window size changed, and the redraw routine prints
that notify buffer between erasing and redrawing the commmnd line.
The effect is that the notification appears to magically insert
itself directly above the line you're typing. (The notification
behaviour when not in the shell line editor, e.g. while running
commands such as external editors, is unchanged.)

Third, a bug is fixed that caused -b/--notify to only report on one
job when more than one terminated at the same time. The rest was
reported on the next command line as if -b were not on. Reproducer:
$ set -b; sleep 1 & sleep 1 & sleep 1 &

This commit also includes a fair number of other window size and
$COLUMNS/$LINES handling tweaks that made all this easier, not all
of which are mentioned below.

src/cmd/ksh93/include/fault.h,
src/cmd/ksh93/sh/fault.c:
- Replace sh_update_columns_lines with a new sh_winsize() function.
  It calls astwinsize() and is to be used instead of it, and
  instead of nv_getval(LINES) and nv_getval(COLUMNS) calls. It:
  - Allows passing one or neither of lines or cols pointers.
  - Updates COLUMNS and LINES, but only if they actually changed
    from the last values. This makes .set discipline functions
    defined for these variables more useful.
  - Sets the sh.winch flag, but only if COLUMNS changes. If only
    the height changes, the command line does not need redrawing.

src/cmd/ksh93/include/io.h:
- Add sh_editor_active() that allows checking whether one of vi,
  emacs or gmacs is active without onerous #if SHOPT_* directives.

src/cmd/ksh93/sh/jobs.c: job_reap():
- Remove the fix backported in fc655f1a, which was really just a
  workaround that papered over the real bug.
- Move a check for errno==ECHILD so it is only done when waitpid()
  returns an error (pid < 0); the check was not correct because C
  library functions that do not error out also do not change errno.
- Move the SH_NOTIFY && SH_TTYWAIT code section to within the
  while(1) loop so it is run for each job, not only the
  last-processed one. This fixes the bug where only one job was
  notified when more than one ended at the same time.
- In that section, check if an editor is active; if so, set the
  output file for job_list() to sh.notifybuffer instead of standard
  error, list the jobs without the initial newline (JOB_NLFLAG),
  and trigger a command line redraw by setting sh.winch.

src/cmd/ksh93/edit/edit.c:
- Obtain not just CURSOR_UP but also ERASE_EOS (renamed from
  KILL_LINE) using 'tput'. The latter had the ANSI sequence
  hardcoded. Define a couple of TPUT_* macros to make it easier to
  deal with terminfo/termcap codes.
- Add get_tput() to make it easier to get several tput values
  robustly (with SIGINT blocked, trace disabled, etc.)
- ed_crlf(): Removed. Going by those ancient #ifdefs, nothing that
  93u+m will ever run on requires a '\r' before a '\n' to start a
  new line on the terminal. Plus, as of 93u+, there were already
  several spots in emacs.c and vi.c where it printed a sole '\n'.
- ed_read():
  - Simplify/modernise command line erase using ERASE_EOS.
  - Between erasing and redrawing, print the contents of the notify
    buffer. This has the effect of inserting notifications above
    the command line while the user is typing.

src/cmd/ksh93/features/cmds:
- To detect terminfo vs termcap codes, use all three codes we're
  currently using. This matters on at least on my system (macOS
  10.14.6) in which /usr/bin/tput has incomplete terminfo support
  (no 'ed') but complete termcap support!
This commit is contained in:
Martijn Dekker 2022-07-24 04:06:46 +02:00
parent aa468f4c55
commit bb4f23e63f
17 changed files with 205 additions and 175 deletions

View file

@ -199,6 +199,11 @@ New command line editor features:
mode, <ESC> 7 <left-arrow> will now move the cursor seven positions to mode, <ESC> 7 <left-arrow> will now move the cursor seven positions to
the left. In vi control mode, this would be entered as: 7 <left-arrow>. the left. In vi control mode, this would be entered as: 7 <left-arrow>.
- When the -b/--notify shell option is on and the vi or emacs/gmacs shell
line editor is in use, 'Done' and similar notifications from completed
background jobs are now inserted directly above the line you're typing,
without affecting your command line display.
New shell language features: New shell language features:
- The &>file redirection shorthand (for >file 2>&1) is now available for - The &>file redirection shorthand (for >file 2>&1) is now available for

11
NEWS
View file

@ -3,6 +3,17 @@ For full details, see the git log at: https://github.com/ksh93/ksh/tree/1.0
Any uppercase BUG_* names are modernish shell bug IDs. Any uppercase BUG_* names are modernish shell bug IDs.
2022-07-24:
- New feature to make 'set -b'/'set -o notify' more usable. When that option
is on (and either the vi or emacs/gmacs line editor is in use), 'Done' and
similar notifications from completed background jobs no longer mess up the
command line you're typing. Instead, the notification is inserted directly
above the line you're typing, without affecting your command line display.
- Fixed a bug that caused the -b/--notify option to only notify on one job if
more than one background job completed at the same time.
2022-07-21: 2022-07-21:
- Fixed a bug where a reproducible $RANDOM sequence (after assigning a - Fixed a bug where a reproducible $RANDOM sequence (after assigning a

View file

@ -48,9 +48,17 @@
#include "edit.h" #include "edit.h"
#include "shlex.h" #include "shlex.h"
static char CURSOR_UP[20] = { ESC, '[', 'A', 0 }; static char *CURSOR_UP = Empty; /* move cursor up one line */
static char KILL_LINE[20] = { ESC, '[', 'J', 0 }; static char *ERASE_EOS = Empty; /* erase to end of screen */
#if _tput_terminfo
#define TPUT_CURSOR_UP "cuu1"
#define TPUT_ERASE_EOS "ed"
#elif _tput_termcap
#define TPUT_CURSOR_UP "up"
#define TPUT_ERASE_EOS "cd"
#else
#undef _pth_tput
#endif /* _tput_terminfo */
#if SHOPT_MULTIBYTE #if SHOPT_MULTIBYTE
# define is_cntrl(c) ((c<=STRIP) && iscntrl(c)) # define is_cntrl(c) ((c<=STRIP) && iscntrl(c))
@ -499,17 +507,11 @@ int tty_alt(register int fd)
*/ */
int ed_window(void) int ed_window(void)
{ {
int rows,cols; int cols;
register char *cp = nv_getval(COLUMNS); sh_winsize(NIL(int*),&cols);
if(cp) if(--cols < 0)
cols = (int)strtol(cp, (char**)0, 10)-1; cols = DFLTWINDOW - 1;
else else if(cols < MINWINDOW)
{
astwinsize(2,&rows,&cols);
if(--cols <0)
cols = DFLTWINDOW-1;
}
if(cols < MINWINDOW)
cols = MINWINDOW; cols = MINWINDOW;
else if(cols > MAXWINDOW) else if(cols > MAXWINDOW)
cols = MAXWINDOW; cols = MAXWINDOW;
@ -546,23 +548,32 @@ void ed_ringbell(void)
* send a carriage return line feed to the terminal * send a carriage return line feed to the terminal
*/ */
void ed_crlf(register Edit_t *ep) #ifdef _pth_tput
/*
* Get or update a tput (terminfo or termcap) capability string.
*/
static void get_tput(char *tp, char **cpp)
{ {
#ifdef cray Shopt_t o = sh.options;
ed_putchar(ep,'\r'); char *cp;
#endif /* cray */ sigblock(SIGINT);
#ifdef u370 sh_offoption(SH_RESTRICTED);
ed_putchar(ep,'\r'); sh_offoption(SH_VERBOSE);
#endif /* u370 */ sh_offoption(SH_XTRACE);
#ifdef VENIX sfprintf(sh.strbuf,".sh.value=$(" _pth_tput " %s 2>/dev/null)",tp);
ed_putchar(ep,'\r'); sh_trap(sfstruse(sh.strbuf),0);
#endif /* VENIX */ if((cp = nv_getval(SH_VALNOD)) && (!*cpp || strcmp(cp,*cpp)!=0))
ed_putchar(ep,'\n'); {
ed_flush(ep); if(*cpp && *cpp!=Empty)
free(*cpp);
*cpp = *cp ? sh_strdup(cp) : Empty;
}
nv_unset(SH_VALNOD);
sh.options = o;
sigrelease(SIGINT);
} }
#endif /* SHOPT_ESH || SHOPT_VSH */ #endif /* _pth_tput */
#if SHOPT_ESH || SHOPT_VSH
/* ED_SETUP( max_prompt_size ) /* ED_SETUP( max_prompt_size )
* *
* This routine sets up the prompt string * This routine sets up the prompt string
@ -584,17 +595,8 @@ void ed_setup(register Edit_t *ep, int fd, int reedit)
register int qlen = 1, qwid; register int qlen = 1, qwid;
char inquote = 0; char inquote = 0;
ep->e_fd = fd; ep->e_fd = fd;
#if SHOPT_ESH && SHOPT_VSH ep->e_multiline = sh_editor_active() && sh_isoption(SH_MULTILINE);
ep->e_multiline = sh_isoption(SH_MULTILINE) && (sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS) || sh_isoption(SH_VI));
#elif SHOPT_ESH
ep->e_multiline = sh_isoption(SH_MULTILINE) && (sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS));
#else
ep->e_multiline = sh_isoption(SH_MULTILINE) && sh_isoption(SH_VI);
#endif
sh_update_columns_lines();
#ifdef SIGWINCH
sh.winch = 0; sh.winch = 0;
#endif
#if SHOPT_EDPREDICT #if SHOPT_EDPREDICT
ep->hlist = 0; ep->hlist = 0;
ep->nhlist = 0; ep->nhlist = 0;
@ -616,18 +618,7 @@ void ed_setup(register Edit_t *ep, int fd, int reedit)
ep->e_hismax = ep->e_hismin = ep->e_hloff = 0; ep->e_hismax = ep->e_hismin = ep->e_hloff = 0;
} }
ep->e_hline = ep->e_hismax; ep->e_hline = ep->e_hismax;
#if SHOPT_ESH && SHOPT_VSH ep->e_wsize = sh_editor_active() ? ed_window()-2 : MAXLINE;
if(!sh_isoption(SH_VI) && !sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS))
#elif SHOPT_ESH
if(!sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS))
#elif SHOPT_VSH
if(!sh_isoption(SH_VI))
#else
if(1)
#endif /* SHOPT_ESH && SHOPT_VSH */
ep->e_wsize = MAXLINE;
else
ep->e_wsize = ed_window()-2;
ep->e_winsz = ep->e_wsize+2; ep->e_winsz = ep->e_wsize+2;
ep->e_crlf = 1; ep->e_crlf = 1;
ep->e_plen = 0; ep->e_plen = 0;
@ -789,35 +780,17 @@ void ed_setup(register Edit_t *ep, int fd, int reedit)
#if SHOPT_ESH || SHOPT_VSH #if SHOPT_ESH || SHOPT_VSH
if(ep->e_multiline) if(ep->e_multiline)
{ {
#if defined(_pth_tput) && (_tput_terminfo || _tput_termcap) #ifdef _pth_tput
char *term; char *term;
if(!ep->e_term) if(!ep->e_term)
ep->e_term = nv_search("TERM",sh.var_tree,0); 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)) if(ep->e_term && (term=nv_getval(ep->e_term)) && strlen(term)<sizeof(ep->e_termname) && strcmp(term,ep->e_termname))
{ {
Shopt_t o = sh.options; get_tput(TPUT_CURSOR_UP,&CURSOR_UP);
sigblock(SIGINT); get_tput(TPUT_ERASE_EOS,&ERASE_EOS);
sh_offoption(SH_RESTRICTED);
sh_offoption(SH_VERBOSE);
sh_offoption(SH_XTRACE);
/* get the cursor up sequence from tput */
#if _tput_terminfo
sh_trap(".sh.subscript=$(" _pth_tput " cuu1 2>/dev/null)",0);
#elif _tput_termcap
sh_trap(".sh.subscript=$(" _pth_tput " up 2>/dev/null)",0);
#else
#error no tput method
#endif
if((pp = nv_getval(SH_SUBSCRNOD)) && strlen(pp) < sizeof(CURSOR_UP))
strcopy(CURSOR_UP,pp);
else
CURSOR_UP[0] = '\0'; /* no escape sequence is better than a faulty one */
nv_unset(SH_SUBSCRNOD);
strcopy(ep->e_termname,term); strcopy(ep->e_termname,term);
sh.options = o;
sigrelease(SIGINT);
} }
#endif #endif /* _pth_tput */
ep->e_wsize = MAXLINE - (ep->e_plen+1); ep->e_wsize = MAXLINE - (ep->e_plen+1);
} }
#endif /* SHOPT_ESH || SHOPT_VSH */ #endif /* SHOPT_ESH || SHOPT_VSH */
@ -880,47 +853,40 @@ int ed_read(void *context, int fd, char *buff, int size, int reedit)
{ {
if(sh.trapnote&(SH_SIGSET|SH_SIGTRAP)) if(sh.trapnote&(SH_SIGSET|SH_SIGTRAP))
goto done; goto done;
#ifdef SIGWINCH /*
#if SHOPT_ESH || SHOPT_VSH * If sh.winch is set, the number of window columns changed and/or there is a buffered
#if SHOPT_ESH && SHOPT_VSH * job notification. When using a line editor, erase and redraw the command line.
if(sh.winch && sh_isstate(SH_INTERACTIVE) && (sh_isoption(SH_VI) || sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS))) */
#elif SHOPT_ESH if(sh.winch && sh_editor_active() && sh_isstate(SH_INTERACTIVE))
if(sh.winch && sh_isstate(SH_INTERACTIVE) && (sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS)))
#else
if(sh.winch && sh_isstate(SH_INTERACTIVE) && sh_isoption(SH_VI))
#endif
{ {
/* redraw the prompt after receiving SIGWINCH */ int n, newsize;
Edpos_t lastpos; char *cp;
int n, rows, newsize; sh_winsize(NIL(int*),&newsize);
/* move cursor to start of first line */ /*
* 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(ep->e_multiline)
{
n = (ep->e_plen + ep->e_cur) / newsize;
while(n--)
ed_putstring(ep,CURSOR_UP);
}
ed_putchar(ep,'\r'); ed_putchar(ep,'\r');
ed_flush(ep);
astwinsize(2,&rows,&newsize);
n = (ep->e_plen+ep->e_cur)/++ep->e_winsz;
while(n--)
ed_putstring(ep,CURSOR_UP);
if(ep->e_multiline && newsize>ep->e_winsz && (lastpos.line=(ep->e_plen+ep->e_peol)/ep->e_winsz))
{
/* clear the current command line */ /* clear the current command line */
n = lastpos.line; ed_putstring(ep,ERASE_EOS);
while(lastpos.line--)
{
ed_nputchar(ep,ep->e_winsz,' ');
ed_putchar(ep,'\n');
}
ed_nputchar(ep,ep->e_winsz,' ');
while(n--)
ed_putstring(ep,CURSOR_UP);
}
ed_flush(ep); ed_flush(ep);
sh_delay(.05,0); /* show any buffered 'set -b' job notification(s) */
astwinsize(2,&rows,&newsize); if(sh.notifybuf && (cp = sfstruse(sh.notifybuf)) && *cp)
sfputr(sfstderr, cp, -1);
/* update window size */
ep->e_winsz = newsize-1; ep->e_winsz = newsize-1;
if(ep->e_winsz < MINWINDOW) if(ep->e_winsz < MINWINDOW)
ep->e_winsz = MINWINDOW; ep->e_winsz = MINWINDOW;
if(!ep->e_multiline && ep->e_wsize < MAXLINE) if(!ep->e_multiline && ep->e_wsize < MAXLINE)
ep->e_wsize = ep->e_winsz-2; ep->e_wsize = ep->e_winsz-2;
/* redraw command line */
#if SHOPT_ESH && SHOPT_VSH #if SHOPT_ESH && SHOPT_VSH
if(sh_isoption(SH_VI)) if(sh_isoption(SH_VI))
vi_redraw(ep->e_vi); vi_redraw(ep->e_vi);
@ -930,11 +896,9 @@ int ed_read(void *context, int fd, char *buff, int size, int reedit)
vi_redraw(ep->e_vi); vi_redraw(ep->e_vi);
#elif SHOPT_ESH #elif SHOPT_ESH
emacs_redraw(ep->e_emacs); emacs_redraw(ep->e_emacs);
#endif #endif /* SHOPT_ESH && SHOPT_VSH */
} }
#endif /* SHOPT_ESH || SHOPT_VSH */
sh.winch = 0; sh.winch = 0;
#endif /* SIGWINCH */
/* an interrupt that should be ignored */ /* an interrupt that should be ignored */
errno = 0; errno = 0;
if(!waitevent || (rv=(*waitevent)(fd,-1L,0))>=0) if(!waitevent || (rv=(*waitevent)(fd,-1L,0))>=0)
@ -1842,7 +1806,7 @@ void ed_histlist(Edit_t *ep,int n)
ep->hlist = 0; ep->hlist = 0;
ep->nhlist = 0; ep->nhlist = 0;
} }
ed_putstring(ep,KILL_LINE); ed_putstring(ep,ERASE_EOS);
if(n) if(n)
{ {
for(i=1; (mp= *mpp) && i <= 16 ; i++,mpp++) for(i=1; (mp= *mpp) && i <= 16 ; i++,mpp++)

View file

@ -613,7 +613,7 @@ update:
} }
continue; continue;
case cntl('L'): case cntl('L'):
ed_crlf(ep->ed); putchar(ep->ed,'\n');
draw(ep,REFRESH); draw(ep,REFRESH);
continue; continue;
case ESC : case ESC :
@ -733,7 +733,8 @@ process:
{ {
out[eol++] = '\n'; out[eol++] = '\n';
out[eol] = '\0'; out[eol] = '\0';
ed_crlf(ep->ed); putchar(ep->ed,'\n');
ed_flush(ep->ed);
} }
#if SHOPT_MULTIBYTE #if SHOPT_MULTIBYTE
ed_external(out,buff); ed_external(out,buff);

View file

@ -474,7 +474,10 @@ int ed_viread(void *context, int fd, register char *shbuf, int nchar, int reedit
pr_string(vp,Prompt); pr_string(vp,Prompt);
putstring(vp,0, last_phys+1); putstring(vp,0, last_phys+1);
if(echoctl) if(echoctl)
ed_crlf(vp->ed); {
putchar('\n');
ed_flush(vp->ed);
}
else else
while(kill_erase-- > 0) while(kill_erase-- > 0)
putchar(' '); putchar(' ');
@ -483,7 +486,10 @@ int ed_viread(void *context, int fd, register char *shbuf, int nchar, int reedit
if( term_char=='\n' ) if( term_char=='\n' )
{ {
if(!echoctl) if(!echoctl)
ed_crlf(vp->ed); {
putchar('\n');
ed_flush(vp->ed);
}
virtual[++last_virt] = '\n'; virtual[++last_virt] = '\n';
} }
vp->last_cmd = 'i'; vp->last_cmd = 'i';
@ -596,7 +602,8 @@ int ed_viread(void *context, int fd, register char *shbuf, int nchar, int reedit
if( vp->addnl ) if( vp->addnl )
{ {
virtual[++last_virt] = '\n'; virtual[++last_virt] = '\n';
ed_crlf(vp->ed); putchar('\n');
ed_flush(vp->ed);
} }
if( ++last_virt >= 0 ) if( ++last_virt >= 0 )
{ {

View file

@ -13,6 +13,7 @@ tput_terminfo note{ does tput support terminfo codes }end run{
\"/*/tput\") \"/*/tput\")
tput=`echo "${_pth_tput}" | sed 's/^"//; s/"$//'` tput=`echo "${_pth_tput}" | sed 's/^"//; s/"$//'`
if "$tput" sgr0 >/dev/null 2>&1 && if "$tput" sgr0 >/dev/null 2>&1 &&
"$tput" ed >/dev/null 2>&1 &&
"$tput" cuu1 >/dev/null 2>&1 "$tput" cuu1 >/dev/null 2>&1
then echo '#define _tput_terminfo 1 /* tput supports terminfo codes */' then echo '#define _tput_terminfo 1 /* tput supports terminfo codes */'
else echo '#define _tput_terminfo 0 /* tput does not support terminfo codes */' else echo '#define _tput_terminfo 0 /* tput does not support terminfo codes */'
@ -29,6 +30,7 @@ tput_termcap note{ does tput support termcap codes }end run{
\"/*/tput\") \"/*/tput\")
tput=`echo "${_pth_tput}" | sed 's/^"//; s/"$//'` tput=`echo "${_pth_tput}" | sed 's/^"//; s/"$//'`
if "$tput" me >/dev/null 2>&1 && if "$tput" me >/dev/null 2>&1 &&
"$tput" cd >/dev/null 2>&1 &&
"$tput" up >/dev/null 2>&1 "$tput" up >/dev/null 2>&1
then echo '#define _tput_termcap 1 /* tput supports termcap codes */' then echo '#define _tput_termcap 1 /* tput supports termcap codes */'
else echo '#define _tput_termcap 0 /* tput does not support termcap codes */' else echo '#define _tput_termcap 0 /* tput does not support termcap codes */'

View file

@ -174,7 +174,6 @@ typedef struct edit
(c<'J'?c+1-'A':(c+10-'J')))))))))))))))) (c<'J'?c+1-'A':(c+10-'J'))))))))))))))))
#endif #endif
extern void ed_crlf(Edit_t*);
extern void ed_putchar(Edit_t*, int); extern void ed_putchar(Edit_t*, int);
extern void ed_ringbell(void); extern void ed_ringbell(void);
extern void ed_setup(Edit_t*,int, int); extern void ed_setup(Edit_t*,int, int);

View file

@ -117,7 +117,7 @@ struct checkpt
extern noreturn void sh_done(int); extern noreturn void sh_done(int);
extern void sh_fault(int); extern void sh_fault(int);
extern void sh_update_columns_lines(void); extern void sh_winsize(int*,int*);
extern void sh_sigclear(int); extern void sh_sigclear(int);
extern void sh_sigdone(void); extern void sh_sigdone(void);
extern void sh_siginit(void); extern void sh_siginit(void);

View file

@ -52,6 +52,21 @@
struct ionod; struct ionod;
#endif /* !ARG_RAW */ #endif /* !ARG_RAW */
/*
* Check if there is an editor active while avoiding repetitive #if flaggery.
* The 0 definition is used to optimize out code if no editor is compiled in.
* (This is here and not in edit.h because io.h is far more widely included.)
*/
#if SHOPT_ESH && SHOPT_VSH
#define sh_editor_active() (sh_isoption(SH_VI) || sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS))
#elif SHOPT_ESH
#define sh_editor_active() (sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS))
#elif SHOPT_VSH
#define sh_editor_active() (sh_isoption(SH_VI)!=0)
#else
#define sh_editor_active() 0
#endif
extern int sh_iocheckfd(int); extern int sh_iocheckfd(int);
extern void sh_ioinit(void); extern void sh_ioinit(void);
extern int sh_iomovefd(int); extern int sh_iomovefd(int);

View file

@ -312,9 +312,7 @@ struct Shell_s
char funload; char funload;
char used_pos; /* used positional parameter */ char used_pos; /* used positional parameter */
char universe; char universe;
#ifdef SIGWINCH char winch; /* set upon window size change or 'set -b' notification */
char winch;
#endif
short arithrecursion; /* current arithmetic recursion level */ short arithrecursion; /* current arithmetic recursion level */
char indebug; /* set when in debug trap */ char indebug; /* set when in debug trap */
unsigned char ignsig; /* ignored signal in subshell */ unsigned char ignsig; /* ignored signal in subshell */
@ -366,6 +364,7 @@ struct Shell_s
void *mktype; void *mktype;
Sfio_t *strbuf; Sfio_t *strbuf;
Sfio_t *strbuf2; Sfio_t *strbuf2;
Sfio_t *notifybuf; /* for 'set -o notify' job notices */
Dt_t *first_root; Dt_t *first_root;
Dt_t *prefix_root; Dt_t *prefix_root;
Dt_t *last_root; Dt_t *last_root;

View file

@ -23,7 +23,7 @@
#define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */ #define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */
#define SH_RELEASE_SVER "1.0.0-beta.2" /* semantic version number: https://semver.org */ #define SH_RELEASE_SVER "1.0.0-beta.2" /* semantic version number: https://semver.org */
#define SH_RELEASE_DATE "2022-07-21" /* must be in this format for $((.sh.version)) */ #define SH_RELEASE_DATE "2022-07-24" /* must be in this format for $((.sh.version)) */
#define SH_RELEASE_CPYR "(c) 2020-2022 Contributors to ksh " SH_RELEASE_FORK #define SH_RELEASE_CPYR "(c) 2020-2022 Contributors to ksh " SH_RELEASE_FORK
/* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */ /* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */

View file

@ -7513,6 +7513,10 @@ above) are only exported while their name space is active.
.B \-b .B \-b
Prints job completion messages as soon as a background job changes Prints job completion messages as soon as a background job changes
state rather than waiting for the next prompt. state rather than waiting for the next prompt.
If one of the shell line editors is in use (see
.I In-line Editing Options
above), the completion message is inserted directly above the
command line being typed.
.TP 8 .TP 8
.B \-e .B \-e
Unless contained in a Unless contained in a

View file

@ -74,10 +74,7 @@ void sh_fault(register int sig)
sig &= ~SH_TRAP; sig &= ~SH_TRAP;
#ifdef SIGWINCH #ifdef SIGWINCH
if(sig==SIGWINCH) if(sig==SIGWINCH)
{ sh_winsize(NIL(int*),NIL(int*));
sh_update_columns_lines();
sh.winch = 1;
}
#endif /* SIGWINCH */ #endif /* SIGWINCH */
trap = sh.st.trapcom[sig]; trap = sh.st.trapcom[sig];
if(sh.savesig) if(sh.savesig)
@ -227,17 +224,35 @@ done:
} }
/* /*
* update $COLUMNS and $LINES * Get window size and update LINES and COLUMNS.
* Returns the sizes in the pointed-to ints if non-null.
* If the number of columns changed, flags a window size change in sh.winch.
*/ */
void sh_update_columns_lines(void) void sh_winsize(int *linesp, int *columnsp)
{ {
int rows=0, cols=0; static int oldlines, oldcolumns;
int32_t v; int lines = oldlines, columns = oldcolumns;
astwinsize(2,&rows,&cols); int32_t i;
if(v = cols) astwinsize(2,&lines,&columns);
nv_putval(COLUMNS, (char*)&v, NV_INT32|NV_RDONLY); if(linesp)
if(v = rows) *linesp = lines;
nv_putval(LINES, (char*)&v, NV_INT32|NV_RDONLY); if(columnsp)
*columnsp = columns;
/*
* Update LINES and COLUMNS only when the values changed; this makes
* LINES.set and COLUMNS.set shell discipline functions more useful.
*/
if((lines != oldlines || nv_isnull(LINES)) && (i = (int32_t)lines))
{
nv_putval(LINES, (char*)&i, NV_INT32|NV_RDONLY);
oldlines = lines;
}
if((columns != oldcolumns || nv_isnull(COLUMNS)) && (i = (int32_t)columns))
{
nv_putval(COLUMNS, (char*)&i, NV_INT32|NV_RDONLY);
oldcolumns = columns;
sh.winch = 1;
}
} }
/* /*
@ -662,18 +677,8 @@ noreturn void sh_done(register int sig)
#if SHOPT_ACCT #if SHOPT_ACCT
sh_accend(); sh_accend();
#endif /* SHOPT_ACCT */ #endif /* SHOPT_ACCT */
#if SHOPT_VSH || SHOPT_ESH if(mbwide() && sh_editor_active())
if(mbwide()
#if SHOPT_ESH
|| sh_isoption(SH_EMACS)
|| sh_isoption(SH_GMACS)
#endif
#if SHOPT_VSH
|| sh_isoption(SH_VI)
#endif
)
tty_cooked(-1); tty_cooked(-1);
#endif /* SHOPT_VSH || SHOPT_ESH */
#ifdef JOBS #ifdef JOBS
if((sh_isoption(SH_INTERACTIVE) && sh_isoption(SH_LOGIN_SHELL)) || (!sh_isoption(SH_INTERACTIVE) && (sig==SIGHUP))) if((sh_isoption(SH_INTERACTIVE) && sh_isoption(SH_LOGIN_SHELL)) || (!sh_isoption(SH_INTERACTIVE) && (sig==SIGHUP)))
job_walk(sfstderr, job_hup, SIGHUP, NIL(char**)); job_walk(sfstderr, job_hup, SIGHUP, NIL(char**));

View file

@ -2445,8 +2445,8 @@ void sh_menu(Sfio_t *outfile,int argn,char *argv[])
register char **arg; register char **arg;
int nrow, ncol=1, ndigits=1; int nrow, ncol=1, ndigits=1;
int fldsize, wsize = ed_window(); int fldsize, wsize = ed_window();
char *cp = nv_getval(sh_scoped(LINES)); sh_winsize(&nrow,NIL(int*));
nrow = (cp?1+2*((int)strtol(cp, (char**)0, 10)/3):NROW); nrow = nrow ? (2 * (nrow / 3) + 1) : NROW;
for(i=argn;i >= 10;i /= 10) for(i=argn;i >= 10;i /= 10)
ndigits++; ndigits++;
if(argn < nrow) if(argn < nrow)

View file

@ -292,7 +292,6 @@ int job_reap(register int sig)
int nochild = 0, oerrno = errno, wstat; int nochild = 0, oerrno = errno, wstat;
Waitevent_f waitevent = sh.waitevent; Waitevent_f waitevent = sh.waitevent;
static int wcontinued = WCONTINUED; static int wcontinued = WCONTINUED;
int was_ttywait_on;
if (vmbusy()) if (vmbusy())
{ {
errormsg(SH_DICT,ERROR_warn(0),"vmbusy() inside job_reap() -- should not happen"); errormsg(SH_DICT,ERROR_warn(0),"vmbusy() inside job_reap() -- should not happen");
@ -310,18 +309,14 @@ int job_reap(register int sig)
else else
flags = WUNTRACED|wcontinued; flags = WUNTRACED|wcontinued;
sh.waitevent = 0; sh.waitevent = 0;
was_ttywait_on = sh_isstate(SH_TTYWAIT); /* save tty wait state */
while(1) while(1)
{ {
if(!(flags&WNOHANG) && !sh.intrap && job.pwlist) if(!(flags&WNOHANG) && !sh.intrap && job.pwlist)
{ {
sh_onstate(SH_TTYWAIT);
if(waitevent && (*waitevent)(-1,-1L,0)) if(waitevent && (*waitevent)(-1,-1L,0))
flags |= WNOHANG; flags |= WNOHANG;
} }
pid = waitpid((pid_t)-1,&wstat,flags); pid = waitpid((pid_t)-1,&wstat,flags);
if(!was_ttywait_on)
sh_offstate(SH_TTYWAIT);
/* /*
* some systems (Linux 2.6) may return EINVAL * some systems (Linux 2.6) may return EINVAL
@ -331,11 +326,21 @@ int job_reap(register int sig)
if (pid<0 && errno==EINVAL && (flags&WCONTINUED)) if (pid<0 && errno==EINVAL && (flags&WCONTINUED))
pid = waitpid((pid_t)-1,&wstat,flags&=~WCONTINUED); pid = waitpid((pid_t)-1,&wstat,flags&=~WCONTINUED);
sh_sigcheck(); sh_sigcheck();
if(pid<0 && errno==EINTR && (sig||job.savesig)) if(pid<0)
{
if(errno==EINTR && (sig||job.savesig))
{ {
errno = 0; errno = 0;
continue; continue;
} }
if(errno==ECHILD)
{
#if SHOPT_BGX
job.numbjob = 0;
#endif /* SHOPT_BGX */
nochild = 1;
}
}
if(pid<=0) if(pid<=0)
break; break;
if(wstat==0) if(wstat==0)
@ -474,23 +479,29 @@ int job_reap(register int sig)
sh.sigflag[SIGCHLD] |= SH_SIGTRAP; sh.sigflag[SIGCHLD] |= SH_SIGTRAP;
sh.trapnote |= SH_SIGTRAP; sh.trapnote |= SH_SIGTRAP;
} }
#endif #endif /* !SHOPT_BGX */
} /* Handle -b/--notify while waiting for command line input */
if(errno==ECHILD) if(sh_isoption(SH_NOTIFY) && sh_isstate(SH_TTYWAIT))
{ {
#if SHOPT_BGX if(sh_editor_active())
job.numbjob = 0; {
#endif /* SHOPT_BGX */ /* Buffer the notification for ed_read() to show */
nochild = 1; if(!sh.notifybuf)
sh.notifybuf = sfstropen();
outfile = sh.notifybuf;
job_list(pw,JOB_NFLAG);
sh.winch = 1;
} }
sh.waitevent = waitevent; else
if(pw && sh_isoption(SH_NOTIFY) && sh_isstate(SH_TTYWAIT))
{ {
outfile = sfstderr; outfile = sfstderr;
job_list(pw,JOB_NFLAG|JOB_NLFLAG); job_list(pw,JOB_NFLAG|JOB_NLFLAG);
job_unpost(pw,1);
sfsync(sfstderr); sfsync(sfstderr);
} }
job_unpost(pw,1);
}
}
sh.waitevent = waitevent;
if(sig) if(sig)
signal(sig, job_waitsafe); signal(sig, job_waitsafe);
/* /*
@ -897,7 +908,8 @@ int job_walk(Sfio_t *file,int (*fun)(struct process*,int),int arg,char *joblist[
/* /*
* list the given job * list the given job
* flag JOB_LFLAG for long listing * flag JOB_LFLAG for long listing
* flag JOB_NFLAG for list only jobs marked for notification * flag JOB_NFLAG to list only jobs marked for notification
* flag JOB_NLFLAG to print an initial newline
* flag JOB_PFLAG for process ID(s) only * flag JOB_PFLAG for process ID(s) only
*/ */
int job_list(struct process *pw,register int flag) int job_list(struct process *pw,register int flag)

View file

@ -341,22 +341,18 @@ int sh_main(int ac, char *av[], Shinit_f userinit)
sh_onstate(SH_INTERACTIVE); sh_onstate(SH_INTERACTIVE);
#if SHOPT_ESH #if SHOPT_ESH
/* do not leave users without a line editor */ /* do not leave users without a line editor */
if(!sh_isoption(SH_GMACS) if(!sh_editor_active() && !is_option(&sh.offoptions,SH_EMACS))
#if SHOPT_VSH
&& !sh_isoption(SH_VI)
#endif /* SHOPT_VSH */
&& !is_option(&sh.offoptions,SH_EMACS))
sh_onoption(SH_EMACS); sh_onoption(SH_EMACS);
#endif /* SHOPT_ESH */ #endif /* SHOPT_ESH */
} }
#ifdef SIGWINCH
else else
{ {
/* keep $COLUMNS and $LINES up to date even for scripts that don't trap SIGWINCH */ /* keep $COLUMNS and $LINES up to date even for scripts that don't trap SIGWINCH */
sh_update_columns_lines(); sh_winsize(NIL(int*),NIL(int*));
#ifdef SIGWINCH
signal(SIGWINCH,sh_fault); signal(SIGWINCH,sh_fault);
#endif /* SIGWINCH */
} }
#endif
/* (Re)set PS4 and IFS, but don't export these now even if allexport is on. */ /* (Re)set PS4 and IFS, but don't export these now even if allexport is on. */
i = (sh_isoption(SH_ALLEXPORT) != 0); i = (sh_isoption(SH_ALLEXPORT) != 0);
sh_offoption(SH_ALLEXPORT); sh_offoption(SH_ALLEXPORT);

View file

@ -1014,5 +1014,15 @@ r echo
r ^ok\r\n$ r ^ok\r\n$
! !
tst $LINENO <<"!"
L --notify does not report all simultaneously terminated jobs
d 15
p :test-1:
w set -b; sleep .1 & sleep .1 & sleep .1 &
u Done
u Done
u Done
!
# ====== # ======
exit $((Errors<125?Errors:125)) exit $((Errors<125?Errors:125))