dvtm

dynamic virtual terminal manager - with my changes
git clone https://pi.duncano.de/git/dvtm.git
Log | Files | Refs | README | LICENSE

commit ab5777d8cf0f7362266d63271f3d5de41f96a70c
parent 7723dfbd2bbae15b125bcb316ed9671698a16a45
Author: Marc Andre Tanner <mat@brain-dump.org>
Date:   Sun, 16 Dec 2012 14:22:39 +0100

Merge branch 'copymode'

Diffstat:
config.h | 6+++++-
dvtm-test | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
dvtm.1 | 46+++++++++++++++++++++++++++++++++++++++++++++-
dvtm.c | 32+++++++++++++++++++++++++++++++-
vt.c | 1023+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
vt.h | 6++++++
6 files changed, 929 insertions(+), 239 deletions(-)

diff --git a/config.h b/config.h @@ -81,9 +81,13 @@ Key keys[] = { { MOD, 'r', { redraw, { NULL } } }, { MOD, 'X', { lock, { NULL } } }, { MOD, 'B', { togglebell, { NULL } } }, + { MOD, 'v', { copymode, { NULL } } }, + { MOD, '/', { copymode, { "/" } } }, + { MOD, '?', { copymode, { "?" } } }, + { MOD, 'p', { paste, { NULL } } }, { MOD, KEY_PPAGE, { scrollback, { "-1" } } }, { MOD, KEY_NPAGE, { scrollback, { "1" } } }, - { MOD, '?', { create, { "man dvtm", "dvtm help" } } }, + { MOD, KEY_F(1), { create, { "man dvtm", "dvtm help" } } }, }; static const ColorRule colorrules[] = { diff --git a/dvtm-test b/dvtm-test @@ -0,0 +1,55 @@ +#!/bin/sh + +MOD="" # CTRL+g +ESC="" # \e +DVTM="./dvtm" +LOG="dvtm.log" +TEST_LOG="$0.log" +UTF8_TEST_URL="http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-demo.txt" + +[ ! -z "$1" ] && DVTM="$1" +[ ! -x "$DVTM" ] && echo "usage: $0 path-to-dvtm-binary" && exit 1 + +dvtm_input() { + printf "$1" +} + +dvtm_cmd() { + printf "${MOD}$1\n" + sleep 1 +} + +sh_cmd() { + printf "$1\n" + sleep 1 +} + +test_copymode() { # requires wget, diff, vi + local FILENAME="UTF-8-demo.txt" + [ ! -e "$FILENAME" ] && (wget "$UTF8_TEST_URL" -O "$FILENAME" > /dev/null 2>&1 || return 1) + sleep 1 + sh_cmd "cat $FILENAME" + dvtm_cmd 'v' + dvtm_input "?UTF-8 encoded\n" + dvtm_input '^kvGk$' + dvtm_input 'y' + rm -f "$FILENAME.copy" + sh_cmd "vi $FILENAME.copy" + dvtm_input 'i' + dvtm_cmd 'p' + dvtm_input "${ESC}dd:wq\n" + sleep 1 + dvtm_cmd 'q' + diff -u "$FILENAME" "$FILENAME.copy" 1>&2 + local RESULT=$? + rm "$FILENAME.copy" + return $RESULT +} + +{ + echo "Testing $DVTM" 1>&2 + $DVTM -v 1>&2 + test_copymode && echo "copymode: OK" 1>&2 || echo "copymode: FAIL" 1>&2; +} 2> "$TEST_LOG" | $DVTM 2> $LOG + +cat "$TEST_LOG" && rm "$TEST_LOG" $LOG diff --git a/dvtm.1 b/dvtm.1 @@ -113,6 +113,21 @@ Toggle bell (off by default). .B Mod\-M Toggle dvtm mouse grabbing. .TP +.B Mod\-v +Enter copy mode (see section below for navigation commands). +.TP +.B Mod\-/ +Enter copy mode and start searching forward. +.TP +.B Mod\-? +Enter copy mode and start searching backwards. +.TP +.B Mod\-p +Paste last copied text from copy mode at current cursor position. +.TP +.B Mod\-F1 +Show this manual page. +.TP .B Mod\-q Quit dvtm. .SS Mouse commands @@ -135,9 +150,38 @@ Zooms/cycles current window to/from master area. .B Button3 click Toggle minimization of current window. +.SS Copy mode +Copy mode gives easy access to past output. The commands use vi style keybindings +and support number prefixes as command multiplier. +.TP +.B Entering +Copy mode can be entered with \fBMod\-v\fR. +.TP +.B Navigation +Once in, navigation works with vi style keybindings (\fBh,j,k,l,^,$,g,H,M,L,G\fR) as well as with the +\fBArrows/Home/End/Page-Down/Page-Up\fR keys. +.TP +.B Searching +Search forward with \fB/\fR and backwards with \fB?\fR. Jump forward to next match with \fBn\fR. +Jump backwards to next match with \fBN\fR. +.TP +.B Selecting +To start making a selection press \fBv\fR (similar to visual mode in vi). +.TP +.B Copying +To copy the current selection use \fBy\fR. If you haven't made a selection the current line is copied. +Add a number prefix to copy n lines starting from the current line. This command leaves the copy mode. +.TP +.B Pasting +The previously copied text can be pasted at the current cursor position with \fBMod\-p\fR. +.TP +.B Leaving +Copy mode is automatically left upon copying something. To manually exit at any +time press \fBESC\fR or \fBq\fR. + .SH EXAMPLE .TP -See the dvtm-status script as an example. +See the dvtm-status script as an example of how to display text in the status bar. .SH CUSTOMIZATION dvtm is customized by creating a custom config.h and (re)compiling the source diff --git a/dvtm.c b/dvtm.c @@ -143,6 +143,7 @@ typedef struct { /* commands for use by keybindings */ static void create(const char *args[]); +static void copymode(const char *args[]); static void escapekey(const char *args[]); static void focusn(const char *args[]); static void focusnext(const char *args[]); @@ -151,6 +152,7 @@ static void focusprev(const char *args[]); static void focusprevnm(const char *args[]); static void killclient(const char *args[]); static void lock(const char *key[]); +static void paste(const char *args[]); static void quit(const char *args[]); static void redraw(const char *args[]); static void scrollback(const char *args[]); @@ -188,6 +190,7 @@ static Layout *layout = layouts; static StatusBar bar = { -1, BAR_POS, 1 }; static CmdFifo cmdfifo = { -1 }; static const char *shell; +static char *copybuf; static bool running = true; static bool runinall = false; @@ -420,6 +423,12 @@ term_event_handler(Vt *term, int event, void *event_data) { draw_border(c); applycolorrules(c); break; + case VT_EVENT_COPY_TEXT: + if (event_data) { + free(copybuf); + copybuf = event_data; + } + break; } } @@ -671,6 +680,7 @@ cleanup() { destroy(clients); vt_shutdown(); endwin(); + free(copybuf); if (bar.fd > 0) close(bar.fd); if (bar.file) @@ -721,6 +731,17 @@ create(const char *args[]) { } static void +copymode(const char *args[]) { + if (!sel) + return; + vt_copymode_enter(sel->term); + if (args[0]) { + vt_copymode_keypress(sel->term, args[0][0]); + draw(sel); + } +} + +static void escapekey(const char *args[]) { int key; if ((key = getch()) >= 0) { @@ -840,6 +861,12 @@ lock(const char *args[]) { } static void +paste(const char *args[]) { + if (sel && copybuf) + vt_write(sel->term, copybuf, strlen(copybuf)); +} + +static void quit(const char *args[]) { cleanup(); exit(EXIT_SUCCESS); @@ -1339,6 +1366,9 @@ main(int argc, char *argv[]) { } } else if ((key = keybinding(0, code))) { key->action.cmd(key->action.args); + } else if (sel && vt_copymode(sel->term)) { + vt_copymode_keypress(sel->term, code); + draw(sel); } else { keypress(code); } @@ -1354,7 +1384,7 @@ main(int argc, char *argv[]) { handle_statusbar(); for (c = clients; c; ) { - if (FD_ISSET(c->pty, &rd)) { + if (FD_ISSET(c->pty, &rd) && !vt_copymode(c->term)) { if (vt_process(c->term) < 0 && errno == EIO) { /* client probably terminated */ t = c->next; diff --git a/vt.c b/vt.c @@ -74,6 +74,7 @@ #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define sstrlen(str) (sizeof(str) - 1) +#define COPYMODE_ATTR A_REVERSE static bool is_utf8, has_default_colors; static short color_pairs_reserved, color_pairs_max, color_pair_current; static short *color2palette, default_fg, default_bg; @@ -124,10 +125,10 @@ typedef struct { Row *scroll_buf; Row *scroll_top; Row *scroll_bot; - int scroll_buf_sz; + int scroll_buf_size; int scroll_buf_ptr; - int scroll_buf_len; - int scroll_amount; + int scroll_amount_above; + int scroll_amount_below; int rows, cols, maxcols; unsigned curattrs, savattrs; int curs_col, curs_srow, curs_scol; @@ -149,14 +150,23 @@ struct Vt { unsigned escaped:1; unsigned curshid:1; unsigned curskeymode:1; + unsigned copymode:1; + unsigned copymode_selecting:1; unsigned bell:1; unsigned relposmode:1; unsigned mousetrack:1; unsigned graphmode:1; bool charsets[2]; - + char copymode_searching; + /* copymode */ + int copymode_curs_srow, copymode_curs_scol; + Row *copymode_sel_start_row; + int copymode_sel_start_col; + wchar_t *searchbuf; + mbstate_t searchbuf_ps; + int searchbuf_curs, searchbuf_size; + int copymode_cmd_multiplier; /* buffers and parsing state */ - mbstate_t ps; char rbuf[BUFSIZ]; char ebuf[BUFSIZ]; unsigned int rlen, elen; @@ -264,51 +274,51 @@ static void row_roll(Row *start, Row *end, int count) } } -static void clamp_cursor_to_bounds(Vt *vt) +static void clamp_cursor_to_bounds(Vt *t) { - Buffer *t = vt->buffer; - Row *lines = vt->relposmode ? t->scroll_top : t->lines; - int rows = vt->relposmode ? t->scroll_bot - t->scroll_top : t->rows; + Buffer *b = t->buffer; + Row *lines = t->relposmode ? b->scroll_top : b->lines; + int rows = t->relposmode ? b->scroll_bot - b->scroll_top : b->rows; - if (t->curs_row < lines) - t->curs_row = lines; - if (t->curs_row >= lines + rows) - t->curs_row = lines + rows - 1; - if (t->curs_col < 0) - t->curs_col = 0; - if (t->curs_col >= t->cols) - t->curs_col = t->cols - 1; + if (b->curs_row < lines) + b->curs_row = lines; + if (b->curs_row >= lines + rows) + b->curs_row = lines + rows - 1; + if (b->curs_col < 0) + b->curs_col = 0; + if (b->curs_col >= b->cols) + b->curs_col = b->cols - 1; } -static void save_curs(Vt *vt) +static void save_curs(Vt *t) { - Buffer *t = vt->buffer; - t->curs_srow = t->curs_row - t->lines; - t->curs_scol = t->curs_col; + Buffer *b = t->buffer; + b->curs_srow = b->curs_row - b->lines; + b->curs_scol = b->curs_col; } -static void restore_curs(Vt *vt) +static void restore_curs(Vt *t) { - Buffer *t = vt->buffer; - t->curs_row = t->lines + t->curs_srow; - t->curs_col = t->curs_scol; - clamp_cursor_to_bounds(vt); + Buffer *b = t->buffer; + b->curs_row = b->lines + b->curs_srow; + b->curs_col = b->curs_scol; + clamp_cursor_to_bounds(t); } -static void save_attrs(Vt *vt) +static void save_attrs(Vt *t) { - Buffer *t = vt->buffer; - t->savattrs = t->curattrs; - t->savfg = t->curfg; - t->savbg = t->curbg; + Buffer *b = t->buffer; + b->savattrs = b->curattrs; + b->savfg = b->curfg; + b->savbg = b->curbg; } -static void restore_attrs(Vt *vt) +static void restore_attrs(Vt *t) { - Buffer *t = vt->buffer; - t->curattrs = t->savattrs; - t->curfg = t->savfg; - t->curbg = t->savbg; + Buffer *b = t->buffer; + b->curattrs = b->savattrs; + b->curfg = b->savfg; + b->curbg = b->savbg; } static void fill_scroll_buf(Buffer *t, int s) @@ -326,27 +336,27 @@ static void fill_scroll_buf(Buffer *t, int s) return; } - t->scroll_buf_len += s; - if (t->scroll_buf_len >= t->scroll_buf_sz) - t->scroll_buf_len = t->scroll_buf_sz; + t->scroll_amount_above += s; + if (t->scroll_amount_above >= t->scroll_buf_size) + t->scroll_amount_above = t->scroll_buf_size; - if (s > 0 && t->scroll_buf_sz) { + if (s > 0 && t->scroll_buf_size) { for (int i = 0; i < s; i++) { Row tmp = t->scroll_top[i]; t->scroll_top[i] = t->scroll_buf[t->scroll_buf_ptr]; t->scroll_buf[t->scroll_buf_ptr] = tmp; t->scroll_buf_ptr++; - if (t->scroll_buf_ptr == t->scroll_buf_sz) + if (t->scroll_buf_ptr == t->scroll_buf_size) t->scroll_buf_ptr = 0; } } row_roll(t->scroll_top, t->scroll_bot, s); - if (s < 0 && t->scroll_buf_sz) { + if (s < 0 && t->scroll_buf_size) { for (int i = (-s) - 1; i >= 0; i--) { t->scroll_buf_ptr--; if (t->scroll_buf_ptr == -1) - t->scroll_buf_ptr = t->scroll_buf_sz - 1; + t->scroll_buf_ptr = t->scroll_buf_size - 1; Row tmp = t->scroll_top[i]; t->scroll_top[i] = t->scroll_buf[t->scroll_buf_ptr]; @@ -356,19 +366,19 @@ static void fill_scroll_buf(Buffer *t, int s) } } -static void cursor_line_down(Vt *vt) +static void cursor_line_down(Vt *t) { - Buffer *t = vt->buffer; - row_set(t->curs_row, t->cols, t->maxcols - t->cols, NULL); - t->curs_row++; - if (t->curs_row < t->scroll_bot) + Buffer *b = t->buffer; + row_set(b->curs_row, b->cols, b->maxcols - b->cols, NULL); + b->curs_row++; + if (b->curs_row < b->scroll_bot) return; - vt_noscroll(vt); + vt_noscroll(t); - t->curs_row = t->scroll_bot - 1; - fill_scroll_buf(t, 1); - row_set(t->curs_row, 0, t->cols, t); + b->curs_row = b->scroll_bot - 1; + fill_scroll_buf(b, 1); + row_set(b->curs_row, 0, b->cols, b); } static void new_escape_sequence(Vt *t) @@ -393,81 +403,81 @@ static bool is_valid_csi_ender(int c) } /* interprets a 'set attribute' (SGR) CSI escape sequence */ -static void interpret_csi_sgr(Vt *vt, int param[], int pcount) +static void interpret_csi_sgr(Vt *t, int param[], int pcount) { - Buffer *t = vt->buffer; + Buffer *b = t->buffer; if (pcount == 0) { /* special case: reset attributes */ - t->curattrs = A_NORMAL; - t->curfg = t->curbg = -1; + b->curattrs = A_NORMAL; + b->curfg = b->curbg = -1; return; } for (int i = 0; i < pcount; i++) { switch (param[i]) { case 0: - t->curattrs = A_NORMAL; - t->curfg = t->curbg = -1; + b->curattrs = A_NORMAL; + b->curfg = b->curbg = -1; break; case 1: - t->curattrs |= A_BOLD; + b->curattrs |= A_BOLD; break; case 4: - t->curattrs |= A_UNDERLINE; + b->curattrs |= A_UNDERLINE; break; case 5: - t->curattrs |= A_BLINK; + b->curattrs |= A_BLINK; break; case 7: - t->curattrs |= A_REVERSE; + b->curattrs |= A_REVERSE; break; case 8: - t->curattrs |= A_INVIS; + b->curattrs |= A_INVIS; break; case 22: - t->curattrs &= ~A_BOLD; + b->curattrs &= ~A_BOLD; break; case 24: - t->curattrs &= ~A_UNDERLINE; + b->curattrs &= ~A_UNDERLINE; break; case 25: - t->curattrs &= ~A_BLINK; + b->curattrs &= ~A_BLINK; break; case 27: - t->curattrs &= ~A_REVERSE; + b->curattrs &= ~A_REVERSE; break; case 28: - t->curattrs &= ~A_INVIS; + b->curattrs &= ~A_INVIS; break; case 30 ... 37: /* fg */ - t->curfg = param[i] - 30; + b->curfg = param[i] - 30; break; case 38: if ((i + 2) < pcount && param[i + 1] == 5) { - t->curfg = param[i + 2]; + b->curfg = param[i + 2]; i += 2; } break; case 39: - t->curfg = -1; + b->curfg = -1; break; case 40 ... 47: /* bg */ - t->curbg = param[i] - 40; + b->curbg = param[i] - 40; break; case 48: if ((i + 2) < pcount && param[i + 1] == 5) { - t->curbg = param[i + 2]; + b->curbg = param[i + 2]; i += 2; } break; case 49: - t->curbg = -1; + b->curbg = -1; break; case 90 ... 97: /* hi fg */ - t->curfg = param[i] - 82; + b->curfg = param[i] - 82; break; case 100 ... 107: /* hi bg */ - t->curbg = param[i] - 92; + b->curbg = param[i] - 92; break; default: break; @@ -476,199 +486,199 @@ static void interpret_csi_sgr(Vt *vt, int param[], int pcount) } /* interprets an 'erase display' (ED) escape sequence */ -static void interpret_csi_ed(Vt *vt, int param[], int pcount) +static void interpret_csi_ed(Vt *t, int param[], int pcount) { Row *row, *start, *end; - Buffer *t = vt->buffer; + Buffer *b = t->buffer; - save_attrs(vt); - t->curattrs = A_NORMAL; - t->curfg = t->curbg = -1; + save_attrs(t); + b->curattrs = A_NORMAL; + b->curfg = b->curbg = -1; if (pcount && param[0] == 2) { - start = t->lines; - end = t->lines + t->rows; + start = b->lines; + end = b->lines + b->rows; } else if (pcount && param[0] == 1) { - start = t->lines; - end = t->curs_row; - row_set(t->curs_row, 0, t->curs_col + 1, t); + start = b->lines; + end = b->curs_row; + row_set(b->curs_row, 0, b->curs_col + 1, b); } else { - row_set(t->curs_row, t->curs_col, t->cols - t->curs_col, t); - start = t->curs_row + 1; - end = t->lines + t->rows; + row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b); + start = b->curs_row + 1; + end = b->lines + b->rows; } for (row = start; row < end; row++) - row_set(row, 0, t->cols, t); + row_set(row, 0, b->cols, b); - restore_attrs(vt); + restore_attrs(t); } /* interprets a 'move cursor' (CUP) escape sequence */ -static void interpret_csi_cup(Vt *vt, int param[], int pcount) +static void interpret_csi_cup(Vt *t, int param[], int pcount) { - Buffer *t = vt->buffer; - Row *lines = vt->relposmode ? t->scroll_top : t->lines; + Buffer *b = t->buffer; + Row *lines = t->relposmode ? b->scroll_top : b->lines; if (pcount == 0) { - t->curs_row = lines; - t->curs_col = 0; + b->curs_row = lines; + b->curs_col = 0; } else if (pcount == 1) { - t->curs_row = lines + param[0] - 1; - t->curs_col = 0; + b->curs_row = lines + param[0] - 1; + b->curs_col = 0; } else { - t->curs_row = lines + param[0] - 1; - t->curs_col = param[1] - 1; + b->curs_row = lines + param[0] - 1; + b->curs_col = param[1] - 1; } - clamp_cursor_to_bounds(vt); + clamp_cursor_to_bounds(t); } /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL, * CPL, CHA, HPR, VPA, VPR, HPA */ -static void interpret_csi_c(Vt *vt, char verb, int param[], int pcount) +static void interpret_csi_c(Vt *t, char verb, int param[], int pcount) { - Buffer *t = vt->buffer; + Buffer *b = t->buffer; int n = (pcount && param[0] > 0) ? param[0] : 1; switch (verb) { case 'A': - t->curs_row -= n; + b->curs_row -= n; break; case 'B': case 'e': - t->curs_row += n; + b->curs_row += n; break; case 'C': case 'a': - t->curs_col += n; + b->curs_col += n; break; case 'D': - t->curs_col -= n; + b->curs_col -= n; break; case 'E': - t->curs_row += n; - t->curs_col = 0; + b->curs_row += n; + b->curs_col = 0; break; case 'F': - t->curs_row -= n; - t->curs_col = 0; + b->curs_row -= n; + b->curs_col = 0; break; case 'G': case '`': - t->curs_col = param[0] - 1; + b->curs_col = param[0] - 1; break; case 'd': - t->curs_row = t->lines + param[0] - 1; + b->curs_row = b->lines + param[0] - 1; break; } - clamp_cursor_to_bounds(vt); + clamp_cursor_to_bounds(t); } /* Interpret the 'erase line' escape sequence */ -static void interpret_csi_el(Vt *vt, int param[], int pcount) +static void interpret_csi_el(Vt *t, int param[], int pcount) { - Buffer *t = vt->buffer; + Buffer *b = t->buffer; switch (pcount ? param[0] : 0) { case 1: - row_set(t->curs_row, 0, t->curs_col + 1, t); + row_set(b->curs_row, 0, b->curs_col + 1, b); break; case 2: - row_set(t->curs_row, 0, t->cols, t); + row_set(b->curs_row, 0, b->cols, b); break; default: - row_set(t->curs_row, t->curs_col, t->cols - t->curs_col, t); + row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b); break; } } /* Interpret the 'insert blanks' sequence (ICH) */ -static void interpret_csi_ich(Vt *vt, int param[], int pcount) +static void interpret_csi_ich(Vt *t, int param[], int pcount) { - Buffer *t = vt->buffer; - Row *row = t->curs_row; + Buffer *b = t->buffer; + Row *row = b->curs_row; int n = (pcount && param[0] > 0) ? param[0] : 1; - if (t->curs_col + n > t->cols) - n = t->cols - t->curs_col; + if (b->curs_col + n > b->cols) + n = b->cols - b->curs_col; - for (int i = t->cols - 1; i >= t->curs_col + n; i--) + for (int i = b->cols - 1; i >= b->curs_col + n; i--) row->cells[i] = row->cells[i - n]; - row_set(row, t->curs_col, n, t); + row_set(row, b->curs_col, n, b); } /* Interpret the 'delete chars' sequence (DCH) */ -static void interpret_csi_dch(Vt *vt, int param[], int pcount) +static void interpret_csi_dch(Vt *t, int param[], int pcount) { - Buffer *t = vt->buffer; - Row *row = t->curs_row; + Buffer *b = t->buffer; + Row *row = b->curs_row; int n = (pcount && param[0] > 0) ? param[0] : 1; - if (t->curs_col + n > t->cols) - n = t->cols - t->curs_col; + if (b->curs_col + n > b->cols) + n = b->cols - b->curs_col; - for (int i = t->curs_col; i < t->cols - n; i++) + for (int i = b->curs_col; i < b->cols - n; i++) row->cells[i] = row->cells[i + n]; - row_set(row, t->cols - n, n, t); + row_set(row, b->cols - n, n, b); } /* Interpret an 'insert line' sequence (IL) */ -static void interpret_csi_il(Vt *vt, int param[], int pcount) +static void interpret_csi_il(Vt *t, int param[], int pcount) { - Buffer *t = vt->buffer; + Buffer *b = t->buffer; int n = (pcount && param[0] > 0) ? param[0] : 1; - if (t->curs_row + n >= t->scroll_bot) { - for (Row *row = t->curs_row; row < t->scroll_bot; row++) - row_set(row, 0, t->cols, t); + if (b->curs_row + n >= b->scroll_bot) { + for (Row *row = b->curs_row; row < b->scroll_bot; row++) + row_set(row, 0, b->cols, b); } else { - row_roll(t->curs_row, t->scroll_bot, -n); - for (Row *row = t->curs_row; row < t->curs_row + n; row++) - row_set(row, 0, t->cols, t); + row_roll(b->curs_row, b->scroll_bot, -n); + for (Row *row = b->curs_row; row < b->curs_row + n; row++) + row_set(row, 0, b->cols, b); } } /* Interpret a 'delete line' sequence (DL) */ -static void interpret_csi_dl(Vt *vt, int param[], int pcount) +static void interpret_csi_dl(Vt *t, int param[], int pcount) { - Buffer *t = vt->buffer; + Buffer *b = t->buffer; int n = (pcount && param[0] > 0) ? param[0] : 1; - if (t->curs_row + n >= t->scroll_bot) { - for (Row *row = t->curs_row; row < t->scroll_bot; row++) - row_set(row, 0, t->cols, t); + if (b->curs_row + n >= b->scroll_bot) { + for (Row *row = b->curs_row; row < b->scroll_bot; row++) + row_set(row, 0, b->cols, b); } else { - row_roll(t->curs_row, t->scroll_bot, n); - for (Row *row = t->scroll_bot - n; row < t->scroll_bot; row++) - row_set(row, 0, t->cols, t); + row_roll(b->curs_row, b->scroll_bot, n); + for (Row *row = b->scroll_bot - n; row < b->scroll_bot; row++) + row_set(row, 0, b->cols, b); } } /* Interpret an 'erase characters' (ECH) sequence */ -static void interpret_csi_ech(Vt *vt, int param[], int pcount) +static void interpret_csi_ech(Vt *t, int param[], int pcount) { - Buffer *t = vt->buffer; + Buffer *b = t->buffer; int n = (pcount && param[0] > 0) ? param[0] : 1; - if (t->curs_col + n > t->cols) - n = t->cols - t->curs_col; + if (b->curs_col + n > b->cols) + n = b->cols - b->curs_col; - row_set(t->curs_row, t->curs_col, n, t); + row_set(b->curs_row, b->curs_col, n, b); } /* Interpret a 'set scrolling region' (DECSTBM) sequence */ -static void interpret_csi_decstbm(Vt *vt, int param[], int pcount) +static void interpret_csi_decstbm(Vt *t, int param[], int pcount) { - Buffer *t = vt->buffer; + Buffer *b = t->buffer; int new_top, new_bot; switch (pcount) { case 0: - t->scroll_top = t->lines; - t->scroll_bot = t->lines + t->rows; + b->scroll_top = b->lines; + b->scroll_bot = b->lines + b->rows; break; case 2: new_top = param[0] - 1; @@ -677,17 +687,17 @@ static void interpret_csi_decstbm(Vt *vt, int param[], int pcount) /* clamp to bounds */ if (new_top < 0) new_top = 0; - if (new_top >= t->rows) - new_top = t->rows - 1; + if (new_top >= b->rows) + new_top = b->rows - 1; if (new_bot < 0) new_bot = 0; - if (new_bot >= t->rows) - new_bot = t->rows; + if (new_bot >= b->rows) + new_bot = b->rows; /* check for range validity */ if (new_top < new_bot) { - t->scroll_top = t->lines + new_top; - t->scroll_bot = t->lines + new_bot; + b->scroll_top = b->lines + new_top; + b->scroll_bot = b->lines + new_bot; } break; default: @@ -731,6 +741,7 @@ static void interpret_csi(Vt *t) t->curshid = false; break; case 47: /* use alternate screen buffer */ + vt_copymode_leave(t); t->buffer = &t->buffer_alternate; vt_dirty(t); break; @@ -750,6 +761,7 @@ static void interpret_csi(Vt *t) t->curshid = true; break; case 47: /* use normal screen buffer */ + vt_copymode_leave(t); t->buffer = &t->buffer_normal; vt_dirty(t); break; @@ -831,32 +843,32 @@ static void interpret_csi(Vt *t) } /* Interpret an 'index' (IND) sequence */ -static void interpret_csi_ind(Vt *vt) +static void interpret_csi_ind(Vt *t) { - Buffer *t = vt->buffer; - if (t->curs_row < t->lines + t->rows - 1) - t->curs_row++; + Buffer *b = t->buffer; + if (b->curs_row < b->lines + b->rows - 1) + b->curs_row++; } /* Interpret a 'reverse index' (RI) sequence */ -static void interpret_csi_ri(Vt *vt) +static void interpret_csi_ri(Vt *t) { - Buffer *t = vt->buffer; - if (t->curs_row > t->lines) - t->curs_row--; + Buffer *b = t->buffer; + if (b->curs_row > b->lines) + b->curs_row--; else { - row_roll(t->scroll_top, t->scroll_bot, -1); - row_set(t->scroll_top, 0, t->cols, t); + row_roll(b->scroll_top, b->scroll_bot, -1); + row_set(b->scroll_top, 0, b->cols, b); } } /* Interpret a 'next line' (NEL) sequence */ -static void interpret_csi_nel(Vt *vt) +static void interpret_csi_nel(Vt *t) { - Buffer *t = vt->buffer; - if (t->curs_row < t->lines + t->rows - 1) { - t->curs_row++; - t->curs_col = 0; + Buffer *b = t->buffer; + if (b->curs_row < b->lines + b->rows - 1) { + b->curs_row++; + b->curs_col = 0; } } @@ -969,39 +981,39 @@ handled: } } -static void process_nonprinting(Vt *vt, wchar_t wc) +static void process_nonprinting(Vt *t, wchar_t wc) { - Buffer *t = vt->buffer; + Buffer *b = t->buffer; switch (wc) { case C0_ESC: - new_escape_sequence(vt); + new_escape_sequence(t); break; case C0_BEL: - if (vt->bell) + if (t->bell) beep(); break; case C0_BS: - if (t->curs_col > 0) - t->curs_col--; + if (b->curs_col > 0) + b->curs_col--; break; case C0_HT: /* tab */ - t->curs_col = (t->curs_col + 8) & ~7; - if (t->curs_col >= t->cols) - t->curs_col = t->cols - 1; + b->curs_col = (b->curs_col + 8) & ~7; + if (b->curs_col >= b->cols) + b->curs_col = b->cols - 1; break; case C0_CR: - t->curs_col = 0; + b->curs_col = 0; break; case C0_VT: case C0_FF: case C0_LF: - cursor_line_down(vt); + cursor_line_down(t); break; case C0_SO: /* shift out, invoke the G1 character set */ - vt->graphmode = vt->charsets[1]; + t->graphmode = t->charsets[1]; break; case C0_SI: /* shift in, invoke the G0 character set */ - vt->graphmode = vt->charsets[0]; + t->graphmode = t->charsets[0]; break; } } @@ -1101,6 +1113,8 @@ int vt_process(Vt *t) { int res; unsigned int pos = 0; + mbstate_t ps; + memset(&ps, 0, sizeof(ps)); if (t->pty < 0) { errno = EINVAL; @@ -1116,7 +1130,7 @@ int vt_process(Vt *t) wchar_t wc; ssize_t len; - len = (ssize_t)mbrtowc(&wc, t->rbuf + pos, t->rlen - pos, &t->ps); + len = (ssize_t)mbrtowc(&wc, t->rbuf + pos, t->rlen - pos, &ps); if (len == -2) { t->rlen -= pos; memmove(t->rbuf, t->rbuf + pos, t->rlen); @@ -1149,12 +1163,12 @@ static void buffer_free(Buffer *t) for (int i = 0; i < t->rows; i++) free(t->lines[i].cells); free(t->lines); - for (int i = 0; i < t->scroll_buf_sz; i++) + for (int i = 0; i < t->scroll_buf_size; i++) free(t->scroll_buf[i].cells); free(t->scroll_buf); } -static bool buffer_init(Buffer *t, int rows, int cols, int scroll_buf_sz) +static bool buffer_init(Buffer *t, int rows, int cols, int scroll_buf_size) { Row *lines, *scroll_buf; t->lines = lines = calloc(rows, sizeof(Row)); @@ -1171,15 +1185,15 @@ static bool buffer_init(Buffer *t, int rows, int cols, int scroll_buf_sz) row_set(row, 0, cols, NULL); } t->rows = rows; - if (scroll_buf_sz < 0) - scroll_buf_sz = 0; - t->scroll_buf = scroll_buf = calloc(scroll_buf_sz, sizeof(Row)); + if (scroll_buf_size < 0) + scroll_buf_size = 0; + t->scroll_buf = scroll_buf = calloc(scroll_buf_size, sizeof(Row)); if (!scroll_buf) goto fail; - for (Row *row = scroll_buf, *end = scroll_buf + scroll_buf_sz; row < end; row++) { + for (Row *row = scroll_buf, *end = scroll_buf + scroll_buf_size; row < end; row++) { row->cells = calloc(cols, sizeof(Cell)); if (!row->cells) { - t->scroll_buf_sz = row - scroll_buf; + t->scroll_buf_size = row - scroll_buf; goto fail; } } @@ -1188,7 +1202,7 @@ static bool buffer_init(Buffer *t, int rows, int cols, int scroll_buf_sz) /* initial scrolling area is the whole window */ t->scroll_top = lines; t->scroll_bot = lines + rows; - t->scroll_buf_sz = scroll_buf_sz; + t->scroll_buf_size = scroll_buf_size; t->maxcols = t->cols = cols; return true; @@ -1197,7 +1211,7 @@ fail: return false; } -Vt *vt_create(int rows, int cols, int scroll_buf_sz) +Vt *vt_create(int rows, int cols, int scroll_buf_size) { Vt *t; @@ -1210,12 +1224,13 @@ Vt *vt_create(int rows, int cols, int scroll_buf_sz) t->pty = -1; t->deffg = t->defbg = -1; - if (!buffer_init(&t->buffer_normal, rows, cols, scroll_buf_sz) || + if (!buffer_init(&t->buffer_normal, rows, cols, scroll_buf_size) || !buffer_init(&t->buffer_alternate, rows, cols, 0)) { free(t); return NULL; } t->buffer = &t->buffer_normal; + t->copymode_cmd_multiplier = 0; return t; } @@ -1244,7 +1259,7 @@ static void buffer_resize(Buffer *t, int rows, int cols) lines[row].dirty = true; } Row *sbuf = t->scroll_buf; - for (int row = 0; row < t->scroll_buf_sz; row++) { + for (int row = 0; row < t->scroll_buf_size; row++) { sbuf[row].cells = realloc(sbuf[row].cells, sizeof(Cell) * cols); if (t->cols < cols) row_set(sbuf + row, t->cols, cols - t->cols, NULL); @@ -1268,8 +1283,8 @@ static void buffer_resize(Buffer *t, int rows, int cols) /* prepare for backfill */ if (t->curs_row >= t->scroll_bot - 1) { deltarows = t->lines + rows - t->curs_row - 1; - if (deltarows > t->scroll_buf_len) - deltarows = t->scroll_buf_len; + if (deltarows > t->scroll_amount_above) + deltarows = t->scroll_amount_above; } } @@ -1293,6 +1308,8 @@ void vt_resize(Vt *t, int rows, int cols) return; vt_noscroll(t); + if (t->copymode) + vt_copymode_leave(t); buffer_resize(&t->buffer_normal, rows, cols); buffer_resize(&t->buffer_alternate, rows, cols); clamp_cursor_to_bounds(t); @@ -1306,43 +1323,113 @@ void vt_destroy(Vt *t) return; buffer_free(&t->buffer_normal); buffer_free(&t->buffer_alternate); + free(t->searchbuf); free(t); } -void vt_dirty(Vt *vt) +void vt_dirty(Vt *t) { - Buffer *t = vt->buffer; - for (Row *row = t->lines, *end = row + t->rows; row < end; row++) + Buffer *b = t->buffer; + for (Row *row = b->lines, *end = row + b->rows; row < end; row++) row->dirty = true; } -void vt_draw(Vt *vt, WINDOW * win, int srow, int scol) +static void copymode_get_selection_boundry(Vt *t, Row **start_row, int *start_col, Row **end_row, int *end_col, bool clip) { + Buffer *b = t->buffer; + if (t->copymode_sel_start_row >= b->lines && t->copymode_sel_start_row < b->lines + b->rows) { + /* within the current page */ + if (b->curs_row >= t->copymode_sel_start_row) { + *start_row = t->copymode_sel_start_row; + *end_row = b->curs_row; + *start_col = t->copymode_sel_start_col; + *end_col = b->curs_col; + } else { + *start_row = b->curs_row; + *end_row = t->copymode_sel_start_row; + *start_col = b->curs_col; + *end_col = t->copymode_sel_start_col; + } + if (b->curs_col < *start_col && *start_row == *end_row) { + *start_col = b->curs_col; + *end_col = t->copymode_sel_start_col; + } + } else { + /* part of the scrollback buffer is also selected */ + if (t->copymode_sel_start_row < b->lines) { + /* above the current page */ + if (clip) { + *start_row = b->lines; + *start_col = 0; + } else { + int copied_lines = b->lines - t->copymode_sel_start_row; + *start_row = &b->scroll_buf + [(b->scroll_buf_ptr - copied_lines + b->scroll_buf_size) % b->scroll_buf_size]; + *start_col = t->copymode_sel_start_col; + } + *end_row = b->curs_row; + *end_col = b->curs_col; + } else { + /* below the current page */ + *start_row = b->curs_row; + *start_col = b->curs_col; + if (clip) { + *end_row = b->lines + b->rows; + *end_col = b->cols - 1; + } else { + int copied_lines = t->copymode_sel_start_row -(b->lines + b->rows); + *end_row = &b->scroll_buf + [(b->scroll_buf_ptr + copied_lines) % b->scroll_buf_size]; + *end_col = t->copymode_sel_start_col; + } + } + } +} + +void vt_draw(Vt *t, WINDOW * win, int srow, int scol) { - Buffer *t = vt->buffer; + Buffer *b = t->buffer; + bool sel = false; + Row *sel_row_start, *sel_row_end; + int sel_col_start, sel_col_end; + + copymode_get_selection_boundry(t, &sel_row_start, &sel_col_start, &sel_row_end, &sel_col_end, true); curs_set(0); - for (int i = 0; i < t->rows; i++) { - Row *row = t->lines + i; + + for (int i = 0; i < b->rows; i++) { + Row *row = b->lines + i; if (!row->dirty) continue; wmove(win, srow + i, scol); Cell *cell = NULL; - for (int j = 0; j < t->cols; j++) { + for (int j = 0; j < b->cols; j++) { Cell *prev_cell = cell; cell = row->cells + j; if (!prev_cell || cell->attr != prev_cell->attr || cell->fg != prev_cell->fg || cell->bg != prev_cell->bg) { if (cell->attr == A_NORMAL) - cell->attr = vt->defattrs; + cell->attr = t->defattrs; if (cell->fg == -1) - cell->fg = vt->deffg; + cell->fg = t->deffg; if (cell->bg == -1) - cell->bg = vt->defbg; + cell->bg = t->defbg; + wattrset(win, (attr_t) cell->attr << NCURSES_ATTR_SHIFT); + wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), NULL); + } + + if (t->copymode_selecting && ((row > sel_row_start && row < sel_row_end) || + (row == sel_row_start && j >= sel_col_start && (row != sel_row_end || j <= sel_col_end)) || + (row == sel_row_end && j <= sel_col_end && (row != sel_row_start || j >= sel_col_start)))) { + wattrset(win, (attr_t) ((cell->attr << NCURSES_ATTR_SHIFT)|COPYMODE_ATTR)); + sel = true; + } else if (sel) { wattrset(win, (attr_t) cell->attr << NCURSES_ATTR_SHIFT); - wcolor_set(win, vt_color_get(vt, cell->fg, cell->bg), NULL); + wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), NULL); + sel = false; } + if (is_utf8 && cell->text >= 128) { char buf[MB_CUR_MAX + 1]; int len = wcrtomb(buf, cell->text, NULL); @@ -1356,29 +1443,41 @@ void vt_draw(Vt *vt, WINDOW * win, int srow, int scol) row->dirty = false; } - wmove(win, srow + t->curs_row - t->lines, scol + t->curs_col); - curs_set(vt_cursor(vt)); + wmove(win, srow + b->curs_row - b->lines, scol + b->curs_col); + + if (t->copymode_searching) { + wattrset(win, t->defattrs << NCURSES_ATTR_SHIFT); + mvwaddch(win, srow + b->rows - 1, 0, t->copymode_searching == 1 ? '/' : '?'); + int len = waddnwstr(win, t->searchbuf, b->cols - 1); + whline(win, ' ', b->cols - len - 1); + } + + curs_set(vt_cursor(t)); } -void vt_scroll(Vt *vt, int rows) +void vt_scroll(Vt *t, int rows) { - Buffer *t = vt->buffer; + Buffer *b = t->buffer; + if (!b->scroll_buf_size) + return; if (rows < 0) { /* scroll back */ - if (rows < -t->scroll_buf_len) - rows = -t->scroll_buf_len; + if (rows < -b->scroll_amount_above) + rows = -b->scroll_amount_above; } else { /* scroll forward */ - if (rows > t->scroll_amount) - rows = t->scroll_amount; + if (rows > b->scroll_amount_below) + rows = b->scroll_amount_below; } - fill_scroll_buf(t, rows); - t->scroll_amount -= rows; + fill_scroll_buf(b, rows); + b->scroll_amount_below -= rows; + if (t->copymode_selecting) + t->copymode_sel_start_row -= rows; } void vt_noscroll(Vt *t) { - int scroll_amount = t->buffer->scroll_amount; - if (scroll_amount) - vt_scroll(t, scroll_amount); + int scroll_amount_below = t->buffer->scroll_amount_below; + if (scroll_amount_below) + vt_scroll(t, scroll_amount_below); } void vt_bell(Vt *t, bool bell) @@ -1450,12 +1549,12 @@ int vt_write(Vt *t, const char *buf, int len) return ret; } -static void send_curs(Vt *vt) +static void send_curs(Vt *t) { - Buffer *t = vt->buffer; + Buffer *b = t->buffer; char keyseq[16]; - sprintf(keyseq, "\e[%d;%dR", (int)(t->curs_row - t->lines), t->curs_col); - vt_write(vt, keyseq, strlen(keyseq)); + sprintf(keyseq, "\e[%d;%dR", (int)(b->curs_row - b->lines), b->curs_col); + vt_write(t, keyseq, strlen(keyseq)); } void vt_keypress(Vt *t, int keycode) @@ -1482,6 +1581,424 @@ void vt_keypress(Vt *t, int keycode) } } +static Row *buffer_next_row(Buffer *t, Row *row, int direction) +{ + bool has_scroll_buf = t->scroll_buf_size > 0; + Row *before_start_row, *before_end_row, *after_start_row, *after_end_row; + Row *first_row = t->lines; + Row *last_row = t->lines + t->rows - 1; + + if (has_scroll_buf) { + before_end_row = &t->scroll_buf + [(t->scroll_buf_ptr - 1 + t->scroll_buf_size) % t->scroll_buf_size]; + before_start_row = &t->scroll_buf + [(t->scroll_buf_ptr - t->scroll_amount_above + t->scroll_buf_size) % t->scroll_buf_size]; + after_start_row = &t->scroll_buf[t->scroll_buf_ptr]; + after_end_row = &t->scroll_buf + [(t->scroll_buf_ptr + t->scroll_amount_below - 1) % t->scroll_buf_size]; + } + + if (direction > 0) { + if (row >= first_row && row < last_row) + return ++row; + if (row == last_row) { + if (has_scroll_buf) { + if (t->scroll_amount_below) + return after_start_row; + else if (t->scroll_amount_above) + return before_start_row; + } + return first_row; + } + if (row == before_end_row) + return first_row; + if (row == after_end_row) + return t->scroll_amount_above ? before_start_row : first_row; + if (row == &t->scroll_buf[t->scroll_buf_size - 1]) + return t->scroll_buf; + return ++row; + } else { + if (row > first_row && row <= last_row) + return --row; + if (row == first_row) { + if (has_scroll_buf) { + if (t->scroll_amount_above) + return before_end_row; + else if (t->scroll_amount_below) + return after_end_row; + } + return last_row; + } + if (row == before_start_row) + return t->scroll_amount_below ? after_end_row : last_row; + if (row == after_start_row) + return last_row; + if (row == t->scroll_buf) + return &t->scroll_buf[t->scroll_buf_size - 1]; + return --row; + } +} + +static void row_show(Vt *t, Row *r) +{ + Buffer *b = t->buffer; + int below = b->scroll_amount_below; + int above = b->scroll_amount_above; + int ptr = b->scroll_buf_ptr; + int size = b->scroll_buf_size; + int row = r - b->scroll_buf; + int scroll = 0; + + if (b->lines <= r && r < b->lines + b->rows) { + b->curs_row = r; + return; + } + + if (!size) + return; + + if (row < ptr) { + if (row - ptr + size < below) + scroll = row - ptr + size + 1; + else if (ptr - row <= above) + scroll = row - ptr; + } else { + if (row - ptr < below) + scroll = row - ptr + 1; + else if (ptr - row + size <= above) + scroll = row - ptr - size; + } + + if (scroll) { + vt_scroll(t, scroll); + b->curs_row = b->lines + (scroll > 0 ? b->rows - 1 : 0); + } +} + +static void copymode_search(Vt *t, int direction) +{ + if (!t->searchbuf || t->searchbuf[0] == '\0') + return; + + Buffer *b = t->buffer; + /* avoid match at current cursor position */ + Row *start_row = b->curs_row; + int start_col = b->curs_col + direction; + if (start_col >= b->cols) { + start_col = 0; + start_row = buffer_next_row(b, start_row, 1); + } else if (start_col < 0) { + start_col = b->cols - 1; + start_row = buffer_next_row(b, start_row, -1); + } + + Row *row = start_row, *matched_row = NULL; + int matched_col = 0; + int end_col = direction > 0 ? b->cols - 1 : 0; + int s_start = direction > 0 ? 0 : t->searchbuf_curs - 1; + int s_end = direction > 0 ? t->searchbuf_curs - 1 : 0; + int s = s_start; + + for (;;) { + int col = direction > 0 ? 0 : b->cols - 1; + if (row == start_row) + col = start_col; + for (;;) { + if (t->searchbuf[s] == row->cells[col].text) { + if (s == s_start) { + matched_row = row; + matched_col = col; + } + if (s == s_end) { + b->curs_col = matched_col; + if (matched_row) + row_show(t, matched_row); + return; + } + s += direction; + } else + s = s_start; + + if (col == end_col) + break; + col += direction; + } + + if ((row = buffer_next_row(b, row, direction)) == start_row) + break; + } +} + +void vt_copymode_keypress(Vt *t, int keycode) +{ + Buffer *b = t->buffer; + Row *start_row, *end_row; + int direction, col, start_col, end_col, delta, scroll_page = b->rows / 2; + char *copybuf, keychar = (char)keycode; + wchar_t wc; + ssize_t len; + bool found; + + if (!t->copymode) + return; + + if (t->copymode_searching) { + switch (keycode) { + case KEY_BACKSPACE: + if (--t->searchbuf_curs < 0) + t->searchbuf_curs = 0; + t->searchbuf[t->searchbuf_curs] = '\0'; + break; + case '\n': + copymode_search(t, t->copymode_searching); + case '\e': + t->copymode_searching = 0; + b->lines[b->rows - 1].dirty = true; + break; + default: + len = (ssize_t)mbrtowc(&wc, &keychar, 1, &t->searchbuf_ps); + + if (len == -2) + return; + if (len == -1) + wc = keycode; + if (t->searchbuf_curs >= t->searchbuf_size - 2) { + t->searchbuf_size *= 2; + wchar_t *buf = realloc(t->searchbuf, t->searchbuf_size * sizeof(wchar_t)); + if (!buf) + return; + t->searchbuf = buf; + } + t->searchbuf[t->searchbuf_curs++] = wc; + t->searchbuf[t->searchbuf_curs] = '\0'; + break; + } + } else { + switch (keycode) { + case '0' ... '9': + t->copymode_cmd_multiplier = (t->copymode_cmd_multiplier * 10) + (keychar - '0'); + return; + case KEY_PPAGE: + delta = b->curs_row - b->lines; + if (delta > scroll_page) + b->curs_row -= scroll_page; + else { + b->curs_row = b->lines; + vt_scroll(t, delta - scroll_page); + } + break; + case KEY_NPAGE: + delta = b->rows - (b->curs_row - b->lines); + if (delta > scroll_page) + b->curs_row += scroll_page; + else { + b->curs_row = b->lines + b->rows - 1; + vt_scroll(t, scroll_page - delta); + } + break; + case 'g': + if (b->scroll_amount_above) + vt_scroll(t, -b->scroll_amount_above); + /* fall through */ + case 'H': + b->curs_row = b->lines; + break; + case 'M': + b->curs_row = b->lines + (b->rows / 2); + break; + case 'G': + vt_noscroll(t); + /* fall through */ + case 'L': + b->curs_row = b->lines + b->rows - 1; + break; + case KEY_HOME: + case '^': + b->curs_col = 0; + break; + case KEY_END: + case '$': + start_col = b->cols - 1; + for (int i = 0; i < b->cols; i++) + if (b->curs_row->cells[i].text) + start_col = i; + b->curs_col = start_col; + break; + case '/': + case '?': + memset(&t->searchbuf_ps, 0, sizeof(mbstate_t)); + if (!t->searchbuf) { + t->searchbuf_size = b->cols+1; + t->searchbuf = malloc(t->searchbuf_size * sizeof(wchar_t)); + } + if (!t->searchbuf) + return; + t->searchbuf[0] = L'\0'; + t->searchbuf_curs = 0; + t->copymode_searching = keycode == '/' ? 1 : -1; + break; + case 'n': + case 'N': + copymode_search(t, keycode == 'n' ? 1 : -1); + break; + case 'v': + t->copymode_selecting = true; + t->copymode_sel_start_row = b->curs_row; + t->copymode_sel_start_col = b->curs_col; + break; + case 'y': + if (!t->copymode_selecting) { + b->curs_col = 0; + t->copymode_sel_start_row = b->curs_row + + (t->copymode_cmd_multiplier ? t->copymode_cmd_multiplier - 1 : 0); + if (t->copymode_sel_start_row >= b->lines + b->rows) + t->copymode_sel_start_row = b->lines + b->rows - 1; + t->copymode_sel_start_col = b->cols - 1; + } + + copymode_get_selection_boundry(t, &start_row, &start_col, &end_row, &end_col, false); + int line_count = t->copymode_sel_start_row > b->curs_row ? + t->copymode_sel_start_row - b->curs_row : + b->curs_row - t->copymode_sel_start_row; + copybuf = calloc(1, (line_count + 1) * b->cols * MB_CUR_MAX + 1); + + if (copybuf) { + char *s = copybuf; + mbstate_t ps; + memset(&ps, 0, sizeof(ps)); + Row *row = start_row; + for (;;) { + char *last_non_space = s; + int j = (row == start_row) ? start_col : 0; + int col = (row == end_row) ? end_col : b->cols - 1; + for (size_t len = 0; j <= col; j++) { + if (row->cells[j].text) { + len = wcrtomb(s, row->cells[j].text, &ps); + if (len > 0) + s += len; + last_non_space = s; + } else if (len) { + len = 0; + } else { + *s++ = ' '; + } + } + + s = last_non_space; + + if (row == end_row) + break; + else + *s++ = '\n'; + + row = buffer_next_row(b, row, 1); + } + *s = '\0'; + if (t->event_handler) + t->event_handler(t, VT_EVENT_COPY_TEXT, copybuf); + } + /* fall through */ + case '\e': + case 'q': + vt_copymode_leave(t); + return; + default: + for (int c = 0; c < (t->copymode_cmd_multiplier ? t->copymode_cmd_multiplier : 1); c++) { + switch (keycode) { + case 'w': + case 'W': + case 'b': + case 'B': + direction = (keycode == 'w' || keycode == 'W') ? 1 : -1; + start_col = (direction > 0) ? 0 : b->cols - 1; + end_col = (direction > 0) ? b->cols - 1 : 0; + col = b->curs_col; + found = false; + do { + for (;;) { + if (b->curs_row->cells[col].text == ' ') { + found = true; + break; + } + + if (col == end_col) + break; + col += direction; + } + + if (found) { + while (b->curs_row->cells[col].text == ' ') { + if (col == end_col) { + b->curs_row += direction; + break; + } + col += direction; + } + } else { + col = start_col; + b->curs_row += direction; + } + + if (b->curs_row < b->lines) { + b->curs_row = b->lines; + if (b->scroll_amount_above) + vt_scroll(t, -1); + else + break; + } + + if (b->curs_row >= b->lines + b->rows) { + b->curs_row = b->lines + b->rows - 1; + if (b->scroll_amount_below) + vt_scroll(t, 1); + else + break; + } + } while (!found); + + if (found) + b->curs_col = col; + break; + case KEY_UP: + case 'k': + if (b->curs_row == b->lines) + vt_scroll(t, -1); + else + b->curs_row--; + break; + case KEY_DOWN: + case 'j': + if (b->curs_row == b->lines + b->rows - 1) + vt_scroll(t, 1); + else + b->curs_row++; + break; + case KEY_RIGHT: + case 'l': + b->curs_col++; + if (b->curs_col >= b->cols) { + b->curs_col = b->cols - 1; + t->copymode_cmd_multiplier = 0; + } + break; + case KEY_LEFT: + case 'h': + b->curs_col--; + if (b->curs_col < 0) { + b->curs_col = 0; + t->copymode_cmd_multiplier = 0; + } + break; + } + } + break; + } + } + if (t->copymode_selecting) + vt_dirty(t); + t->copymode_cmd_multiplier = 0; +} + void vt_mouse(Vt *t, int x, int y, mmask_t mask) { #ifdef NCURSES_MOUSE_VERSION @@ -1633,5 +2150,39 @@ void *vt_get_data(Vt *t) unsigned vt_cursor(Vt *t) { - return t->buffer->scroll_amount ? 0 : !t->curshid; + if (t->copymode) + return 1; + return t->buffer->scroll_amount_below ? 0 : !t->curshid; +} + +unsigned vt_copymode(Vt *t) +{ + return t->copymode; +} + +void vt_copymode_enter(Vt *t) +{ + if (t->copymode) + return; + Buffer *b = t->buffer; + t->copymode_curs_srow = b->curs_row - b->lines; + t->copymode_curs_scol = b->curs_col; + t->copymode = true; +} + +void vt_copymode_leave(Vt *t) +{ + if (!t->copymode) + return; + Buffer *b = t->buffer; + t->copymode = false; + t->copymode_selecting = false; + t->copymode_searching = false; + t->copymode_sel_start_row = b->lines; + t->copymode_sel_start_col = 0; + t->copymode_cmd_multiplier = 0; + b->curs_row = b->lines + t->copymode_curs_srow; + b->curs_col = t->copymode_curs_scol; + vt_noscroll(t); + vt_dirty(t); } diff --git a/vt.h b/vt.h @@ -55,6 +55,7 @@ typedef int (*vt_escseq_handler_t)(Vt *, char *es); enum { VT_EVENT_TITLE, + VT_EVENT_COPY_TEXT, }; typedef void (*vt_event_handler_t)(Vt *, int event, void *data); @@ -89,4 +90,9 @@ void vt_noscroll(Vt *); void vt_bell(Vt *, bool bell); void vt_togglebell(Vt *); +void vt_copymode_enter(Vt *t); +void vt_copymode_leave(Vt *t); +unsigned vt_copymode(Vt *t); +void vt_copymode_keypress(Vt *t, int keycode); + #endif /* VT_VT_H */