// Pipe Watch ("pw") // Copyright 2022-2023 Kaz Kylheku // // BSD-2 License // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 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. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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 SUCH DAMAGE. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _DARWIN_C_SOURCE #include "macpoll.h" #endif #define ctrl(ch) ((ch) & 0x1f) #define BS 8 #define TAB 9 #define CR 13 #define ESC 27 #define DEL 127 #define min(a, b) ((a) < (b) ? (a) : (b)) #define max(a, b) ((a) > (b) ? (a) : (b)) #define twocc(ch1, ch2) (((int) (ch1)) << 8 | (ch2)) #ifdef __GNUC__ #define printf_attr(fmtpos, vargpos) __attribute__ ((format (printf, \ fmtpos, vargpos))) #else #define printf_attr(fmtpos, vargpos) #endif #define cmdsize 256 #define maxgrep 64 #define maxtrig 100 #define snhistsize 20 #define workmax 4096 enum status_flags { stat_dirty = 0x0001, // display needs refresh stat_eof = 0x0002, // end of data reached stat_susp = 0x0004, // display refresh suspended stat_htmode = 0x0008, // head trigger mode stat_ttmode = 0x0010, // tail trigger mode stat_trgrd = 0x0020, // triggered flag stat_grep = 0x0040, // grep mode stat_force = 0x0080, // force refresh even if clean stat_lino = 0x0100, // render line numbers stat_bkgnd = 0x0200, // running in the background stat_hlite = 0x0400, // running in the background stat_oneshot = 0x0800, // running in the background stat_diff = 0x1000, // highlight differences stat_save = stat_susp | stat_lino | stat_hlite | stat_diff }; enum dstr_flags { dstr_cmark = 0x0001, // committed trigger mark dstr_tmark = 0x0002, // tentative trigger mark }; typedef enum execode { exec_ok, exec_msg, exec_failed } execode; typedef struct pwstate { char **circbuf; int nlines, maxlines; int hpos; int vsplit1, vsplit2, vs2pos; int hist, prevhist; int columns; int tstop; unsigned stat; int sncount, tcount; char *curcmd, *savedcmd; size_t editpos; char cmdbuf[cmdsize]; } pwstate; typedef struct grep { char *pat; regex_t rx; int inv; int flags; int err; void (*dtor)(char *); } grep; typedef struct dstr { int refs; size_t len; int flags; char str[]; } dstr; #define dstr_of(str) ((dstr *) ((str) - sizeof (dstr))) static char *pw_name; static int poll_interval = 1000; static int long_interval = 10000; static int regex_flags = 0; static char **snapshot[snhistsize]; static int snaplines[snhistsize]; static grep grepstack[maxgrep]; static int ngrep; static grep *triglist[maxtrig]; static int sncount; static int tfreq; static char **cmdhist; static int ncmdhist; static char **pathist; static int npathist; volatile sig_atomic_t winch; static void panic(const char *fmt, ...) { va_list vl; va_start (vl, fmt); fprintf(stderr, "%s: ", pw_name); vfprintf(stderr, fmt, vl); abort(); } printf_attr(1, 2) static void error(const char *fmt, ...) { va_list vl; va_start (vl, fmt); fprintf(stderr, "%s: ", pw_name); vfprintf(stderr, fmt, vl); } static char *dsref(char *str) { dstr *ds = dstr_of(str); ds->refs++; return str; } static void dsdrop(char *str) { if (str) { dstr *ds = dstr_of(str); assert (ds->refs > 0); if (!--ds->refs) free(ds); } } static size_t dslen(const char *str) { if (str) { const dstr *ds = dstr_of(str); return ds->len; } return 0; } static char *dsgrow(char *str, size_t len) { dstr *ds = str ? dstr_of(str) : 0; int flags = str ? ds->flags : 0; size_t size = sizeof *ds + len + 1; assert (ds == 0 || ds->refs == 1); if (size < len) panic("string size overflow"); ds = realloc(ds, size); if (ds == 0) panic("out of memory"); ds->refs = 1; ds->flags = flags; ds->len = len; ds->str[len] = 0; return ds->str; } static char *dsensure(char *str) { if (str) return str; return dsgrow(0, 0); } static char *dsdup(const char *str) { size_t len = strlen(str); char *copy = dsgrow(0, len); memcpy(copy, str, len); return copy; } printf_attr(1, 2) static char *dsdupf(char *fmt, ...) { size_t len = 256, needed; char *out = dsgrow(0, len); for (;;) { va_list vl; va_start (vl, fmt); needed = vsnprintf(out, len + 1, fmt, vl); va_end (vl); if (needed <= len) break; len = needed; } return dsgrow(out, needed); } static char *addch(char *line, int ch) { size_t len = line ? dslen(line) : 0; if (len + 1 > len) { char *nline = dsgrow(line, len + 1); if (nline == 0) panic("out of memory"); nline[len] = ch; return nline; } panic("line overflow"); abort(); } static char *addchesc(char *line, int tstop, int ch) { switch (ch) { case DEL: line = addch(line, '^'); line = addch(line, '?'); break; case TAB: for (size_t len = dslen(line); line = addch(line, ' '), ++len % tstop != 0;) /* empty */; break; default: if (ch < 32) { line = addch(line, '^'); line = addch(line, ch + 64); break; } line = addch(line, ch); break; } return line; } static char *getln(FILE *stream) { for (char *line = 0;;) { int ch = getc(stream); if (ch == EOF) return line; if (ch == '\n') return dsensure(line); line = addch(line, ch); } } static void usage(void) { fprintf(stderr, "\nThis is pw version %s.\n\n" "Usage: | %s [options]\n\n" "-i realnum poll interval (s)\n" "-l realnum long update interval (s)\n" "-n integer display size (# of lines)\n" "-d do not quit on end-of-input\n" "-q integer Require this many repetitions of q to quit\n" "-E treat regular expressions as extended\n" "-B treat regular expressions as basic (default)\n" "-g [!]pattern add pattern to grep stack; ! inverts\n" "-m integer specify maximum line length\n" "-p values set display parameters\n" "-e command execute : / ? command\n" "-f file execute : / ? commands from file\n\n" " represents an arbitrary command that generates the\n" "output to be monitored by %s.\n\n" "Standard input must be redirected; it cannot be the same device\n" "as the controlling tty (/dev/tty) of the terminal session.\n\n" "For a full description, see the manual page.\n\n", CONFIG_PW_VER, pw_name, pw_name); exit(EXIT_FAILURE); } static int grinit(grep *gr, char *pat, int inv, void (*dtor)(char *)) { gr->pat = pat; gr->inv = inv; gr->dtor = dtor; gr->flags = regex_flags; return (gr->err = regcomp(&gr->rx, pat, regex_flags | REG_NOSUB)) != 0; } static void grclean(grep *gr) { if (gr->err == 0) regfree(&gr->rx); if (gr->dtor) gr->dtor(gr->pat); memset(gr, 0, sizeof *gr); } static int grerr(grep *gr) { return gr->err; } static size_t grerrstr(grep *gr, char *buf, size_t size) { return regerror(gr->err, &gr->rx, buf, size); } static grep *grnew(char *pat, int inv, void (*dtor)(char *)) { grep *gr = calloc(sizeof *gr, 1); if (gr == 0) panic("out of memory"); (void) grinit(gr, pat, inv, dtor); return gr; } static void grfree(grep *gr) { if (gr != 0) { grclean(gr); free(gr); } } static int grexec(grep *gr, const char *line) { int match = regexec(&gr->rx, line, 0, NULL, 0) == 0; return match != gr->inv; } static void clrline(unsigned stat) { if ((stat & stat_bkgnd) == 0) printf("\r\033[J"); } static void clreol(int nl) { printf("\033[K"); if (nl) putchar('\n'); } static void hlon(int *phlite) { if (!*phlite) { printf("\033[7m"); *phlite = 1; } } static void hloff(int *phlite) { if (*phlite) { printf("\033[m"); *phlite = 0; } } #define with_hl(on, keep, hlite, expr) \ do { \ if (on) \ hlon(&hlite); \ expr; \ if (!keep) \ hloff(&hlite); \ } while (0) #define with_hlonoff(onoff, hlite, expr) \ do { \ (onoff ? hlon : hloff)(&hlite); \ expr; \ } while(0) static int diff(const char *line, const char *dline, int dlen, int i) { return i >= dlen || line[i] != dline[i]; } static void drawline(pwstate *pw, const char *line, int lineno, char *dline) { const dstr *ds = dstr_of(line); int olen = (int) ds->len, len = olen, pos = 0; int dlen = dslen(dline); int columns = pw->columns; int vsplit1 = pw->vsplit1; int vsplit2 = pw->vsplit2; int vs2pos = pw->vs2pos; int endmark = 0; int hlpanel = (pw->stat & stat_hlite) != 0; int hlineno = hlpanel && (ds->flags & dstr_cmark); int hldiff = (pw->stat & stat_diff) != 0 && dline != 0; int hlite = 0; if (lineno >= 0) with_hl (hlineno, 0, hlite, columns -= printf("%3d ", lineno)); if (vsplit1 > 0) { if (len <= vsplit1) { if (!vsplit2) { if (!hldiff) fputs(line, stdout); else for (int i = 0; i < len; i++) with_hlonoff (diff(line, dline, dlen, i), hlite, putchar(line[i])); columns -= len; } else { int spaces = vsplit1 - len; if (!hldiff) fputs(line, stdout); else for (int i = 0; i < len; i++) with_hlonoff (diff(line, dline, dlen, i), hlite, putchar(line[i])); if (spaces) hloff(&hlite); for (int i = 0; i < spaces; i++) putchar(' '); columns -= vsplit1; } pos += len; len = 0; } else { if (!hldiff) for (int i = 0; i < vsplit1; i++) putchar(line[i]); else for (int i = 0; i < vsplit1; i++) with_hlonoff (diff(line, dline, dlen, i), hlite, putchar(line[i])); len -= vsplit1; pos += vsplit1; columns -= vsplit1; endmark = 1; } } if (vsplit2 > 0) { int width = vsplit2; int i = 0; if (vsplit1 || vs2pos) { with_hl (hlpanel, hldiff, hlite, putchar('|')); i++; } if (vs2pos < olen) { int nchar = min(olen - vs2pos, width); if (!hldiff) for (; i < nchar; i++) putchar(line[vs2pos + i]); else for (; i < nchar; i++) with_hlonoff (diff(line, dline, dlen, vs2pos + i), hlite, putchar(line[vs2pos + i])); endmark = 1; } if (len > vsplit2 + pw->hpos) { if (i < width) hloff(&hlite); for (; i < width; i++) putchar(' '); columns -= vsplit2; pos += vsplit2; len -= vsplit2; endmark = 1; } else { pos += len; len = 0; endmark = (i == width); } } if (pw->hpos < len) { if (pw->hpos || vsplit1 || vsplit2) { pos += pw->hpos + 1; len -= pw->hpos + 1; with_hl (hlpanel, hldiff, hlite, putchar('>')); columns--; } if (len < columns) { if (!hldiff) fputs(line + pos, stdout); else for (; pos < olen; pos++) with_hlonoff (diff(line, dline, dlen, pos), hlite, putchar(line[pos])); clreol(1); } else { if (!hldiff) for (int i = 0; i < columns - 1; i++) putchar(line[pos + i]); else for (int i = 0; i < columns - 1; i++) with_hlonoff (diff(line, dline, dlen, pos + i), hlite, putchar(line[pos + i])); with_hl (hlpanel, hldiff, hlite, putchar('<')); putchar('\n'); } } else { if (endmark) with_hl (hlpanel, hldiff, hlite, putchar('>')); clreol(1); } hloff(&hlite); } static void drawstatus(pwstate *pw) { char status[cmdsize] = "", *ptr = status; size_t lim = sizeof status; if ((pw->stat & stat_bkgnd)) return; if (pw->columns - 1 < (int) lim) lim = pw->columns - 1; char *end = ptr + lim; if (pw->curcmd) { snprintf(status, lim, "%s", pw->curcmd); } else if (pw->hist > 0 || (pw->stat & (stat_eof | stat_susp | stat_htmode | stat_ttmode | stat_grep))) { if (pw->hist > 0) ptr += snprintf(ptr, end - ptr, "HIST%u ", pw->hist); if ((pw->stat & stat_eof)) ptr += snprintf(ptr, end - ptr, "EOF "); if ((pw->stat & stat_grep)) { ptr += snprintf(ptr, end - ptr, "GREP ("); for (int i = 0; i < ngrep; i++) { grep *gr = &grepstack[i]; ptr += snprintf(ptr, end - ptr, "%s%s%c ", gr->inv ? "!" : "", gr->pat, (i < ngrep - 1) ? ',' : ')'); } } if ((pw->stat & (stat_htmode | stat_ttmode))) { ptr += snprintf(ptr, end - ptr, "TRIG%c (", (pw->stat & stat_htmode) ? '/' : '?'); for (int i = 0, first = 1; i < maxtrig; i++) { grep *gr = triglist[i]; if (gr) { if (!first) ptr += snprintf(ptr, end - ptr, ", "); if (i > 0) ptr += snprintf(ptr, end - ptr, "[%d]", i + 1); ptr += snprintf(ptr, end - ptr, "%s%s", gr->inv ? "!" : "", gr->pat); first = 0; } } ptr += snprintf(ptr, end - ptr, ") "); } if ((pw->stat & stat_susp)) ptr += snprintf(ptr, end - ptr, "SUSPENDED "); } fputs(status, stdout); clreol(0); if (pw->curcmd) { if (pw->curcmd[pw->editpos] != 0) { putchar('\r'); if (pw->editpos > 0) printf("\033[%dC", (int) pw->editpos); } } fflush(stdout); } static void freebuf(char **buf, size_t size) { if (buf != 0) for (size_t i = 0; i < size; i++) dsdrop(buf[i]); } static void redraw(pwstate *pw) { int updln = 0; if ((pw->stat & (stat_dirty | stat_susp)) == stat_dirty && (pw->stat & (stat_htmode | stat_trgrd)) != stat_htmode && (pw->stat & (stat_ttmode | stat_trgrd)) != stat_ttmode) { if (snapshot[snhistsize - 1]) { freebuf(snapshot[snhistsize - 1], snaplines[snhistsize - 1]); free(snapshot[snhistsize - 1]); } memmove(snapshot + 1, snapshot, sizeof *snapshot * (snhistsize - 1)); memmove(snaplines + 1, snaplines, sizeof *snaplines * (snhistsize - 1)); snapshot[0] = calloc(sizeof *snapshot[0], pw->nlines); snaplines[0] = pw->nlines; updln = 1; for (int i = 0; i < pw->nlines; i++) snapshot[0][i] = dsref(pw->circbuf[i]); if ((pw->stat & stat_oneshot)) pw->stat |= stat_susp; pw->stat &= ~(stat_dirty | stat_trgrd | stat_oneshot); updln = 1; if (pw->prevhist == 0 && pw->hist == 0) pw->prevhist = 1; } else if ((pw->stat & stat_force)) { pw->stat &= ~stat_force; updln = 1; } if ((pw->stat & stat_bkgnd)) return; if (updln && snaplines[pw->hist] > 0) { printf("\r\033[%dA", snaplines[pw->hist]); if ((pw->stat & stat_lino) == 0) { for (int i = 0; i < snaplines[pw->hist]; i++) drawline(pw, snapshot[pw->hist][i], -1, snapshot[pw->prevhist][i]); } else { int start = 1, step = 1; if (pw->stat & stat_htmode) { start = snaplines[pw->hist]; step = -1; } for (int i = 0, l = start; i < snaplines[pw->hist]; i++, l += step) drawline(pw, snapshot[pw->hist][i], l, snapshot[pw->prevhist][i]); } } else { clrline(pw->stat); } drawstatus(pw); } static int getznn(const char *str, char **err) { char *endp; long val = strtol(str, &endp, 10); if (endp == str) { *err = dsdup("number expected"); return -1; } if (val < 0) { *err = dsdup("non-negative value required"); return -1; } if (val >= INT_MAX) { *err = dsdup("unreasonably large value"); return -1; } return val; } static int getzp(const char *str, char **err) { int val = getznn(str, err); if (val <= 0) { *err = dsdup("positive value required"); return -1; } return val; } static int getms(const char *str, char **err) { errno = 0; char *endp; double sec = strtod(str, &endp); if (endp == str) { *err = dsdup("number expected"); return -1; } if ((sec == 0 || sec == HUGE_VAL) && errno != 0) { *err = dsdup("unreasonable real value"); return -1; } if (sec < 0) { *err = dsdup("positive value required"); return -1; } double msec = sec * 1000; if (msec > (double) INT_MAX) { *err = dsdupf("maximum interval is %f", INT_MAX / 1000.0); return -1; } return msec; } static int decodeparms(pwstate *pw, char *parms, char *resbuf, size_t size) { char *err; char *hpos = strtok(parms, ", \t"); char *lpane = strtok(0, ", \t"); char *rpane = strtok(0, ", \t"); char *vs2pos = strtok(0, ", \t"); char *flags = strtok(0, ", \t"); char *tstop = strtok(0, ", \t"); pw->hpos = pw->vsplit1 = pw->vsplit2 = pw->vs2pos = 0; pw->stat &= ~stat_save; if (hpos && (pw->hpos = getznn(hpos, &err)) < 0) { snprintf(resbuf, size, "bad horizontal scroll offset %s: %s\n", hpos, err); return 0; } if (lpane && (pw->vsplit1 = getznn(lpane, &err)) < 0) { snprintf(resbuf, size, "bad left pane width %s: %s\n", lpane, err); return 0; } if (rpane && (pw->vsplit2 = getznn(rpane, &err)) < 0) { snprintf(resbuf, size, "bad right pane width %s: %s\n", rpane, err); return 0; } if (vs2pos && (pw->vs2pos = getznn(vs2pos, &err)) < 0) { snprintf(resbuf, size, "bad right pane view offset %s: %s\n", vs2pos, err); return 0; } if (flags) { int stat = getznn(flags, &err); if (stat < 0) { snprintf(resbuf, size, "bad flags %s: %s\n", flags, err); return 0; } pw->stat |= (stat & stat_save); } if (tstop && ((pw->tstop = getznn(tstop, &err)) < 0 || pw->tstop > 16)) { snprintf(resbuf, size, "bad tab stop size%s: %s\n", tstop, err); if (pw->tstop == 0) pw->tstop = 8; return 0; } return 1; } static execode execute(pwstate *pw, char *cmd, char *resbuf, size_t size, int count) { execode res = exec_failed; char cmdbuf[16]; int cmdlen = 0; int ntok = sscanf(cmd + 1, "%15[a-zA-Z]%n", cmdbuf, &cmdlen); char *arg = cmd + 1 + cmdlen; if (*arg == ' ') arg++; if (cmd[0] == ':' && ntok == 1 && cmdlen == 1) switch (cmd[1]) { case 'w': case 'a': if (arg[0] == 0) { snprintf(resbuf, size, "file name required!"); break; } else { FILE *f = fopen(arg, cmd[1] == 'w' ? "w" : "a"); if (!f) { snprintf(resbuf, size, "unable to open file"); break; } res = exec_msg; for (int i = 0; res == exec_msg && i < snaplines[pw->hist]; i++) if (fprintf(f, "%s\n", snapshot[pw->hist][i]) < 0) { snprintf(resbuf, size, "write error!"); res = exec_failed; break; } fclose(f); if (res == exec_msg) snprintf(resbuf, size, "saved!"); } break; case '!': if (arg[0] == 0) { snprintf(resbuf, size, "command required!"); break; } else { FILE *p = popen(arg, "w"); if (!p) { snprintf(resbuf, size, "unable to open command"); break; } res = exec_msg; for (int i = 0; i < snaplines[pw->hist] && res == exec_msg; i++) if (fprintf(p, "%s\n", snapshot[pw->hist][i]) < 0) { snprintf(resbuf, size, "write error!"); res = exec_failed; break; } pclose(p); if (res == exec_msg) snprintf(resbuf, size, "piped!"); } break; case 'g': case 'v': { grep *gr = &grepstack[ngrep]; if (arg[0] == 0) { snprintf(resbuf, size, "pattern required!"); break; } if (ngrep >= maxgrep) { snprintf(resbuf, size, "too many greps"); break; } if ((grinit(gr, dsdup(arg), cmd[1] == 'v', dsdrop)) != 0) { grerrstr(gr, resbuf, size); grclean(gr); break; } if (ngrep++ == 0) pw->stat |= stat_grep; res = exec_ok; } break; case 'r': while (ngrep > 0) { grclean(&grepstack[--ngrep]); if (cmd[2] != '!') break; } if (ngrep == 0) pw->stat &= ~stat_grep; res = exec_ok; break; case 'i': case 'l': { char *err = 0; int interval = getms(arg, &err); if (interval < 0) { snprintf(resbuf, size, "%s", err); break; } if (cmd[1] == 'i') poll_interval = interval; else long_interval = interval; dsdrop(err); res = exec_ok; } break; case 'E': regex_flags = REG_EXTENDED; res = exec_ok; break; case 'B': regex_flags = 0; res = exec_ok; break; case 'c': if (arg[0] == 0) { pw->sncount = 0; } else { char *err = 0; int val = getznn(arg, &err); if (val < 0) { snprintf(resbuf, size, "bad trigger count: %s", err); break; } sncount = val; dsdrop(err); res = exec_ok; } break; case 'f': if (arg[0] == 0) { snprintf(resbuf, size, "frequency argument required!"); break; } { char *err = 0; int val = getznn(arg, &err); if (val < 0) { snprintf(resbuf, size, "bad trigger freq: %s", err); break; } tfreq = val; dsdrop(err); res = exec_ok; } break; case 'p': { if (decodeparms(pw, arg, resbuf, size)) { pw->stat |= stat_force; res = exec_ok; } } break; default: goto badcmd; } else if (cmd[0] == ':' && ntok == 1) switch (twocc(cmd[1], cmd[2])) { case twocc('s', 'a'): { int rflg = 0; FILE *f; if (arg[0] == 0) { snprintf(resbuf, size, "file name required!"); break; } if ((f = fopen(arg, "w")) == 0) { snprintf(resbuf, size, "unable to open %s", arg); break; } fprintf(f, ":p%d,%d,%d,%d,%d,%d\n", pw->hpos, pw->vsplit1, pw->vsplit2, pw->vs2pos, (int) pw->stat & stat_save, pw->tstop); if (tfreq) fprintf(f, ":f%d\n", tfreq); if (sncount) fprintf(f, ":c%d\n", sncount); for (int i = 0; i < ngrep; i++) { grep *gr = &grepstack[i]; if (gr->flags != rflg) { rflg = gr->flags; fputs(((gr->flags & REG_EXTENDED)) ? ":E\n" : ":B\n", f); } fputs(gr->inv ? ":v " : ":g ", f); fputs(gr->pat, f); putc('\n', f); } if ((pw->stat & (stat_htmode | stat_ttmode))) { int tch = ((pw->stat & stat_htmode)) ? '/' : '?'; for (int i = 0; i < maxtrig; i++) { grep *gr = triglist[i]; if (gr != 0) { if (gr->flags != rflg) { rflg = gr->flags; fputs(((gr->flags & REG_EXTENDED)) ? ":E\n" : ":B\n", f); } if (gr->inv) fprintf(f, "%d%c!%s\n", i + 1, tch, gr->pat); else if (gr->pat[0] == '!') fprintf(f, "%d%c\\!%s\n", i + 1, tch, gr->pat); else fprintf(f, "%d%c%s\n", i + 1, tch, gr->pat); } } } if (ferror(f)) { snprintf(resbuf, size, "write error!"); } else { snprintf(resbuf, size, "config saved!"); res = exec_msg; } fclose(f); } break; default: goto badcmd; } else if (cmd[0] == '?' || cmd[0] == '/') { int trig = count > 0 ? count - 1 : count; if (trig < pw->maxlines && trig < maxtrig) { const char *rx = cmd + 1; int inv = 0; grep *gr = 0; if (strncmp(rx, "\\!", 2) == 0) { rx++; } else if (rx[0] == '!') { rx++; inv = 1; } char *pat = dsdup(rx); if (*pat && (gr = grnew(dsref(pat), inv, dsdrop), grerr(gr) != 0)) { grerrstr(gr, resbuf, size); grfree(gr); } else { if ((cmd[0] == '/' && (pw->stat & stat_ttmode)) || (cmd[0] == '?' && (pw->stat & stat_htmode))) { for (int i = 0; i < maxtrig; i++) { grfree(triglist[i]); triglist[i] = 0; } } grfree(triglist[trig]); triglist[trig] = gr; res = exec_ok; } dsdrop(pat); } else { snprintf(resbuf, size, "trigger position out of range"); res = exec_failed; } if (res == exec_ok) { pw->stat &= ~(stat_htmode | stat_ttmode); for (int i = 0; i < maxtrig; i++) { if (triglist[i]) { pw->stat |= (cmd[0] == '/' ? stat_htmode : stat_ttmode); break; } } } } else if (cmd[1] == 0) { res = exec_ok; } else { badcmd: snprintf(resbuf, size, "unrecognized command"); } return res; } static execode batchexe(pwstate *pw, char *cmd, char *resbuf, size_t size) { size_t ndigits = strspn(cmd, "0123456789"); int count = 0; if (ndigits > 3) { snprintf(resbuf, size, "command count out of 0-999 range"); return exec_failed; } else if (ndigits) { (void) sscanf(cmd, "%3d", &count); cmd += ndigits; } switch (cmd[0]) { case ':': case '?': case '/': return execute(pw, cmd, resbuf, size, count); } snprintf(resbuf, size, "missing command prefix [:/?]"); return exec_failed; } static void ttyset(int fd, struct termios *tty) { if (tcsetattr(fd, TCSANOW, tty) < 0) panic("unable to set TTY parameters"); } static void ttyget(int fd, struct termios *tty) { if (tcgetattr(fd, tty) < 0) panic("unable to get TTY parameters"); } #ifdef SIGWINCH static void sigwinch(int sig) { (void) sig; winch = 1; } #endif static char **resizebuf(char **buf, size_t nlfrom, size_t nlto) { if (nlfrom > nlto) { for (size_t i = nlto; i < nlfrom; i++) dsdrop(buf[i]); } else if (nlfrom < nlto) { if ((buf = realloc(buf, sizeof *buf * nlto)) == 0) panic("out of memory"); memset(buf + nlfrom, 0, (nlto - nlfrom) * sizeof *buf); } return buf; } int isbkgnd(int ttyfd) { pid_t grp = getpgrp(); pid_t fgrp = tcgetpgrp(ttyfd); return (grp != fgrp); } int ismytty(int ttyfd) { pid_t fgrp = tcgetpgrp(ttyfd); return fgrp != -1; } void clipsplits(pwstate *pw) { int columns = pw->columns; if ((pw->stat & stat_lino)) columns -= 4; if (columns < 8 || (int) pw->vsplit1 > columns - 2) { pw->vsplit2 = 0; pw->vsplit1 = columns - 2; } else if ((int) (pw->vsplit1 + pw->vsplit2) >= columns - 2) { pw->vsplit2 = columns - 2 - pw->vsplit1; } } int main(int argc, char **argv) { struct pwstate pw = { .columns = 80, .maxlines = 15, .tstop = 8 }; char *line = 0; FILE *tty = fopen("/dev/tty", "r+"); int maxed = 0; size_t maxlen = 2047; int opt; int ifd = fileno(stdin); int ofd = fileno(stdout); int ttyfd = tty ? fileno(tty) : -1; struct termios tty_saved, tty_new; struct winsize ws = { 0 }; enum kbd_state { kbd_cmd, kbd_esc, kbd_bkt, kbd_exit, kbd_lcmd, kbd_result }; int auto_quit = 1; int quit_count = 1, quit_cntdwn = quit_count; int exit_status = EXIT_FAILURE; FILE *out = NULL; #ifdef SIGWINCH static struct sigaction sa; #endif pw_name = argv[0] ? argv[0] : "pw"; if (ifd < 0) panic("unable to obtain input file descriptor\n"); if (ttyfd < 0) panic("unable to open /dev/tty\n"); { pid_t igrp = tcgetpgrp(ifd); pid_t tgrp = tcgetpgrp(ttyfd); if (igrp == tgrp) { error("standard input cannot be the TTY used for display\n"); usage(); } } if (!isatty(ofd) || !ismytty(ttyfd)) { int dup_ofd = dup(ofd); dup2(ttyfd, ofd); out = fdopen(dup_ofd, "w"); } while ((opt = getopt(argc, argv, "n:i:l:dEBg:q:m:p:e:f:")) != -1) { switch (opt) { case 'n': { char *err; if ((pw.maxlines = getzp(optarg, &err)) < 0) { error("-%c option: %s\n", opt, err); return EXIT_FAILURE; } } break; case 'i': case 'l': { char *err; int interval = getms(optarg, &err); if (interval < 0) { error("-%c option: %s\n", opt, err); return EXIT_FAILURE; } if (opt == 'i') poll_interval = interval; else long_interval = interval; } break; case 'd': auto_quit = 0; break; case 'q': { char *err; if ((quit_cntdwn = quit_count = getzp(optarg, &err)) < 0) { error("-%c option: %s\n", opt, err); return EXIT_FAILURE; } break; } case 'E': regex_flags = REG_EXTENDED; break; case 'B': regex_flags = 0; break; case 'g': { grep *gr = &grepstack[ngrep]; char *pat = optarg; int inv = 0; if (ngrep >= maxgrep) { error("too many patterns specified with -g\n"); return EXIT_FAILURE; } if (*pat == '!') { inv = 1; pat++; } else if (strncmp(pat, "\\!", 2) == 0) { pat++; } if ((grinit(gr, dsdup(pat), inv, dsdrop)) != 0) { char grmsg[cmdsize]; grerrstr(gr, grmsg, sizeof grmsg); error("-%c option: bad pattern %s: %s\n", opt, pat, grmsg); return EXIT_FAILURE; } ngrep++; pw.stat |= stat_grep; } break; case 'm': { char *err; int val = getzp(optarg, &err); if (val < 0) { error("-%c option: bad value %s: %s\n", opt, optarg, err); return EXIT_FAILURE; } if ((int) (size_t) val != val) val = -1; maxlen = max(72, val); } break; case 'p': { int ok = decodeparms(&pw, optarg, pw.cmdbuf, cmdsize); if (!ok) { error("-%c option: %s\n", opt, pw.cmdbuf); return EXIT_FAILURE; } } break; case 'e': { if (batchexe(&pw, optarg, pw.cmdbuf, cmdsize) == exec_failed) { error("-%c option: %s: %s\n", opt, optarg, pw.cmdbuf); return EXIT_FAILURE; } } break; case 'f': { FILE *f = fopen(optarg, "r"); long lino = 1; int errors = 0; char *line; if (f == 0) { error("-%c option: unable to open %s\n", opt, optarg); return EXIT_FAILURE; } for (; (line = getln(f)) != 0; lino++) { if (line[0] == '#') continue; if (batchexe(&pw, line, pw.cmdbuf, cmdsize) == exec_failed) { error("%s:%ld: %s\n", optarg, lino, pw.cmdbuf); errors = 1; } dsdrop(line); } fclose(f); if (errors) { return EXIT_FAILURE; dsdrop(line); } } break; default: usage(); } } if (pw.maxlines <= 0 || pw.maxlines > 1000) { error("%d is an unreasonable number of lines to display\n", pw.maxlines); return EXIT_FAILURE; } if ((pw.circbuf = calloc(sizeof *pw.circbuf, pw.maxlines)) == 0) panic("out of memory"); if ((snapshot[0] = calloc(sizeof *snapshot[0], pw.maxlines)) == 0) panic("out of memory"); if (ioctl(ttyfd, TIOCGWINSZ, &ws) == 0 && ws.ws_row != 0) { if (pw.maxlines >= ws.ws_row) { pw.maxlines = ws.ws_row - 1; maxed = 1; } pw.columns = ws.ws_col; } clipsplits(&pw); ttyget(ttyfd, &tty_saved); tty_new = tty_saved; tty_new.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); tty_new.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); tty_new.c_cc[VMIN] = 1; tty_new.c_cc[VTIME] = 0; setvbuf(tty, NULL, _IONBF, 0); if (fcntl(ifd, F_SETFL, O_NONBLOCK) < 0) panic("unable to set stdin nonblocking"); if (!isbkgnd(ttyfd)) ttyset(ttyfd, &tty_new); else pw.stat = stat_bkgnd; #ifdef SIGWINCH sa.sa_handler = sigwinch; sigaction(SIGWINCH, &sa, NULL); #endif for (int kbd_state = kbd_cmd, kbd_prev = kbd_cmd, lasttime = -1, workbout = workmax, work = workbout, histpos = 0, cmdcount = INT_MAX, prevcmd = 0, prevcount = 0; kbd_state != kbd_exit ;) { int force = 0, nfds = 2, pollms = poll_interval; struct pollfd pe[2] = { { .fd = ttyfd, .events = POLLIN | POLLHUP | POLLERR }, { .fd = ifd, .events = POLLIN | POLLHUP | POLLERR }, }; if ((pw.stat & stat_eof) == 0) { int ch; while ((ch = getchar()) != EOF && ch != '\n' && dslen(line) < maxlen) { line = addchesc(line, pw.tstop, ch); if (out) putc(ch, out); } if (ch == EOF) { if (out) fflush(out); if (feof(stdin) || (errno != EAGAIN && errno != EWOULDBLOCK)) { nfds = 1; if (!ferror(stdin)) exit_status = 0; if (auto_quit) { if ((pw.stat & stat_bkgnd) == 0) clrline(pw.stat); break; } pw.stat |= stat_eof; clrline(pw.stat); drawstatus(&pw); if (out) fflush(out); } clearerr(stdin); } else if (ch == '\n') { if (out) putc(ch, out); nfds = 1; line = dsensure(line); if ((pw.stat & stat_grep)) { int i; for (i = 0; i < ngrep; i++) if (!grexec(&grepstack[i], line)) break; if (i < ngrep) { dsdrop(line); line = 0; } } if (line) { if (pw.nlines == pw.maxlines) { int trig = 0; dsdrop(pw.circbuf[0]); memmove(pw.circbuf, pw.circbuf + 1, (pw.nlines - 1) * sizeof *pw.circbuf); pw.circbuf[pw.nlines - 1] = line; pw.stat |= stat_dirty; if ((pw.stat & (stat_ttmode | stat_susp)) == stat_ttmode) { int lim = min(maxtrig, pw.nlines); int match = 1; for (int i = 0; i < lim; i++) { grep *gr = triglist[i]; if (gr) { if (!grexec(gr, pw.circbuf[i])) { match = 0; break; } dstr_of(pw.circbuf[i])->flags |= dstr_tmark; } } trig = match; } else if ((pw.stat & (stat_htmode | stat_susp)) == stat_htmode) { int match = 1; for (int j = pw.nlines - 1, i = 0; j >= 0 && i < maxtrig; j--, i++) { grep *gr = triglist[i]; if (gr) { if (!grexec(gr, pw.circbuf[j])) { match = 0; break; } dstr_of(pw.circbuf[j])->flags |= dstr_tmark; } } trig = match; } if (trig) { trig = 0; if (tfreq == 0 || ++pw.tcount >= tfreq) { pw.tcount = 0; if (sncount == 0 || ++pw.sncount < sncount) { pw.stat |= stat_trgrd; trig = 1; } else if (sncount) { pw.stat |= stat_trgrd | stat_oneshot; pw.sncount = 0; trig = 1; } } } for (int i = 0; i < pw.nlines; i++) { dstr *ds = dstr_of(pw.circbuf[i]); int flags = ds->flags; int addflag = trig ? dstr_cmark : 0; if ((flags & dstr_tmark) != 0) { flags |= addflag; flags &= ~dstr_tmark; ds->flags = flags; } } } else { pw.circbuf[pw.nlines++] = line; if ((pw.stat & (stat_susp | stat_bkgnd)) == 0) { snapshot[0] = resizebuf(snapshot[0], snaplines[0], snaplines[0] + 1); snapshot[0][snaplines[0]++] = dsref(line); clrline(pw.stat); drawline(&pw, line, (pw.stat & stat_lino) ? 0: -1, 0); drawstatus(&pw); } } line = 0; } } } else { nfds = 1; } if (winch) { winch = 0; if (ioctl(ttyfd, TIOCGWINSZ, &ws) == 0) { int oldmax = pw.maxlines; if (maxed || pw.maxlines >= ws.ws_row - 1) { maxed = 1; pw.maxlines = ws.ws_row - 1; } if (maxed) { pw.prevhist = pw.hist = 0; pw.circbuf = resizebuf(pw.circbuf, oldmax, pw.maxlines); snapshot[0] = resizebuf(snapshot[0], oldmax, pw.maxlines); if (pw.nlines > pw.maxlines) pw.nlines = pw.maxlines; if (snaplines[0] > pw.maxlines) snaplines[0] = pw.maxlines; for (int i = 1; i < snhistsize; i++) { freebuf(snapshot[i], snaplines[i]); free(snapshot[i]); snapshot[i] = 0; } } pw.columns = ws.ws_col; clipsplits(&pw); } pw.stat |= stat_force; force = 1; } if ((pw.stat & stat_eof)) pollms = -1; else if (nfds < 2) pollms = 0; if ((pw.stat & (stat_trgrd | stat_susp)) == stat_trgrd) force = 1; if (pollms == 0 && !force && work-- > 0) continue; work = workbout; if ((pw.stat & stat_bkgnd)) { if (!isbkgnd(ttyfd)) { pw.stat &= ~stat_bkgnd; ttyset(ttyfd, &tty_new); for (int i = 0; i < pw.nlines; i++) puts(""); pw.stat |= stat_force; redraw(&pw); } else { if ((pw.stat & stat_eof)) { sleep(1); continue; } else { pe[0].events = 0; } } } if ((pw.stat & stat_susp) == 0) { if (!force) { struct timeval tv; int now; gettimeofday(&tv, NULL); now = tv.tv_sec % 1000000 * 1000 + tv.tv_usec / 1000; if (lasttime == -1 || (now + 1000000000 - lasttime) % 1000000000 > long_interval) { if ((pw.stat & stat_dirty) && pw.nlines == pw.maxlines) force = 1; lasttime = now; } } if (force) redraw(&pw); } if (poll(pe, nfds, pollms) <= 0) { if (pollms) { if ((pw.stat & stat_dirty) && pw.nlines == pw.maxlines) redraw(&pw); if (kbd_state == kbd_esc) { kbd_state = kbd_cmd; pw.curcmd = 0; clrline(pw.stat); drawstatus(&pw); } } if (workbout < workmax) work = workbout += workbout / 4; } else { if ((pe[0].revents)) { int ch = getc(tty); if (workbout > 16) work = workbout /= 2; if (ch == ctrl('z')) { ttyset(ttyfd, &tty_saved); pw.stat |= stat_bkgnd; kill(0, SIGTSTP); continue; } fakecmd: switch (kbd_state) { case kbd_result: kbd_state = kbd_cmd; pw.stat |= stat_force; pw.curcmd = 0; if (ch == CR) // Prevent accidental resume of suspended mode. break; // fallthrough case kbd_cmd: if (ch != 'q' && ch != 3) quit_cntdwn = quit_count; switch (ch) { case 'q': case 3: if (--quit_cntdwn == 0) { kbd_state = kbd_exit; } else { pw.editpos = sprintf(pw.cmdbuf, "%d more to quit", quit_cntdwn); pw.curcmd = pw.cmdbuf; kbd_state = kbd_result; } break; case 'h': if (cmdcount == INT_MAX) cmdcount = 8; if (pw.hpos >= cmdcount) pw.hpos -= cmdcount; else pw.hpos = 0; pw.stat |= stat_force; break; case 'l': if (cmdcount == INT_MAX) cmdcount = 8; if ((size_t) pw.hpos < maxlen) pw.hpos += cmdcount; pw.stat |= stat_force; break; case '>': if (cmdcount == INT_MAX) cmdcount = 1; pw.vsplit1 += cmdcount; clipsplits(&pw); pw.stat |= stat_force; break; case '<': if (cmdcount == INT_MAX) cmdcount = 1; pw.vsplit1 = max(0, pw.vsplit1 - cmdcount); pw.stat |= stat_force; break; case ']': if (cmdcount == INT_MAX) cmdcount = 1; if (pw.vsplit2 == 0) pw.vs2pos = pw.hpos + pw.vsplit1; pw.vsplit2 += cmdcount; clipsplits(&pw); pw.stat |= stat_force; break; case '[': if (cmdcount == INT_MAX) cmdcount = 1; pw.vsplit2 = max(0, pw.vsplit2 - cmdcount); pw.stat |= stat_force; break; case '}': if (cmdcount == INT_MAX) cmdcount = 1; pw.vs2pos = min((int) maxlen, pw.vs2pos + cmdcount); pw.stat |= stat_force; break; case '{': if (cmdcount == INT_MAX) cmdcount = 1; pw.vs2pos = max(0, pw.vs2pos - cmdcount); pw.stat |= stat_force; break; case ctrl('i'): pw.stat ^= stat_hlite; pw.stat |= stat_force; break; case ctrl('d'): pw.stat ^= stat_diff; pw.stat |= stat_force; break; case 'j': if (pw.hist > 0) { pw.prevhist = pw.hist--; pw.stat |= stat_force; } break; case 'k': if (pw.hist < snhistsize - 1 && snapshot[pw.hist + 1]) { pw.prevhist = pw.hist++; pw.stat |= stat_force; } break; case ' ': if ((pw.stat & stat_eof) == 0) pw.stat |= stat_susp; break; case 't': if (cmdcount < 1 || cmdcount > 16) cmdcount = 8; pw.tstop = cmdcount; pw.stat |= stat_force; break; case CR: pw.stat &= ~stat_susp; break; case ESC: kbd_prev = kbd_state; kbd_state = kbd_esc; break; case ':': case '/': case '?': kbd_state = kbd_lcmd; histpos = 0; pw.editpos = 1; pw.cmdbuf[0] = ch; pw.cmdbuf[1] = 0; pw.curcmd = pw.cmdbuf; break; case 'a': case 'd': if ((pw.stat & (stat_htmode | stat_ttmode))) { int step = ((((pw.stat & stat_htmode) && ch == 'a') || ((pw.stat & stat_ttmode) && ch == 'd')) ? -1 : 1); if (cmdcount == INT_MAX) cmdcount = 1; if (step < 0) { for (; cmdcount && !triglist[0]; cmdcount --) { memmove(triglist, triglist + 1, (maxtrig - 1) * sizeof *triglist); triglist[maxtrig - 1] = 0; } } else if (pw.nlines <= maxtrig) { for (; (cmdcount && !triglist[pw.nlines - 1] && !triglist[maxtrig - 1]); cmdcount --) { memmove(triglist + 1, triglist, (maxtrig - 1) * sizeof *triglist); triglist[0] = 0; } } } break; case '+': if (pw.hist > 0 || (ws.ws_row && pw.maxlines >= ws.ws_row - 1)) { break; } else { int count = (cmdcount == INT_MAX) ? 1 : cmdcount; int oldmax = pw.maxlines; pw.maxlines += count; if (pw.maxlines >= ws.ws_row - 1) { maxed = 1; pw.maxlines = ws.ws_row - 1; } pw.circbuf = resizebuf(pw.circbuf, oldmax, pw.maxlines); snapshot[0] = resizebuf(snapshot[0], oldmax, pw.maxlines); for (int i = 1; i < snhistsize; i++) { freebuf(snapshot[i], snaplines[i]); free(snapshot[i]); snapshot[i] = 0; } pw.prevhist = 0; } break; case '#': pw.stat ^= stat_lino; if ((pw.stat & stat_lino)) clipsplits(&pw); // fallthrough case ctrl('l'): pw.stat |= stat_force; break; case ctrl('g'): pw.editpos = snprintf(pw.cmdbuf, sizeof pw.cmdbuf, "-p %d,%d,%d,%d,%d", pw.hpos, pw.vsplit1, pw.vsplit2, pw.vs2pos, (int) pw.stat & stat_save); pw.curcmd = pw.cmdbuf; kbd_state = kbd_result; break; case '.': if (prevcmd) { ch = prevcmd; cmdcount = prevcount; goto fakecmd; } break; case '0': if (cmdcount == INT_MAX) { pw.hpos = 0; pw.stat |= stat_force; break; } // fallthrough default: if (isdigit(ch)) { if (cmdcount == INT_MAX) cmdcount = 0; cmdcount = (cmdcount * 10 + (ch - '0')) % 1000; break; } ch = 0; break; } if (!isdigit(ch)) { if (ch && ch != '.') { prevcmd = ch; prevcount = cmdcount; } if (kbd_state == kbd_cmd) cmdcount = INT_MAX; } break; case kbd_esc: if (ch == '[') { kbd_state = kbd_bkt; break; } kbd_state = kbd_cmd; pw.curcmd = 0; break; case kbd_bkt: kbd_state = kbd_prev; if (kbd_prev == kbd_cmd) switch (ch) { case 'D': ch = 'h'; goto fakecmd; case 'C': ch = 'l'; goto fakecmd; case 'H': ch = '0'; goto fakecmd; case 'A': ch = 'k'; goto fakecmd; case 'B': ch = 'j'; goto fakecmd; } switch (ch) { case 'A': ch = ctrl('p'); goto fakecmd; case 'B': ch = ctrl('n'); goto fakecmd; case 'D': ch = ctrl('b'); goto fakecmd; case 'C': ch = ctrl('f'); goto fakecmd; } break; case kbd_lcmd: switch (ch) { case ESC: kbd_prev = kbd_state; kbd_state = kbd_esc; break; case CR: case ctrl('c'): if (ch == CR) { int count = (cmdcount == INT_MAX) ? 0 : cmdcount; if (pw.cmdbuf[1]) { int iscolon = pw.cmdbuf[0] == ':'; int *pnhist = (iscolon ? &ncmdhist : &npathist); int nhist = *pnhist; char ***hist = (iscolon ? &cmdhist : &pathist); if (nhist == 0 || strcmp(pw.cmdbuf, (*hist)[0]) != 0) { if ((*hist = resizebuf(*hist, nhist, nhist + 1)) == 0) panic("out of memory"); memmove(*hist + 1, *hist, sizeof **hist * nhist); *pnhist = nhist + 1; (*hist)[0] = dsdup(pw.cmdbuf); } } if (execute(&pw, pw.cmdbuf, pw.cmdbuf, cmdsize, count) != exec_ok) { pw.editpos = strlen(pw.cmdbuf); kbd_state = kbd_result; cmdcount = INT_MAX; break; } } kbd_state = kbd_cmd; pw.curcmd = 0; cmdcount = INT_MAX; prevcmd = 0; break; case ctrl('b'): if (pw.editpos > 1) pw.editpos--; break; case ctrl('f'): if (pw.cmdbuf[pw.editpos] != 0) pw.editpos++; break; case ctrl('k'): pw.cmdbuf[pw.editpos] = 0; break; case ctrl('a'): pw.editpos = 1; break; case ctrl('e'): pw.editpos = strlen(pw.cmdbuf); break; case BS: case DEL: { size_t len = strlen(pw.cmdbuf); if (pw.editpos > 1 || (pw.editpos == 1 && len == 1)) { pw.editpos--; memmove(pw.cmdbuf + pw.editpos, pw.cmdbuf + pw.editpos + 1, len - pw.editpos); } if (len == 1) { kbd_state = kbd_cmd; pw.curcmd = 0; // cmdcount deliberately not reset to INT_MAX } } break; case ctrl('d'): { size_t len = strlen(pw.cmdbuf); if (pw.editpos < len) memmove(pw.cmdbuf + pw.editpos, pw.cmdbuf + pw.editpos + 1, len - pw.editpos + 1); } break; case ctrl('u'): memmove(pw.cmdbuf + 1, pw.cmdbuf + pw.editpos, strlen(pw.cmdbuf + pw.editpos) + 1); pw.editpos = 1; break; case ctrl('w'): { size_t npos = pw.editpos; while (npos > 1 && isspace((unsigned char) pw.cmdbuf[npos - 1])) npos--; while (npos > 1 && !isspace((unsigned char) pw.cmdbuf[npos - 1])) npos--; memmove(pw.cmdbuf + npos, pw.cmdbuf + pw.editpos, strlen(pw.cmdbuf + pw.editpos) + 1); pw.editpos = npos; } break; case ctrl('p'): case ctrl('n'): { int iscolon = pw.cmdbuf[0] == ':'; int nhist = (iscolon ? ncmdhist : npathist); char ***hist = (iscolon ? &cmdhist : &pathist); if (ch == ctrl('p')) { if (histpos == 0) { dsdrop(pw.savedcmd); pw.savedcmd = dsdup(pw.cmdbuf); } else { dsdrop((*hist)[histpos-1]); (*hist)[histpos-1] = dsdup(pw.cmdbuf); } if (histpos < nhist) { char *cmd = (*hist)[histpos++]; strcpy(pw.cmdbuf, cmd); pw.editpos = strlen(cmd); } } else { if (histpos >= 1) { dsdrop((*hist)[histpos-1]); (*hist)[histpos-1] = dsdup(pw.cmdbuf); } if (histpos > 1) { char *cmd = (*hist)[--histpos - 1]; strcpy(pw.cmdbuf, cmd); pw.editpos = strlen(cmd); } else if (histpos == 1) { --histpos; strcpy(pw.cmdbuf, pw.savedcmd); dsdrop(pw.savedcmd); pw.savedcmd = 0; } } } break; default: if (isprint(ch)) { size_t len = strlen(pw.cmdbuf); if (len < sizeof pw.cmdbuf - 1 && (int) len < pw.columns - 1) { memmove(pw.cmdbuf + pw.editpos + 1, pw.cmdbuf + pw.editpos, len - pw.editpos + 1); pw.cmdbuf[pw.editpos++] = ch; } } break; } break; case kbd_exit: break; } if ((pw.stat & (stat_dirty | stat_force))) { redraw(&pw); } else switch (kbd_state) { case kbd_lcmd: case kbd_result: case kbd_cmd: clrline(pw.stat); drawstatus(&pw); } } else { if (workbout < workmax) work = workbout += workbout / 4; } } } if ((pw.stat & stat_bkgnd) == 0) { clrline(pw.stat); ttyset(ttyfd, &tty_saved); } if (out) fclose(out); #if CONFIG_DEBUG_LEAKS freebuf(pw.circbuf, pw.maxlines); free(pw.circbuf); for (int i = 0; i < snhistsize; i++) { freebuf(snapshot[i], snaplines[i]); free(snapshot[i]); } for (int i = 0; i < ngrep; i++) grclean(&grepstack[i]); for (int i = 0; i < maxtrig; i++) { if (triglist[i]) grclean(triglist[i]); } freebuf(cmdhist, ncmdhist); freebuf(pathist, npathist); fclose(tty); #endif return exit_status; }