The branch, master has been updated via bdea2f9eda1abc8f4a6c423af62ef7538b2ad249 (commit) via f5b041e3949e9a129d68d9919725c3afcd81ed5a (commit) via a96a8a1aabd64f0d79602ed7248cf73655066d92 (commit) via 064124cc5f3df250f0f866ae1df7bc4cd19bf833 (commit) via 7af5fec0387008c71bf29ad1e394871873acaf40 (commit) via f884fff8693de02cea0347f1c267dbd925e3b5f5 (commit) via 3977dba76139b5d4b0a6e60458d39a374beeedf3 (commit) via a41cd8d75b55da5a1e75e187b30b33917c18c1b2 (commit) via 662d471215d66d3c44a9251cef7d00b25587851d (commit) from e496a548d7b07c9a4be9ce8e750cf5423e3bafe3 (commit)
- Log ----------------------------------------------------------------- commit bdea2f9eda1abc8f4a6c423af62ef7538b2ad249 Merge: e496a54 f5b041e Author: Thomas Adam <tho...@xteddy.org> Commit: Thomas Adam <tho...@xteddy.org> Merge branch 'obsd-master' cmd-set-option.c | 14 ++++++++++++-- format.c | 2 ++ mode-key.c | 21 ++++++++++++++------- server-fn.c | 14 ++++++++++---- status.c | 13 ++++++++++--- tmux.1 | 1 + tmux.h | 3 +++ window-copy.c | 7 ++++++- 8 files changed, 58 insertions(+), 17 deletions(-) commit f5b041e3949e9a129d68d9919725c3afcd81ed5a Author: Nicholas Marriott <n...@openbsd.org> Commit: Nicholas Marriott <n...@openbsd.org> Add pane_synchronized format, from Romain Francoise. --- format.c | 2 ++ tmux.1 | 1 + 2 files changed, 3 insertions(+), 0 deletions(-) diff --git a/format.c b/format.c index 8ed1c1b..168ff5b 100644 --- a/format.c +++ b/format.c @@ -460,6 +460,8 @@ format_window_pane(struct format_tree *ft, struct window_pane *wp) format_add(ft, "pane_dead", "%d", wp->fd == -1); format_add(ft, "pane_in_mode", "%d", wp->screen != &wp->base); + format_add(ft, "pane_synchronized", "%d", + !!options_get_number(&wp->window->options, "synchronize-panes")); if (wp->tty != NULL) format_add(ft, "pane_tty", "%s", wp->tty); diff --git a/tmux.1 b/tmux.1 index 012f206..da010d1 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3068,6 +3068,7 @@ The following variables are available, where appropriate: .It Li "pane_height" Ta "" Ta "Height of pane" .It Li "pane_id" Ta "#D" Ta "Unique pane ID" .It Li "pane_in_mode" Ta "" Ta "If pane is in a mode" +.It Li "pane_synchronized" Ta "" Ta "If pane is synchronized" .It Li "pane_index" Ta "#P" Ta "Index of pane" .It Li "pane_pid" Ta "" Ta "PID of first process in pane" .It Li "pane_start_command" Ta "" Ta "Command pane started with" commit a96a8a1aabd64f0d79602ed7248cf73655066d92 Author: Nicholas Marriott <n...@openbsd.org> Commit: Nicholas Marriott <n...@openbsd.org> Clarify error messages when setting options, from Thomas Adam. --- cmd-set-option.c | 14 ++++++++++++-- 1 files changed, 12 insertions(+), 2 deletions(-) diff --git a/cmd-set-option.c b/cmd-set-option.c index 6c0fefb..3b822d8 100644 --- a/cmd-set-option.c +++ b/cmd-set-option.c @@ -128,8 +128,13 @@ cmd_set_option_exec(struct cmd *self, struct cmd_q *cmdq) oo = &global_w_options; else { wl = cmd_find_window(cmdq, args_get(args, 't'), NULL); - if (wl == NULL) + if (wl == NULL) { + cmdq_error(cmdq, + "couldn't set '%s'%s", optstr, + (!args_has(args, 't') && !args_has(args, + 'g')) ? " need target window or -g" : ""); return (CMD_RETURN_ERROR); + } oo = &wl->window->options; } } else if (table == session_options_table) { @@ -137,8 +142,13 @@ cmd_set_option_exec(struct cmd *self, struct cmd_q *cmdq) oo = &global_s_options; else { s = cmd_find_session(cmdq, args_get(args, 't'), 0); - if (s == NULL) + if (s == NULL) { + cmdq_error(cmdq, + "couldn't set '%s'%s", optstr, + (!args_has(args, 't') && !args_has(args, + 'g')) ? " need target session or -g" : ""); return (CMD_RETURN_ERROR); + } oo = &s->options; } } else { commit 064124cc5f3df250f0f866ae1df7bc4cd19bf833 Author: Nicholas Marriott <n...@openbsd.org> Commit: Nicholas Marriott <n...@openbsd.org> When the session option renumber-window is used, ensure we iterate over all sessions in that group when the winlinks are reordered, otherwise the winlink lists are out of sync with one another. From Thomas Adam. --- server-fn.c | 14 ++++++++++---- 1 files changed, 10 insertions(+), 4 deletions(-) diff --git a/server-fn.c b/server-fn.c index d92754e..efb95ac 100644 --- a/server-fn.c +++ b/server-fn.c @@ -262,8 +262,9 @@ server_lock_client(struct client *c) void server_kill_window(struct window *w) { - struct session *s, *next_s; - struct winlink *wl; + struct session *s, *next_s, *target_s; + struct session_group *sg; + struct winlink *wl; next_s = RB_MIN(sessions, &sessions); while (next_s != NULL) { @@ -280,8 +281,13 @@ server_kill_window(struct window *w) server_redraw_session_group(s); } - if (options_get_number(&s->options, "renumber-windows")) - session_renumber_windows(s); + if (options_get_number(&s->options, "renumber-windows")) { + if ((sg = session_group_find(s)) != NULL) { + TAILQ_FOREACH(target_s, &sg->sessions, gentry) + session_renumber_windows(target_s); + } else + session_renumber_windows(s); + } } recalculate_sizes(); } commit 7af5fec0387008c71bf29ad1e394871873acaf40 Author: Nicholas Marriott <n...@openbsd.org> Commit: Nicholas Marriott <n...@openbsd.org> Whitespace nits, from Ben Boeckel. --- mode-key.c | 21 ++++++++++++++------- status.c | 13 ++++++++++--- window-copy.c | 7 ++++++- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/mode-key.c b/mode-key.c index 54abcf3..78a8bdf 100644 --- a/mode-key.c +++ b/mode-key.c @@ -35,9 +35,7 @@ * * vi command mode is handled by having a mode flag in the struct which allows * two sets of bindings to be swapped between. A couple of editing commands - * (MODEKEYEDIT_SWITCHMODE, MODEKEYEDIT_SWITCHMODEAPPEND, - * MODEKEYEDIT_SWITCHMODEAPPENDLINE, and MODEKEYEDIT_SWITCHMODEBEGINLINE) - * are special-cased to do this. + * (any matching MODEKEYEDIT_SWITCHMODE*) are special-cased to do this. */ /* Edit keys command strings. */ @@ -67,6 +65,9 @@ const struct mode_key_cmdstr mode_key_cmdstr_edit[] = { { MODEKEYEDIT_SWITCHMODEAPPEND, "switch-mode-append" }, { MODEKEYEDIT_SWITCHMODEAPPENDLINE, "switch-mode-append-line" }, { MODEKEYEDIT_SWITCHMODEBEGINLINE, "switch-mode-begin-line" }, + { MODEKEYEDIT_SWITCHMODECHANGELINE, "switch-mode-change-line" }, + { MODEKEYEDIT_SWITCHMODESUBSTITUTE, "switch-mode-substitute" }, + { MODEKEYEDIT_SWITCHMODESUBSTITUTELINE, "switch-mode-substitute-line" }, { MODEKEYEDIT_TRANSPOSECHARS, "transpose-chars" }, { 0, NULL } @@ -166,9 +167,11 @@ const struct mode_key_entry mode_key_vi_edit[] = { { '0', 1, MODEKEYEDIT_STARTOFLINE }, { 'A', 1, MODEKEYEDIT_SWITCHMODEAPPENDLINE }, { 'B', 1, MODEKEYEDIT_PREVIOUSSPACE }, + { 'C', 1, MODEKEYEDIT_SWITCHMODECHANGELINE }, { 'D', 1, MODEKEYEDIT_DELETETOENDOFLINE }, { 'E', 1, MODEKEYEDIT_NEXTSPACEEND }, { 'I', 1, MODEKEYEDIT_SWITCHMODEBEGINLINE }, + { 'S', 1, MODEKEYEDIT_SWITCHMODESUBSTITUTELINE }, { 'W', 1, MODEKEYEDIT_NEXTSPACE }, { 'X', 1, MODEKEYEDIT_BACKSPACE }, { '\003' /* C-c */, 1, MODEKEYEDIT_CANCEL }, @@ -185,6 +188,7 @@ const struct mode_key_entry mode_key_vi_edit[] = { { 'k', 1, MODEKEYEDIT_HISTORYUP }, { 'l', 1, MODEKEYEDIT_CURSORRIGHT }, { 'p', 1, MODEKEYEDIT_PASTE }, + { 's', 1, MODEKEYEDIT_SWITCHMODESUBSTITUTE }, { 'w', 1, MODEKEYEDIT_NEXTWORD }, { 'x', 1, MODEKEYEDIT_DELETE }, { KEYC_BSPACE, 1, MODEKEYEDIT_BACKSPACE }, @@ -227,8 +231,8 @@ const struct mode_key_entry mode_key_vi_choice[] = { { KEYC_UP | KEYC_CTRL, 0, MODEKEYCHOICE_SCROLLUP }, { KEYC_UP, 0, MODEKEYCHOICE_UP }, { ' ', 0, MODEKEYCHOICE_TREE_TOGGLE }, - { KEYC_LEFT, 0, MODEKEYCHOICE_TREE_COLLAPSE }, - { KEYC_RIGHT, 0, MODEKEYCHOICE_TREE_EXPAND }, + { KEYC_LEFT, 0, MODEKEYCHOICE_TREE_COLLAPSE }, + { KEYC_RIGHT, 0, MODEKEYCHOICE_TREE_EXPAND }, { KEYC_LEFT | KEYC_CTRL, 0, MODEKEYCHOICE_TREE_COLLAPSE_ALL }, { KEYC_RIGHT | KEYC_CTRL, 0, MODEKEYCHOICE_TREE_EXPAND_ALL }, @@ -369,8 +373,8 @@ const struct mode_key_entry mode_key_emacs_choice[] = { { KEYC_UP | KEYC_CTRL, 0, MODEKEYCHOICE_SCROLLUP }, { KEYC_UP, 0, MODEKEYCHOICE_UP }, { ' ', 0, MODEKEYCHOICE_TREE_TOGGLE }, - { KEYC_LEFT, 0, MODEKEYCHOICE_TREE_COLLAPSE }, - { KEYC_RIGHT, 0, MODEKEYCHOICE_TREE_EXPAND }, + { KEYC_LEFT, 0, MODEKEYCHOICE_TREE_COLLAPSE }, + { KEYC_RIGHT, 0, MODEKEYCHOICE_TREE_EXPAND }, { KEYC_LEFT | KEYC_CTRL, 0, MODEKEYCHOICE_TREE_COLLAPSE_ALL }, { KEYC_RIGHT | KEYC_CTRL, 0, MODEKEYCHOICE_TREE_EXPAND_ALL }, @@ -545,6 +549,9 @@ mode_key_lookup(struct mode_key_data *mdata, int key, const char **arg) case MODEKEYEDIT_SWITCHMODEAPPEND: case MODEKEYEDIT_SWITCHMODEAPPENDLINE: case MODEKEYEDIT_SWITCHMODEBEGINLINE: + case MODEKEYEDIT_SWITCHMODECHANGELINE: + case MODEKEYEDIT_SWITCHMODESUBSTITUTE: + case MODEKEYEDIT_SWITCHMODESUBSTITUTELINE: mdata->mode = 1 - mdata->mode; /* FALLTHROUGH */ default: diff --git a/status.c b/status.c index ecfd7e5..c5572b7 100644 --- a/status.c +++ b/status.c @@ -142,10 +142,8 @@ status_set_window_at(struct client *c, u_int x) x += c->wlmouse; RB_FOREACH(wl, winlinks, &s->windows) { - if (x < wl->status_width && - session_select(s, wl->idx) == 0) { + if (x < wl->status_width && session_select(s, wl->idx) == 0) server_redraw_session(s); - } x -= wl->status_width + 1; } } @@ -938,6 +936,7 @@ status_prompt_redraw(struct client *c) off = 0; memcpy(&gc, &grid_default_cell, sizeof gc); + /* Change colours for command mode. */ if (c->prompt_mdata.mode == 1) { colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg")); @@ -1099,6 +1098,7 @@ status_prompt_key(struct client *c, int key) } break; case MODEKEYEDIT_DELETE: + case MODEKEYEDIT_SWITCHMODESUBSTITUTE: if (c->prompt_index != size) { memmove(c->prompt_buffer + c->prompt_index, c->prompt_buffer + c->prompt_index + 1, @@ -1107,11 +1107,13 @@ status_prompt_key(struct client *c, int key) } break; case MODEKEYEDIT_DELETELINE: + case MODEKEYEDIT_SWITCHMODESUBSTITUTELINE: *c->prompt_buffer = '\0'; c->prompt_index = 0; c->flags |= CLIENT_STATUS; break; case MODEKEYEDIT_DELETETOENDOFLINE: + case MODEKEYEDIT_SWITCHMODECHANGELINE: if (c->prompt_index < size) { c->prompt_buffer[c->prompt_index] = '\0'; c->flags |= CLIENT_STATUS; @@ -1190,6 +1192,11 @@ status_prompt_key(struct client *c, int key) break; } + /* Back up to the end-of-word like vi. */ + if (options_get_number(oo, "status-keys") == MODEKEY_VI && + c->prompt_index != 0) + c->prompt_index--; + c->flags |= CLIENT_STATUS; break; case MODEKEYEDIT_PREVIOUSSPACE: diff --git a/window-copy.c b/window-copy.c index f5f78cf..9d4ff20 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1116,7 +1116,7 @@ window_copy_write_line( struct options *oo = &wp->window->options; struct grid_cell gc; char hdr[32]; - size_t last, xoff = 0, size = 0; + size_t last, xoff = 0, size = 0; window_mode_attrs(&gc, oo); @@ -1894,6 +1894,7 @@ void window_copy_cursor_next_word_end(struct window_pane *wp, const char *separators) { struct window_copy_mode_data *data = wp->modedata; + struct options *oo = &wp->window->options; struct screen *back_s = data->backing; u_int px, py, xx, yy; int expected = 1; @@ -1927,6 +1928,10 @@ window_copy_cursor_next_word_end(struct window_pane *wp, const char *separators) expected = !expected; } while (expected == 0); + /* Back up to the end-of-word like vi. */ + if (options_get_number(oo, "status-keys") == MODEKEY_VI && px != 0) + px--; + window_copy_update_cursor(wp, px, data->cy); if (window_copy_update_selection(wp)) window_copy_redraw_lines(wp, data->cy, 1); commit f884fff8693de02cea0347f1c267dbd925e3b5f5 Author: Nicholas Marriott <n...@openbsd.org> Commit: Nicholas Marriott <n...@openbsd.org> Implement s, S, C mode switch commands in vi(1) mode, from Ben Boeckel. --- tmux.h | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diff --git a/tmux.h b/tmux.h index b3f314c..b5608da 100644 --- a/tmux.h +++ b/tmux.h @@ -548,6 +548,9 @@ enum mode_key_cmd { MODEKEYEDIT_SWITCHMODEAPPEND, MODEKEYEDIT_SWITCHMODEAPPENDLINE, MODEKEYEDIT_SWITCHMODEBEGINLINE, + MODEKEYEDIT_SWITCHMODECHANGELINE, + MODEKEYEDIT_SWITCHMODESUBSTITUTE, + MODEKEYEDIT_SWITCHMODESUBSTITUTELINE, MODEKEYEDIT_TRANSPOSECHARS, /* Menu (choice) keys. */ commit 3977dba76139b5d4b0a6e60458d39a374beeedf3 Author: Nicholas Marriott <n...@openbsd.org> Commit: Nicholas Marriott <n...@openbsd.org> Focus events can cause trouble if left on and they can't be turned off during idle periods (like the other states are) because we'd miss events. So add a server option to control them. Defaults to off. --- options-table.c | 5 +++++ server-client.c | 13 +++++++++++-- tmux.1 | 8 ++++++++ tmux.h | 5 +++++ tty.c | 14 ++++++++++++-- 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/options-table.c b/options-table.c index c73842f..cf0202b 100644 --- a/options-table.c +++ b/options-table.c @@ -76,6 +76,11 @@ const struct options_table_entry server_options_table[] = { .default_num = 0 }, + { .name = "focus-events", + .type = OPTIONS_TABLE_FLAG, + .default_num = 0 + }, + { .name = "quiet", .type = OPTIONS_TABLE_FLAG, .default_num = 0 /* overridden in main() */ diff --git a/server-client.c b/server-client.c index 3b7b988..5f61f5c 100644 --- a/server-client.c +++ b/server-client.c @@ -548,6 +548,15 @@ server_client_check_focus(struct window_pane *wp) { u_int i; struct client *c; + int push; + + /* Are focus events off? */ + if (!options_get_number(&global_options, "focus-events")) + return; + + /* Do we need to push the focus state? */ + push = wp->flags & PANE_FOCUSPUSH; + wp->flags &= ~PANE_FOCUSPUSH; /* If we don't care about focus, forget it. */ if (!(wp->base.mode & MODE_FOCUSON)) @@ -580,13 +589,13 @@ server_client_check_focus(struct window_pane *wp) } not_focused: - if (wp->flags & PANE_FOCUSED) + if (push || (wp->flags & PANE_FOCUSED)) bufferevent_write(wp->event, "\033[O", 3); wp->flags &= ~PANE_FOCUSED; return; focused: - if (!(wp->flags & PANE_FOCUSED)) + if (push || !(wp->flags & PANE_FOCUSED)) bufferevent_write(wp->event, "\033[I", 3); wp->flags |= PANE_FOCUSED; } diff --git a/tmux.1 b/tmux.1 index e59b9a6..012f206 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2092,6 +2092,14 @@ The default is 500 milliseconds. .Op Ic on | off .Xc If enabled, the server will exit when there are no attached clients. +.It Xo Ic focus-events +.Op Ic on | off +.Xc +When enabled, focus events are requested from the terminal if supported and +passed through to applications running in +.Nm . +Attached clients should be detached and attached again after changing this +option. .It Xo Ic quiet .Op Ic on | off .Xc diff --git a/tmux.h b/tmux.h index b1624b6..b3f314c 100644 --- a/tmux.h +++ b/tmux.h @@ -941,6 +941,7 @@ struct window_pane { #define PANE_DROP 0x2 #define PANE_FOCUSED 0x4 #define PANE_RESIZE 0x8 +#define PANE_FOCUSPUSH 0x10 char *cmd; char *shell; @@ -1232,6 +1233,7 @@ struct tty { #define TTY_UTF8 0x8 #define TTY_STARTED 0x10 #define TTY_OPENED 0x20 +#define TTY_FOCUS 0x40 int flags; int term_flags; @@ -1383,6 +1385,9 @@ struct cmd { char *file; u_int line; +#define CMD_CONTROL 0x1 + int flags; + TAILQ_ENTRY(cmd) qentry; }; struct cmd_list { diff --git a/tty.c b/tty.c index cf6e965..c989aaa 100644 --- a/tty.c +++ b/tty.c @@ -219,8 +219,13 @@ tty_start_tty(struct tty *tty) if (tty_term_has(tty->term, TTYC_KMOUS)) tty_puts(tty, "\033[?1000l\033[?1006l\033[?1005l"); - if (tty_term_has(tty->term, TTYC_XT)) + if (tty_term_has(tty->term, TTYC_XT)) { + if (options_get_number(&global_options, "focus-events")) { + tty->flags |= TTY_FOCUS; + tty_puts(tty, "\033[?1004h"); + } tty_puts(tty, "\033[c\033[>4;1m\033[m"); + } tty->cx = UINT_MAX; tty->cy = UINT_MAX; @@ -282,8 +287,13 @@ tty_stop_tty(struct tty *tty) if (tty_term_has(tty->term, TTYC_KMOUS)) tty_raw(tty, "\033[?1000l\033[?1006l\033[?1005l"); - if (tty_term_has(tty->term, TTYC_XT)) + if (tty_term_has(tty->term, TTYC_XT)) { + if (tty->flags & TTY_FOCUS) { + tty->flags &= ~TTY_FOCUS; + tty_puts(tty, "\033[?1004l"); + } tty_raw(tty, "\033[>4m\033[m"); + } tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP)); commit a41cd8d75b55da5a1e75e187b30b33917c18c1b2 Author: Nicholas Marriott <n...@openbsd.org> Commit: Nicholas Marriott <n...@openbsd.org> Always push a focus event when the application turns it on, prompted by discussion with Hayaki Saito a while ago. --- input.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/input.c b/input.c index 0dcdee9..5f70f87 100644 --- a/input.c +++ b/input.c @@ -1333,7 +1333,7 @@ input_csi_dispatch(struct input_ctx *ictx) if (s->mode & MODE_FOCUSON) break; screen_write_mode_set(&ictx->ctx, MODE_FOCUSON); - wp->flags &= ~PANE_FOCUSED; /* force update if needed */ + wp->flags |= PANE_FOCUSPUSH; /* force update */ break; case 1005: screen_write_mode_set(&ictx->ctx, MODE_MOUSE_UTF8); commit 662d471215d66d3c44a9251cef7d00b25587851d Author: Nicholas Marriott <n...@openbsd.org> Commit: Nicholas Marriott <n...@openbsd.org> Mark control commands specially so the client can identify them, based on a diff from George Nachman a while back. --- cmd-queue.c | 7 +++++-- control.c | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd-queue.c b/cmd-queue.c index a64d332..904b092 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -157,14 +157,17 @@ int cmdq_guard(struct cmd_q *cmdq, const char *guard) { struct client *c = cmdq->client; + int flags; if (c == NULL) return 0; if (!(c->flags & CLIENT_CONTROL)) return 0; - evbuffer_add_printf(c->stdout_data, "%%%s %ld %u\n", guard, - (long) cmdq->time, cmdq->number); + flags = !!(cmdq->cmd->flags & CMD_CONTROL); + + evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard, + (long) cmdq->time, cmdq->number, flags); server_push_stdout(c); return 1; } diff --git a/control.c b/control.c index 8986f5c..1f3739f 100644 --- a/control.c +++ b/control.c @@ -55,6 +55,7 @@ control_callback(struct client *c, int closed, unused void *data) { char *line, *cause; struct cmd_list *cmdlist; + struct cmd *cmd; if (closed) c->flags |= CLIENT_EXIT; @@ -78,6 +79,8 @@ control_callback(struct client *c, int closed, unused void *data) free(cause); } else { + TAILQ_FOREACH(cmd, &cmdlist->list, qentry) + cmd->flags |= CMD_CONTROL; cmdq_run(c->cmdq, cmdlist); cmd_list_free(cmdlist); } ----------------------------------------------------------------------- Summary of changes: cmd-set-option.c | 14 ++++++++++++-- format.c | 2 ++ mode-key.c | 21 ++++++++++++++------- server-fn.c | 14 ++++++++++---- status.c | 13 ++++++++++--- tmux.1 | 1 + tmux.h | 3 +++ window-copy.c | 7 ++++++- 8 files changed, 58 insertions(+), 17 deletions(-) hooks/post-receive -- tmux ------------------------------------------------------------------------------ This SF.net email is sponsored by Windows: Build for Windows Store. http://p.sf.net/sfu/windows-dev2dev _______________________________________________ tmux-cvs mailing list tmux-cvs@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/tmux-cvs