From 6369ea10d7f45dfc5c77e7ba163403b37e254e0f Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 8 May 2014 05:53:29 +0000 Subject: [PATCH 1/9] Handle colour 8 properly in the 256 colour palette, from Timothy Allen. --- style.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/style.c b/style.c index 99744086..2a049fb6 100644 --- a/style.c +++ b/style.c @@ -117,7 +117,7 @@ style_tostring(struct grid_cell *gc) *s = '\0'; - if (gc->fg != 8) { + if (gc->fg != 8 || gc->flags & GRID_FLAG_FG256) { if (gc->flags & GRID_FLAG_FG256) c = gc->fg | 0x100; else @@ -126,7 +126,7 @@ style_tostring(struct grid_cell *gc) comma = 1; } - if (gc->bg != 8) { + if (gc->bg != 8 || gc->flags & GRID_FLAG_BG256) { if (gc->flags & GRID_FLAG_BG256) c = gc->bg | 0x100; else @@ -221,13 +221,13 @@ style_apply_update(struct grid_cell *gc, struct options *oo, const char *name) struct grid_cell *gcp; gcp = options_get_style(oo, name); - if (gcp->fg != 8) { + if (gcp->fg != 8 || gcp->flags & GRID_FLAG_FG256) { if (gcp->flags & GRID_FLAG_FG256) colour_set_fg(gc, gcp->fg | 0x100); else colour_set_fg(gc, gcp->fg); } - if (gcp->bg != 8) { + if (gcp->bg != 8 || gcp->flags & GRID_FLAG_BG256) { if (gcp->flags & GRID_FLAG_BG256) colour_set_bg(gc, gcp->bg | 0x100); else From 540f0b3e45acae9b0c5fa12f7e9170c6c78d177f Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 8 May 2014 06:03:30 +0000 Subject: [PATCH 2/9] Both the two previous ways of navigating panes by direction have irritating flaws: a) The old way of always using the top or left if the choice is ambiguous is annoying when the layout is unbalanced. b) The new way of remembering the last used pane is annoying if the layout is balanced and the leftmost is obvious to the user (because clearly if we go right from the top-left in a tiled set of four we want to end up in top-right, even if we were last using the bottom-right). So instead, use a combination of both: if there is only one possible pane alongside the current pane, move to it, otherwise choose the most recently used of the choice. --- layout.c | 1 - tmux.h | 4 +- window.c | 267 +++++++++++++++++++++++++++++-------------------------- 3 files changed, 143 insertions(+), 129 deletions(-) diff --git a/layout.c b/layout.c index 1c76f986..b91b86cd 100644 --- a/layout.c +++ b/layout.c @@ -53,7 +53,6 @@ layout_create_cell(struct layout_cell *lcparent) lc->yoff = UINT_MAX; lc->wp = NULL; - lc->lastwp = NULL; return (lc); } diff --git a/tmux.h b/tmux.h index 68a8517b..b0a37f25 100644 --- a/tmux.h +++ b/tmux.h @@ -892,6 +892,7 @@ struct window_choose_mode_item { /* Child window structure. */ struct window_pane { u_int id; + u_int active_point; struct window *window; @@ -948,6 +949,7 @@ struct window_pane { }; TAILQ_HEAD(window_panes, window_pane); RB_HEAD(window_pane_tree, window_pane); +ARRAY_DECL(window_pane_list, struct window_pane *); /* Window structure. */ struct window { @@ -1025,8 +1027,6 @@ struct layout_cell { u_int yoff; struct window_pane *wp; - struct window_pane *lastwp; - struct layout_cells cells; TAILQ_ENTRY(layout_cell) entry; diff --git a/window.c b/window.c index 1b36f7d7..40ebcdb5 100644 --- a/window.c +++ b/window.c @@ -56,15 +56,14 @@ struct windows windows; struct window_pane_tree all_window_panes; u_int next_window_pane_id; u_int next_window_id; - -struct window_pane *window_pane_active_set(struct window_pane *, - struct window_pane *); -void window_pane_active_lost(struct window_pane *, struct window_pane *); +u_int next_active_point; void window_pane_timer_callback(int, short, void *); void window_pane_read_callback(struct bufferevent *, void *); void window_pane_error_callback(struct bufferevent *, short, void *); +struct window_pane *window_pane_choose_best(struct window_pane_list *); + RB_GENERATE(winlinks, winlink, entry, winlink_cmp); int @@ -387,64 +386,6 @@ window_resize(struct window *w, u_int sx, u_int sy) w->sy = sy; } -/* - * Restore previously active pane when changing from wp to nextwp. The intended - * pane is in nextwp and it returns the previously focused pane. - */ -struct window_pane * -window_pane_active_set(struct window_pane *wp, struct window_pane *nextwp) -{ - struct layout_cell *lc; - struct window_pane *lastwp; - - /* Target pane's parent must not be an ancestor of source pane. */ - for (lc = wp->layout_cell->parent; lc != NULL; lc = lc->parent) { - if (lc == nextwp->layout_cell->parent) - return (nextwp); - } - - /* - * Previously active pane, if any, must not be the same as the source - * pane. - */ - lc = nextwp->layout_cell->parent; - if (lc != NULL && lc->lastwp != NULL) { - lastwp = lc->lastwp; - if (lastwp != wp && window_pane_visible(lastwp)) - return (lastwp); - } - return (nextwp); -} - -/* Remember previously active pane when changing from wp to nextwp. */ -void -window_pane_active_lost(struct window_pane *wp, struct window_pane *nextwp) -{ - struct layout_cell *lc, *lc2, *lcparent; - - /* Get the parent cell. */ - lcparent = nextwp->layout_cell->parent; - if (lcparent == NULL) - return; - - /* Save the target pane in its parent. */ - lcparent->lastwp = nextwp; - - /* - * Save the source pane in all of its parents up to, but not including, - * the common ancestor of itself and the target panes. - */ - if (wp == NULL) - return; - for (lc = wp->layout_cell->parent; lc != NULL; lc = lc->parent) { - for (lc2 = lcparent; lc2 != NULL; lc2 = lc2->parent) { - if (lc == lc2) - return; - } - lc->lastwp = wp; - } -} - void window_set_active_pane(struct window *w, struct window_pane *wp) { @@ -452,7 +393,6 @@ window_set_active_pane(struct window *w, struct window_pane *wp) return; w->last = w->active; w->active = wp; - window_pane_active_lost(w->last, wp); while (!window_pane_visible(w->active)) { w->active = TAILQ_PREV(w->active, window_panes, entry); if (w->active == NULL) @@ -460,6 +400,7 @@ window_set_active_pane(struct window *w, struct window_pane *wp) if (w->active == wp) return; } + w->active->active_point = next_active_point++; } struct window_pane * @@ -771,16 +712,6 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) void window_pane_destroy(struct window_pane *wp) { - struct window_pane *wp2; - - /* Forget removed pane in all layout cells that remember it. */ - RB_FOREACH(wp2, window_pane_tree, &all_window_panes) { - if (wp2->layout_cell != NULL && - wp2->layout_cell->parent != NULL && - wp2->layout_cell->parent->lastwp == wp) - wp2->layout_cell->parent->lastwp = NULL; - } - window_pane_reset_mode(wp); if (event_initialized(&wp->changes_timer)) @@ -1187,114 +1118,198 @@ window_pane_search(struct window_pane *wp, const char *searchstr, return (msg); } -/* Find the pane directly above another. */ +/* Get MRU pane from a list. */ +struct window_pane * +window_pane_choose_best(struct window_pane_list *list) +{ + struct window_pane *next, *best; + u_int i; + + if (ARRAY_LENGTH(list) == 0) + return (NULL); + + best = ARRAY_FIRST(list); + for (i = 1; i < ARRAY_LENGTH(list); i++) { + next = ARRAY_ITEM(list, i); + if (next->active_point > best->active_point) + best = next; + } + return (best); +} + +/* + * Find the pane directly above another. We build a list of those adjacent to + * top edge and then choose the best. + */ struct window_pane * window_pane_find_up(struct window_pane *wp) { - struct window_pane *wp2; - u_int left, top; + struct window_pane *next, *best; + u_int edge, left, right, end; + struct window_pane_list list; + int found; if (wp == NULL || !window_pane_visible(wp)) return (NULL); + ARRAY_INIT(&list); + + edge = wp->yoff; + if (edge == 0) + edge = wp->window->sy + 1; - top = wp->yoff; - if (top == 0) - top = wp->window->sy + 1; left = wp->xoff; + right = wp->xoff + wp->sx; - TAILQ_FOREACH(wp2, &wp->window->panes, entry) { - if (!window_pane_visible(wp2)) + TAILQ_FOREACH(next, &wp->window->panes, entry) { + if (next == wp || !window_pane_visible(next)) continue; - if (wp2->yoff + wp2->sy + 1 != top) + if (next->yoff + next->sy + 1 != edge) continue; - if (left >= wp2->xoff && left <= wp2->xoff + wp2->sx) - return (window_pane_active_set(wp, wp2)); + end = next->xoff + next->sx - 1; + + found = 0; + if (next->xoff < left && end > right) + found = 1; + else if (next->xoff >= left && next->xoff <= right) + found = 1; + else if (end >= left && end <= right) + found = 1; + if (found) + ARRAY_ADD(&list, next); } - return (NULL); + + best = window_pane_choose_best(&list); + ARRAY_FREE(&list); + return (best); } /* Find the pane directly below another. */ struct window_pane * window_pane_find_down(struct window_pane *wp) { - struct window_pane *wp2; - u_int left, bottom; + struct window_pane *next, *best; + u_int edge, left, right, end; + struct window_pane_list list; + int found; if (wp == NULL || !window_pane_visible(wp)) return (NULL); + ARRAY_INIT(&list); + + edge = wp->yoff + wp->sy + 1; + if (edge >= wp->window->sy) + edge = 0; - bottom = wp->yoff + wp->sy + 1; - if (bottom >= wp->window->sy) - bottom = 0; left = wp->xoff; + right = wp->xoff + wp->sx; - TAILQ_FOREACH(wp2, &wp->window->panes, entry) { - if (!window_pane_visible(wp2)) + TAILQ_FOREACH(next, &wp->window->panes, entry) { + if (next == wp || !window_pane_visible(next)) continue; - if (wp2->yoff != bottom) + if (next->yoff != edge) continue; - if (left >= wp2->xoff && left <= wp2->xoff + wp2->sx) - return (window_pane_active_set(wp, wp2)); + end = next->xoff + next->sx - 1; + + found = 0; + if (next->xoff < left && end > right) + found = 1; + else if (next->xoff >= left && next->xoff <= right) + found = 1; + else if (end >= left && end <= right) + found = 1; + if (found) + ARRAY_ADD(&list, next); } - return (NULL); + + best = window_pane_choose_best(&list); + ARRAY_FREE(&list); + return (best); } -/* - * Find the pane directly to the left of another, adjacent to the left side and - * containing the top edge. - */ +/* Find the pane directly to the left of another. */ struct window_pane * window_pane_find_left(struct window_pane *wp) { - struct window_pane *wp2; - u_int left, top; + struct window_pane *next, *best; + u_int edge, top, bottom, end; + struct window_pane_list list; + int found; if (wp == NULL || !window_pane_visible(wp)) return (NULL); + ARRAY_INIT(&list); + + edge = wp->xoff; + if (edge == 0) + edge = wp->window->sx + 1; - left = wp->xoff; - if (left == 0) - left = wp->window->sx + 1; top = wp->yoff; + bottom = wp->yoff + wp->sy; - TAILQ_FOREACH(wp2, &wp->window->panes, entry) { - if (!window_pane_visible(wp2)) + TAILQ_FOREACH(next, &wp->window->panes, entry) { + if (next == wp || !window_pane_visible(next)) continue; - if (wp2->xoff + wp2->sx + 1 != left) + if (next->xoff + next->sx + 1 != edge) continue; - if (top >= wp2->yoff && top <= wp2->yoff + wp2->sy) - return (window_pane_active_set(wp, wp2)); + end = next->yoff + next->sy - 1; + + found = 0; + if (next->yoff < top && end > bottom) + found = 1; + else if (next->yoff >= top && next->yoff <= bottom) + found = 1; + else if (end >= top && end <= bottom) + found = 1; + if (found) + ARRAY_ADD(&list, next); } - return (NULL); + + best = window_pane_choose_best(&list); + ARRAY_FREE(&list); + return (best); } -/* - * Find the pane directly to the right of another, that is adjacent to the - * right edge and including the top edge. - */ +/* Find the pane directly to the right of another. */ struct window_pane * window_pane_find_right(struct window_pane *wp) { - struct window_pane *wp2; - u_int right, top; + struct window_pane *next, *best; + u_int edge, top, bottom, end; + struct window_pane_list list; + int found; if (wp == NULL || !window_pane_visible(wp)) return (NULL); + ARRAY_INIT(&list); + + edge = wp->xoff + wp->sx + 1; + if (edge >= wp->window->sx) + edge = 0; - right = wp->xoff + wp->sx + 1; - if (right >= wp->window->sx) - right = 0; top = wp->yoff; + bottom = wp->yoff + wp->sy; - TAILQ_FOREACH(wp2, &wp->window->panes, entry) { - if (!window_pane_visible(wp2)) + TAILQ_FOREACH(next, &wp->window->panes, entry) { + if (next == wp || !window_pane_visible(next)) continue; - if (wp2->xoff != right) + if (next->xoff != edge) continue; - if (top >= wp2->yoff && top <= wp2->yoff + wp2->sy) - return (window_pane_active_set(wp, wp2)); + end = next->yoff + next->sy - 1; + + found = 0; + if (next->yoff < top && end > bottom) + found = 1; + else if (next->yoff >= top && next->yoff <= bottom) + found = 1; + else if (end >= top && end <= bottom) + found = 1; + if (found) + ARRAY_ADD(&list, next); } - return (NULL); + + best = window_pane_choose_best(&list); + ARRAY_FREE(&list); + return (best); } /* Clear alert flags for a winlink */ From 189017c078b7870c18ced485c1fd99f65fcc4801 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 8 May 2014 06:06:07 +0000 Subject: [PATCH 3/9] Plug a memory leak, from J Raynor. --- cmd-find-window.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd-find-window.c b/cmd-find-window.c index ccc14fa0..ccf7f0f2 100644 --- a/cmd-find-window.c +++ b/cmd-find-window.c @@ -200,6 +200,8 @@ cmd_find_window_exec(struct cmd *self, struct cmd_q *cmdq) window_choose_ready(wl->window->active, 0, cmd_find_window_callback); out: + for (i = 0; i < ARRAY_LENGTH(&find_list); i++) + free(ARRAY_ITEM(&find_list, i).list_ctx); ARRAY_FREE(&find_list); return (CMD_RETURN_NORMAL); } From 94ccc6aeaa72c84d66436d3f0cd23b4ab4d69c8e Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 8 May 2014 07:54:47 +0000 Subject: [PATCH 4/9] Instead of forcing mouse scroll to 1 in choose mode, scale it down instead. Means modifier keys still increase the line count, just not as much. Based on a diff from Marcel Partap. --- tmux.h | 3 +++ tty-keys.c | 6 +++--- window-choose.c | 19 ++++++++++++------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/tmux.h b/tmux.h index b0a37f25..04bbd935 100644 --- a/tmux.h +++ b/tmux.h @@ -1130,6 +1130,9 @@ LIST_HEAD(tty_terms, tty_term); #define MOUSE_WHEEL_UP 0 #define MOUSE_WHEEL_DOWN 1 +/* Mouse wheel multipler. */ +#define MOUSE_WHEEL_SCALE 3 + /* Mouse event bits. */ #define MOUSE_EVENT_DOWN 0x1 #define MOUSE_EVENT_DRAG 0x2 diff --git a/tty-keys.c b/tty-keys.c index 4f55a80c..297e22c8 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -752,11 +752,11 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size) if (b & MOUSE_MASK_SHIFT) m->scroll = 1; else - m->scroll = 3; + m->scroll = MOUSE_WHEEL_SCALE; if (b & MOUSE_MASK_META) - m->scroll *= 3; + m->scroll *= MOUSE_WHEEL_SCALE; if (b & MOUSE_MASK_CTRL) - m->scroll *= 3; + m->scroll *= MOUSE_WHEEL_SCALE; b &= MOUSE_MASK_BUTTONS; if (b == 0) diff --git a/window-choose.c b/window-choose.c index 34ec2eb7..99e88036 100644 --- a/window-choose.c +++ b/window-choose.c @@ -721,17 +721,22 @@ window_choose_mouse(struct window_pane *wp, struct session *sess, struct window_choose_mode_data *data = wp->modedata; struct screen *s = &data->screen; struct window_choose_mode_item *item; - u_int idx; + u_int idx, i, n; if (m->event == MOUSE_EVENT_WHEEL) { /* - * Don't use m->scroll and just move line-by-line or it's - * annoying. + * Multiple line scrolling by default is annoying, so scale + * m->scroll back down. */ - if (m->wheel == MOUSE_WHEEL_UP) - window_choose_key(wp, sess, KEYC_UP); - else - window_choose_key(wp, sess, KEYC_DOWN); + n = m->scroll; + if (n >= MOUSE_WHEEL_SCALE) + n /= MOUSE_WHEEL_SCALE; + for (i = 0; i < n; i++) { + if (m->wheel == MOUSE_WHEEL_UP) + window_choose_key(wp, sess, KEYC_UP); + else + window_choose_key(wp, sess, KEYC_DOWN); + } return; } From 353d1825d5a3a6efb1c3c71d9afbb96ba57cc948 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 8 May 2014 07:59:16 +0000 Subject: [PATCH 5/9] Send up and down keys for mouse wheel in alternate screen mode (when it normally does nothing), from Marcel Partap. --- input-keys.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/input-keys.c b/input-keys.c index 6e21cc0c..24566dfd 100644 --- a/input-keys.c +++ b/input-keys.c @@ -205,6 +205,21 @@ input_mouse(struct window_pane *wp, struct session *s, struct mouse_event *m) char buf[40]; size_t len; struct paste_buffer *pb; + u_int i; + + /* + * If the alternate screen is active and hasn't enabled the mouse, send + * up and down key presses for the mouse wheel. + */ + if (wp->saved_grid != NULL && !(wp->screen->mode & ALL_MOUSE_MODES)) { + for (i = 0; i < m->scroll; i++) { + if (m->wheel == MOUSE_WHEEL_UP) + input_key(wp, KEYC_UP); + else + input_key(wp, KEYC_DOWN); + } + return; + } if (wp->screen->mode & ALL_MOUSE_MODES) { /* From f4ffaf5a7fad4c02ad2d08c7b2c80fdec21b64a9 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 9 May 2014 09:11:24 +0000 Subject: [PATCH 6/9] Just use char ** for argv like normal people, not char *const *. --- cmd.c | 2 +- tmux.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd.c b/cmd.c index 726799a8..5660dc72 100644 --- a/cmd.c +++ b/cmd.c @@ -180,7 +180,7 @@ cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv) } char ** -cmd_copy_argv(int argc, char *const *argv) +cmd_copy_argv(int argc, char **argv) { char **new_argv; int i; diff --git a/tmux.h b/tmux.h index 04bbd935..a866d5b7 100644 --- a/tmux.h +++ b/tmux.h @@ -1738,7 +1738,7 @@ long long args_strtonum( /* cmd.c */ int cmd_pack_argv(int, char **, char *, size_t); int cmd_unpack_argv(char *, size_t, int, char ***); -char **cmd_copy_argv(int, char *const *); +char **cmd_copy_argv(int, char **); void cmd_free_argv(int, char **); struct cmd *cmd_parse(int, char **, const char *, u_int, char **); size_t cmd_print(struct cmd *, char *, size_t); From 3dbacbb62b033c04185fb87da5b0622f0aafee86 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 13 May 2014 07:34:35 +0000 Subject: [PATCH 7/9] Add support for named buffers. If you don't name a buffer, things work much as before - buffers are automatically named "buffer0000", "buffer0001" and so on and ordered as a stack. Buffers can be named explicitly when creating ("loadb -b foo" etc) or renamed ("setb -b buffer0000 -n foo"). If buffers are named explicitly, they are not deleted when buffer-limit is reached. Diff from J Raynor. --- cmd-capture-pane.c | 25 ++--- cmd-choose-buffer.c | 9 +- cmd-delete-buffer.c | 15 +-- cmd-list-buffers.c | 6 +- cmd-load-buffer.c | 55 ++++------- cmd-paste-buffer.c | 29 ++---- cmd-save-buffer.c | 18 ++-- cmd-set-buffer.c | 67 +++++++++----- format.c | 1 + paste.c | 218 +++++++++++++++++++++++++++++++++----------- tmux.1 | 63 +++++++++---- tmux.h | 25 +++-- window-copy.c | 70 +++++++------- 13 files changed, 353 insertions(+), 248 deletions(-) diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index 759063d1..c6a80ebd 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -38,7 +38,7 @@ char *cmd_capture_pane_history(struct args *, struct cmd_q *, const struct cmd_entry cmd_capture_pane_entry = { "capture-pane", "capturep", "ab:CeE:JpPqS:t:", 0, 0, - "[-aCeJpPq] [-b buffer-index] [-E end-line] [-S start-line]" + "[-aCeJpPq] " CMD_BUFFER_USAGE " [-E end-line] [-S start-line]" CMD_TARGET_PANE_USAGE, 0, NULL, @@ -165,8 +165,7 @@ cmd_capture_pane_exec(struct cmd *self, struct cmd_q *cmdq) struct client *c; struct window_pane *wp; char *buf, *cause; - int buffer; - u_int limit; + const char *bufname; size_t len; if (cmd_find_pane(cmdq, args_get(args, 't'), NULL, &wp) == NULL) @@ -192,25 +191,17 @@ cmd_capture_pane_exec(struct cmd *self, struct cmd_q *cmdq) evbuffer_add(c->stdout_data, "\n", 1); server_push_stdout(c); } else { - limit = options_get_number(&global_options, "buffer-limit"); - if (!args_has(args, 'b')) { - paste_add(buf, len, limit); - return (CMD_RETURN_NORMAL); - } - buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause); - if (cause != NULL) { - cmdq_error(cmdq, "buffer %s", cause); + bufname = NULL; + if (args_has(args, 'b')) + bufname = args_get(args, 'b'); + + if (paste_set(buf, len, bufname, &cause) != 0) { + cmdq_error(cmdq, "%s", cause); free(buf); free(cause); return (CMD_RETURN_ERROR); } - - if (paste_replace(buffer, buf, len) != 0) { - cmdq_error(cmdq, "no buffer %d", buffer); - free(buf); - return (CMD_RETURN_ERROR); - } } return (CMD_RETURN_NORMAL); diff --git a/cmd-choose-buffer.c b/cmd-choose-buffer.c index c6c70a0d..42caa7cd 100644 --- a/cmd-choose-buffer.c +++ b/cmd-choose-buffer.c @@ -75,19 +75,20 @@ cmd_choose_buffer_exec(struct cmd *self, struct cmd_q *cmdq) action = xstrdup("paste-buffer -b '%%'"); idx = 0; - while ((pb = paste_walk_stack(&idx)) != NULL) { + pb = NULL; + while ((pb = paste_walk(pb)) != NULL) { cdata = window_choose_data_create(TREE_OTHER, c, c->session); - cdata->idx = idx - 1; + cdata->idx = idx; cdata->ft_template = xstrdup(template); - format_add(cdata->ft, "line", "%u", idx - 1); format_paste_buffer(cdata->ft, pb, utf8flag); - xasprintf(&action_data, "%u", idx - 1); + xasprintf(&action_data, "%s", pb->name); cdata->command = cmd_template_replace(action, action_data, 1); free(action_data); window_choose_add(wl->window->active, cdata); + idx++; } free(action); diff --git a/cmd-delete-buffer.c b/cmd-delete-buffer.c index 464d2b78..755d7ea3 100644 --- a/cmd-delete-buffer.c +++ b/cmd-delete-buffer.c @@ -41,23 +41,16 @@ enum cmd_retval cmd_delete_buffer_exec(struct cmd *self, struct cmd_q *cmdq) { struct args *args = self->args; - char *cause; - int buffer; + const char *bufname; if (!args_has(args, 'b')) { paste_free_top(); return (CMD_RETURN_NORMAL); } + bufname = args_get(args, 'b'); - buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause); - if (cause != NULL) { - cmdq_error(cmdq, "buffer %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - - if (paste_free_index(buffer) != 0) { - cmdq_error(cmdq, "no buffer %d", buffer); + if (paste_free_name(bufname) != 0) { + cmdq_error(cmdq, "no buffer %s", bufname); return (CMD_RETURN_ERROR); } diff --git a/cmd-list-buffers.c b/cmd-list-buffers.c index 2363c806..9d79072e 100644 --- a/cmd-list-buffers.c +++ b/cmd-list-buffers.c @@ -44,17 +44,15 @@ cmd_list_buffers_exec(unused struct cmd *self, struct cmd_q *cmdq) struct args *args = self->args; struct paste_buffer *pb; struct format_tree *ft; - u_int idx; char *line; const char *template; if ((template = args_get(args, 'F')) == NULL) template = LIST_BUFFERS_TEMPLATE; - idx = 0; - while ((pb = paste_walk_stack(&idx)) != NULL) { + pb = NULL; + while ((pb = paste_walk(pb)) != NULL) { ft = format_create(); - format_add(ft, "line", "%u", idx - 1); format_paste_buffer(ft, pb, 0); line = format_expand(ft, template); diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c index 5e9b6748..6c69391c 100644 --- a/cmd-load-buffer.c +++ b/cmd-load-buffer.c @@ -50,30 +50,19 @@ cmd_load_buffer_exec(struct cmd *self, struct cmd_q *cmdq) struct client *c = cmdq->client; struct session *s; FILE *f; - const char *path; + const char *path, *bufname; char *pdata, *new_pdata, *cause; size_t psize; - u_int limit; - int ch, error, buffer, *buffer_ptr, cwd, fd; + int ch, error, cwd, fd; - if (!args_has(args, 'b')) - buffer = -1; - else { - buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause); - if (cause != NULL) { - cmdq_error(cmdq, "buffer %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - } + bufname = NULL; + if (args_has(args, 'b')) + bufname = args_get(args, 'b'); path = args->argv[0]; if (strcmp(path, "-") == 0) { - buffer_ptr = xmalloc(sizeof *buffer_ptr); - *buffer_ptr = buffer; - error = server_set_stdin_callback(c, cmd_load_buffer_callback, - buffer_ptr, &cause); + (void*)bufname, &cause); if (error != 0) { cmdq_error(cmdq, "%s: %s", path, cause); free(cause); @@ -117,14 +106,10 @@ cmd_load_buffer_exec(struct cmd *self, struct cmd_q *cmdq) fclose(f); - limit = options_get_number(&global_options, "buffer-limit"); - if (buffer == -1) { - paste_add(pdata, psize, limit); - return (CMD_RETURN_NORMAL); - } - if (paste_replace(buffer, pdata, psize) != 0) { - cmdq_error(cmdq, "no buffer %d", buffer); + if (paste_set(pdata, psize, bufname, &cause) != 0) { + cmdq_error(cmdq, "%s", cause); free(pdata); + free(cause); return (CMD_RETURN_ERROR); } @@ -140,10 +125,9 @@ error: void cmd_load_buffer_callback(struct client *c, int closed, void *data) { - int *buffer = data; - char *pdata; - size_t psize; - u_int limit; + const char *bufname = data; + char *pdata, *cause; + size_t psize; if (!closed) return; @@ -154,26 +138,21 @@ cmd_load_buffer_callback(struct client *c, int closed, void *data) return; psize = EVBUFFER_LENGTH(c->stdin_data); - if (psize == 0 || (pdata = malloc(psize + 1)) == NULL) { - free(data); + if (psize == 0 || (pdata = malloc(psize + 1)) == NULL) goto out; - } + memcpy(pdata, EVBUFFER_DATA(c->stdin_data), psize); pdata[psize] = '\0'; evbuffer_drain(c->stdin_data, psize); - limit = options_get_number(&global_options, "buffer-limit"); - if (*buffer == -1) - paste_add(pdata, psize, limit); - else if (paste_replace(*buffer, pdata, psize) != 0) { + if (paste_set(pdata, psize, bufname, &cause) != 0) { /* No context so can't use server_client_msg_error. */ - evbuffer_add_printf(c->stderr_data, "no buffer %d\n", *buffer); + evbuffer_add_printf(c->stderr_data, "%s", cause); server_push_stderr(c); free(pdata); + free(cause); } - free(data); - out: cmdq_continue(c->cmdq); } diff --git a/cmd-paste-buffer.c b/cmd-paste-buffer.c index c3837c6d..9462b28f 100644 --- a/cmd-paste-buffer.c +++ b/cmd-paste-buffer.c @@ -36,7 +36,7 @@ void cmd_paste_buffer_filter(struct window_pane *, const struct cmd_entry cmd_paste_buffer_entry = { "paste-buffer", "pasteb", "db:prs:t:", 0, 0, - "[-dpr] [-s separator] [-b buffer-index] " CMD_TARGET_PANE_USAGE, + "[-dpr] [-s separator] " CMD_BUFFER_USAGE " " CMD_TARGET_PANE_USAGE, 0, NULL, cmd_paste_buffer_exec @@ -49,31 +49,22 @@ cmd_paste_buffer_exec(struct cmd *self, struct cmd_q *cmdq) struct window_pane *wp; struct session *s; struct paste_buffer *pb; - const char *sepstr; - char *cause; - int buffer; + const char *sepstr, *bufname; int pflag; if (cmd_find_pane(cmdq, args_get(args, 't'), &s, &wp) == NULL) return (CMD_RETURN_ERROR); - if (!args_has(args, 'b')) - buffer = -1; - else { - buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause); - if (cause != NULL) { - cmdq_error(cmdq, "buffer %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - } + bufname = NULL; + if (args_has(args, 'b')) + bufname = args_get(args, 'b'); - if (buffer == -1) + if (bufname == NULL) pb = paste_get_top(); else { - pb = paste_get_index(buffer); + pb = paste_get_name(bufname); if (pb == NULL) { - cmdq_error(cmdq, "no buffer %d", buffer); + cmdq_error(cmdq, "no buffer %s", bufname); return (CMD_RETURN_ERROR); } } @@ -92,10 +83,10 @@ cmd_paste_buffer_exec(struct cmd *self, struct cmd_q *cmdq) /* Delete the buffer if -d. */ if (args_has(args, 'd')) { - if (buffer == -1) + if (bufname == NULL) paste_free_top(); else - paste_free_index(buffer); + paste_free_name(bufname); } return (CMD_RETURN_NORMAL); diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c index 0a1853d4..ea238450 100644 --- a/cmd-save-buffer.c +++ b/cmd-save-buffer.c @@ -59,10 +59,10 @@ cmd_save_buffer_exec(struct cmd *self, struct cmd_q *cmdq) struct client *c = cmdq->client; struct session *s; struct paste_buffer *pb; - const char *path; - char *cause, *start, *end, *msg; + const char *path, *bufname; + char *start, *end, *msg; size_t size, used, msglen; - int cwd, fd, buffer; + int cwd, fd; FILE *f; if (!args_has(args, 'b')) { @@ -71,16 +71,10 @@ cmd_save_buffer_exec(struct cmd *self, struct cmd_q *cmdq) return (CMD_RETURN_ERROR); } } else { - buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause); - if (cause != NULL) { - cmdq_error(cmdq, "buffer %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - - pb = paste_get_index(buffer); + bufname = args_get(args, 'b'); + pb = paste_get_name(bufname); if (pb == NULL) { - cmdq_error(cmdq, "no buffer %d", buffer); + cmdq_error(cmdq, "no buffer %s", bufname); return (CMD_RETURN_ERROR); } } diff --git a/cmd-set-buffer.c b/cmd-set-buffer.c index 5dabc1ee..5a82b4d8 100644 --- a/cmd-set-buffer.c +++ b/cmd-set-buffer.c @@ -31,8 +31,8 @@ enum cmd_retval cmd_set_buffer_exec(struct cmd *, struct cmd_q *); const struct cmd_entry cmd_set_buffer_entry = { "set-buffer", "setb", - "ab:", 1, 1, - "[-a] " CMD_BUFFER_USAGE " data", + "ab:n:", 0, 1, + "[-a] " CMD_BUFFER_USAGE " [-n new-buffer-name] data", 0, NULL, cmd_set_buffer_exec @@ -43,38 +43,59 @@ cmd_set_buffer_exec(struct cmd *self, struct cmd_q *cmdq) { struct args *args = self->args; struct paste_buffer *pb; - u_int limit; char *pdata, *cause; + const char *bufname; size_t psize, newsize; - int buffer; - limit = options_get_number(&global_options, "buffer-limit"); + bufname = NULL; + + if (args_has(args, 'n')) { + if (args->argc > 0) { + cmdq_error(cmdq, "don't provide data with n flag"); + return (CMD_RETURN_ERROR); + } + + if (args_has(args, 'b')) + bufname = args_get(args, 'b'); + + if (bufname == NULL) { + pb = paste_get_top(); + if (pb == NULL) { + cmdq_error(cmdq, "no buffer"); + return (CMD_RETURN_ERROR); + } + bufname = pb->name; + } + + if (paste_rename(bufname, args_get(args, 'n'), &cause) != 0) { + cmdq_error(cmdq, "%s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + + return (CMD_RETURN_NORMAL); + } + + if (args->argc != 1) { + cmdq_error(cmdq, "no data specified"); + return (CMD_RETURN_ERROR); + } psize = 0; pdata = NULL; pb = NULL; - buffer = -1; if ((newsize = strlen(args->argv[0])) == 0) return (CMD_RETURN_NORMAL); if (args_has(args, 'b')) { - buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause); - if (cause != NULL) { - cmdq_error(cmdq, "buffer %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - pb = paste_get_index(buffer); - if (pb == NULL) { - cmdq_error(cmdq, "no buffer %d", buffer); - return (CMD_RETURN_ERROR); - } + bufname = args_get(args, 'b'); + pb = paste_get_name(bufname); } else if (args_has(args, 'a')) { pb = paste_get_top(); if (pb != NULL) - buffer = 0; + bufname = pb->name; } if (args_has(args, 'a') && pb != NULL) { @@ -87,10 +108,12 @@ cmd_set_buffer_exec(struct cmd *self, struct cmd_q *cmdq) memcpy(pdata + psize, args->argv[0], newsize); psize += newsize; - if (buffer == -1) - paste_add(pdata, psize, limit); - else - paste_replace(buffer, pdata, psize); + if (paste_set(pdata, psize, bufname, &cause) != 0) { + cmdq_error(cmdq, "%s", cause); + free(pdata); + free(cause); + return (CMD_RETURN_ERROR); + } return (CMD_RETURN_NORMAL); } diff --git a/format.c b/format.c index 7c8ce779..028031a0 100644 --- a/format.c +++ b/format.c @@ -608,6 +608,7 @@ format_paste_buffer(struct format_tree *ft, struct paste_buffer *pb, char *s; format_add(ft, "buffer_size", "%zu", pb->size); + format_add(ft, "buffer_name", "%s", pb->name); s = paste_make_sample(pb, utf8flag); format_add(ft, "buffer_sample", "%s", s); diff --git a/paste.c b/paste.c index 9492fd91..11a4f006 100644 --- a/paste.c +++ b/paste.c @@ -26,127 +26,237 @@ #include "tmux.h" /* - * Stack of paste buffers. Note that paste buffer data is not necessarily a C + * Set of paste buffers. Note that paste buffer data is not necessarily a C * string! */ -ARRAY_DECL(, struct paste_buffer *) paste_buffers = ARRAY_INITIALIZER; +u_int paste_next_index; +u_int paste_next_order; +u_int paste_num_automatic; +RB_HEAD(paste_name_tree, paste_buffer) paste_by_name; +RB_HEAD(paste_time_tree, paste_buffer) paste_by_time; -/* Return each item of the stack in turn. */ -struct paste_buffer * -paste_walk_stack(u_int *idx) +int paste_cmp_names(const struct paste_buffer *, const struct paste_buffer *); +RB_PROTOTYPE(paste_name_tree, paste_buffer, name_entry, paste_cmp_names); +RB_GENERATE(paste_name_tree, paste_buffer, name_entry, paste_cmp_names); + +int paste_cmp_times(const struct paste_buffer *, const struct paste_buffer *); +RB_PROTOTYPE(paste_time_tree, paste_buffer, time_entry, paste_cmp_times); +RB_GENERATE(paste_time_tree, paste_buffer, time_entry, paste_cmp_times); + +int +paste_cmp_names(const struct paste_buffer *a, const struct paste_buffer *b) { - struct paste_buffer *pb; - - pb = paste_get_index(*idx); - (*idx)++; - return (pb); + return (strcmp(a->name, b->name)); } -/* Get the top item on the stack. */ +int +paste_cmp_times(const struct paste_buffer *a, const struct paste_buffer *b) +{ + if (a->order > b->order) + return (-1); + if (a->order < b->order) + return (1); + return (0); +} + +/* Walk paste buffers by name. */ +struct paste_buffer * +paste_walk(struct paste_buffer *pb) +{ + if (pb == NULL) + return (RB_MIN(paste_time_tree, &paste_by_time)); + return (RB_NEXT(paste_time_tree, &paste_by_time, pb)); +} + +/* Get the most recent automatic buffer */ struct paste_buffer * paste_get_top(void) { - if (ARRAY_LENGTH(&paste_buffers) == 0) + struct paste_buffer *pb; + + pb = RB_MIN(paste_time_tree, &paste_by_time); + if (pb == NULL) return (NULL); - return (ARRAY_FIRST(&paste_buffers)); + return (pb); } -/* Get an item by its index. */ -struct paste_buffer * -paste_get_index(u_int idx) -{ - if (idx >= ARRAY_LENGTH(&paste_buffers)) - return (NULL); - return (ARRAY_ITEM(&paste_buffers, idx)); -} - -/* Free the top item on the stack. */ +/* Free the most recent buffer */ int paste_free_top(void) { struct paste_buffer *pb; - if (ARRAY_LENGTH(&paste_buffers) == 0) + pb = paste_get_top(); + if (pb == NULL) return (-1); - - pb = ARRAY_FIRST(&paste_buffers); - ARRAY_REMOVE(&paste_buffers, 0); - - free(pb->data); - free(pb); - - return (0); + return (paste_free_name(pb->name)); } -/* Free an item by index. */ -int -paste_free_index(u_int idx) +/* Get a paste buffer by name. */ +struct paste_buffer * +paste_get_name(const char *name) { - struct paste_buffer *pb; + struct paste_buffer pbfind; - if (idx >= ARRAY_LENGTH(&paste_buffers)) + if (name == NULL || *name == '\0') + return (NULL); + + pbfind.name = (char*)name; + return (RB_FIND(paste_name_tree, &paste_by_name, &pbfind)); +} + +/* Free a paste buffer by name. */ +int +paste_free_name(const char *name) +{ + struct paste_buffer *pb, pbfind; + + if (name == NULL || *name == '\0') return (-1); - pb = ARRAY_ITEM(&paste_buffers, idx); - ARRAY_REMOVE(&paste_buffers, idx); + pbfind.name = (char*)name; + pb = RB_FIND(paste_name_tree, &paste_by_name, &pbfind); + if (pb == NULL) + return (-1); + + RB_REMOVE(paste_name_tree, &paste_by_name, pb); + RB_REMOVE(paste_time_tree, &paste_by_time, pb); + if (pb->automatic) + paste_num_automatic--; free(pb->data); + free(pb->name); free(pb); - return (0); } /* - * Add an item onto the top of the stack, freeing the bottom if at limit. Note + * Add an automatic buffer, freeing the oldest automatic item if at limit. Note * that the caller is responsible for allocating data. */ void -paste_add(char *data, size_t size, u_int limit) +paste_add(char *data, size_t size) { - struct paste_buffer *pb; + struct paste_buffer *pb, *pb1; + u_int limit; if (size == 0) return; - while (ARRAY_LENGTH(&paste_buffers) >= limit) { - pb = ARRAY_LAST(&paste_buffers); - free(pb->data); - free(pb); - ARRAY_TRUNC(&paste_buffers, 1); + limit = options_get_number(&global_options, "buffer-limit"); + RB_FOREACH_REVERSE_SAFE(pb, paste_time_tree, &paste_by_time, pb1) { + if (paste_num_automatic < limit) + break; + if (pb->automatic) + paste_free_name(pb->name); } pb = xmalloc(sizeof *pb); - ARRAY_INSERT(&paste_buffers, 0, pb); + + pb->name = NULL; + do { + free(pb->name); + xasprintf(&pb->name, "buffer%04u", paste_next_index); + paste_next_index++; + } while (paste_get_name(pb->name) != NULL); pb->data = data; pb->size = size; + + pb->automatic = 1; + paste_num_automatic++; + + pb->order = paste_next_order++; + RB_INSERT(paste_name_tree, &paste_by_name, pb); + RB_INSERT(paste_time_tree, &paste_by_time, pb); } +/* Rename a paste buffer. */ +int +paste_rename(const char *oldname, const char *newname, char **cause) +{ + struct paste_buffer *pb; + + if (cause != NULL) + *cause = NULL; + + if (oldname == NULL || *oldname == '\0') { + if (cause != NULL) + *cause = xstrdup("no buffer"); + return (-1); + } + if (newname == NULL || *newname == '\0') { + if (cause != NULL) + *cause = xstrdup("new name is empty"); + return (-1); + } + + pb = paste_get_name(oldname); + if (pb == NULL) { + if (cause != NULL) + xasprintf(cause, "no buffer %s", oldname); + return (-1); + } + + RB_REMOVE(paste_name_tree, &paste_by_name, pb); + + free(pb->name); + pb->name = xstrdup(newname); + + if (pb->automatic) + paste_num_automatic--; + pb->automatic = 0; + + RB_INSERT(paste_name_tree, &paste_by_name, pb); + + return (0); +} /* - * Replace an item on the stack. Note that the caller is responsible for + * Add or replace an item in the store. Note that the caller is responsible for * allocating data. */ int -paste_replace(u_int idx, char *data, size_t size) +paste_set(char *data, size_t size, const char *name, char **cause) { struct paste_buffer *pb; + if (cause != NULL) + *cause = NULL; + if (size == 0) { free(data); return (0); } + if (name == NULL) { + paste_add(data, size); + return (0); + } - if (idx >= ARRAY_LENGTH(&paste_buffers)) + if (*name == '\0') { + if (cause != NULL) + *cause = xstrdup("empty buffer name"); return (-1); + } - pb = ARRAY_ITEM(&paste_buffers, idx); - free(pb->data); + pb = paste_get_name(name); + if (pb != NULL) + paste_free_name(name); + + pb = xmalloc(sizeof *pb); + + pb->name = xstrdup(name); pb->data = data; pb->size = size; + pb->automatic = 0; + pb->order = paste_next_order++; + + RB_INSERT(paste_name_tree, &paste_by_name, pb); + RB_INSERT(paste_time_tree, &paste_by_time, pb); + return (0); } diff --git a/tmux.1 b/tmux.1 index 44e69f6e..ae6190e4 100644 --- a/tmux.1 +++ b/tmux.1 @@ -930,9 +930,6 @@ in emacs mode, and .Ql 10w in vi. .Pp -When copying the selection, the repeat count indicates the buffer index to -replace, if used. -.Pp Mode key bindings are defined in a set of named tables: .Em vi-edit and @@ -1090,7 +1087,7 @@ but a different format may be specified with .Fl F . .It Xo Ic capture-pane .Op Fl aepPq -.Op Fl b Ar buffer-index +.Op Fl b Ar buffer-name .Op Fl E Ar end-line .Op Fl S Ar start-line .Op Fl t Ar target-pane @@ -3366,19 +3363,40 @@ is given, otherwise the active pane for the session attached to .El .Sh BUFFERS .Nm -maintains a stack of +maintains a set of named .Em paste buffers . -Up to the value of the +Each buffer may be either explicitly or automatically named. +Explicitly named buffers are named when created with the +.Ic set-buffer +or +.Ic load-buffer +commands, or by renaming an automatically named buffer with +.Ic set-buffer +.Fl n . +Automatically named buffers are given a name such as +.Ql buffer0001 , +.Ql buffer0002 +and so on. +When the .Ic buffer-limit -option are kept; when a new buffer is added, the buffer at the bottom of the -stack is removed. +option is reached, the oldest automatically named buffer is deleted. +Explicitly named are not subject to +.Ic buffer-limit +and may be deleted with +.Ic delete-buffer +command. +.Pp Buffers may be added using .Ic copy-mode or the .Ic set-buffer -command, and pasted into a window using the +and +.Ic load-buffer +commands, and pasted into a window using the .Ic paste-buffer command. +If a buffer command is used and no buffer is specified, the most +recently added automatically named buffer is assumed. .Pp A configurable history buffer is also maintained for each window. By default, up to 2000 lines are kept; this can be altered with the @@ -3399,7 +3417,7 @@ Put a window into buffer choice mode, where a buffer may be chosen interactively from a list. After a buffer is selected, .Ql %% -is replaced by the buffer index in +is replaced by the buffer name in .Ar template and the result executed as a command. If @@ -3414,11 +3432,11 @@ This command works only if at least one client is attached. .It Ic clear-history Op Fl t Ar target-pane .D1 (alias: Ic clearhist ) Remove and free the history for the specified pane. -.It Ic delete-buffer Op Fl b Ar buffer-index +.It Ic delete-buffer Op Fl b Ar buffer-name .D1 (alias: Ic deleteb ) -Delete the buffer at -.Ar buffer-index , -or the top buffer if not specified. +Delete the buffer named +.Ar buffer-name , +or the most recently added automatically named buffer if not specified. .It Xo Ic list-buffers .Op Fl F Ar format .Xc @@ -3430,7 +3448,7 @@ flag, see the .Sx FORMATS section. .It Xo Ic load-buffer -.Op Fl b Ar buffer-index +.Op Fl b Ar buffer-name .Ar path .Xc .D1 (alias: Ic loadb ) @@ -3438,7 +3456,7 @@ Load the contents of the specified paste buffer from .Ar path . .It Xo Ic paste-buffer .Op Fl dpr -.Op Fl b Ar buffer-index +.Op Fl b Ar buffer-name .Op Fl s Ar separator .Op Fl t Ar target-pane .Xc @@ -3447,7 +3465,7 @@ Insert the contents of a paste buffer into the specified pane. If not specified, paste into the current one. With .Fl d , -also delete the paste buffer from the stack. +also delete the paste buffer. When output, any linefeed (LF) characters in the paste buffer are replaced with a separator, by default carriage return (CR). A custom separator may be specified using the @@ -3462,7 +3480,7 @@ is specified, paste bracket control codes are inserted around the buffer if the application has requested bracketed paste mode. .It Xo Ic save-buffer .Op Fl a -.Op Fl b Ar buffer-index +.Op Fl b Ar buffer-name .Ar path .Xc .D1 (alias: Ic saveb ) @@ -3473,7 +3491,8 @@ The option appends to rather than overwriting the file. .It Xo Ic set-buffer .Op Fl a -.Op Fl b Ar buffer-index +.Op Fl b Ar buffer-name +.Op Fl n Ar new-buffer-name .Ar data .Xc .D1 (alias: Ic setb ) @@ -3482,8 +3501,12 @@ Set the contents of the specified buffer to The .Fl a option appends to rather than overwriting the buffer. +The +.Fl n +option renames the buffer to +.Ar new-buffer-name . .It Xo Ic show-buffer -.Op Fl b Ar buffer-index +.Op Fl b Ar buffer-name .Xc .D1 (alias: Ic showb ) Display the contents of the specified buffer. diff --git a/tmux.h b/tmux.h index a866d5b7..9e70915b 100644 --- a/tmux.h +++ b/tmux.h @@ -85,7 +85,7 @@ extern char **environ; /* Default template for choose-buffer. */ #define CHOOSE_BUFFER_TEMPLATE \ - "#{line}: #{buffer_size} bytes: #{buffer_sample}" + "#{buffer_name}: #{buffer_size} bytes: #{buffer_sample}" /* Default template for choose-client. */ #define CHOOSE_CLIENT_TEMPLATE \ @@ -118,7 +118,8 @@ extern char **environ; /* Default template for list-buffers. */ #define LIST_BUFFERS_TEMPLATE \ - "#{line}: #{buffer_size} bytes: \"#{buffer_sample}\"" + "#{buffer_name}: #{buffer_size} bytes: " \ + "\"#{buffer_sample}\"" /* Default template for list-clients. */ #define LIST_CLIENTS_TEMPLATE \ @@ -1036,6 +1037,13 @@ struct layout_cell { struct paste_buffer { char *data; size_t size; + + char *name; + int automatic; + u_int order; + + RB_ENTRY(paste_buffer) name_entry; + RB_ENTRY(paste_buffer) time_entry; }; /* Environment variable. */ @@ -1499,7 +1507,7 @@ RB_HEAD(format_tree, format_entry); #define CMD_SRCDST_WINDOW_USAGE "[-s src-window] [-t dst-window]" #define CMD_SRCDST_SESSION_USAGE "[-s src-session] [-t dst-session]" #define CMD_SRCDST_CLIENT_USAGE "[-s src-client] [-t dst-client]" -#define CMD_BUFFER_USAGE "[-b buffer-index]" +#define CMD_BUFFER_USAGE "[-b buffer-name]" /* tmux.c */ extern struct options global_options; @@ -1711,13 +1719,14 @@ void tty_keys_free(struct tty *); int tty_keys_next(struct tty *); /* paste.c */ -struct paste_buffer *paste_walk_stack(u_int *); +struct paste_buffer *paste_walk(struct paste_buffer *); struct paste_buffer *paste_get_top(void); -struct paste_buffer *paste_get_index(u_int); +struct paste_buffer *paste_get_name(const char *); int paste_free_top(void); -int paste_free_index(u_int); -void paste_add(char *, size_t, u_int); -int paste_replace(u_int, char *, size_t); +int paste_free_name(const char *); +void paste_add(char *, size_t); +int paste_rename(const char *, const char *, char **); +int paste_set(char *, size_t, const char *, char **); char *paste_make_sample(struct paste_buffer *, int); void paste_send_pane(struct paste_buffer *, struct window_pane *, const char *, int); diff --git a/window-copy.c b/window-copy.c index 296443a2..4763c230 100644 --- a/window-copy.c +++ b/window-copy.c @@ -54,11 +54,12 @@ void window_copy_update_cursor(struct window_pane *, u_int, u_int); void window_copy_start_selection(struct window_pane *); int window_copy_update_selection(struct window_pane *, int); void *window_copy_get_selection(struct window_pane *, size_t *); -void window_copy_copy_buffer(struct window_pane *, int, void *, size_t); -void window_copy_copy_pipe( - struct window_pane *, struct session *, int, const char *); -void window_copy_copy_selection(struct window_pane *, int); -void window_copy_append_selection(struct window_pane *, int); +void window_copy_copy_buffer(struct window_pane *, const char *, void *, + size_t); +void window_copy_copy_pipe(struct window_pane *, struct session *, + const char *, const char *); +void window_copy_copy_selection(struct window_pane *, const char *); +void window_copy_append_selection(struct window_pane *, const char *); void window_copy_clear_selection(struct window_pane *); void window_copy_copy_line( struct window_pane *, char **, size_t *, u_int, u_int, u_int); @@ -417,7 +418,7 @@ window_copy_key(struct window_pane *wp, struct session *sess, int key) switch (cmd) { case MODEKEYCOPY_APPENDSELECTION: if (sess != NULL) { - window_copy_append_selection(wp, data->numprefix); + window_copy_append_selection(wp, NULL); window_pane_reset_mode(wp); return; } @@ -543,7 +544,7 @@ window_copy_key(struct window_pane *wp, struct session *sess, int key) if (sess != NULL && (cmd == MODEKEYCOPY_COPYLINE || cmd == MODEKEYCOPY_COPYENDOFLINE)) { - window_copy_copy_selection(wp, -1); + window_copy_copy_selection(wp, NULL); window_pane_reset_mode(wp); return; } @@ -554,14 +555,14 @@ window_copy_key(struct window_pane *wp, struct session *sess, int key) break; case MODEKEYCOPY_COPYPIPE: if (sess != NULL) { - window_copy_copy_pipe(wp, sess, data->numprefix, arg); + window_copy_copy_pipe(wp, sess, NULL, arg); window_pane_reset_mode(wp); return; } break; case MODEKEYCOPY_COPYSELECTION: if (sess != NULL) { - window_copy_copy_selection(wp, data->numprefix); + window_copy_copy_selection(wp, NULL); window_pane_reset_mode(wp); return; } @@ -918,7 +919,7 @@ reset_mode: s->mode &= ~MODE_MOUSE_BUTTON; s->mode |= MODE_MOUSE_STANDARD; if (sess != NULL) { - window_copy_copy_selection(wp, -1); + window_copy_copy_selection(wp, NULL); window_pane_reset_mode(wp); } } @@ -1452,9 +1453,9 @@ window_copy_get_selection(struct window_pane *wp, size_t *len) } void -window_copy_copy_buffer(struct window_pane *wp, int idx, void *buf, size_t len) +window_copy_copy_buffer(struct window_pane *wp, const char *bufname, void *buf, + size_t len) { - u_int limit; struct screen_write_ctx ctx; if (options_get_number(&global_options, "set-clipboard")) { @@ -1463,16 +1464,13 @@ window_copy_copy_buffer(struct window_pane *wp, int idx, void *buf, size_t len) screen_write_stop(&ctx); } - if (idx == -1) { - limit = options_get_number(&global_options, "buffer-limit"); - paste_add(buf, len, limit); - } else if (paste_replace(idx, buf, len) != 0) + if (paste_set(buf, len, bufname, NULL) != 0) free(buf); } void -window_copy_copy_pipe( - struct window_pane *wp, struct session *sess, int idx, const char *arg) +window_copy_copy_pipe(struct window_pane *wp, struct session *sess, + const char *bufname, const char *arg) { void *buf; size_t len; @@ -1486,11 +1484,11 @@ window_copy_copy_pipe( job = job_run(arg, sess, NULL, NULL, NULL); bufferevent_write(job->event, buf, len); - window_copy_copy_buffer(wp, idx, buf, len); + window_copy_copy_buffer(wp, bufname, buf, len); } void -window_copy_copy_selection(struct window_pane *wp, int idx) +window_copy_copy_selection(struct window_pane *wp, const char *bufname) { void* buf; size_t len; @@ -1499,17 +1497,16 @@ window_copy_copy_selection(struct window_pane *wp, int idx) if (buf == NULL) return; - window_copy_copy_buffer(wp, idx, buf, len); + window_copy_copy_buffer(wp, bufname, buf, len); } void -window_copy_append_selection(struct window_pane *wp, int idx) +window_copy_append_selection(struct window_pane *wp, const char *bufname) { - char *buf; - struct paste_buffer *pb; - size_t len; - u_int limit; - struct screen_write_ctx ctx; + char *buf; + struct paste_buffer *pb; + size_t len; + struct screen_write_ctx ctx; buf = window_copy_get_selection(wp, &len); if (buf == NULL) @@ -1521,24 +1518,19 @@ window_copy_append_selection(struct window_pane *wp, int idx) screen_write_stop(&ctx); } - if (idx == -1) - idx = 0; - - if (idx == 0 && paste_get_top() == NULL) { - limit = options_get_number(&global_options, "buffer-limit"); - paste_add(buf, len, limit); - return; - } - - pb = paste_get_index(idx); + if (bufname == NULL || *bufname == '\0') { + pb = paste_get_top(); + if (pb != NULL) + bufname = pb->name; + } else + pb = paste_get_name(bufname); if (pb != NULL) { buf = xrealloc(buf, 1, len + pb->size); memmove(buf + pb->size, buf, len); memcpy(buf, pb->data, pb->size); len += pb->size; } - - if (paste_replace(idx, buf, len) != 0) + if (paste_set(buf, len, bufname, NULL) != 0) free(buf); } From b1a06ef22e54e943d733db8dcc98fe60051c93de Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 13 May 2014 07:54:20 +0000 Subject: [PATCH 8/9] Add a copy mode key binding to copy to a named buffer. From J Raynor. --- mode-key.c | 2 ++ tmux.1 | 1 + tmux.h | 1 + window-copy.c | 16 ++++++++++++++-- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/mode-key.c b/mode-key.c index 7f2b9471..c2bc0439 100644 --- a/mode-key.c +++ b/mode-key.c @@ -141,6 +141,7 @@ const struct mode_key_cmdstr mode_key_cmdstr_copy[] = { { MODEKEYCOPY_SEARCHREVERSE, "search-reverse" }, { MODEKEYCOPY_SEARCHUP, "search-backward" }, { MODEKEYCOPY_SELECTLINE, "select-line" }, + { MODEKEYCOPY_STARTNAMEDBUFFER, "start-named-buffer" }, { MODEKEYCOPY_STARTNUMBERPREFIX, "start-number-prefix" }, { MODEKEYCOPY_STARTOFLINE, "start-of-line" }, { MODEKEYCOPY_STARTSELECTION, "begin-selection" }, @@ -257,6 +258,7 @@ struct mode_key_tree mode_key_tree_vi_choice; /* vi copy mode keys. */ const struct mode_key_entry mode_key_vi_copy[] = { { ' ', 0, MODEKEYCOPY_STARTSELECTION }, + { '"', 0, MODEKEYCOPY_STARTNAMEDBUFFER }, { '$', 0, MODEKEYCOPY_ENDOFLINE }, { ',', 0, MODEKEYCOPY_JUMPREVERSE }, { ';', 0, MODEKEYCOPY_JUMPAGAIN }, diff --git a/tmux.1 b/tmux.1 index ae6190e4..397a228b 100644 --- a/tmux.1 +++ b/tmux.1 @@ -849,6 +849,7 @@ The following keys are supported as appropriate for the mode: .It Sy "Function" Ta Sy "vi" Ta Sy "emacs" .It Li "Append selection" Ta "A" Ta "" .It Li "Back to indentation" Ta "^" Ta "M-m" +.It Li "Copy to named buffer" Ta \&" Ta "" .It Li "Bottom of history" Ta "G" Ta "M-<" .It Li "Clear selection" Ta "Escape" Ta "C-g" .It Li "Copy selection" Ta "Enter" Ta "M-w" diff --git a/tmux.h b/tmux.h index 9e70915b..a8d8e01f 100644 --- a/tmux.h +++ b/tmux.h @@ -583,6 +583,7 @@ enum mode_key_cmd { MODEKEYCOPY_SEARCHREVERSE, MODEKEYCOPY_SEARCHUP, MODEKEYCOPY_SELECTLINE, + MODEKEYCOPY_STARTNAMEDBUFFER, MODEKEYCOPY_STARTNUMBERPREFIX, MODEKEYCOPY_STARTOFLINE, MODEKEYCOPY_STARTSELECTION, diff --git a/window-copy.c b/window-copy.c index 4763c230..aa6b2d73 100644 --- a/window-copy.c +++ b/window-copy.c @@ -61,8 +61,8 @@ void window_copy_copy_pipe(struct window_pane *, struct session *, void window_copy_copy_selection(struct window_pane *, const char *); void window_copy_append_selection(struct window_pane *, const char *); void window_copy_clear_selection(struct window_pane *); -void window_copy_copy_line( - struct window_pane *, char **, size_t *, u_int, u_int, u_int); +void window_copy_copy_line(struct window_pane *, char **, size_t *, u_int, + u_int, u_int); int window_copy_in_set(struct window_pane *, u_int, u_int, const char *); u_int window_copy_find_length(struct window_pane *, u_int); void window_copy_cursor_start_of_line(struct window_pane *); @@ -95,6 +95,7 @@ const struct window_mode window_copy_mode = { enum window_copy_input_type { WINDOW_COPY_OFF, + WINDOW_COPY_NAMEDBUFFER, WINDOW_COPY_NUMERICPREFIX, WINDOW_COPY_SEARCHUP, WINDOW_COPY_SEARCHDOWN, @@ -677,6 +678,7 @@ window_copy_key(struct window_pane *wp, struct session *sess, int key) case WINDOW_COPY_JUMPBACK: case WINDOW_COPY_JUMPTOFORWARD: case WINDOW_COPY_JUMPTOBACK: + case WINDOW_COPY_NAMEDBUFFER: case WINDOW_COPY_NUMERICPREFIX: break; case WINDOW_COPY_SEARCHUP: @@ -712,6 +714,11 @@ window_copy_key(struct window_pane *wp, struct session *sess, int key) data->inputprompt = "Goto Line"; *data->inputstr = '\0'; goto input_on; + case MODEKEYCOPY_STARTNAMEDBUFFER: + data->inputtype = WINDOW_COPY_NAMEDBUFFER; + data->inputprompt = "Buffer"; + *data->inputstr = '\0'; + goto input_on; case MODEKEYCOPY_STARTNUMBERPREFIX: key &= KEYC_MASK_KEY; if (key >= '0' && key <= '9') { @@ -815,6 +822,11 @@ window_copy_key_input(struct window_pane *wp, int key) data->searchtype = data->inputtype; data->searchstr = xstrdup(data->inputstr); break; + case WINDOW_COPY_NAMEDBUFFER: + window_copy_copy_selection(wp, data->inputstr); + *data->inputstr = '\0'; + window_pane_reset_mode(wp); + return (0); case WINDOW_COPY_GOTOLINE: window_copy_goto_line(wp, data->inputstr); *data->inputstr = '\0'; From b3e8d440ed0477e88232c3ba1779c67eafce3a48 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 13 May 2014 08:08:32 +0000 Subject: [PATCH 9/9] If multiple arguments are given to new-session, new-window, split-window, respawn-window or respawn-pane, pass them directly to execvp() to help avoid quoting problems. One argument still goes to "sh -c" like before. Requested by many over the years. Patch from J Raynor. --- cmd-new-session.c | 34 ++++++++++++++++--------- cmd-new-window.c | 24 ++++++++++++------ cmd-respawn-pane.c | 13 +++------- cmd-respawn-window.c | 12 +++------ cmd-split-window.c | 25 ++++++++++++------ cmd.c | 28 ++++++++++++++++++++- format.c | 8 +++--- names.c | 12 ++++++--- session.c | 18 +++++++------ tmux.1 | 29 +++++++++++++++++++-- tmux.h | 20 ++++++++------- window.c | 60 ++++++++++++++++++++++++++++---------------- 12 files changed, 192 insertions(+), 91 deletions(-) diff --git a/cmd-new-session.c b/cmd-new-session.c index 3f8ebe69..802cb6c9 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -35,10 +35,10 @@ enum cmd_retval cmd_new_session_exec(struct cmd *, struct cmd_q *); const struct cmd_entry cmd_new_session_entry = { "new-session", "new", - "Ac:dDF:n:Ps:t:x:y:", 0, 1, + "Ac:dDF:n:Ps:t:x:y:", 0, -1, "[-AdDP] [-c start-directory] [-F format] [-n window-name] " - "[-s session-name] " CMD_TARGET_SESSION_USAGE " [-x width] [-y height] " - "[command]", + "[-s session-name] " CMD_TARGET_SESSION_USAGE " [-x width] " + "[-y height] [command]", CMD_STARTSERVER|CMD_CANTNEST, NULL, cmd_new_session_exec @@ -55,8 +55,9 @@ cmd_new_session_exec(struct cmd *self, struct cmd_q *cmdq) struct termios tio, *tiop; const char *newname, *target, *update, *errstr, *template; const char *path; - char *cmd, *cause, *cp; + char **argv, *cmd, *cause, *cp; int detached, already_attached, idx, cwd, fd = -1; + int argc; u_int sx, sy; struct format_tree *ft; struct environ_entry *envent; @@ -183,12 +184,21 @@ cmd_new_session_exec(struct cmd *self, struct cmd_q *cmdq) sy = 1; /* Figure out the command for the new window. */ - if (target != NULL) - cmd = NULL; - else if (args->argc != 0) - cmd = args->argv[0]; - else + argc = -1; + argv = NULL; + if (target == NULL && args->argc != 0) { + argc = args->argc; + argv = args->argv; + } else if (target == NULL) { cmd = options_get_string(&global_s_options, "default-command"); + if (cmd != NULL && *cmd != '\0') { + argc = 1; + argv = &cmd; + } else { + argc = 0; + argv = NULL; + } + } path = NULL; if (c != NULL && c->session == NULL) @@ -206,8 +216,8 @@ cmd_new_session_exec(struct cmd *self, struct cmd_q *cmdq) /* Create the new session. */ idx = -1 - options_get_number(&global_s_options, "base-index"); - s = session_create(newname, cmd, path, cwd, &env, tiop, idx, sx, sy, - &cause); + s = session_create(newname, argc, argv, path, cwd, &env, tiop, idx, sx, + sy, &cause); if (s == NULL) { cmdq_error(cmdq, "create session failed: %s", cause); free(cause); @@ -216,7 +226,7 @@ cmd_new_session_exec(struct cmd *self, struct cmd_q *cmdq) environ_free(&env); /* Set the initial window name if one given. */ - if (cmd != NULL && args_has(args, 'n')) { + if (argc >= 0 && args_has(args, 'n')) { w = s->curw->window; window_set_name(w, args_get(args, 'n')); options_set_number(&w->options, "automatic-rename", 0); diff --git a/cmd-new-window.c b/cmd-new-window.c index dc1bbfaa..fc2eb120 100644 --- a/cmd-new-window.c +++ b/cmd-new-window.c @@ -34,7 +34,7 @@ enum cmd_retval cmd_new_window_exec(struct cmd *, struct cmd_q *); const struct cmd_entry cmd_new_window_entry = { "new-window", "neww", - "ac:dF:kn:Pt:", 0, 1, + "ac:dF:kn:Pt:", 0, -1, "[-adkP] [-c start-directory] [-F format] [-n window-name] " CMD_TARGET_WINDOW_USAGE " [command]", 0, @@ -50,8 +50,8 @@ cmd_new_window_exec(struct cmd *self, struct cmd_q *cmdq) struct winlink *wl; struct client *c; const char *cmd, *path, *template; - char *cause, *cp; - int idx, last, detached, cwd, fd = -1; + char **argv, *cause, *cp; + int argc, idx, last, detached, cwd, fd = -1; struct format_tree *ft; struct environ_entry *envent; @@ -84,10 +84,19 @@ cmd_new_window_exec(struct cmd *self, struct cmd_q *cmdq) } detached = args_has(args, 'd'); - if (args->argc == 0) + if (args->argc == 0) { cmd = options_get_string(&s->options, "default-command"); - else - cmd = args->argv[0]; + if (cmd != NULL && *cmd != '\0') { + argc = 1; + argv = (char**)&cmd; + } else { + argc = 0; + argv = NULL; + } + } else { + argc = args->argc; + argv = args->argv; + } path = NULL; if (cmdq->client != NULL && cmdq->client->session == NULL) @@ -145,7 +154,8 @@ cmd_new_window_exec(struct cmd *self, struct cmd_q *cmdq) if (idx == -1) idx = -1 - options_get_number(&s->options, "base-index"); - wl = session_new(s, args_get(args, 'n'), cmd, path, cwd, idx, &cause); + wl = session_new(s, args_get(args, 'n'), argc, argv, path, cwd, idx, + &cause); if (wl == NULL) { cmdq_error(cmdq, "create window failed: %s", cause); free(cause); diff --git a/cmd-respawn-pane.c b/cmd-respawn-pane.c index 1df592e7..c2dcf9e6 100644 --- a/cmd-respawn-pane.c +++ b/cmd-respawn-pane.c @@ -32,7 +32,7 @@ enum cmd_retval cmd_respawn_pane_exec(struct cmd *, struct cmd_q *); const struct cmd_entry cmd_respawn_pane_entry = { "respawn-pane", "respawnp", - "kt:", 0, 1, + "kt:", 0, -1, "[-k] " CMD_TARGET_PANE_USAGE " [command]", 0, NULL, @@ -48,7 +48,7 @@ cmd_respawn_pane_exec(struct cmd *self, struct cmd_q *cmdq) struct window_pane *wp; struct session *s; struct environ env; - const char *cmd, *path; + const char *path; char *cause; u_int idx; struct environ_entry *envent; @@ -74,11 +74,6 @@ cmd_respawn_pane_exec(struct cmd *self, struct cmd_q *cmdq) screen_reinit(&wp->base); input_init(wp); - if (args->argc != 0) - cmd = args->argv[0]; - else - cmd = NULL; - path = NULL; if (cmdq->client != NULL && cmdq->client->session == NULL) envent = environ_find(&cmdq->client->environ, "PATH"); @@ -87,8 +82,8 @@ cmd_respawn_pane_exec(struct cmd *self, struct cmd_q *cmdq) if (envent != NULL) path = envent->value; - if (window_pane_spawn(wp, cmd, path, NULL, -1, &env, s->tio, - &cause) != 0) { + if (window_pane_spawn(wp, args->argc, args->argv, path, NULL, -1, &env, + s->tio, &cause) != 0) { cmdq_error(cmdq, "respawn pane failed: %s", cause); free(cause); environ_free(&env); diff --git a/cmd-respawn-window.c b/cmd-respawn-window.c index 9dc3f07f..5f59cb8b 100644 --- a/cmd-respawn-window.c +++ b/cmd-respawn-window.c @@ -31,7 +31,7 @@ enum cmd_retval cmd_respawn_window_exec(struct cmd *, struct cmd_q *); const struct cmd_entry cmd_respawn_window_entry = { "respawn-window", "respawnw", - "kt:", 0, 1, + "kt:", 0, -1, "[-k] " CMD_TARGET_WINDOW_USAGE " [command]", 0, NULL, @@ -47,7 +47,7 @@ cmd_respawn_window_exec(struct cmd *self, struct cmd_q *cmdq) struct window_pane *wp; struct session *s; struct environ env; - const char *cmd, *path; + const char *path; char *cause; struct environ_entry *envent; @@ -76,10 +76,6 @@ cmd_respawn_window_exec(struct cmd *self, struct cmd_q *cmdq) window_destroy_panes(w); TAILQ_INSERT_HEAD(&w->panes, wp, entry); window_pane_resize(wp, w->sx, w->sy); - if (args->argc != 0) - cmd = args->argv[0]; - else - cmd = NULL; path = NULL; if (cmdq->client != NULL && cmdq->client->session == NULL) @@ -89,8 +85,8 @@ cmd_respawn_window_exec(struct cmd *self, struct cmd_q *cmdq) if (envent != NULL) path = envent->value; - if (window_pane_spawn(wp, cmd, path, NULL, -1, &env, s->tio, - &cause) != 0) { + if (window_pane_spawn(wp, args->argc, args->argv, path, NULL, -1, &env, + s->tio, &cause) != 0) { cmdq_error(cmdq, "respawn window failed: %s", cause); free(cause); environ_free(&env); diff --git a/cmd-split-window.c b/cmd-split-window.c index 50949b39..55231134 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -36,7 +36,7 @@ enum cmd_retval cmd_split_window_exec(struct cmd *, struct cmd_q *); const struct cmd_entry cmd_split_window_entry = { "split-window", "splitw", - "c:dF:l:hp:Pt:v", 0, 1, + "c:dF:l:hp:Pt:v", 0, -1, "[-dhvP] [-c start-directory] [-F format] [-p percentage|-l size] " CMD_TARGET_PANE_USAGE " [command]", 0, @@ -62,9 +62,9 @@ cmd_split_window_exec(struct cmd *self, struct cmd_q *cmdq) struct window_pane *wp, *new_wp = NULL; struct environ env; const char *cmd, *path, *shell, *template; - char *cause, *new_cause, *cp; + char **argv, *cause, *new_cause, *cp; u_int hlimit; - int size, percentage, cwd, fd = -1; + int argc, size, percentage, cwd, fd = -1; enum layout_type type; struct layout_cell *lc; struct client *c; @@ -81,10 +81,19 @@ cmd_split_window_exec(struct cmd *self, struct cmd_q *cmdq) environ_copy(&s->environ, &env); server_fill_environ(s, &env); - if (args->argc == 0) + if (args->argc == 0) { cmd = options_get_string(&s->options, "default-command"); - else - cmd = args->argv[0]; + if (cmd != NULL && *cmd != '\0') { + argc = 1; + argv = (char**)&cmd; + } else { + argc = 0; + argv = NULL; + } + } else { + argc = args->argc; + argv = args->argv; + } if (args_has(args, 'c')) { ft = format_create(); @@ -158,8 +167,8 @@ cmd_split_window_exec(struct cmd *self, struct cmd_q *cmdq) if (envent != NULL) path = envent->value; - if (window_pane_spawn( - new_wp, cmd, path, shell, cwd, &env, s->tio, &cause) != 0) + if (window_pane_spawn(new_wp, argc, argv, path, shell, cwd, &env, + s->tio, &cause) != 0) goto error; layout_assign_pane(lc, new_wp); diff --git a/cmd.c b/cmd.c index 5660dc72..d8e010cb 100644 --- a/cmd.c +++ b/cmd.c @@ -187,7 +187,7 @@ cmd_copy_argv(int argc, char **argv) if (argc == 0) return (NULL); - new_argv = xcalloc(argc, sizeof *new_argv); + new_argv = xcalloc(argc + 1, sizeof *new_argv); for (i = 0; i < argc; i++) { if (argv[i] != NULL) new_argv[i] = xstrdup(argv[i]); @@ -207,6 +207,32 @@ cmd_free_argv(int argc, char **argv) free(argv); } +char * +cmd_stringify_argv(int argc, char **argv) +{ + char *buf; + int i; + size_t len; + + if (argc == 0) + return (xstrdup("")); + + len = 0; + buf = NULL; + + for (i = 0; i < argc; i++) { + len += strlen(argv[i]) + 1; + buf = xrealloc(buf, 1, len); + + if (i == 0) + *buf = '\0'; + else + strlcat(buf, " ", len); + strlcat(buf, argv[i], len); + } + return (buf); +} + struct cmd * cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause) { diff --git a/format.c b/format.c index 028031a0..10e5e0a8 100644 --- a/format.c +++ b/format.c @@ -368,7 +368,7 @@ format_get_command(struct window_pane *wp) cmd = get_proc_name(wp->fd, wp->tty); if (cmd == NULL || *cmd == '\0') { free(cmd); - cmd = xstrdup(wp->cmd); + cmd = cmd_stringify_argv(wp->argc, wp->argv); if (cmd == NULL || *cmd == '\0') { free(cmd); cmd = xstrdup(wp->shell); @@ -559,8 +559,10 @@ format_window_pane(struct format_tree *ft, struct window_pane *wp) if (wp->tty != NULL) format_add(ft, "pane_tty", "%s", wp->tty); format_add(ft, "pane_pid", "%ld", (long) wp->pid); - if (wp->cmd != NULL) - format_add(ft, "pane_start_command", "%s", wp->cmd); + if ((cmd = cmd_stringify_argv(wp->argc, wp->argv)) != NULL) { + format_add(ft, "pane_start_command", "%s", cmd); + free(cmd); + } if ((cmd = format_get_command(wp)) != NULL) { format_add(ft, "pane_current_command", "%s", cmd); free(cmd); diff --git a/names.c b/names.c index cfdaff83..8e01d347 100644 --- a/names.c +++ b/names.c @@ -68,9 +68,15 @@ window_name_callback(unused int fd, unused short events, void *data) char * default_window_name(struct window *w) { - if (w->active->cmd != NULL && *w->active->cmd != '\0') - return (parse_window_name(w->active->cmd)); - return (parse_window_name(w->active->shell)); + char *cmd, *s; + + cmd = cmd_stringify_argv(w->active->argc, w->active->argv); + if (cmd != NULL && *cmd != '\0') + s = parse_window_name(cmd); + else + s = parse_window_name(w->active->shell); + free(cmd); + return (s); } char * diff --git a/session.c b/session.c index b7d9844d..3bc2363e 100644 --- a/session.c +++ b/session.c @@ -85,11 +85,12 @@ session_find_by_id(u_int id) /* Create a new session. */ struct session * -session_create(const char *name, const char *cmd, const char *path, int cwd, - struct environ *env, struct termios *tio, int idx, u_int sx, u_int sy, - char **cause) +session_create(const char *name, int argc, char **argv, const char *path, + int cwd, struct environ *env, struct termios *tio, int idx, u_int sx, + u_int sy, char **cause) { struct session *s; + struct winlink *wl; s = xmalloc(sizeof *s); s->references = 0; @@ -132,8 +133,9 @@ session_create(const char *name, const char *cmd, const char *path, int cwd, } RB_INSERT(sessions, &sessions, s); - if (cmd != NULL) { - if (session_new(s, NULL, cmd, path, cwd, idx, cause) == NULL) { + if (argc >= 0) { + wl = session_new(s, NULL, argc, argv, path, cwd, idx, cause); + if (wl == NULL) { session_destroy(s); return (NULL); } @@ -227,7 +229,7 @@ session_previous_session(struct session *s) /* Create a new window on a session. */ struct winlink * -session_new(struct session *s, const char *name, const char *cmd, +session_new(struct session *s, const char *name, int argc, char **argv, const char *path, int cwd, int idx, char **cause) { struct window *w; @@ -251,8 +253,8 @@ session_new(struct session *s, const char *name, const char *cmd, shell = _PATH_BSHELL; hlimit = options_get_number(&s->options, "history-limit"); - w = window_create(name, cmd, path, shell, cwd, &env, s->tio, s->sx, - s->sy, hlimit, cause); + w = window_create(name, argc, argv, path, shell, cwd, &env, s->tio, + s->sx, s->sy, hlimit, cause); if (w == NULL) { winlink_remove(&s->windows, wl); environ_free(&env); diff --git a/tmux.1 b/tmux.1 index 397a228b..24dde9fa 100644 --- a/tmux.1 +++ b/tmux.1 @@ -478,12 +478,37 @@ It may be used alone to target a pane or the window containing it. arguments are .Xr sh 1 commands. -These must be passed as a single item, which typically means quoting them, for -example: +This may be a single argument passed to the shell, for example: .Bd -literal -offset indent new-window 'vi /etc/passwd' .Ed .Pp +Will run: +.Bd -literal -offset indent +/bin/sh -c 'vi /etc/passwd' +.Ed +.Pp +Additionally, the +.Ic new-window , +.Ic new-session , +.Ic split-window , +.Ic respawn-window +and +.Ic respawn-pane +commands allow +.Ar shell-command +to be given as multiple arguments and executed directly (without +.Ql sh -c ) . +This can avoid issues with shell quoting. +For example: +.Bd -literal -offset indent +$ tmux new-window vi /etc/passwd +.Ed +.Pp +Will run +.Xr vi 1 +directly without invoking the shell. +.Pp .Ar command .Op Ar arguments refers to a diff --git a/tmux.h b/tmux.h index a8d8e01f..c42df6ab 100644 --- a/tmux.h +++ b/tmux.h @@ -914,7 +914,8 @@ struct window_pane { #define PANE_RESIZE 0x8 #define PANE_FOCUSPUSH 0x10 - char *cmd; + int argc; + char **argv; char *shell; int cwd; @@ -1750,6 +1751,7 @@ int cmd_pack_argv(int, char **, char *, size_t); int cmd_unpack_argv(char *, size_t, int, char ***); char **cmd_copy_argv(int, char **); void cmd_free_argv(int, char **); +char *cmd_stringify_argv(int, char **); struct cmd *cmd_parse(int, char **, const char *, u_int, char **); size_t cmd_print(struct cmd *, char *, size_t); struct session *cmd_current_session(struct cmd_q *, int); @@ -2140,7 +2142,7 @@ void winlink_stack_remove(struct winlink_stack *, struct winlink *); int window_index(struct window *, u_int *); struct window *window_find_by_id(u_int); struct window *window_create1(u_int, u_int); -struct window *window_create(const char *, const char *, const char *, +struct window *window_create(const char *, int, char **, const char *, const char *, int, struct environ *, struct termios *, u_int, u_int, u_int, char **); void window_destroy(struct window *); @@ -2166,7 +2168,7 @@ struct window_pane *window_pane_find_by_id(u_int); struct window_pane *window_pane_create(struct window *, u_int, u_int, u_int); void window_pane_destroy(struct window_pane *); void window_pane_timer_start(struct window_pane *); -int window_pane_spawn(struct window_pane *, const char *, +int window_pane_spawn(struct window_pane *, int, char **, const char *, const char *, int, struct environ *, struct termios *, char **); void window_pane_resize(struct window_pane *, u_int, u_int); @@ -2304,18 +2306,18 @@ RB_PROTOTYPE(sessions, session, entry, session_cmp); int session_alive(struct session *); struct session *session_find(const char *); struct session *session_find_by_id(u_int); -struct session *session_create(const char *, const char *, const char *, int, - struct environ *, struct termios *, int, u_int, u_int, - char **); +struct session *session_create(const char *, int, char **, const char *, + int, struct environ *, struct termios *, int, u_int, + u_int, char **); void session_destroy(struct session *); int session_check_name(const char *); void session_update_activity(struct session *); struct session *session_next_session(struct session *); struct session *session_previous_session(struct session *); -struct winlink *session_new(struct session *, const char *, const char *, +struct winlink *session_new(struct session *, const char *, int, char **, const char *, int, int, char **); -struct winlink *session_attach( - struct session *, struct window *, int, char **); +struct winlink *session_attach(struct session *, struct window *, int, + char **); int session_detach(struct session *, struct winlink *); struct winlink *session_has(struct session *, struct window *); int session_next(struct session *, int); diff --git a/window.c b/window.c index 40ebcdb5..9a7226e9 100644 --- a/window.c +++ b/window.c @@ -307,7 +307,7 @@ window_create1(u_int sx, u_int sy) } struct window * -window_create(const char *name, const char *cmd, const char *path, +window_create(const char *name, int argc, char **argv, const char *path, const char *shell, int cwd, struct environ *env, struct termios *tio, u_int sx, u_int sy, u_int hlimit, char **cause) { @@ -318,7 +318,7 @@ window_create(const char *name, const char *cmd, const char *path, wp = window_add_pane(w, hlimit); layout_init(w, wp); - if (window_pane_spawn(wp, cmd, path, shell, cwd, env, tio, + if (window_pane_spawn(wp, argc, argv, path, shell, cwd, env, tio, cause) != 0) { window_destroy(w); return (NULL); @@ -678,7 +678,8 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) wp->id = next_window_pane_id++; RB_INSERT(window_pane_tree, &all_window_panes, wp); - wp->cmd = NULL; + wp->argc = 0; + wp->argv = NULL; wp->shell = NULL; wp->cwd = -1; @@ -737,27 +738,29 @@ window_pane_destroy(struct window_pane *wp) close(wp->cwd); free(wp->shell); - free(wp->cmd); + cmd_free_argv(wp->argc, wp->argv); free(wp); } int -window_pane_spawn(struct window_pane *wp, const char *cmd, const char *path, - const char *shell, int cwd, struct environ *env, struct termios *tio, - char **cause) +window_pane_spawn(struct window_pane *wp, int argc, char **argv, + const char *path, const char *shell, int cwd, struct environ *env, + struct termios *tio, char **cause) { struct winsize ws; - char *argv0, paneid[16]; - const char *ptr; + char *argv0, *cmd, **argvp, paneid[16]; + const char *ptr, *first; struct termios tio2; + int i; if (wp->fd != -1) { bufferevent_free(wp->event); close(wp->fd); } - if (cmd != NULL) { - free(wp->cmd); - wp->cmd = xstrdup(cmd); + if (argc > 0) { + cmd_free_argv(wp->argc, wp->argv); + wp->argc = argc; + wp->argv = cmd_copy_argv(argc, argv); } if (shell != NULL) { free(wp->shell); @@ -768,7 +771,10 @@ window_pane_spawn(struct window_pane *wp, const char *cmd, const char *path, wp->cwd = dup(cwd); } - log_debug("spawn: %s -- %s", wp->shell, wp->cmd); + cmd = cmd_stringify_argv(wp->argc, wp->argv); + log_debug("spawn: %s -- %s", wp->shell, cmd); + for (i = 0; i < wp->argc; i++) + log_debug("spawn: argv[%d] = %s", i, wp->argv[i]); memset(&ws, 0, sizeof ws); ws.ws_col = screen_size_x(&wp->base); @@ -778,6 +784,7 @@ window_pane_spawn(struct window_pane *wp, const char *cmd, const char *path, case -1: wp->fd = -1; xasprintf(cause, "%s: %s", cmd, strerror(errno)); + free(cmd); return (-1); case 0: if (fchdir(wp->cwd) != 0) @@ -805,31 +812,42 @@ window_pane_spawn(struct window_pane *wp, const char *cmd, const char *path, setenv("SHELL", wp->shell, 1); ptr = strrchr(wp->shell, '/'); - if (*wp->cmd != '\0') { - /* Use the command. */ + /* + * If given one argument, assume it should be passed to sh -c; + * with more than one argument, use execvp(). If there is no + * arguments, create a login shell. + */ + if (wp->argc > 0) { + if (wp->argc != 1) { + /* Copy to ensure argv ends in NULL. */ + argvp = cmd_copy_argv(wp->argc, wp->argv); + execvp(argvp[0], argvp); + fatal("execvp failed"); + } + first = wp->argv[0]; + if (ptr != NULL && *(ptr + 1) != '\0') xasprintf(&argv0, "%s", ptr + 1); else xasprintf(&argv0, "%s", wp->shell); - execl(wp->shell, argv0, "-c", wp->cmd, (char *) NULL); + execl(wp->shell, argv0, "-c", first, (char *)NULL); fatal("execl failed"); } - - /* No command; fork a login shell. */ if (ptr != NULL && *(ptr + 1) != '\0') xasprintf(&argv0, "-%s", ptr + 1); else xasprintf(&argv0, "-%s", wp->shell); - execl(wp->shell, argv0, (char *) NULL); + execl(wp->shell, argv0, (char *)NULL); fatal("execl failed"); } setblocking(wp->fd, 0); - wp->event = bufferevent_new(wp->fd, - window_pane_read_callback, NULL, window_pane_error_callback, wp); + wp->event = bufferevent_new(wp->fd, window_pane_read_callback, NULL, + window_pane_error_callback, wp); bufferevent_enable(wp->event, EV_READ|EV_WRITE); + free(cmd); return (0); }