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
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:
- 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.
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:
- Fixed a bug where a reproducible $RANDOM sequence (after assigning a

View file

@ -48,9 +48,17 @@
#include "edit.h"
#include "shlex.h"
static char CURSOR_UP[20] = { ESC, '[', 'A', 0 };
static char KILL_LINE[20] = { ESC, '[', 'J', 0 };
static char *CURSOR_UP = Empty; /* move cursor up one line */
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
# define is_cntrl(c) ((c<=STRIP) && iscntrl(c))
@ -499,17 +507,11 @@ int tty_alt(register int fd)
*/
int ed_window(void)
{
int rows,cols;
register char *cp = nv_getval(COLUMNS);
if(cp)
cols = (int)strtol(cp, (char**)0, 10)-1;
else
{
astwinsize(2,&rows,&cols);
if(--cols <0)
cols = DFLTWINDOW-1;
}
if(cols < MINWINDOW)
int cols;
sh_winsize(NIL(int*),&cols);
if(--cols < 0)
cols = DFLTWINDOW - 1;
else if(cols < MINWINDOW)
cols = MINWINDOW;
else if(cols > MAXWINDOW)
cols = MAXWINDOW;
@ -546,23 +548,32 @@ void ed_ringbell(void)
* 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
ed_putchar(ep,'\r');
#endif /* cray */
#ifdef u370
ed_putchar(ep,'\r');
#endif /* u370 */
#ifdef VENIX
ed_putchar(ep,'\r');
#endif /* VENIX */
ed_putchar(ep,'\n');
ed_flush(ep);
Shopt_t o = sh.options;
char *cp;
sigblock(SIGINT);
sh_offoption(SH_RESTRICTED);
sh_offoption(SH_VERBOSE);
sh_offoption(SH_XTRACE);
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)
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 )
*
* 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;
char inquote = 0;
ep->e_fd = fd;
#if SHOPT_ESH && SHOPT_VSH
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
ep->e_multiline = sh_editor_active() && sh_isoption(SH_MULTILINE);
sh.winch = 0;
#endif
#if SHOPT_EDPREDICT
ep->hlist = 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_hline = ep->e_hismax;
#if SHOPT_ESH && SHOPT_VSH
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_wsize = sh_editor_active() ? ed_window()-2 : MAXLINE;
ep->e_winsz = ep->e_wsize+2;
ep->e_crlf = 1;
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(ep->e_multiline)
{
#if defined(_pth_tput) && (_tput_terminfo || _tput_termcap)
#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))
{
Shopt_t o = sh.options;
sigblock(SIGINT);
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);
get_tput(TPUT_CURSOR_UP,&CURSOR_UP);
get_tput(TPUT_ERASE_EOS,&ERASE_EOS);
strcopy(ep->e_termname,term);
sh.options = o;
sigrelease(SIGINT);
}
#endif
#endif /* _pth_tput */
ep->e_wsize = MAXLINE - (ep->e_plen+1);
}
#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))
goto done;
#ifdef SIGWINCH
#if SHOPT_ESH || SHOPT_VSH
#if SHOPT_ESH && SHOPT_VSH
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_isstate(SH_INTERACTIVE) && (sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS)))
#else
if(sh.winch && sh_isstate(SH_INTERACTIVE) && sh_isoption(SH_VI))
#endif
/*
* If sh.winch is set, the number of window columns changed and/or there is a buffered
* job notification. When using a line editor, erase and redraw the command line.
*/
if(sh.winch && sh_editor_active() && sh_isstate(SH_INTERACTIVE))
{
/* redraw the prompt after receiving SIGWINCH */
Edpos_t lastpos;
int n, rows, newsize;
/* move cursor to start of first line */
int n, newsize;
char *cp;
sh_winsize(NIL(int*),&newsize);
/*
* 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_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 */
n = lastpos.line;
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_putstring(ep,ERASE_EOS);
ed_flush(ep);
sh_delay(.05,0);
astwinsize(2,&rows,&newsize);
/* show any buffered 'set -b' job notification(s) */
if(sh.notifybuf && (cp = sfstruse(sh.notifybuf)) && *cp)
sfputr(sfstderr, cp, -1);
/* update window size */
ep->e_winsz = newsize-1;
if(ep->e_winsz < MINWINDOW)
ep->e_winsz = MINWINDOW;
if(!ep->e_multiline && ep->e_wsize < MAXLINE)
ep->e_wsize = ep->e_winsz-2;
/* redraw command line */
#if SHOPT_ESH && SHOPT_VSH
if(sh_isoption(SH_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);
#elif SHOPT_ESH
emacs_redraw(ep->e_emacs);
#endif
#endif /* SHOPT_ESH && SHOPT_VSH */
}
#endif /* SHOPT_ESH || SHOPT_VSH */
sh.winch = 0;
#endif /* SIGWINCH */
/* an interrupt that should be ignored */
errno = 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->nhlist = 0;
}
ed_putstring(ep,KILL_LINE);
ed_putstring(ep,ERASE_EOS);
if(n)
{
for(i=1; (mp= *mpp) && i <= 16 ; i++,mpp++)

View file

@ -613,7 +613,7 @@ update:
}
continue;
case cntl('L'):
ed_crlf(ep->ed);
putchar(ep->ed,'\n');
draw(ep,REFRESH);
continue;
case ESC :
@ -733,7 +733,8 @@ process:
{
out[eol++] = '\n';
out[eol] = '\0';
ed_crlf(ep->ed);
putchar(ep->ed,'\n');
ed_flush(ep->ed);
}
#if SHOPT_MULTIBYTE
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);
putstring(vp,0, last_phys+1);
if(echoctl)
ed_crlf(vp->ed);
{
putchar('\n');
ed_flush(vp->ed);
}
else
while(kill_erase-- > 0)
putchar(' ');
@ -483,7 +486,10 @@ int ed_viread(void *context, int fd, register char *shbuf, int nchar, int reedit
if( term_char=='\n' )
{
if(!echoctl)
ed_crlf(vp->ed);
{
putchar('\n');
ed_flush(vp->ed);
}
virtual[++last_virt] = '\n';
}
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 )
{
virtual[++last_virt] = '\n';
ed_crlf(vp->ed);
putchar('\n');
ed_flush(vp->ed);
}
if( ++last_virt >= 0 )
{

View file

@ -13,6 +13,7 @@ tput_terminfo note{ does tput support terminfo codes }end run{
\"/*/tput\")
tput=`echo "${_pth_tput}" | sed 's/^"//; s/"$//'`
if "$tput" sgr0 >/dev/null 2>&1 &&
"$tput" ed >/dev/null 2>&1 &&
"$tput" cuu1 >/dev/null 2>&1
then echo '#define _tput_terminfo 1 /* tput supports 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=`echo "${_pth_tput}" | sed 's/^"//; s/"$//'`
if "$tput" me >/dev/null 2>&1 &&
"$tput" cd >/dev/null 2>&1 &&
"$tput" up >/dev/null 2>&1
then echo '#define _tput_termcap 1 /* tput supports 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'))))))))))))))))
#endif
extern void ed_crlf(Edit_t*);
extern void ed_putchar(Edit_t*, int);
extern void ed_ringbell(void);
extern void ed_setup(Edit_t*,int, int);

