From 411630a95f6c856c4a627d67c6afe4be2ca71ab6 Mon Sep 17 00:00:00 2001 From: Kaz Kylheku Date: Tue, 26 Apr 2022 23:04:32 -0700 Subject: Add a vi-like colon mode with :w, :a and :! commands. --- pw.1 | 45 +++++++++++++++++++ pw.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 192 insertions(+), 14 deletions(-) diff --git a/pw.1 b/pw.1 index db849fa..e33e2e4 100644 --- a/pw.1 +++ b/pw.1 @@ -156,6 +156,45 @@ status string disappears. If the status is already .B EOF then suspend mode cannot be entered. +.IP \fB:\fP +Enter colon command mode. In colon command mode, the status line clears +and a colon prompt appears there. An extended command can be entered. +Pressing +.I Enter +in colon mode dispatches the command. Simple editing is available: +backspace, +.B Ctrl-W +word erase and +.B Ctrl-U +line erase. If backspace is used in an empty colon line, colon mode +terminates without executing a command. +.B Ctrl-C +and +.B Esc +also terminate colon mode without executing a command. Colon commands +are documented in the COLON COMMANDS section below. + +.SH COLON COMMANDS + +First, some general remarks. the current contents of the FIFO buffer may have +changed relative to what is displayed on the screen. The space between the +command and the argument may be omitted. The command is replaced with +a result string, which persists over the poll interval period. The longer +the poll interval +.RB ( -i " option), +the longer the result message persists. + +.IP "\fB:w\fP \fIfilename\fP" +Write the current contents of the FIFO buffer into the specified file. + +.IP "\fB:a\fP \fIfilename\fP" +Append the current contents of the FIFO buffer into the specified file. + +.IP "\fB:!\fP \fIfilename\fP" +Pipe the current contents of the FIFO buffers into the specified command. +.PP +Any other command results in a brief error message. + .SH OPTIONS .IP "\fB-i\fP \fIreal\fP" @@ -241,6 +280,12 @@ screen. There is no support for unwrapping long lines, which would be useful for copy and paste. +Ctrl-Z suspend is missing. + +During the +.B :! +command's execution, the TTY settings are not restored. + .SH AUTHOR Kaz Kylheku diff --git a/pw.c b/pw.c index 8d79183..5ef006d 100644 --- a/pw.c +++ b/pw.c @@ -114,29 +114,93 @@ static void drawline(const char *line, int hpos, int columns) } } -static void drawstatus(unsigned stat) +static void clear_cur_line() +{ + printf("\r\033[J"); +} + +static void drawstatus(unsigned stat, char *cmd) { - if ((stat & (stat_eof | stat_susp))) { + if (cmd) { + printf(":%s", cmd); + } else if ((stat & (stat_eof | stat_susp))) { if ((stat & stat_eof)) printf("EOF "); if ((stat & stat_susp)) printf("SUSPENDED "); - fflush(stdout); + } else { + clear_cur_line(); } + fflush(stdout); } static void redraw(char **circbuf, int nlines, int hpos, - int columns, unsigned stat) + int columns, unsigned stat, char *cmd) { printf("\r\033[%dA\033[J", nlines); for (int i = 0; i < nlines; i++) drawline(circbuf[i], hpos, columns); - drawstatus(stat); + drawstatus(stat, cmd); } -static void clear_cur_line() +static void execute(char *cmd, char **circbuf, int nlines) { - printf("\r\033[J"); + char *arg = cmd + 1 + strspn(cmd + 1, " \t"); + + clear_cur_line(); + + switch (cmd[0]) { + case 'w': case 'a': + { + FILE *f = fopen(arg, cmd[0] == 'w' ? "w" : "a"); + int ok = 1; + + if (!f) { + sprintf(cmd, "unable to open file"); + break; + } + + for (int i = 0; ok && i < nlines; i++) + if (fprintf(f, "%s\n", circbuf[i]) < 0) { + sprintf(cmd, "write error!"); + ok = 0; + break; + } + + fclose(f); + if (ok) + sprintf(cmd, "saved!"); + } + break; + case '!': + { + FILE *p = popen(arg, "w"); + int ok = 1; + + if (!p) { + sprintf(cmd, "unable to open command"); + fflush(stdout); + break; + } + + for (int i = 0; i < nlines && ok; i++) + if (fprintf(p, "%s\n", circbuf[i]) < 0) { + sprintf(cmd, "write error!"); + ok = 0; + break; + } + + pclose(p); + if (ok) + sprintf(cmd, "piped!"); + } + break; + default: + sprintf(cmd, "bad command"); + break; + } + + fflush(stdout); } int main(int argc, char **argv) @@ -153,9 +217,10 @@ int main(int argc, char **argv) struct termios tty_saved, tty_new; struct winsize ws = { 0 }; int columns = 80; - enum kbd_state { kbd_cmd, kbd_esc, kbd_bkt, kbd_exit }; + enum kbd_state { kbd_cmd, kbd_esc, kbd_bkt, kbd_exit, kbd_colon, kbd_result }; int auto_quit = 1; int exit_status = EXIT_FAILURE; + char cmdbuf[100], *colcmd = 0; if (fd < 0) panic("unable to obtain input file descriptor"); @@ -241,7 +306,7 @@ int main(int argc, char **argv) now = (((unsigned) tv.tv_sec)%1000000)*1000 + tv.tv_usec/1000; if (lasttime == ~0U || now - lasttime > (unsigned) long_interval) { if ((stat & stat_dirty) && nlines == maxlines) - redraw(circbuf, nlines, hpos, columns, stat); + redraw(circbuf, nlines, hpos, columns, stat, colcmd); lasttime = now; stat &= ~stat_dirty; } @@ -249,10 +314,15 @@ int main(int argc, char **argv) if (poll(pe, ((stat & stat_eof)) ? 1 : 2, poll_interval) <= 0) { if ((stat & stat_dirty) && nlines == maxlines) { - redraw(circbuf, nlines, hpos, columns, stat); + redraw(circbuf, nlines, hpos, columns, stat, colcmd); stat &= ~stat_dirty; } - kbd_state = kbd_cmd; + if (kbd_state == kbd_esc || kbd_state == kbd_result) { + kbd_state = kbd_cmd; + colcmd = 0; + clear_cur_line(); + drawstatus(stat, colcmd); + } } else { if ((stat & stat_eof) == 0 && pe[1].revents) { if ((line = getln(stdin))) { @@ -269,14 +339,14 @@ int main(int argc, char **argv) circbuf[nlines++] = line; clear_cur_line(); drawline(line, hpos, columns); - drawstatus(stat); + drawstatus(stat, colcmd); } } else { if (auto_quit) kbd_state = kbd_exit; else stat |= stat_eof; - redraw(circbuf, nlines, hpos, columns, stat); + redraw(circbuf, nlines, hpos, columns, stat, colcmd); stat |= stat_eof; stat &= ~stat_dirty; if (!ferror(stdin)) @@ -326,6 +396,11 @@ int main(int argc, char **argv) case 27: kbd_state = kbd_esc; break; + case ':': + kbd_state = kbd_colon; + cmdbuf[0] = 0; + colcmd = cmdbuf; + break; } break; case kbd_esc: @@ -347,13 +422,71 @@ int main(int argc, char **argv) goto fakecmd; } break; + case kbd_colon: + switch (ch) { + case 27: case 13: case 3: + kbd_state = kbd_cmd; + stat |= stat_dirty; + if (ch == 13 && cmdbuf[0]) { + execute(cmdbuf, circbuf, nlines); + stat &= ~stat_dirty; + kbd_state = kbd_result; + break; + } + colcmd = 0; + break; + case 8: case 127: + { + size_t len = strlen(cmdbuf); + if (len == 0) { + kbd_state = kbd_cmd; + colcmd = 0; + stat |= stat_dirty; + } else { + cmdbuf[--len] = 0; + } + } + break; + case 21: + cmdbuf[0] = 0; + break; + case 23: + { + size_t len = strlen(cmdbuf); + while (len > 0 && isspace((unsigned char) cmdbuf[len - 1])) + len--; + while (len > 0 && !isspace((unsigned char) cmdbuf[len - 1])) + len--; + cmdbuf[len] = 0; + } + break; + default: + if (isprint(ch)) + { + size_t len = strlen(cmdbuf); + if (len < sizeof cmdbuf - 1 && (int) len < columns - 1) { + cmdbuf[len++] = ch; + cmdbuf[len] = 0; + } + } + break; + } + break; + case kbd_result: + kbd_state = kbd_cmd; + stat |= stat_dirty; + colcmd = 0; + break; case kbd_exit: break; } if ((stat & stat_dirty)) { - redraw(circbuf, nlines, hpos, columns, stat); + redraw(circbuf, nlines, hpos, columns, stat, colcmd); stat &= ~stat_dirty; + } else if (kbd_state == kbd_colon || kbd_state == kbd_result) { + clear_cur_line(); + drawstatus(stat, colcmd); } } } -- cgit v1.2.3