View file

@ -117,7 +117,7 @@ struct checkpt
extern noreturn void sh_done(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_sigdone(void);
extern void sh_siginit(void);

View file

@ -52,6 +52,21 @@
struct ionod;
#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 void sh_ioinit(void);
extern int sh_iomovefd(int);

View file

@ -312,9 +312,7 @@ struct Shell_s
char funload;
char used_pos; /* used positional parameter */
char universe;
#ifdef SIGWINCH
char winch;
#endif
char winch; /* set upon window size change or 'set -b' notification */
short arithrecursion; /* current arithmetic recursion level */
char indebug; /* set when in debug trap */
unsigned char ignsig; /* ignored signal in subshell */
@ -366,6 +364,7 @@ struct Shell_s
void *mktype;
Sfio_t *strbuf;
Sfio_t *strbuf2;
Sfio_t *notifybuf; /* for 'set -o notify' job notices */
Dt_t *first_root;
Dt_t *prefix_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_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
/* 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
Prints job completion messages as soon as a background job changes
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
.B \-e
Unless contained in a

View file

@ -74,10 +74,7 @@ void sh_fault(register int sig)
sig &= ~SH_TRAP;
#ifdef SIGWINCH
if(sig==SIGWINCH)
{
sh_update_columns_lines();
sh.winch = 1;
}
sh_winsize(NIL(int*),NIL(int*));
#endif /* SIGWINCH */
trap = sh.st.trapcom[sig];
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;
int32_t v;
astwinsize(2,&rows,&cols);
if(v = cols)
nv_putval(COLUMNS, (char*)&v, NV_INT32|NV_RDONLY);
if(v = rows)
nv_putval(LINES, (char*)&v, NV_INT32|NV_RDONLY);
static int oldlines, oldcolumns;
int lines = oldlines, columns = oldcolumns;
int32_t i;
astwinsize(2,&lines,&columns);
if(linesp)
*linesp = lines;
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
sh_accend();
#endif /* SHOPT_ACCT */
#if SHOPT_VSH || SHOPT_ESH
if(mbwide()
#if SHOPT_ESH
|| sh_isoption(SH_EMACS)
|| sh_isoption(SH_GMACS)
#endif
#if SHOPT_VSH
|| sh_isoption(SH_VI)
#endif
)
if(mbwide() && sh_editor_active())
tty_cooked(-1);
#endif /* SHOPT_VSH || SHOPT_ESH */
#ifdef JOBS
if((sh_isoption(SH_INTERACTIVE) && sh_isoption(SH_LOGIN_SHELL)) || (!sh_isoption(SH_INTERACTIVE) && (sig==SIGHUP)))
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;
int nrow, ncol=1, ndigits=1;
int fldsize, wsize = ed_window();
char *cp = nv_getval(sh_scoped(LINES));
nrow = (cp?1+2*((int)strtol(cp, (char**)0, 10)/3):NROW);
sh_winsize(&nrow,NIL(int*));
nrow = nrow ? (2 * (nrow / 3) + 1) : NROW;
for(i=argn;i >= 10;i /= 10)
ndigits++;
if(argn < nrow)

View file

@ -292,7 +292,6 @@ int job_reap(register int sig)
int nochild = 0, oerrno = errno, wstat;
Waitevent_f waitevent = sh.waitevent;
static int wcontinued = WCONTINUED;
int was_ttywait_on;
if (vmbusy())
{
errormsg(SH_DICT,ERROR_warn(0),"vmbusy() inside job_reap() -- should not happen");
@ -310,18 +309,14 @@ int job_reap(register int sig)
else
flags = WUNTRACED|wcontinued;
sh.waitevent = 0;
was_ttywait_on = sh_isstate(SH_TTYWAIT); /* save tty wait state */
while(1)
{
if(!(flags&WNOHANG) && !sh.intrap && job.pwlist)
{
sh_onstate(SH_TTYWAIT);
if(waitevent && (*waitevent)(-1,-1L,0))
flags |= WNOHANG;
}
pid = waitpid((pid_t)-1,&wstat,flags);
if(!was_ttywait_on)
sh_offstate(SH_TTYWAIT);
/*
* 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))
pid = waitpid((pid_t)-1,&wstat,flags&=~WCONTINUED);
sh_sigcheck();
if(pid<0 && errno==EINTR && (sig||job.savesig))
if(pid<0)
{
if(errno==EINTR && (sig||job.savesig))
{
errno = 0;
continue;
}
if(errno==ECHILD)
{
#if SHOPT_BGX
job.numbjob = 0;
#endif /* SHOPT_BGX */
nochild = 1;
}
}
if(pid<=0)
break;
if(wstat==0)
@ -474,23 +479,29 @@ int job_reap(register int sig)
sh.sigflag[SIGCHLD] |= SH_SIGTRAP;
sh.trapnote |= SH_SIGTRAP;
}
#endif
}
if(errno==ECHILD)
#endif /* !SHOPT_BGX */
/* Handle -b/--notify while waiting for command line input */
if(sh_isoption(SH_NOTIFY) && sh_isstate(SH_TTYWAIT))
{
#if SHOPT_BGX
job.numbjob = 0;
#endif /* SHOPT_BGX */
nochild = 1;
if(sh_editor_active())
{
/* Buffer the notification for ed_read() to show */
if(!sh.notifybuf)
sh.notifybuf = sfstropen();
outfile = sh.notifybuf;
job_list(pw,JOB_NFLAG);
sh.winch = 1;
}
sh.waitevent = waitevent;
if(pw && sh_isoption(SH_NOTIFY) && sh_isstate(SH_TTYWAIT))
else
{
outfile = sfstderr;
job_list(pw,JOB_NFLAG|JOB_NLFLAG);
job_unpost(pw,1);
sfsync(sfstderr);
}
job_unpost(pw,1);
}
}
sh.waitevent = waitevent;
if(sig)
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
* 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
*/
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);
#if SHOPT_ESH
/* do not leave users without a line editor */
if(!sh_isoption(SH_GMACS)
#if SHOPT_VSH
&& !sh_isoption(SH_VI)
#endif /* SHOPT_VSH */
&& !is_option(&sh.offoptions,SH_EMACS))
if(!sh_editor_active() && !is_option(&sh.offoptions,SH_EMACS))
sh_onoption(SH_EMACS);
#endif /* SHOPT_ESH */
}
#ifdef SIGWINCH
else
{
/* 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);
#endif /* SIGWINCH */
}
#endif
/* (Re)set PS4 and IFS, but don't export these now even if allexport is on. */
i = (sh_isoption(SH_ALLEXPORT) != 0);
sh_offoption(SH_ALLEXPORT);

View file

@ -1014,5 +1014,15 @@ r echo
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))