diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index 0b42dd688..96cee88b4 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -77,3 +77,11 @@ jobs: cd regress export ASAN_OPTIONS="abort_on_error=1:detect_leaks=0" ${{ matrix.make }} + + - name: logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: regress-logs-${{ matrix.name }} + path: regress/logs/*.log + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index 46e3ad595..31ca7add0 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ configure core etc/ fuzz/*-fuzzer +regress/logs/ tags tmux tmux.1.* diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 7eb371c25..3891534a0 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -51,10 +51,9 @@ cmd_break_pane_float(struct cmdq_item *item, struct args *args, struct window *w, struct window_pane *wp) { struct layout_cell *lc = wp->layout_cell; - u_int sx = lc->saved_sx, sy = lc->saved_sy; - int ox = lc->saved_xoff, oy = lc->saved_yoff; char *cause = NULL; enum pane_lines lines = window_get_pane_lines(w); + struct layout_geometry *fg = &lc->fg; if (window_pane_is_floating(wp)) { cmdq_error(item, "pane is already floating"); @@ -65,14 +64,13 @@ cmd_break_pane_float(struct cmdq_item *item, struct args *args, return (CMD_RETURN_ERROR); } - if (layout_floating_args_parse(item, args, lines, w, &sx, &sy, &ox, &oy, - &cause) != 0) { + if (layout_floating_args_parse(item, args, lines, w, fg, &cause) != 0) { cmdq_error(item, "failed to float pane: %s", cause); free(cause); return (CMD_RETURN_ERROR); } layout_remove_tile(w, lc); - layout_set_size(lc, sx, sy, ox, oy); + layout_set_size(lc, fg->sx, fg->sy, fg->xoff, fg->yoff); lc->flags |= LAYOUT_CELL_FLOATING; TAILQ_REMOVE(&w->z_index, wp, zentry); diff --git a/cmd-display-menu.c b/cmd-display-menu.c index e1766fc2e..22d4bd503 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -126,6 +126,10 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item, format_add(ft, "popup_mouse_y", "%u", event->m.y); } + /* Position of the previous menu, for -x/-y L. */ + format_add(ft, "popup_last_x", "%u", tc->menu_last_px); + format_add(ft, "popup_last_y", "%u", tc->menu_last_py + h); + /* * If there are any status lines, add this window position and the * status line position. @@ -238,6 +242,8 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item, xp = "#{popup_pane_left}"; else if (strcmp(xp, "M") == 0) xp = "#{popup_mouse_centre_x}"; + else if (strcmp(xp, "L") == 0) + xp = "#{popup_last_x}"; else if (strcmp(xp, "W") == 0) xp = "#{popup_window_status_line_x}"; p = format_expand(ft, xp); @@ -258,6 +264,8 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item, yp = "#{popup_pane_bottom}"; else if (strcmp(yp, "M") == 0) yp = "#{popup_mouse_top}"; + else if (strcmp(yp, "L") == 0) + yp = "#{popup_last_y}"; else if (strcmp(yp, "S") == 0) yp = "#{popup_status_line_y}"; else if (strcmp(yp, "W") == 0) diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 99b2e80f7..314fc396f 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -71,8 +71,9 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl, struct window *w = wl->window; struct layout_cell *lc = wp->layout_cell; struct window_pane *owp; - int wx = w->sx, wy = w->sy, px = lc->sx; - int py = lc->sy, xoff = lc->xoff, yoff = lc->yoff; + int wx = w->sx, wy = w->sy; + int px = lc->g.sx, py = lc->g.sy; + int xoff = lc->g.xoff, yoff = lc->g.yoff; int border = 1; if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE) @@ -180,9 +181,9 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl, return (CMD_RETURN_ERROR); } - if (xoff != lc->xoff || yoff != lc->yoff) { - lc->xoff = xoff; - lc->yoff = yoff; + if (xoff != lc->g.xoff || yoff != lc->g.yoff) { + lc->g.xoff = xoff; + lc->g.yoff = yoff; layout_fix_panes(w, NULL); } notify_window("window-layout-changed", w); @@ -200,7 +201,7 @@ cmd_join_pane_move(struct cmdq_item *item, struct args *args, const char *errstr, *argval; const char flags[] = { 'U', 'D', 'L', 'R' }; char *cause = NULL, flag; - int xoff = lc->xoff, yoff = lc->yoff, adjust; + int xoff = lc->g.xoff, yoff = lc->g.yoff, adjust; u_int i; enum pane_lines lines = window_pane_get_pane_lines(wp); @@ -251,9 +252,9 @@ cmd_join_pane_move(struct cmdq_item *item, struct args *args, xoff += adjust; } - if (xoff != lc->xoff || yoff != lc->yoff) { - lc->xoff = xoff; - lc->yoff = yoff; + if (xoff != lc->g.xoff || yoff != lc->g.yoff) { + lc->g.xoff = xoff; + lc->g.yoff = yoff; layout_fix_panes(w, NULL); notify_window("window-layout-changed", w); server_redraw_window(w); @@ -319,8 +320,8 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m) ly = m->statusat - 1; if (x != lx || y != ly) { - lc->xoff += x - lx; - lc->yoff += y - ly; + lc->g.xoff += x - lx; + lc->g.yoff += y - ly; layout_fix_panes(w, NULL); server_redraw_window(w); server_redraw_window_borders(w); @@ -378,10 +379,11 @@ cmd_join_pane_tile(struct cmdq_item *item, struct args *args, struct window *w, return (CMD_RETURN_ERROR); } - lc->saved_sx = lc->sx; - lc->saved_sy = lc->sy; - lc->saved_xoff = lc->xoff; - lc->saved_yoff = lc->yoff; + lc->fg.sx = lc->g.sx; + lc->fg.sy = lc->g.sy; + lc->fg.xoff = lc->g.xoff; + lc->fg.yoff = lc->g.yoff; + if (layout_insert_tile(w, lc) != 0) { cmdq_error(item, "no space for a new pane"); return (CMD_RETURN_ERROR); diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 348923528..3ce805115 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -270,10 +270,10 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c, if ((lx == left || lx == left + 1) && ly == wp->yoff - 1) { /* Top left corner. */ - new_sx = lc->sx + (lx - x); + new_sx = lc->g.sx + (lx - x); if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = lc->sy + (ly - y); + new_sy = lc->g.sy + (ly - y); if (new_sy < PANE_MINIMUM) new_sy = PANE_MINIMUM; new_xoff = x + 1; /* because mouse is on border at xoff - 1 */ @@ -283,65 +283,65 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c, } else if ((lx == right + 1 || lx == right) && ly == wp->yoff - 1) { /* Top right corner. */ - new_sx = x - lc->xoff; + new_sx = x - lc->g.xoff; if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = lc->sy + (ly - y); + new_sy = lc->g.sy + (ly - y); if (new_sy < PANE_MINIMUM) new_sy = PANE_MINIMUM; new_yoff = y + 1; - layout_set_size(lc, new_sx, new_sy, lc->xoff, new_yoff); + layout_set_size(lc, new_sx, new_sy, lc->g.xoff, new_yoff); resizes++; } else if ((lx == left || lx == left + 1) && ly == wp->yoff + sy) { /* Bottom left corner. */ - new_sx = lc->sx + (lx - x); + new_sx = lc->g.sx + (lx - x); if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = y - lc->yoff; + new_sy = y - lc->g.yoff; if (new_sy < PANE_MINIMUM) return; new_xoff = x + 1; - layout_set_size(lc, new_sx, new_sy, new_xoff, lc->yoff); + layout_set_size(lc, new_sx, new_sy, new_xoff, lc->g.yoff); resizes++; } else if ((lx == right + 1 || lx == right) && ly == wp->yoff + sy) { /* Bottom right corner. */ - new_sx = x - lc->xoff; + new_sx = x - lc->g.xoff; if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = y - lc->yoff; + new_sy = y - lc->g.yoff; if (new_sy < PANE_MINIMUM) new_sy = PANE_MINIMUM; - layout_set_size(lc, new_sx, new_sy, lc->xoff, lc->yoff); + layout_set_size(lc, new_sx, new_sy, lc->g.xoff, lc->g.yoff); resizes++; } else if (lx == right) { /* Right border. */ - new_sx = x - lc->xoff; + new_sx = x - lc->g.xoff; if (new_sx < PANE_MINIMUM) return; - layout_set_size(lc, new_sx, lc->sy, lc->xoff, lc->yoff); + layout_set_size(lc, new_sx, lc->g.sy, lc->g.xoff, lc->g.yoff); resizes++; } else if (lx == left) { /* Left border. */ - new_sx = lc->sx + (lx - x); + new_sx = lc->g.sx + (lx - x); if (new_sx < PANE_MINIMUM) return; new_xoff = x + 1; - layout_set_size(lc, new_sx, lc->sy, new_xoff, lc->yoff); + layout_set_size(lc, new_sx, lc->g.sy, new_xoff, lc->g.yoff); resizes++; } else if (ly == wp->yoff + sy) { /* Bottom border. */ - new_sy = y - lc->yoff; + new_sy = y - lc->g.yoff; if (new_sy < PANE_MINIMUM) return; - layout_set_size(lc, lc->sx, new_sy, lc->xoff, lc->yoff); + layout_set_size(lc, lc->g.sx, new_sy, lc->g.xoff, lc->g.yoff); resizes++; } else if (ly == wp->yoff - 1) { /* Top border (move instead of resize). */ - new_xoff = lc->xoff + (x - lx); + new_xoff = lc->g.xoff + (x - lx); new_yoff = y + 1; - layout_set_size(lc, lc->sx, lc->sy, new_xoff, new_yoff); + layout_set_size(lc, lc->g.sx, lc->g.sy, new_xoff, new_yoff); resizes++; } if (resizes != 0) { diff --git a/cmd-set-option.c b/cmd-set-option.c index 5b5ea3d88..1b6de1e86 100644 --- a/cmd-set-option.c +++ b/cmd-set-option.c @@ -178,10 +178,10 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) struct window_pane *loop; struct options *oo; struct options_entry *parent, *o, *po; - char *name, *argument, *expanded = NULL; - char *cause; + char *name, *argument, *cause; + char *expanded = NULL, *array_key = NULL; const char *value; - int window, idx, already, error, ambiguous; + int window, already, error, ambiguous; int scope; window = (cmd_get_entry(self) == &cmd_set_window_option_entry); @@ -202,8 +202,8 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } - /* Parse option name and index. */ - name = options_match(argument, &idx, &ambiguous); + /* Parse option name and array key. */ + name = options_match(argument, &array_key, &ambiguous); if (name == NULL) { if (args_has(args, 'q')) goto out; @@ -235,21 +235,23 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) o = options_get_only(oo, name); parent = options_get(oo, name); - /* Check that array options and indexes match up. */ - if (idx != -1 && (*name == '@' || !options_is_array(parent))) { + /* Check that array options and keys match up. */ + if (array_key != NULL && (*name == '@' || !options_is_array(parent))) { cmdq_error(item, "not an array: %s", argument); goto fail; } /* With -o, check this option is not already set. */ if (!args_has(args, 'u') && args_has(args, 'o')) { - if (idx == -1) + if (array_key == NULL) already = (o != NULL); else { if (o == NULL) already = 0; + else if (options_array_get(o, array_key) != NULL) + already = 1; else - already = (options_array_get(o, idx) != NULL); + already = 0; } if (already) { if (args_has(args, 'q')) @@ -265,7 +267,8 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) po = options_get_only(loop->options, name); if (po == NULL) continue; - if (options_remove_or_default(po, idx, &cause) != 0) { + if (options_remove_or_default(po, array_key, + &cause) != 0) { cmdq_error(item, "%s", cause); free(cause); goto fail; @@ -275,7 +278,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'u') || args_has(args, 'U')) { if (o == NULL) goto out; - if (options_remove_or_default(o, idx, &cause) != 0) { + if (options_remove_or_default(o, array_key, &cause) != 0) { cmdq_error(item, "%s", cause); free(cause); goto fail; @@ -286,7 +289,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) goto fail; } options_set_string(oo, name, append, "%s", value); - } else if (idx == -1 && !options_is_array(parent)) { + } else if (array_key == NULL && !options_is_array(parent)) { error = options_from_string(oo, options_table_entry(parent), options_table_entry(parent)->name, value, args_has(args, 'a'), &cause); @@ -302,7 +305,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) } if (o == NULL) o = options_empty(oo, options_table_entry(parent)); - if (idx == -1) { + if (array_key == NULL) { if (!append) options_array_clear(o); if (options_array_assign(o, value, &cause) != 0) { @@ -310,7 +313,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) free(cause); goto fail; } - } else if (options_array_set(o, idx, value, append, + } else if (options_array_set(o, array_key, value, append, &cause) != 0) { cmdq_error(item, "%s", cause); free(cause); @@ -324,11 +327,13 @@ out: free(argument); free(expanded); free(name); + free(array_key); return (CMD_RETURN_NORMAL); fail: free(argument); free(expanded); free(name); + free(array_key); return (CMD_RETURN_ERROR); } diff --git a/cmd-show-options.c b/cmd-show-options.c index ce85f70c9..cd15c80d8 100644 --- a/cmd-show-options.c +++ b/cmd-show-options.c @@ -30,7 +30,7 @@ static enum cmd_retval cmd_show_options_exec(struct cmd *, struct cmdq_item *); static void cmd_show_options_print(struct cmd *, struct cmdq_item *, - struct options_entry *, int, int); + struct options_entry *, const char *, int); static void cmd_show_hooks_print_monitor(struct cmdq_item *, struct options_entry *); static enum cmd_retval cmd_show_hooks_monitor(struct cmd *, struct cmdq_item *, @@ -84,7 +84,8 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) struct cmd_find_state *target = cmdq_get_target(item); struct options *oo; char *argument, *name = NULL, *cause; - int window, idx, ambiguous, parent, scope; + char *array_key = NULL; + int window, ambiguous, parent, scope; struct options_entry *o; window = (cmd_get_entry(self) == &cmd_show_window_options_entry); @@ -106,7 +107,7 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) } argument = format_single_from_target(item, args_string(args, 0)); - name = options_match(argument, &idx, &ambiguous); + name = options_match(argument, &array_key, &ambiguous); if (name == NULL) { if (args_has(args, 'q')) goto out; @@ -136,7 +137,7 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) args_has(args, 'B')) cmd_show_hooks_print_monitor(item, o); else - cmd_show_options_print(self, item, o, idx, parent); + cmd_show_options_print(self, item, o, array_key, parent); } else if (*name == '@') { if (args_has(args, 'q')) @@ -147,26 +148,28 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) out: free(name); + free(array_key); free(argument); return (CMD_RETURN_NORMAL); fail: free(name); + free(array_key); free(argument); return (CMD_RETURN_ERROR); } static void cmd_show_options_print(struct cmd *self, struct cmdq_item *item, - struct options_entry *o, int idx, int parent) + struct options_entry *o, const char *array_key, int parent) { struct args *args = cmd_get_args(self); struct options_array_item *a; const char *name = options_name(o); char *value, *tmp = NULL, *escaped; - if (idx != -1) { - xasprintf(&tmp, "%s[%d]", name, idx); + if (array_key != NULL) { + xasprintf(&tmp, "%s[%s]", name, array_key); name = tmp; } else { if (options_is_array(o)) { @@ -177,8 +180,8 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item, return; } while (a != NULL) { - idx = options_array_item_index(a); - cmd_show_options_print(self, item, o, idx, + array_key = options_array_item_key(a); + cmd_show_options_print(self, item, o, array_key, parent); a = options_array_next(a); } @@ -186,7 +189,7 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item, } } - value = options_to_string(o, idx, 0); + value = options_to_string(o, array_key, 0); if (args_has(args, 'v')) cmdq_print(item, "%s", value); else if (options_is_string(o)) { @@ -242,15 +245,14 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, const struct options_table_entry *oe; struct options_entry *o; struct options_array_item *a; - const char *name; - u_int idx; + const char *name, *array_key; int parent; if (cmd_get_entry(self) != &cmd_show_hooks_entry) { o = options_first(oo); while (o != NULL) { if (options_table_entry(o) == NULL) - cmd_show_options_print(self, item, o, -1, 0); + cmd_show_options_print(self, item, o, NULL, 0); o = options_next(o); } } @@ -277,7 +279,7 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, parent = 0; if (!options_is_array(o)) - cmd_show_options_print(self, item, o, -1, parent); + cmd_show_options_print(self, item, o, NULL, parent); else if ((a = options_array_first(o)) == NULL) { if (!args_has(args, 'v')) { name = options_name(o); @@ -288,8 +290,8 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, } } else { while (a != NULL) { - idx = options_array_item_index(a); - cmd_show_options_print(self, item, o, idx, + array_key = options_array_item_key(a); + cmd_show_options_print(self, item, o, array_key, parent); a = options_array_next(a); } diff --git a/colour.c b/colour.c index b37882a88..f563fd5aa 100644 --- a/colour.c +++ b/colour.c @@ -1294,7 +1294,8 @@ colour_palette_from_option(struct colour_palette *p, struct options *oo) { struct options_entry *o; struct options_array_item *a; - u_int i, n; + union options_value *ov; + u_int i; int c; if (p == NULL) @@ -1312,12 +1313,11 @@ colour_palette_from_option(struct colour_palette *p, struct options *oo) p->default_palette = xcalloc(256, sizeof *p->default_palette); for (i = 0; i < 256; i++) p->default_palette[i] = -1; - while (a != NULL) { - n = options_array_item_index(a); - if (n < 256) { - c = options_array_item_value(a)->number; - p->default_palette[n] = c; + for (i = 0; i < 256; i++) { + ov = options_array_getv(o, "%u", i); + if (ov != NULL) { + c = ov->number; + p->default_palette[i] = c; } - a = options_array_next(a); } } diff --git a/format.c b/format.c index fe3eeb528..fd11fcb5b 100644 --- a/format.c +++ b/format.c @@ -4241,25 +4241,26 @@ format_find(struct format_tree *ft, const char *key, uint64_t modifiers, struct format_entry *fe, fe_find; struct environ_entry *envent; struct options_entry *o; - int idx; char *found = NULL, *saved, s[512]; + char *array_key = NULL; const char *errstr; time_t t = 0; struct tm tm; - o = options_parse_get(global_options, key, &idx, 0); + o = options_parse_get(global_options, key, &array_key, 0); if (o == NULL && ft->wp != NULL) - o = options_parse_get(ft->wp->options, key, &idx, 0); + o = options_parse_get(ft->wp->options, key, &array_key, 0); if (o == NULL && ft->w != NULL) - o = options_parse_get(ft->w->options, key, &idx, 0); + o = options_parse_get(ft->w->options, key, &array_key, 0); if (o == NULL) - o = options_parse_get(global_w_options, key, &idx, 0); + o = options_parse_get(global_w_options, key, &array_key, 0); if (o == NULL && ft->s != NULL) - o = options_parse_get(ft->s->options, key, &idx, 0); + o = options_parse_get(ft->s->options, key, &array_key, 0); if (o == NULL) - o = options_parse_get(global_s_options, key, &idx, 0); + o = options_parse_get(global_s_options, key, &array_key, 0); if (o != NULL) { - found = options_to_string(o, idx, 1); + found = options_to_string(o, array_key, 1); + free(array_key); goto found; } @@ -4953,7 +4954,7 @@ format_add_window_neighbour(struct format_tree *nft, struct winlink *wl, oname = options_name(o); if (*oname == '@') { xasprintf(&prefixed, "%s_%s", prefix, oname); - oval = options_to_string(o, -1, 1); + oval = options_to_string(o, NULL, 1); format_add(nft, prefixed, "%s", oval); free(oval); free(prefixed); @@ -5125,11 +5126,12 @@ format_loop_add_option(struct format_expand_state *es, const char *fmt, nft = format_create(ft->client, ft->item, FORMAT_NONE, ft->flags); format_add(nft, "option_name", "%s", name); - s = options_to_string(o, -1, 0); + s = options_to_string(o, NULL, 0); format_add(nft, "option_value", "%s", s); free(s); format_add(nft, "option_is_array", "%d", is_array); + format_add(nft, "option_array_key", "%s", ""); format_add(nft, "option_array_index", "%s", ""); format_add(nft, "option_array_first", "%d", is_array); format_add(nft, "option_array_last", "%d", is_array); @@ -5167,20 +5169,21 @@ format_loop_add_array_item(struct format_expand_state *es, const char *fmt, struct format_expand_state next; const struct options_table_entry *oe = options_table_entry(o); const char *name = options_name(o); + const char *array_key; char *expanded, *s; - u_int idx; - idx = options_array_item_index(a); - format_log(es, "option loop: %s[%u]", name, idx); + array_key = options_array_item_key(a); + format_log(es, "option loop: %s[%s]", name, array_key); nft = format_create(ft->client, ft->item, FORMAT_NONE, ft->flags); format_add(nft, "option_name", "%s", name); - s = options_to_string(o, idx, 0); + s = options_to_string(o, array_key, 0); format_add(nft, "option_value", "%s", s); free(s); format_add(nft, "option_is_array", "1"); - format_add(nft, "option_array_index", "%u", idx); + format_add(nft, "option_array_key", "%s", array_key); + format_add(nft, "option_array_index", "%s", array_key); if (a == options_array_first(o)) format_add(nft, "option_array_first", "1"); else diff --git a/input.c b/input.c index 5f7f9d132..a85b555f8 100644 --- a/input.c +++ b/input.c @@ -2818,7 +2818,7 @@ input_exit_rename(struct input_ctx *ictx) if (ictx->input_len == 0) { o = options_get_only(w->options, "automatic-rename"); if (o != NULL) - options_remove_or_default(o, -1, NULL); + options_remove_or_default(o, NULL, NULL); if (!options_get_number(w->options, "automatic-rename")) window_set_name(w, "", 1); } else { diff --git a/key-bindings.c b/key-bindings.c index 641957103..c73fd7c33 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -59,6 +59,8 @@ " '#{?mouse_hyperlink,Type #[underscore]#{=/9/...:mouse_hyperlink},}' 'C-h' {copy-mode -q; send-keys -l -- \"#{q:mouse_hyperlink}\"}" \ " '#{?mouse_hyperlink,Copy #[underscore]#{=/9/...:mouse_hyperlink},}' 'h' {copy-mode -q; set-buffer -- \"#{q:mouse_hyperlink}\"}" \ " ''" \ + " '#{?#{#{pane_floating_flag}},Move,}' '' {display-menu -xL -yL -T '#[align=centre]Move' " DEFAULT_MOVE_MENU " }" \ + " '#{?#{#{pane_floating_flag}},Move & Resize,}' '' {display-menu -xL -yL -T '#[align=centre]Move & Resize' " DEFAULT_MOVE_RESIZE_MENU " }" \ " '#{?#{#{pane_floating_flag}},Tile,}' 't' { join-pane }" \ " '#{?#{!:#{pane_floating_flag}},Float,}' 'f' { break-pane -W }" \ " '#{?#{!:#{pane_floating_flag}},Horizontal Split,}' 'h' {split-window -h}" \ @@ -72,6 +74,30 @@ " 'Respawn' 'R' {respawn-pane -k}" \ " '#{?pane_marked,Unmark,Mark}' 'm' {select-pane -m}" \ " '#{?#{>:#{window_panes},1},,-}#{?window_zoomed_flag,Unzoom,Zoom}' 'z' {resize-pane -Z}" +#define DEFAULT_MOVE_MENU \ + " 'Centre' 'c' {move-pane -P centre}" \ + " ''" \ + " 'Top Left' '1' {move-pane -P top-left}" \ + " 'Top Right' '2' {move-pane -P top-right}" \ + " 'Bottom Left' '3' {move-pane -P bottom-left}" \ + " 'Bottom Right' '4' {move-pane -P bottom-right}" \ + " ''" \ + " 'Top' 't' {move-pane -P top-centre}" \ + " 'Bottom' 'b' {move-pane -P bottom-centre}" \ + " 'Left' 'l' {move-pane -P centre-left}" \ + " 'Right' 'r' {move-pane -P centre-right}" +#define DEFAULT_MOVE_RESIZE_MENU \ + " 'Fill' '0' {resize-pane -x100% -y100%; move-pane -P top-left}" \ + " ''" \ + " 'Top Left' '1' {resize-pane -x50% -y50%; move-pane -P top-left}" \ + " 'Top Right' '2' {resize-pane -x50% -y50%; move-pane -P top-right}" \ + " 'Bottom Left' '3' {resize-pane -x50% -y50%; move-pane -P bottom-left}" \ + " 'Bottom Right' '4' {resize-pane -x50% -y50%; move-pane -P bottom-right}" \ + " ''" \ + " 'Top' 't' {resize-pane -x100% -y50%; move-pane -P top-centre}" \ + " 'Bottom' 'b' {resize-pane -x100% -y50%; move-pane -P bottom-centre}" \ + " 'Left' 'l' {resize-pane -x50% -y100%; move-pane -P centre-left}" \ + " 'Right' 'r' {resize-pane -x50% -y100%; move-pane -P centre-right}" static int key_bindings_cmp(struct key_binding *, struct key_binding *); RB_GENERATE_STATIC(key_bindings, key_binding, entry, key_bindings_cmp); @@ -362,14 +388,14 @@ key_bindings_init(void) "bind -N 'Rename current session' '$' { command-prompt -I'#S' { rename-session -- '%%' } }", "bind -N 'Split window horizontally' % { split-window -h }", "bind -N 'Kill current window' & { confirm-before -p\"kill-window #W? (y/n)\" kill-window }", - "bind -N 'Prompt for window index to select' \"'\" { command-prompt -T window-target -pindex { select-window -t ':%%' } }", + "bind -N 'Prompt for window index to select' \"'\" { command-prompt -pindex { select-window -t ':%%' } }", "bind -N 'New floating pane' * { new-pane }", "bind -N 'Toggle pane between floating and tiled' @ { if -F '#{pane_floating_flag}' { join-pane } { break-pane -W } }", "bind -N 'Switch to previous client' ( { switch-client -p }", "bind -N 'Switch to next client' ) { switch-client -n }", "bind -N 'Rename current window' , { command-prompt -I'#W' { rename-window -- '%%' } }", "bind -N 'Delete the most recent paste buffer' - { delete-buffer }", - "bind -N 'Move the current window' . { command-prompt -T target { move-window -t '%%' } }", + "bind -N 'Move the current window' . { command-prompt { move-window -t '%%' } }", "bind -N 'Describe key binding' '/' { command-prompt -kpkey { list-keys -1N '%%' } }", "bind -N 'Select window 0' 0 { select-window -t:=0 }", "bind -N 'Select window 1' 1 { select-window -t:=1 }", @@ -410,10 +436,8 @@ key_bindings_init(void) "bind -N 'Choose a window from a list' w { choose-tree -Zw }", "bind -N 'Kill the active pane' x { confirm-before -p\"kill-pane #P? (y/n)\" kill-pane }", "bind -N 'Zoom the active pane' z { resize-pane -Z }", - "bind -N 'Move pane to top-left corner' '{' { resize-pane -x50% -y50%; move-pane -P top-left }", - "bind -N 'Move pane to top-right corner' '}' { resize-pane -x50% -y50%; move-pane -P top-right }", - "bind -N 'Move pane to bottom-left corner' 'M-{' { resize-pane -x50% -y50%; move-pane -P bottom-left }", - "bind -N 'Move pane to bottom-right corner' 'M-}' { resize-pane -x50% -y50%; move-pane -P bottom-right }", + "bind -N 'Swap the active pane with the pane above' '{' { swap-pane -U }", + "bind -N 'Swap the active pane with the pane below' '}' { swap-pane -D }", "bind -N 'Show messages' '~' { show-messages }", "bind -N 'Enter copy mode and scroll up' PPage { copy-mode -u }", "bind -N 'Select the pane above the active pane' -r Up { select-pane -U }", @@ -444,6 +468,28 @@ key_bindings_init(void) "bind -N 'Resize the pane left' -r C-Left if -F '#{?floating_pane_flag}' { resizep -R-1 } { resize-pane -L }", "bind -N 'Resize the pane right' -r C-Right { resize-pane -R }", + /* Floating pane movement. */ + "bind -N 'Move a floating pane' g { switch-client -Tmove }", + "bind -Tmove -N 'Move pane to top-left corner' 1 { move-pane -P top-left }", + "bind -Tmove -N 'Move pane to top-right corner' 2 { move-pane -P top-right }", + "bind -Tmove -N 'Move pane to bottom-left corner' 3 { move-pane -P bottom-left }", + "bind -Tmove -N 'Move pane to bottom-right corner' 4 { move-pane -P bottom-right }", + "bind -Tmove -N 'Move pane to top-left corner and resize' M-1 { resize-pane -x50% -y50%; move-pane -P top-left }", + "bind -Tmove -N 'Move pane to top-right corner and resize' M-2 { resize-pane -x50% -y50%; move-pane -P top-right }", + "bind -Tmove -N 'Move pane to bottom-left corner and resize' M-3 { resize-pane -x50% -y50%; move-pane -P bottom-left }", + "bind -Tmove -N 'Move pane to bottom-right corner and resize' M-4 { resize-pane -x50% -y50%; move-pane -P bottom-right }", + "bind -Tmove -N 'Move pane to top' 'Up' { move-pane -P top-centre }", + "bind -Tmove -N 'Move pane to bottom' 'Down' { move-pane -P bottom-centre }", + "bind -Tmove -N 'Move pane to left' 'Left' { move-pane -P centre-left }", + "bind -Tmove -N 'Move pane to right' 'Right' { move-pane -P centre-right }", + "bind -Tmove -N 'Move pane to top and resize' 'M-Up' { resizep -x100% -y50%; move-pane -P top-centre }", + "bind -Tmove -N 'Move pane to bottom and resize' 'M-Down' { resizep -x100% -y50%; move-pane -P bottom-centre }", + "bind -Tmove -N 'Move pane to left and resize' 'M-Left' { resizep -x50% -y100%; move-pane -P centre-left }", + "bind -Tmove -N 'Move pane to right and resize' 'M-Right' { resizep -x50% -y100%; move-pane -P centre-right }", + "bind -Tmove -N 'Move pane to fill the window' 0 { resize-pane -x100% -y100%; move-pane -P top-left }", + "bind -Tmove -N 'Display move menu' , { if -F '#{pane_floating_flag}' { display-menu -xP -yP -T '#[align=centre]Move' " DEFAULT_MOVE_MENU " } }", + "bind -Tmove -N 'Display move and resize menu' . { if -F '#{pane_floating_flag}' { display-menu -xP -yP -T '#[align=centre]Move & Resize' " DEFAULT_MOVE_RESIZE_MENU " } }", + /* Menu keys */ "bind -N 'Display window menu' < { display-menu -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU " }", "bind -N 'Display pane menu' > { display-menu -xP -yP -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " }", diff --git a/layout-custom.c b/layout-custom.c index f8c666965..a284b5fe4 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -231,19 +231,19 @@ layout_append_legacy(struct layout_cell *lc, char *buf, size_t len) if (lc == NULL) return (0); - sx = lc->sx; - sy = lc->sy; - xoff = lc->xoff; - yoff = lc->yoff; + sx = lc->g.sx; + sy = lc->g.sy; + xoff = lc->g.xoff; + yoff = lc->g.yoff; if (lc->flags & LAYOUT_CELL_HIDDEN) { - if (lc->saved_sx != UINT_MAX) - sx = lc->saved_sx; - if (lc->saved_sy != UINT_MAX) - sy = lc->saved_sy; - if (lc->saved_xoff != INT_MAX) - xoff = lc->saved_xoff; - if (lc->saved_yoff != INT_MAX) - yoff = lc->saved_yoff; + if (lc->fg.sx != UINT_MAX) + sx = lc->fg.sx; + if (lc->fg.sy != UINT_MAX) + sy = lc->fg.sy; + if (lc->fg.xoff != INT_MAX) + xoff = lc->fg.xoff; + if (lc->fg.yoff != INT_MAX) + yoff = lc->fg.yoff; } if (lc->wp != NULL) { @@ -293,19 +293,19 @@ layout_append(struct window *w, struct layout_cell *lc, char *buf, size_t len) if (lc == NULL) return (0); - sx = lc->sx; - sy = lc->sy; - xoff = lc->xoff; - yoff = lc->yoff; + sx = lc->g.sx; + sy = lc->g.sy; + xoff = lc->g.xoff; + yoff = lc->g.yoff; if (lc->flags & LAYOUT_CELL_HIDDEN) { - if (lc->saved_sx != UINT_MAX) - sx = lc->saved_sx; - if (lc->saved_sy != UINT_MAX) - sy = lc->saved_sy; - if (lc->saved_xoff != INT_MAX) - xoff = lc->saved_xoff; - if (lc->saved_yoff != INT_MAX) - yoff = lc->saved_yoff; + if (lc->fg.sx != UINT_MAX) + sx = lc->fg.sx; + if (lc->fg.sy != UINT_MAX) + sy = lc->fg.sy; + if (lc->fg.xoff != INT_MAX) + xoff = lc->fg.xoff; + if (lc->fg.yoff != INT_MAX) + yoff = lc->fg.yoff; } if (lc->wp != NULL) { @@ -375,24 +375,24 @@ layout_check(struct layout_cell *lc) TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (!layout_has_tiled(lcchild)) continue; - if (lcchild->sy != lc->sy || !layout_check(lcchild)) + if (lcchild->g.sy != lc->g.sy || !layout_check(lcchild)) return (0); - n += lcchild->sx + 1; + n += lcchild->g.sx + 1; children++; } - if (children != 0 && n - 1 != lc->sx) + if (children != 0 && n - 1 != lc->g.sx) return (0); break; case LAYOUT_TOPBOTTOM: TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (!layout_has_tiled(lcchild)) continue; - if (lcchild->sx != lc->sx || !layout_check(lcchild)) + if (lcchild->g.sx != lc->g.sx || !layout_check(lcchild)) return (0); - n += lcchild->sy + 1; + n += lcchild->g.sy + 1; children++; } - if (children != 0 && n - 1 != lc->sy) + if (children != 0 && n - 1 != lc->g.sy) return (0); break; } @@ -423,21 +423,21 @@ layout_fix_legacy_root(struct layout_cell *root) return (0); if (root->type == LAYOUT_LEFTRIGHT) { TAILQ_FOREACH(lcchild, &root->cells, entry) { - sy = lcchild->sy + 1; - sx += lcchild->sx + 1; + sy = lcchild->g.sy + 1; + sx += lcchild->g.sx + 1; } } else { TAILQ_FOREACH(lcchild, &root->cells, entry) { - sx = lcchild->sx + 1; - sy += lcchild->sy + 1; + sx = lcchild->g.sx + 1; + sy += lcchild->g.sy + 1; } } if (sx != 0 && sy != 0 && - (root->sx != sx - 1 || root->sy != sy - 1)) { + (root->g.sx != sx - 1 || root->g.sy != sy - 1)) { if (sx - 1 > PANE_MAXIMUM || sy - 1 > PANE_MAXIMUM) return (-1); - root->sx = sx - 1; - root->sy = sy - 1; + root->g.sx = sx - 1; + root->g.sy = sy - 1; } return (0); } @@ -736,10 +736,10 @@ layout_construct(struct layout_parse_ctx *ctx, struct layout_cell *parent, (lc->flags & LAYOUT_CELL_FLOATING) == 0) goto fail; if (lc->flags & LAYOUT_CELL_HIDDEN) { - lc->saved_sx = lc->sx; - lc->saved_sy = lc->sy; - lc->saved_xoff = lc->xoff; - lc->saved_yoff = lc->yoff; + lc->fg.sx = lc->g.sx; + lc->fg.sy = lc->g.sy; + lc->fg.xoff = lc->g.xoff; + lc->fg.yoff = lc->g.yoff; } } else if (ctx->ptr != ctx->end && (*ctx->ptr == '{' || *ctx->ptr == '[')) { @@ -815,26 +815,26 @@ layout_resolve_relative(struct layout_cell *lc, u_int sx, u_int sy) return (0); } if (lc->flags & LAYOUT_CELL_X_RELATIVE) { - resolved = (long long)sx - lc->sx - lc->xoff; + resolved = (long long)sx - lc->g.sx - lc->g.xoff; if (resolved < INT_MIN || resolved > INT_MAX) return (-1); - lc->xoff = resolved; + lc->g.xoff = resolved; lc->flags &= ~LAYOUT_CELL_X_RELATIVE; } if (lc->flags & LAYOUT_CELL_Y_RELATIVE) { - resolved = (long long)sy - lc->sy - lc->yoff; + resolved = (long long)sy - lc->g.sy - lc->g.yoff; if (resolved < INT_MIN || resolved > INT_MAX) return (-1); - lc->yoff = resolved; + lc->g.yoff = resolved; lc->flags &= ~LAYOUT_CELL_Y_RELATIVE; } if (lc->flags & LAYOUT_CELL_HIDDEN) { - lc->saved_sx = lc->sx; - lc->saved_sy = lc->sy; - lc->saved_xoff = lc->xoff; - lc->saved_yoff = lc->yoff; + lc->fg.sx = lc->g.sx; + lc->fg.sy = lc->g.sy; + lc->fg.xoff = lc->g.xoff; + lc->fg.yoff = lc->g.yoff; } - if (!layout_check_geometry(lc->sx, lc->sy, lc->xoff, lc->yoff)) + if (!layout_check_geometry(lc->g.sx, lc->g.sy, lc->g.xoff, lc->g.yoff)) return (-1); return (0); } @@ -1009,8 +1009,8 @@ layout_prepare(struct window *w, const char *layout, char **cause) return (NULL); } if (layout_resolve_relative(root, - root->type == LAYOUT_WINDOWPANE ? w->sx : root->sx, - root->type == LAYOUT_WINDOWPANE ? w->sy : root->sy) != 0) { + root->type == LAYOUT_WINDOWPANE ? w->sx : root->g.sx, + root->type == LAYOUT_WINDOWPANE ? w->sy : root->g.sy) != 0) { *cause = xstrdup("invalid layout"); layout_free_cell(root, 0); return (NULL); @@ -1127,7 +1127,7 @@ layout_apply_prepared(struct window *w, struct layout_prepared *prepared) layout_fix_zindexes(w, root); if (layout_has_tiled(root)) - window_resize(w, root->sx, root->sy, -1, -1); + window_resize(w, root->g.sx, root->g.sy, -1, -1); layout_fix_offsets(w); layout_fix_panes(w, NULL); layout_print_cell(root, __func__, 0); diff --git a/layout-set.c b/layout-set.c index 8b3c8c302..add81b413 100644 --- a/layout-set.c +++ b/layout-set.c @@ -170,8 +170,8 @@ layout_set_even(struct window *w, enum layout_type type) TAILQ_INSERT_TAIL(&lcroot->cells, lcchild, entry); lcchild->parent = lcroot; if (layout_cell_is_tiled(lcchild)) { - lcchild->sx = w->sx; - lcchild->sy = w->sy; + lcchild->g.sx = w->sx; + lcchild->g.sy = w->sy; } } @@ -182,7 +182,7 @@ layout_set_even(struct window *w, enum layout_type type) layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lcroot->sx, lcroot->sy, -1, -1); + window_resize(w, lcroot->g.sx, lcroot->g.sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } @@ -292,7 +292,7 @@ layout_set_main_h(struct window *w) layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lcroot->sx, lcroot->sy, -1, -1); + window_resize(w, lcroot->g.sx, lcroot->g.sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } @@ -390,7 +390,7 @@ layout_set_main_h_mirrored(struct window *w) layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lcroot->sx, lcroot->sy, -1, -1); + window_resize(w, lcroot->g.sx, lcroot->g.sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } @@ -488,7 +488,7 @@ layout_set_main_v(struct window *w) layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lcroot->sx, lcroot->sy, -1, -1); + window_resize(w, lcroot->g.sx, lcroot->g.sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } @@ -587,7 +587,7 @@ layout_set_main_v_mirrored(struct window *w) layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lcroot->sx, lcroot->sy, -1, -1); + window_resize(w, lcroot->g.sx, lcroot->g.sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } @@ -709,7 +709,7 @@ layout_set_tiled(struct window *w) layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lcroot->sx, lcroot->sy, -1, -1); + window_resize(w, lcroot->g.sx, lcroot->g.sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } diff --git a/layout.c b/layout.c index ac0bfb40f..1f78c446d 100644 --- a/layout.c +++ b/layout.c @@ -56,34 +56,31 @@ static int layout_set_size_check(struct window *, struct layout_cell *, static void layout_resize_child_cells(struct window *, struct layout_cell *); +/* Initializes cell geometry to sentinel values. */ +static void +layout_geometry_init(struct layout_geometry *lg) +{ + lg->sx = UINT_MAX; + lg->sy = UINT_MAX; + lg->xoff = INT_MAX; + lg->yoff = INT_MAX; +} + /* Create a new layout cell. */ struct layout_cell * layout_create_cell(struct layout_cell *lcparent) { struct layout_cell *lc; - lc = xmalloc(sizeof *lc); + lc = xcalloc(1, sizeof *lc); lc->type = LAYOUT_WINDOWPANE; - lc->flags = 0; lc->z_index = UINT_MAX; lc->pane_id = UINT_MAX; lc->parent = lcparent; - TAILQ_INIT(&lc->cells); - lc->sx = UINT_MAX; - lc->sy = UINT_MAX; - - lc->xoff = INT_MAX; - lc->yoff = INT_MAX; - - lc->saved_sx = UINT_MAX; - lc->saved_sy = UINT_MAX; - - lc->saved_xoff = INT_MAX; - lc->saved_yoff = INT_MAX; - - lc->wp = NULL; + layout_geometry_init(&lc->g); + layout_geometry_init(&lc->fg); return (lc); } @@ -146,8 +143,8 @@ layout_print_cell(struct layout_cell *lc, const char *hdr, u_int n) break; } log_debug("%s:%*s%p type %s [parent %p] wp=%p [%d,%d %ux%u]", hdr, n, - " ", lc, type, lc->parent, lc->wp, lc->xoff, lc->yoff, lc->sx, - lc->sy); + " ", lc, type, lc->parent, lc->wp, lc->g.xoff, lc->g.yoff, lc->g.sx, + lc->g.sy); switch (lc->type) { case LAYOUT_LEFTRIGHT: case LAYOUT_TOPBOTTOM: @@ -166,10 +163,10 @@ layout_search_by_border(struct layout_cell *lc, u_int x, u_int y) struct layout_cell *lcchild, *last = NULL; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if ((int)x >= lcchild->xoff && - (int)x < lcchild->xoff + (int)lcchild->sx && - (int)y >= lcchild->yoff && - (int)y < lcchild->yoff + (int)lcchild->sy) { + if ((int)x >= lcchild->g.xoff && + (int)x < lcchild->g.xoff + (int)lcchild->g.sx && + (int)y >= lcchild->g.yoff && + (int)y < lcchild->g.yoff + (int)lcchild->g.sy) { /* Inside the cell - recurse. */ return (layout_search_by_border(lcchild, x, y)); } @@ -181,13 +178,13 @@ layout_search_by_border(struct layout_cell *lc, u_int x, u_int y) switch (lc->type) { case LAYOUT_LEFTRIGHT: - if ((int)x < lcchild->xoff && - (int)x >= last->xoff + (int)last->sx) + if ((int)x < lcchild->g.xoff && + (int)x >= last->g.xoff + (int)last->g.sx) return (last); break; case LAYOUT_TOPBOTTOM: - if ((int)y < lcchild->yoff && - (int)y >= last->yoff + (int)last->sy) + if ((int)y < lcchild->g.yoff && + (int)y >= last->g.yoff + (int)last->g.sy) return (last); break; case LAYOUT_WINDOWPANE: @@ -204,11 +201,11 @@ layout_search_by_border(struct layout_cell *lc, u_int x, u_int y) void layout_set_size(struct layout_cell *lc, u_int sx, u_int sy, int xoff, int yoff) { - lc->sx = sx; - lc->sy = sy; + lc->g.sx = sx; + lc->g.sy = sy; - lc->xoff = xoff; - lc->yoff = yoff; + lc->g.xoff = xoff; + lc->g.yoff = yoff; } /* Make a cell a leaf cell. */ @@ -332,26 +329,26 @@ layout_fix_offsets1(struct layout_cell *lc) int xoff, yoff; if (lc->type == LAYOUT_LEFTRIGHT) { - xoff = lc->xoff; + xoff = lc->g.xoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (!layout_has_tiled(lcchild)) continue; - lcchild->xoff = xoff; - lcchild->yoff = lc->yoff; + lcchild->g.xoff = xoff; + lcchild->g.yoff = lc->g.yoff; if (lcchild->type != LAYOUT_WINDOWPANE) layout_fix_offsets1(lcchild); - xoff += lcchild->sx + 1; + xoff += lcchild->g.sx + 1; } } else { - yoff = lc->yoff; + yoff = lc->g.yoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (!layout_has_tiled(lcchild)) continue; - lcchild->xoff = lc->xoff; - lcchild->yoff = yoff; + lcchild->g.xoff = lc->g.xoff; + lcchild->g.yoff = yoff; if (lcchild->type != LAYOUT_WINDOWPANE) layout_fix_offsets1(lcchild); - yoff += lcchild->sy + 1; + yoff += lcchild->g.sy + 1; } } } @@ -366,8 +363,8 @@ layout_fix_offsets(struct window *w) if (lc->flags & LAYOUT_CELL_FLOATING) return; - lc->xoff = 0; - lc->yoff = 0; + lc->g.xoff = 0; + lc->g.yoff = 0; layout_fix_offsets1(lc); } @@ -451,16 +448,17 @@ layout_fix_panes(struct window *w, struct window_pane *skip) old_sx = wp->sx; old_sy = wp->sy; - wp->xoff = lc->xoff; - wp->yoff = lc->yoff; - sx = lc->sx; - sy = lc->sy; + wp->xoff = lc->g.xoff; + wp->yoff = lc->g.yoff; + sx = lc->g.sx; + sy = lc->g.sy; if (!window_pane_is_floating(wp) && layout_add_horizontal_border(w, lc, status)) { if (status == PANE_STATUS_TOP) wp->yoff++; - sy--; + if (sy > 1) + sy--; } if (window_pane_scrollbar_reserve(wp)) { @@ -534,14 +532,14 @@ layout_resize_check(struct window *w, struct layout_cell *lc, if (lc->type == LAYOUT_WINDOWPANE) { /* Space available in this cell only. */ if (type == LAYOUT_LEFTRIGHT) { - available = lc->sx; + available = lc->g.sx; if (w->sb == PANE_SCROLLBARS_ALWAYS) minimum = PANE_MINIMUM + sb_style->width + sb_style->pad; else minimum = PANE_MINIMUM; } else { - available = lc->sy; + available = lc->g.sy; if (layout_add_horizontal_border(w, lc, status)) minimum = PANE_MINIMUM + 1; else @@ -579,12 +577,13 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, enum layout_type type, int change) { struct layout_cell *lcchild; + int changed; /* Adjust the cell size. */ if (type == LAYOUT_LEFTRIGHT) - lc->sx += change; + lc->g.sx += change; else - lc->sy += change; + lc->g.sy += change; /* If this is a leaf cell, that is all that is necessary. */ if (type == LAYOUT_WINDOWPANE) @@ -611,6 +610,7 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, * until no further change is possible. */ while (change != 0) { + changed = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (change == 0) break; @@ -619,13 +619,17 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, if (change > 0) { layout_resize_adjust(w, lcchild, type, 1); change--; + changed = 1; continue; } if (layout_resize_check(w, lcchild, type) > 0) { layout_resize_adjust(w, lcchild, type, -1); change++; + changed = 1; } } + if (!changed) + break; } } @@ -637,9 +641,9 @@ layout_resize_set_size(struct window *w, struct layout_cell *lc, int change; if (type == LAYOUT_LEFTRIGHT) - change = size - lc->sx; + change = size - lc->g.sx; else - change = size - lc->sy; + change = size - lc->g.sy; layout_resize_adjust(w, lc, type, change); } @@ -711,9 +715,9 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, lcother = layout_cell_get_neighbour(lc); if (lcother != NULL) { if (lcparent->type == LAYOUT_LEFTRIGHT) - change = lc->sx + 1; + change = lc->g.sx + 1; else - change = lc->sy + 1; + change = lc->g.sy + 1; layout_resize_adjust(w, lcother, lcparent->type, change); } else layout_remove_tile(w, lcparent); @@ -734,8 +738,8 @@ out: lc->parent = lcparent->parent; if (lc->parent == NULL) { if (layout_cell_is_tiled(lc)) { - lc->xoff = 0; - lc->yoff = 0; + lc->g.xoff = 0; + lc->g.yoff = 0; } *lcroot = lc; } else @@ -786,29 +790,29 @@ layout_resize(struct window *w, u_int sx, u_int sy) */ if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) return; - xchange = sx - lc->sx; + xchange = sx - lc->g.sx; xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT); if (xchange < 0 && xchange < -xlimit) xchange = -xlimit; if (xlimit == 0) { - if (sx <= lc->sx) /* lc->sx is minimum possible */ + if (sx <= lc->g.sx) /* lc->g.sx is minimum possible */ xchange = 0; else - xchange = sx - lc->sx; + xchange = sx - lc->g.sx; } if (xchange != 0) layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, xchange); /* Adjust vertically in a similar fashion. */ - ychange = sy - lc->sy; + ychange = sy - lc->g.sy; ylimit = layout_resize_check(w, lc, LAYOUT_TOPBOTTOM); if (ychange < 0 && ychange < -ylimit) ychange = -ylimit; if (ylimit == 0) { - if (sy <= lc->sy) /* lc->sy is minimum possible */ + if (sy <= lc->g.sy) /* lc->g.sy is minimum possible */ ychange = 0; else - ychange = sy - lc->sy; + ychange = sy - lc->g.sy; } if (ychange != 0) layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, ychange); @@ -839,9 +843,9 @@ layout_resize_pane_to(struct window_pane *wp, enum layout_type type, /* Work out the size adjustment. */ if (type == LAYOUT_LEFTRIGHT) - size = lc->sx; + size = lc->g.sx; else - size = lc->sy; + size = lc->g.sy; if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) change = size - new_size; else @@ -872,13 +876,13 @@ layout_resize_floating_pane_to(struct window_pane *wp, enum layout_type type, } if (type == LAYOUT_TOPBOTTOM) { - if (lc->sy == size) + if (lc->g.sy == size) return (0); - lc->sy = size; + lc->g.sy = size; } else { - if (lc->sx == size) + if (lc->g.sx == size) return (0); - lc->sx = size; + lc->g.sx = size; } redraw_invalidate_scene(wp->window); return (0); @@ -900,23 +904,23 @@ layout_resize_floating_pane(struct window_pane *wp, enum layout_type type, return (0); if (type == LAYOUT_TOPBOTTOM) { - size = lc->sy + change; + size = lc->g.sy + change; if (size < PANE_MINIMUM || size > PANE_MAXIMUM) { *cause = xstrdup("change is too big or too small"); return (-1); } - lc->sy = size; + lc->g.sy = size; if (opposite) - lc->yoff -= change; + lc->g.yoff -= change; } else { - size = lc->sx + change; + size = lc->g.sx + change; if (size < PANE_MINIMUM || size > PANE_MAXIMUM) { *cause = xstrdup("change is too big or too small"); return (-1); } - lc->sx = size; + lc->g.sx = size; if (opposite) - lc->xoff -= change; + lc->g.xoff -= change; } redraw_invalidate_scene(wp->window); return (0); @@ -1082,13 +1086,13 @@ layout_new_pane_size(struct window *w, u_int previous, struct layout_cell *lc, */ min = (PANE_MINIMUM + 1) * (count_left - 1); if (type == LAYOUT_LEFTRIGHT) { - if (lc->sx - available > min) - min = lc->sx - available; - new_size = (lc->sx * size) / previous; + if (lc->g.sx - available > min) + min = lc->g.sx - available; + new_size = (lc->g.sx * size) / previous; } else { - if (lc->sy - available > min) - min = lc->sy - available; - new_size = (lc->sy * size) / previous; + if (lc->g.sy - available > min) + min = lc->g.sy - available; + new_size = (lc->g.sy * size) / previous; } /* Check against the maximum and minimum size. */ @@ -1124,9 +1128,9 @@ layout_set_size_check(struct window *w, struct layout_cell *lc, return (0); if (type == LAYOUT_LEFTRIGHT) - previous = lc->sx; + previous = lc->g.sx; else - previous = lc->sy; + previous = lc->g.sy; idx = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { @@ -1162,31 +1166,31 @@ static void layout_resize_child_cells(struct window *w, struct layout_cell *lc) { struct layout_cell *lcchild; - u_int previous, available, count, idx; + u_int prev, available, count, idx; if (lc->type == LAYOUT_WINDOWPANE) return; /* What is the current size used? */ count = 0; - previous = 0; + prev = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (!layout_has_tiled(lcchild)) continue; count++; if (lc->type == LAYOUT_LEFTRIGHT) - previous += lcchild->sx; + prev += lcchild->g.sx; else if (lc->type == LAYOUT_TOPBOTTOM) - previous += lcchild->sy; + prev += lcchild->g.sy; } - previous += (count - 1); + prev += (count - 1); /* And how much is available? */ available = 0; if (lc->type == LAYOUT_LEFTRIGHT) - available = lc->sx; + available = lc->g.sx; else if (lc->type == LAYOUT_TOPBOTTOM) - available = lc->sy; + available = lc->g.sy; /* Resize children into the new size. */ idx = 0; @@ -1194,20 +1198,20 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) if (!layout_has_tiled(lcchild)) continue; if (lc->type == LAYOUT_TOPBOTTOM) { - lcchild->sx = lc->sx; - lcchild->xoff = lc->xoff; + lcchild->g.sx = lc->g.sx; + lcchild->g.xoff = lc->g.xoff; } else { - lcchild->sx = layout_new_pane_size(w, previous, lcchild, - lc->type, lc->sx, count - idx, available); - available -= (lcchild->sx + 1); + lcchild->g.sx = layout_new_pane_size(w, prev, lcchild, + lc->type, lc->g.sx, count - idx, available); + available -= (lcchild->g.sx + 1); } if (lc->type == LAYOUT_LEFTRIGHT) { - lcchild->sy = lc->sy; - lcchild->yoff = lc->yoff; + lcchild->g.sy = lc->g.sy; + lcchild->g.yoff = lc->g.yoff; } else { - lcchild->sy = layout_new_pane_size(w, previous, lcchild, - lc->type, lc->sy, count - idx, available); - available -= (lcchild->sy + 1); + lcchild->g.sy = layout_new_pane_size(w, prev, lcchild, + lc->type, lc->g.sy, count - idx, available); + available -= (lcchild->g.sy + 1); } layout_resize_child_cells(w, lcchild); idx++; @@ -1227,7 +1231,7 @@ layout_replace_with_node(struct window *w, struct layout_cell *lc, lcparent = layout_create_cell(lc->parent); layout_make_node(lcparent, type); - layout_set_size(lcparent, lc->sx, lc->sy, lc->xoff, lc->yoff); + layout_set_size(lcparent, lc->g.sx, lc->g.sy, lc->g.xoff, lc->g.yoff); if (lc->parent == NULL) w->layout_root = lcparent; else @@ -1246,7 +1250,7 @@ layout_split_check_space(struct window_pane *wp, struct layout_cell *lc, enum layout_type type) { struct style *sb_style = &wp->scrollbar_style; - u_int minimum, sx = lc->sx, sy = lc->sy; + u_int minimum, sx = lc->g.sx, sy = lc->g.sy; int status; if (lc->flags & LAYOUT_CELL_FLOATING) @@ -1285,7 +1289,7 @@ layout_split_sizes(struct layout_cell *lc, int size, int before, enum layout_type type, u_int *size1, u_int *size2, u_int *saved_size) { u_int s1, s2, ss; - u_int sx = lc->sx, sy = lc->sy; + u_int sx = lc->g.sx, sy = lc->g.sy; if (type == LAYOUT_LEFTRIGHT) ss = sx; @@ -1332,10 +1336,10 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, lc = wp->layout_cell; /* Copy the old cell size. */ - sx = lc->sx; - sy = lc->sy; - xoff = lc->xoff; - yoff = lc->yoff; + sx = lc->g.sx; + sy = lc->g.sy; + xoff = lc->g.xoff; + yoff = lc->g.yoff; /* Check there is enough space for the two new panes. */ if (!layout_split_check_space(wp, lc, type)) @@ -1376,13 +1380,13 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, * must be resized before inserting the new cell. */ if (lc->type == LAYOUT_LEFTRIGHT) { - lc->sx = new_size; + lc->g.sx = new_size; layout_resize_child_cells(wp->window, lc); - lc->sx = saved_size; + lc->g.sx = saved_size; } else if (lc->type == LAYOUT_TOPBOTTOM) { - lc->sy = new_size; + lc->g.sy = new_size; layout_resize_child_cells(wp->window, lc); - lc->sy = saved_size; + lc->g.sy = saved_size; } resize_first = 1; @@ -1426,10 +1430,10 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, */ if (!resize_first && type == LAYOUT_LEFTRIGHT) { layout_set_size(lc1, size1, sy, xoff, yoff); - layout_set_size(lc2, size2, sy, xoff + lc1->sx + 1, yoff); + layout_set_size(lc2, size2, sy, xoff + lc1->g.sx + 1, yoff); } else if (!resize_first && type == LAYOUT_TOPBOTTOM) { layout_set_size(lc1, sx, size1, xoff, yoff); - layout_set_size(lc2, sx, size2, xoff, yoff + lc1->sy + 1); + layout_set_size(lc2, sx, size2, xoff, yoff + lc1->g.sy + 1); } if (full_size) { if (!resize_first) @@ -1446,8 +1450,8 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, * layout_assign_pane before much else happens! */ struct layout_cell * -layout_floating_pane(struct window *w, struct window_pane *wp, u_int sx, - u_int sy, int ox, int oy) +layout_floating_pane(struct window *w, struct window_pane *wp, + struct layout_geometry *lg) { struct layout_cell *lc, *lcnew, *lcparent; @@ -1459,16 +1463,16 @@ layout_floating_pane(struct window *w, struct window_pane *wp, u_int sx, if (lcparent == NULL) { /* - * Adding a pane to a root that isn't node. Must create and - * insert a new root. - */ + * Adding a pane to a root that isn't a node. Must create and + * insert a new root. + */ lcparent = layout_replace_with_node(w, lc, LAYOUT_TOPBOTTOM); } lcnew = layout_create_cell(lcparent); TAILQ_INSERT_AFTER(&lcparent->cells, lc, lcnew, entry); lcnew->flags |= LAYOUT_CELL_FLOATING; - layout_set_size(lcnew, sx, sy, ox, oy); + layout_set_size(lcnew, lg->sx, lg->sy, lg->xoff, lg->yoff); return (lcnew); } @@ -1511,12 +1515,12 @@ layout_spread_cell(struct window *w, struct layout_cell *parent) status = window_get_pane_status(w); if (parent->type == LAYOUT_LEFTRIGHT) - size = parent->sx; + size = parent->g.sx; else if (parent->type == LAYOUT_TOPBOTTOM) { if (layout_add_horizontal_border(w, parent, status)) - size = parent->sy - 1; + size = parent->g.sy - 1; else - size = parent->sy; + size = parent->g.sy; } else return (0); if (size < number - 1) @@ -1537,7 +1541,7 @@ layout_spread_cell(struct window *w, struct layout_cell *parent) continue; change = 0; if (parent->type == LAYOUT_LEFTRIGHT) { - change = each - (int)lc->sx; + change = each - (int)lc->g.sx; if (remainder > 0) { change++; remainder--; @@ -1552,7 +1556,7 @@ layout_spread_cell(struct window *w, struct layout_cell *parent) this++; remainder--; } - change = this - (int)lc->sy; + change = this - (int)lc->g.sy; layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, change); } if (change != 0) @@ -1649,29 +1653,29 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, char **cause) { struct layout_cell *lcnew; - u_int sx = UINT_MAX, sy = UINT_MAX; - int ox = INT_MAX, oy = INT_MAX; + struct layout_geometry fg; - if (layout_floating_args_parse(item, args, lines, w, &sx, &sy, &ox, &oy, - cause) != 0) + layout_geometry_init(&fg); + if (layout_floating_args_parse(item, args, lines, w, &fg, cause) != 0) return (NULL); - lcnew = layout_floating_pane(w, wp, sx, sy, ox, oy); + window_push_zoom(wp->window, 1, args_has(args, 'Z')); + lcnew = layout_floating_pane(w, wp, &fg); return (lcnew); } int layout_floating_args_parse(struct cmdq_item *item, struct args *args, - enum pane_lines lines, struct window *w, u_int *sxp, u_int *syp, int *oxp, - int *oyp, char **cause) + enum pane_lines lines, struct window *w, struct layout_geometry *lg, + char **cause) { int sx, sy, ox, oy; char *error = NULL; - sx = *sxp == UINT_MAX ? w->sx / 2 : *sxp; - sy = *syp == UINT_MAX ? w->sy / 4 : *syp; - ox = *oxp == INT_MAX ? INT_MAX : *oxp; - oy = *oyp == INT_MAX ? INT_MAX : *oyp; + sx = lg->sx == UINT_MAX ? w->sx / 2 : lg->sx; + sy = lg->sy == UINT_MAX ? w->sy / 4 : lg->sy; + ox = lg->xoff; + oy = lg->yoff; if (args_has(args, 'x')) { sx = args_percentage_and_expand(args, 'x', 0, PANE_MAXIMUM, @@ -1748,10 +1752,10 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args, return (-1); } - *sxp = sx; - *syp = sy; - *oxp = ox; - *oyp = oy; + lg->sx = sx; + lg->sy = sy; + lg->xoff = ox; + lg->yoff = oy; return (0); } @@ -1780,9 +1784,9 @@ layout_remove_tile(struct window *w, struct layout_cell *lc) * neighbour. */ if (type == LAYOUT_TOPBOTTOM) - change = lc->sy + 1; + change = lc->g.sy + 1; else - change = lc->sx + 1; + change = lc->g.sx + 1; layout_resize_adjust(w, lcneighbour, type, change); } @@ -1828,9 +1832,9 @@ layout_insert_tile(struct window *w, struct layout_cell *lc) */ layout_insert_tile(w, lcparent); if (type == LAYOUT_LEFTRIGHT) - size1 = lcparent->sx; + size1 = lcparent->g.sx; else - size1 = lcparent->sy; + size1 = lcparent->g.sy; layout_resize_set_size(w, lc, type, size1); } else { /* @@ -1848,10 +1852,10 @@ layout_insert_tile(struct window *w, struct layout_cell *lc) /* Setting opposite of the 'split' size to that of the parent. */ if (lcparent->type == LAYOUT_LEFTRIGHT) { - size1 = lcparent->sy; + size1 = lcparent->g.sy; type = LAYOUT_TOPBOTTOM; } else { - size1 = lcparent->sx; + size1 = lcparent->g.sx; type = LAYOUT_LEFTRIGHT; } layout_resize_set_size(w, lc, type, size1); diff --git a/menu.c b/menu.c index 756bb5693..257452fd7 100644 --- a/menu.c +++ b/menu.c @@ -313,6 +313,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) struct mouse_event *m = &event->m; u_int i; int count = menu->count, old = md->choice; + int move; const char *name = NULL; const struct menu_item *item; struct cmdq_state *state; @@ -320,6 +321,14 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) char *error; if (KEYC_IS_MOUSE(event->key)) { + /* + * A mouse move with no button held reports as a release, so + * treat it as highlight-only: it must never select or close the + * menu, otherwise a menu opened without a button already down + * (such as a submenu opened from another menu) would vanish as + * soon as the mouse moved over it. + */ + move = MOUSE_DRAG(m->b) && MOUSE_RELEASE(m->b); if (md->flags & MENU_NOMOUSE) { if (MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1) return (1); @@ -330,7 +339,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) m->y < md->py + 1 || m->y > md->py + 1 + count - 1) { if (~md->flags & MENU_STAYOPEN) { - if (MOUSE_RELEASE(m->b)) + if (!move && MOUSE_RELEASE(m->b)) return (1); } else { if (!MOUSE_RELEASE(m->b) && @@ -345,7 +354,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) return (0); } if (~md->flags & MENU_STAYOPEN) { - if (MOUSE_RELEASE(m->b)) + if (!move && MOUSE_RELEASE(m->b)) goto chosen; } else { if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b)) @@ -558,6 +567,10 @@ menu_prepare(struct menu *menu, int flags, int starting_choice, if (py + menu->count + 2 > c->tty.sy) py = c->tty.sy - menu->count - 2; + /* Remember where this menu is so -x/-y L can reuse the position. */ + c->menu_last_px = px; + c->menu_last_py = py; + if (lines == BOX_LINES_DEFAULT) lines = options_get_number(o, "menu-border-lines"); diff --git a/mode-tree.c b/mode-tree.c index 2a6f9a220..59d9e4fa9 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -834,6 +834,11 @@ mode_tree_draw(struct mode_tree_data *mtd) if (mtd->line_size == 0) return; + w = mtd->width; + h = mtd->height; + if (w == 0 || h == 0) + return; + memcpy(&gc0, &grid_default_cell, sizeof gc0); memcpy(&gc, &grid_default_cell, sizeof gc); style_apply(&gc, oo, "tree-mode-selection-style", NULL); @@ -843,9 +848,6 @@ mode_tree_draw(struct mode_tree_data *mtd) dfg = gc.fg; dfg0 = gc0.fg; - w = mtd->width; - h = mtd->height; - screen_write_start(&ctx, s); screen_write_clearscreen(&ctx, 8); ft = format_create_defaults(NULL, NULL, NULL, NULL, wp); diff --git a/monitor.c b/monitor.c index 5a2408009..6d1a747fd 100644 --- a/monitor.c +++ b/monitor.c @@ -93,6 +93,18 @@ monitor_get_session(struct monitor_set *ms) return (s); } +/* Create a format tree for a subscription. */ +static struct format_tree * +monitor_create_formats(struct client *c, struct session *s, struct winlink *wl, + struct window_pane *wp) +{ + struct format_tree *ft; + + ft = format_create(NULL, NULL, 0, FORMAT_NOJOBS); + format_defaults(ft, c, s, wl, wp); + return (ft); +} + /* Compare subscriptions. */ static int monitor_item_cmp(struct monitor_item *m1, struct monitor_item *m2) @@ -236,7 +248,7 @@ monitor_check_pane(struct monitor_set *ms, struct monitor_item *me) if (wl->session != s) continue; - ft = format_create_defaults(NULL, c, s, wl, wp); + ft = monitor_create_formats(c, s, wl, wp); value = format_expand(ft, me->format); format_free(ft); @@ -314,7 +326,7 @@ monitor_check_window(struct monitor_set *ms, struct monitor_item *me) if (wl->session != s) continue; - ft = format_create_defaults(NULL, c, s, wl, NULL); + ft = monitor_create_formats(c, s, wl, NULL); value = format_expand(ft, me->format); format_free(ft); @@ -382,7 +394,7 @@ monitor_check_sessions(struct monitor_set *ms) struct monitor_item *me, *me1; struct format_tree *ft; - ft = format_create_defaults(NULL, c, s, NULL, NULL); + ft = monitor_create_formats(c, s, NULL, NULL); RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) { if (me->type == MONITOR_SESSION) monitor_check_session(ms, me, ft); @@ -427,7 +439,7 @@ monitor_check_all_panes(struct monitor_set *ms) ms->generation = 1; RB_FOREACH(wl, winlinks, &s->windows) { TAILQ_FOREACH(wp, &wl->window->panes, entry) { - ft = format_create_defaults(NULL, c, s, wl, wp); + ft = monitor_create_formats(c, s, wl, wp); RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) { if (me->type != MONITOR_ALL_PANES) continue; @@ -455,7 +467,7 @@ monitor_check_all_windows(struct monitor_set *ms) if (++ms->generation == 0) ms->generation = 1; RB_FOREACH(wl, winlinks, &s->windows) { - ft = format_create_defaults(NULL, c, s, wl, NULL); + ft = monitor_create_formats(c, s, wl, NULL); RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) { if (me->type != MONITOR_ALL_WINDOWS) continue; diff --git a/options.c b/options.c index 19da38886..3b81ed57d 100644 --- a/options.c +++ b/options.c @@ -32,18 +32,69 @@ */ struct options_array_item { - u_int index; + char *key; union options_value value; RB_ENTRY(options_array_item) entry; }; + +static int +options_array_key_to_number(const char *key, u_int *idx) +{ + const char *errstr; + long long n; + + if (*key == '\0') + return (-1); + for (const char *cp = key; *cp != '\0'; cp++) { + if (!isdigit((u_char)*cp)) + return (0); + } + + n = strtonum(key, 0, UINT_MAX, &errstr); + if (errstr != NULL) + return (-1); + if (idx != NULL) + *idx = n; + return (1); +} + +static char * +options_array_correct_key(const char *key) +{ + u_int idx; + int numeric; + char *out; + + numeric = options_array_key_to_number(key, &idx); + if (numeric == -1) + return (NULL); + if (numeric == 1) { + xasprintf(&out, "%u", idx); + return (out); + } + return (xstrdup(key)); +} + static int options_array_cmp(struct options_array_item *a1, struct options_array_item *a2) { - if (a1->index < a2->index) + u_int i1, i2; + int n1, n2; + + n1 = options_array_key_to_number(a1->key, &i1); + n2 = options_array_key_to_number(a2->key, &i2); + if (n1 && n2) { + if (i1 < i2) + return (-1); + if (i1 > i2) + return (1); + return (0); + } + if (n1) return (-1); - if (a1->index > a2->index) + if (n2) return (1); - return (0); + return (strcmp(a1->key, a2->key)); } RB_GENERATE_STATIC(options_array, options_array_item, entry, options_array_cmp); @@ -260,6 +311,7 @@ options_default(struct options *oo, const struct options_table_entry *oe) { struct options_entry *o; union options_value *ov; + char key[32]; u_int i; struct cmd_parse_result *pr; @@ -271,8 +323,10 @@ options_default(struct options *oo, const struct options_table_entry *oe) options_array_assign(o, oe->default_str, NULL); return (o); } - for (i = 0; oe->default_arr[i] != NULL; i++) - options_array_set(o, i, oe->default_arr[i], 0, NULL); + for (i = 0; oe->default_arr[i] != NULL; i++) { + xsnprintf(key, sizeof key, "%u", i); + options_array_set(o, key, oe->default_arr[i], 0, NULL); + } return (o); } @@ -393,21 +447,21 @@ options_table_entry(struct options_entry *o) } static struct options_array_item * -options_array_item(struct options_entry *o, u_int idx) +options_array_item(struct options_entry *o, const char *key) { struct options_array_item a; - a.index = idx; + a.key = (char *)key; return (RB_FIND(options_array, &o->value.array, &a)); } static struct options_array_item * -options_array_new(struct options_entry *o, u_int idx) +options_array_new(struct options_entry *o, const char *key) { struct options_array_item *a; a = xcalloc(1, sizeof *a); - a->index = idx; + a->key = xstrdup(key); RB_INSERT(options_array, &o->value.array, a); return (a); } @@ -417,6 +471,7 @@ options_array_free(struct options_entry *o, struct options_array_item *a) { options_value_free(o, &a->value); RB_REMOVE(options_array, &o->value.array, a); + free(a->key); free(a); } @@ -433,24 +488,45 @@ options_array_clear(struct options_entry *o) } union options_value * -options_array_get(struct options_entry *o, u_int idx) +options_array_get(struct options_entry *o, const char *key) { struct options_array_item *a; + char *new_key; if (!OPTIONS_IS_ARRAY(o)) return (NULL); - a = options_array_item(o, idx); + new_key = options_array_correct_key(key); + if (new_key == NULL) + return (NULL); + a = options_array_item(o, new_key); + free(new_key); if (a == NULL) return (NULL); return (&a->value); } +union options_value * +options_array_getv(struct options_entry *o, const char *fmt, ...) +{ + union options_value *ov; + va_list ap; + char *key; + + va_start(ap, fmt); + xvasprintf(&key, fmt, ap); + va_end(ap); + + ov = options_array_get(o, key); + free(key); + return (ov); +} + int -options_array_set(struct options_entry *o, u_int idx, const char *value, +options_array_set(struct options_entry *o, const char *key, const char *value, int append, char **cause) { struct options_array_item *a; - char *new; + char *new, *new_key; struct cmd_parse_result *pr; long long number; @@ -460,10 +536,18 @@ options_array_set(struct options_entry *o, u_int idx, const char *value, return (-1); } + new_key = options_array_correct_key(key); + if (new_key == NULL) { + if (cause != NULL) + xasprintf(cause, "bad array key: %s", key); + return (-1); + } + if (value == NULL) { - a = options_array_item(o, idx); + a = options_array_item(o, new_key); if (a != NULL) options_array_free(o, a); + free(new_key); return (0); } @@ -475,50 +559,56 @@ options_array_set(struct options_entry *o, u_int idx, const char *value, *cause = pr->error; else free(pr->error); + free(new_key); return (-1); case CMD_PARSE_SUCCESS: break; } - a = options_array_item(o, idx); + a = options_array_item(o, new_key); if (a == NULL) - a = options_array_new(o, idx); + a = options_array_new(o, new_key); else options_value_free(o, &a->value); a->value.cmdlist = pr->cmdlist; + free(new_key); return (0); } if (OPTIONS_IS_STRING(o)) { - a = options_array_item(o, idx); + a = options_array_item(o, new_key); if (a != NULL && append) xasprintf(&new, "%s%s", a->value.string, value); else new = xstrdup(value); if (a == NULL) - a = options_array_new(o, idx); + a = options_array_new(o, new_key); else options_value_free(o, &a->value); a->value.string = new; + free(new_key); return (0); } if (o->tableentry->type == OPTIONS_TABLE_COLOUR) { if ((number = colour_fromstring(value)) == -1) { xasprintf(cause, "bad colour: %s", value); + free(new_key); return (-1); } - a = options_array_item(o, idx); + a = options_array_item(o, new_key); if (a == NULL) - a = options_array_new(o, idx); + a = options_array_new(o, new_key); else options_value_free(o, &a->value); a->value.number = number; + free(new_key); return (0); } if (cause != NULL) *cause = xstrdup("wrong array type"); + free(new_key); return (-1); } @@ -527,6 +617,7 @@ options_array_assign(struct options_entry *o, const char *s, char **cause) { const char *separator; char *copy, *next, *string; + char key[32]; u_int i; separator = o->tableentry->separator; @@ -536,10 +627,11 @@ options_array_assign(struct options_entry *o, const char *s, char **cause) if (*s == '\0') return (0); for (i = 0; i < UINT_MAX; i++) { - if (options_array_item(o, i) == NULL) + if (options_array_getv(o, "%u", i) == NULL) break; } - return (options_array_set(o, i, s, 0, cause)); + xsnprintf(key, sizeof key, "%u", i); + return (options_array_set(o, key, s, 0, cause)); } if (*s == '\0') @@ -549,12 +641,13 @@ options_array_assign(struct options_entry *o, const char *s, char **cause) if (*next == '\0') continue; for (i = 0; i < UINT_MAX; i++) { - if (options_array_item(o, i) == NULL) + if (options_array_getv(o, "%u", i) == NULL) break; } if (i == UINT_MAX) break; - if (options_array_set(o, i, next, 0, cause) != 0) { + xsnprintf(key, sizeof key, "%u", i); + if (options_array_set(o, key, next, 0, cause) != 0) { free(copy); return (-1); } @@ -574,13 +667,13 @@ options_array_first(struct options_entry *o) struct options_array_item * options_array_next(struct options_array_item *a) { - return (RB_NEXT(options_array, &o->value.array, a)); + return (RB_NEXT(options_array, , a)); } -u_int -options_array_item_index(struct options_array_item *a) +const char * +options_array_item_key(struct options_array_item *a) { - return (a->index); + return (a->key); } union options_value * @@ -602,15 +695,16 @@ options_is_string(struct options_entry *o) } char * -options_to_string(struct options_entry *o, int idx, int numeric) +options_to_string(struct options_entry *o, const char *key, int numeric) { struct options_array_item *a; char *result = NULL; char *last = NULL; char *next; + char *new_key; if (OPTIONS_IS_ARRAY(o)) { - if (idx == -1) { + if (key == NULL) { RB_FOREACH(a, options_array, &o->value.array) { next = options_value_to_string(o, &a->value, numeric); @@ -627,7 +721,11 @@ options_to_string(struct options_entry *o, int idx, int numeric) return (xstrdup("")); return (result); } - a = options_array_item(o, idx); + new_key = options_array_correct_key(key); + if (new_key == NULL) + return (xstrdup("")); + a = options_array_item(o, new_key); + free(new_key); if (a == NULL) return (xstrdup("")); return (options_value_to_string(o, &a->value, numeric)); @@ -636,37 +734,41 @@ options_to_string(struct options_entry *o, int idx, int numeric) } char * -options_parse(const char *name, int *idx) +options_parse(const char *name, char **key) { - char *copy, *cp, *end; + char *copy, *cp, *end, *raw, *new_key; if (*name == '\0') return (NULL); + *key = NULL; copy = xstrdup(name); if ((cp = strchr(copy, '[')) == NULL) { - *idx = -1; return (copy); } end = strchr(cp + 1, ']'); - if (end == NULL || end[1] != '\0' || !isdigit((u_char)end[-1])) { + if (end == NULL || end[1] != '\0' || end == cp + 1) { free(copy); return (NULL); } - if (sscanf(cp, "[%d]", idx) != 1 || *idx < 0) { + raw = xstrndup(cp + 1, end - (cp + 1)); + new_key = options_array_correct_key(raw); + free(raw); + if (new_key == NULL) { free(copy); return (NULL); } + *key = new_key; *cp = '\0'; return (copy); } struct options_entry * -options_parse_get(struct options *oo, const char *s, int *idx, int only) +options_parse_get(struct options *oo, const char *s, char **key, int only) { struct options_entry *o; char *name; - name = options_parse(s, idx); + name = options_parse(s, key); if (name == NULL) return (NULL); if (only) @@ -674,6 +776,10 @@ options_parse_get(struct options *oo, const char *s, int *idx, int only) else o = options_get(oo, name); free(name); + if (o == NULL) { + free(*key); + *key = NULL; + } return (o); } @@ -690,14 +796,14 @@ options_search(const char *name) } char * -options_match(const char *s, int *idx, int *ambiguous) +options_match(const char *s, char **key, int *ambiguous) { const struct options_table_entry *oe, *found; char *parsed; const char *name; size_t namelen; - parsed = options_parse(s, idx); + parsed = options_parse(s, key); if (parsed == NULL) return (NULL); if (*parsed == '@') { @@ -718,6 +824,8 @@ options_match(const char *s, int *idx, int *ambiguous) if (found != NULL) { *ambiguous = 1; free(parsed); + free(*key); + *key = NULL; return (NULL); } found = oe; @@ -726,19 +834,21 @@ options_match(const char *s, int *idx, int *ambiguous) free(parsed); if (found == NULL) { *ambiguous = 0; + free(*key); + *key = NULL; return (NULL); } return (xstrdup(found->name)); } struct options_entry * -options_match_get(struct options *oo, const char *s, int *idx, int only, +options_match_get(struct options *oo, const char *s, char **key, int only, int *ambiguous) { char *name; struct options_entry *o; - name = options_match(s, idx, ambiguous); + name = options_match(s, key, ambiguous); if (name == NULL) return (NULL); *ambiguous = 0; @@ -747,6 +857,10 @@ options_match_get(struct options *oo, const char *s, int *idx, int only, else o = options_get(oo, name); free(name); + if (o == NULL) { + free(*key); + *key = NULL; + } return (o); } @@ -1340,11 +1454,12 @@ options_push_changes(const char *name) } int -options_remove_or_default(struct options_entry *o, int idx, char **cause) +options_remove_or_default(struct options_entry *o, const char *key, + char **cause) { struct options *oo = o->owner; - if (idx == -1) { + if (key == NULL) { if (o->tableentry != NULL && (oo == global_options || oo == global_s_options || @@ -1352,7 +1467,7 @@ options_remove_or_default(struct options_entry *o, int idx, char **cause) options_default(oo, o->tableentry); else options_remove(o); - } else if (options_array_set(o, idx, NULL, 0, cause) != 0) + } else if (options_array_set(o, key, NULL, 0, cause) != 0) return (-1); return (0); } diff --git a/regress/Makefile b/regress/Makefile index c354a8187..6370c77c9 100644 --- a/regress/Makefile +++ b/regress/Makefile @@ -1,22 +1,30 @@ TESTS!= echo *.sh +LOGDIR=logs -.PHONY: all $(TESTS) -.NOTPARALLEL: all $(TESTS) +.PHONY: all +.NOTPARALLEL: all all: - @failed=0; failures=; \ + @mkdir -p "$(LOGDIR)"; \ + rm -f "$(LOGDIR)"/*.log; \ + failed=0; failures=; \ for test in $(TESTS); do \ + base=$${test##*/}; \ + log="$(LOGDIR)/$${base%.sh}.log"; \ + rm -f "$$log"; \ printf '%-40s ' "$$test"; \ start=$$(date +%s); \ ASAN_OPTIONS="abort_on_error=1:detect_leaks=0:$$ASAN_OPTIONS"; \ env -i LC_CTYPE=C.UTF-8 ASAN_OPTIONS="$$ASAN_OPTIONS" \ - sh "$$test" >/dev/null 2>&1; \ + sh -x "$$test" >"$$log" 2>&1; \ if [ $$? -eq 0 ]; then \ end=$$(date +%s); \ + rm -f "$$log"; \ echo "PASS ($$((end - start))s)"; \ else \ end=$$(date +%s); \ echo "FAIL ($$((end - start))s)"; \ + echo " log: $$log"; \ failed=1; \ failures="$$failures $$test"; \ fi; \ @@ -28,9 +36,7 @@ all: for test in $$failures; do \ echo " $$test"; \ done; \ + else \ + rmdir "$(LOGDIR)" 2>/dev/null || true; \ fi; \ exit $$failed - -$(TESTS): - sh $@ - sleep 1 diff --git a/regress/alerts.sh b/regress/alerts.sh new file mode 100644 index 000000000..cfb84c711 --- /dev/null +++ b/regress/alerts.sh @@ -0,0 +1,291 @@ +#!/bin/sh + +# monitor-activity, monitor-bell and monitor-silence: both the +# alert-activity, alert-bell and alert-silence hooks and the winlink alert +# flags (window_activity_flag, window_bell_flag, window_silence_flag and +# the #, !, ~ characters in window_flags). The sessions are detached so +# alert flags are set even on the current window; the *-action options +# still decide whether the hooks fire. Panes run cat: activity is +# generated by the tty echo of send-keys and a bell by sending a BEL and +# newline for cat to write back. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +LANG=C.UTF-8 +export TERM LC_ALL LANG + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT=$(mktemp -d) +TMUX_TMPDIR="$OUT" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" + exit 1 +} + +cleanup() +{ + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" +} +trap cleanup EXIT + +wait_for() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 30 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] && return 0 + i=$((i + 1)) + sleep 0.2 + done + fail "expected $option to be '$expected' but got '$value'" +} + +assert_unchanged() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 10 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] || \ + fail "expected $option to remain '$expected' but got '$value'" + i=$((i + 1)) + sleep 0.2 + done +} + +wait_for_fmt() +{ + target=$1 + fmt=$2 + expected=$3 + i=0 + + while [ $i -lt 30 ]; do + value=$($TMUX display -pt "$target" "$fmt" 2>/dev/null || true) + [ "$value" = "$expected" ] && return 0 + i=$((i + 1)) + sleep 0.2 + done + fail "expected $fmt for $target to be '$expected' but got '$value'" +} + +assert_fmt_unchanged() +{ + target=$1 + fmt=$2 + expected=$3 + i=0 + + while [ $i -lt 10 ]; do + value=$($TMUX display -pt "$target" "$fmt" 2>/dev/null || true) + [ "$value" = "$expected" ] || \ + fail "expected $fmt for $target to remain '$expected' but got '$value'" + i=$((i + 1)) + sleep 0.2 + done +} + +flags_have() +{ + target=$1 + char=$2 + + flags=$($TMUX display -pt "$target" '#{window_flags}') || + fail "display window_flags failed" + case "$flags" in + *"$char"*) ;; + *) fail "expected '$char' in window_flags for $target: '$flags'" ;; + esac +} + +flags_lack() +{ + target=$1 + char=$2 + + flags=$($TMUX display -pt "$target" '#{window_flags}') || + fail "display window_flags failed" + case "$flags" in + *"$char"*) fail "unexpected '$char' in window_flags for $target: '$flags'" ;; + esac +} + +bell() +{ + $TMUX send-keys -t "$1" -H 07 0a || fail "send-keys bell failed" +} + +activity() +{ + $TMUX send-keys -t "$1" -l x || fail "send-keys activity failed" +} + +$TMUX new -d -s mon -n w0 cat || fail "new-session mon failed" + +$TMUX set -g @log '' || fail "set @log failed" +$TMUX set-hook -g alert-bell \ + 'set -gF @log "#{@log}|alert-bell:#{hook_session_name}:#{hook_window_name}"' || + fail "set-hook alert-bell failed" +$TMUX set-hook -g alert-activity \ + 'set -gF @log "#{@log}|alert-activity:#{hook_session_name}:#{hook_window_name}"' || + fail "set-hook alert-activity failed" +$TMUX set-hook -g alert-silence \ + 'set -gF @log "#{@log}|alert-silence:#{hook_session_name}:#{hook_window_name}"' || + fail "set-hook alert-silence failed" + +# A bell in a non-current window (monitor-bell defaults to on) fires +# alert-bell and sets the bell flag, shown as ! in window_flags. +$TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed" +assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0 +flags_lack mon:bellw '!' +bell mon:bellw +wait_for @log '|alert-bell:mon:bellw' +wait_for_fmt mon:bellw '#{window_bell_flag}' 1 +flags_have mon:bellw '!' + +# Bells are not deduplicated: a second bell fires the hook again even +# though the flag is still set. +bell mon:bellw +wait_for @log '|alert-bell:mon:bellw|alert-bell:mon:bellw' + +# Selecting the window clears the alert flags. +$TMUX selectw -t mon:bellw || fail "select-window bellw failed" +wait_for_fmt mon:bellw '#{window_bell_flag}' 0 +flags_lack mon:bellw '!' +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" + +# A bell in the current window of a detached session still sets the flag +# and bell-action any (the default) fires the hook for the current window. +$TMUX set -g @log '' || fail "reset @log failed" +bell mon:w0 +wait_for @log '|alert-bell:mon:w0' +wait_for_fmt mon:w0 '#{window_bell_flag}' 1 +$TMUX selectw -t mon:bellw || fail "select-window bellw failed" +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" +wait_for_fmt mon:w0 '#{window_bell_flag}' 0 + +# bell-action none: the flag is still set but the hook does not fire. +$TMUX set -t mon bell-action none || fail "set bell-action none failed" +$TMUX set -g @log '' || fail "reset @log failed" +bell mon:bellw +wait_for_fmt mon:bellw '#{window_bell_flag}' 1 +assert_unchanged @log '' +$TMUX set -ut mon bell-action || fail "unset bell-action failed" +$TMUX selectw -t mon:bellw || fail "select-window bellw failed" +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" + +# monitor-bell off: neither the flag nor the hook. +$TMUX set -wt mon:bellw monitor-bell off || fail "set monitor-bell failed" +$TMUX set -g @log '' || fail "reset @log failed" +bell mon:bellw +assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0 +assert_unchanged @log '' + +# monitor-activity defaults to off: output sets neither flag nor hook. +$TMUX neww -d -t mon: -n actw cat || fail "new-window actw failed" +$TMUX set -g @log '' || fail "reset @log failed" +activity mon:actw +assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0 +assert_unchanged @log '' + +# With monitor-activity on, output fires alert-activity and sets the +# activity flag, shown as # in window_flags. +$TMUX set -wt mon:actw monitor-activity on || + fail "set monitor-activity failed" +activity mon:actw +wait_for @log '|alert-activity:mon:actw' +wait_for_fmt mon:actw '#{window_activity_flag}' 1 +flags_have mon:actw '#' + +# While the flag is set further activity does not fire the hook again. +$TMUX set -g @log '' || fail "reset @log failed" +activity mon:actw +assert_unchanged @log '' + +# On a detached session selecting the window does not clear the activity +# flag: session_set_current treats the selection itself as activity and +# the flag is raised again at once because the session is not attached. +# No hook fires as activity-action other does not apply to the current +# window. +$TMUX selectw -t mon:actw || fail "select-window actw failed" +assert_fmt_unchanged mon:actw '#{window_activity_flag}' 1 +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" +assert_fmt_unchanged mon:actw '#{window_activity_flag}' 1 +assert_unchanged @log '' + +# With a client attached, selecting the window does clear the activity +# flag and it stays clear for the current window. +mkfifo "$OUT/fifo" || fail "mkfifo failed" +$TMUX -C attach -t mon <"$OUT/fifo" >"$OUT/control.out" 2>&1 & +exec 3>"$OUT/fifo" +wait_for_fmt mon: '#{session_attached}' 1 +$TMUX selectw -t mon:actw || fail "select-window actw failed" +wait_for_fmt mon:actw '#{window_activity_flag}' 0 +flags_lack mon:actw '#' +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" +exec 3>&- +wait_for_fmt mon: '#{session_attached}' 0 + +# Once the flag is clear the alert is re-armed. +activity mon:actw +wait_for @log '|alert-activity:mon:actw' + +# Activity in the current window of a detached session sets the flag but +# activity-action other (the default) does not fire the hook. +$TMUX set -wt mon:w0 monitor-activity on || + fail "set monitor-activity w0 failed" +$TMUX set -g @log '' || fail "reset @log failed" +activity mon:w0 +wait_for_fmt mon:w0 '#{window_activity_flag}' 1 +assert_unchanged @log '' +$TMUX set -wut mon:w0 monitor-activity || + fail "unset monitor-activity w0 failed" + +# monitor-silence: a quiet window fires alert-silence after the interval +# and sets the silence flag, shown as ~ in window_flags. The hook fires +# only once while the flag remains set even though the timer keeps +# running. +$TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed" +assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0 +$TMUX set -g @log '' || fail "reset @log failed" +$TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed" +wait_for @log '|alert-silence:mon:silw' +wait_for_fmt mon:silw '#{window_silence_flag}' 1 +flags_have mon:silw '~' +assert_unchanged @log '|alert-silence:mon:silw' +$TMUX set -wt mon:silw monitor-silence 0 || + fail "reset monitor-silence failed" +$TMUX selectw -t mon:silw || fail "select-window silw failed" +wait_for_fmt mon:silw '#{window_silence_flag}' 0 +flags_lack mon:silw '~' +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" + +# A window linked into two sessions: the hook fires once per winlink and +# the flag is set in both sessions. Selecting the window in one session +# clears the flags on every winlink. +$TMUX new -d -s mon2 || fail "new-session mon2 failed" +$TMUX neww -d -t mon: -n shw cat || fail "new-window shw failed" +$TMUX link-window -d -s mon:shw -t mon2:5 || fail "link-window failed" +$TMUX set -g @log '' || fail "reset @log failed" +bell mon:shw +wait_for @log '|alert-bell:mon:shw|alert-bell:mon2:shw' +wait_for_fmt mon:shw '#{window_bell_flag}' 1 +wait_for_fmt mon2:5 '#{window_bell_flag}' 1 +$TMUX selectw -t mon2:5 || fail "select-window mon2:5 failed" +wait_for_fmt mon2:5 '#{window_bell_flag}' 0 +wait_for_fmt mon:shw '#{window_bell_flag}' 0 + +exit 0 diff --git a/regress/am-terminal.sh b/regress/am-terminal.sh index bf1cabfe7..fae0e502d 100644 --- a/regress/am-terminal.sh +++ b/regress/am-terminal.sh @@ -4,9 +4,9 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" $TMUX2 kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/border-arrows.sh b/regress/border-arrows.sh index ea571b05a..a67171bf5 100644 --- a/regress/border-arrows.sh +++ b/regress/border-arrows.sh @@ -11,9 +11,9 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null -TMUX_OUTER="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX_OUTER="$TEST_TMUX -LtestB$$ -f/dev/null" $TMUX_OUTER kill-server 2>/dev/null trap "$TMUX kill-server 2>/dev/null; $TMUX_OUTER kill-server 2>/dev/null" 0 1 15 diff --git a/regress/buffers.sh b/regress/buffers.sh index 0b3267610..fdacacb5b 100644 --- a/regress/buffers.sh +++ b/regress/buffers.sh @@ -23,7 +23,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/capture-pane-hyperlink.sh b/regress/capture-pane-hyperlink.sh index 80092e7f2..ec89c6c5d 100644 --- a/regress/capture-pane-hyperlink.sh +++ b/regress/capture-pane-hyperlink.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMP=$(mktemp) TMP2=$(mktemp) trap "rm -f $TMP $TMP2" 0 1 15 diff --git a/regress/capture-pane-sgr0.sh b/regress/capture-pane-sgr0.sh index 7c9c32315..1e73e7191 100644 --- a/regress/capture-pane-sgr0.sh +++ b/regress/capture-pane-sgr0.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/check-names.sh b/regress/check-names.sh index 886299f2d..be8ad617b 100644 --- a/regress/check-names.sh +++ b/regress/check-names.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null fail() diff --git a/regress/choose-buffer.sh b/regress/choose-buffer.sh index 7d689f523..ca9a26758 100644 --- a/regress/choose-buffer.sh +++ b/regress/choose-buffer.sh @@ -17,18 +17,23 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMP=$(mktemp -d) || exit 1 +TMUX_TMPDIR="$TMP" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" cleanup() { $TMUX kill-server 2>/dev/null $TMUX2 kill-server 2>/dev/null + rm -rf "$TMP" } +trap cleanup EXIT + fail() { - echo "$1" - cleanup + echo "$1" >&2 exit 1 } @@ -46,16 +51,50 @@ wait_for() { i=0 while [ "$i" -lt 50 ]; do - if capture | grep -q "$1"; then - sleep 0.5 + CAPTURED=$(capture) + if printf '%s\n' "$CAPTURED" | grep -F -q "$1"; then return 0 fi - sleep 0.5 + sleep 0.2 i=$((i + 1)) done fail "timed out waiting for '$1'" } +# wait_gone $marker +# +# Wait (up to ~10s) until the rendered screen no longer contains $marker. +wait_gone() +{ + i=0 + while [ "$i" -lt 50 ]; do + CAPTURED=$(capture) + if ! printf '%s\n' "$CAPTURED" | grep -F -q "$1"; then + return 0 + fi + sleep 0.2 + i=$((i + 1)) + done + fail "timed out waiting for '$1' to disappear" +} + +# wait_count $marker $n +# +# Wait (up to ~10s) until exactly $n rendered lines contain $marker. The +# matching capture is left in CAPTURED. +wait_count() +{ + i=0 + while [ "$i" -lt 50 ]; do + CAPTURED=$(capture) + c=$(printf '%s\n' "$CAPTURED" | grep -F -c "$1") + [ "$c" -eq "$2" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "timed out waiting for $2 lines of '$1' (have $c)" +} + # wait_buffers $n # # Wait (up to ~10s) until the test server has exactly $n paste buffers. @@ -86,10 +125,36 @@ wait_clients() return 1 } -$TMUX kill-server 2>/dev/null -$TMUX2 kill-server 2>/dev/null +# wait_mode $target $state +# +# Wait (up to ~10s) until a pane enters or leaves mode. +wait_mode() +{ + t=$1 + want=$2 -$TMUX new-session -d -s aaa -x 80 -y 24 || exit 1 + i=0 + while [ "$i" -lt 50 ]; do + got=$($TMUX display-message -p -t "$t" '#{pane_in_mode}' \ + 2>/dev/null) + [ "$got" = "$want" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "pane $t mode state is $got, expected $want" +} + +exit_mode() +{ + marker=$1 + shift + + $TMUX send-keys -t aaa:0 "$@" || fail "send-keys $* failed" + wait_mode aaa:0 0 + wait_gone "$marker" +} + +$TMUX new-session -d -s aaa -x 80 -y 24 'cat' || exit 1 $TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t aaa" || exit 1 wait_clients 1 || fail "no client attached to test server" @@ -100,57 +165,70 @@ $TMUX set-buffer -b bufz "other buffer" || exit 1 # --- filter by buffer name --------------------------------------------------- $TMUX choose-buffer -t aaa:0 -F 'B1' -f '#{==:#{buffer_name},bufa}' || exit 1 -wait_for 'B1' -out=$(capture) -echo "$out" | grep -q 'bufa: B1' || fail "bufa missing when it matches" -echo "$out" | grep -q 'bufz: B1' && fail "bufz shown but does not match" -[ "$(echo "$out" | grep -c ': B1')" -eq 1 ] || fail "expected 1 buffer" -$TMUX send-keys -t aaa:0 q +wait_count ': B1' 1 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'bufa: B1' || \ + fail "bufa missing when it matches" +printf '%s\n' "$out" | grep -F -q 'bufz: B1' && \ + fail "bufz shown but does not match" +[ "$(printf '%s\n' "$out" | grep -F -c ': B1')" -eq 1 ] || \ + fail "expected 1 buffer" +exit_mode ': B1' q # --- filter by buffer content ------------------------------------------------ $TMUX choose-buffer -t aaa:0 -F 'B2' -f '#{m:*hello*,#{buffer_sample}}' || \ exit 1 -wait_for 'B2' -out=$(capture) -echo "$out" | grep -q 'bufa: B2' || fail "bufa missing when content matches" -echo "$out" | grep -q 'bufz: B2' && fail "bufz shown but content not matched" -$TMUX send-keys -t aaa:0 q +wait_count ': B2' 1 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'bufa: B2' || \ + fail "bufa missing when content matches" +printf '%s\n' "$out" | grep -F -q 'bufz: B2' && \ + fail "bufz shown but content not matched" +exit_mode ': B2' q # --- no filter shows both buffers --------------------------------------------- $TMUX choose-buffer -t aaa:0 -F 'B3' || exit 1 -wait_for 'B3' -out=$(capture) -echo "$out" | grep -q 'bufa: B3' || fail "bufa missing with no filter" -echo "$out" | grep -q 'bufz: B3' || fail "bufz missing with no filter" -[ "$(echo "$out" | grep -c ': B3')" -eq 2 ] || fail "expected 2 buffers" -$TMUX send-keys -t aaa:0 q +wait_count ': B3' 2 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'bufa: B3' || \ + fail "bufa missing with no filter" +printf '%s\n' "$out" | grep -F -q 'bufz: B3' || \ + fail "bufz missing with no filter" +[ "$(printf '%s\n' "$out" | grep -F -c ': B3')" -eq 2 ] || \ + fail "expected 2 buffers" +exit_mode ': B3' q # --- filter matching nothing --------------------------------------------------- # # Everything is shown and the filter indicator reports no matches. $TMUX choose-buffer -t aaa:0 -F 'B4' -f '#{==:#{buffer_name},nosuch}' || \ exit 1 -wait_for 'B4' -out=$(capture) -echo "$out" | grep -q 'bufa: B4' || fail "bufa missing with no-match filter" -echo "$out" | grep -q 'bufz: B4' || fail "bufz missing with no-match filter" -echo "$out" | grep -q 'no matches' || fail "no matches indicator missing" -$TMUX send-keys -t aaa:0 q +wait_count ': B4' 2 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'bufa: B4' || \ + fail "bufa missing with no-match filter" +printf '%s\n' "$out" | grep -F -q 'bufz: B4' || \ + fail "bufz missing with no-match filter" +printf '%s\n' "$out" | grep -F -q 'no matches' || \ + fail "no matches indicator missing" +exit_mode ': B4' q # --- sort orders --------------------------------------------------------------- # # By name bufa sorts first and -r reverses. $TMUX choose-buffer -t aaa:0 -F 'B5' -O name || exit 1 -wait_for 'B5' -capture | grep ': B5' | head -1 | grep -q 'bufa: B5' || \ +wait_count ': B5' 2 +printf '%s\n' "$CAPTURED" | grep -F ': B5' | head -1 | \ + grep -F -q 'bufa: B5' || \ fail "bufa not first with -O name" -$TMUX send-keys -t aaa:0 q +exit_mode ': B5' q $TMUX choose-buffer -t aaa:0 -F 'B6' -O name -r || exit 1 -wait_for 'B6' -capture | grep ': B6' | head -1 | grep -q 'bufz: B6' || \ +wait_count ': B6' 2 +printf '%s\n' "$CAPTURED" | grep -F ': B6' | head -1 | \ + grep -F -q 'bufz: B6' || \ fail "bufz not first with -O name -r" -$TMUX send-keys -t aaa:0 q +exit_mode ': B6' q # --- d deletes the selected buffer -------------------------------------------- # @@ -159,18 +237,19 @@ $TMUX choose-buffer -t aaa:0 -F 'G1' -f '#{==:#{buffer_name},bufz}' || exit 1 wait_for 'bufz: G1' $TMUX send-keys -t aaa:0 d wait_buffers 1 -$TMUX list-buffers -F '#{buffer_name}' | grep -q 'bufa' || \ +$TMUX list-buffers -F '#{buffer_name}' | grep -F -q 'bufa' || \ fail "wrong buffer deleted" -$TMUX send-keys -t aaa:0 q +exit_mode ': G1' q # --- C-t tags all buffers and D deletes the tagged ------------------------------ $TMUX set-buffer -b bufz "other buffer" || exit 1 $TMUX set-buffer -b bufb "third buffer" || exit 1 $TMUX choose-buffer -t aaa:0 -F 'G2' || exit 1 -wait_for ': G2' +wait_count ': G2' 3 $TMUX send-keys -t aaa:0 C-t D wait_buffers 0 -$TMUX send-keys -t aaa:0 q +wait_mode aaa:0 0 +wait_gone ': G2' # --- Enter runs the default command (paste-buffer) ------------------------------ # @@ -178,22 +257,15 @@ $TMUX send-keys -t aaa:0 q # into the shell in the pane, where it appears on the screen. $TMUX set-buffer -b bufa "hello buffer" || exit 1 $TMUX choose-buffer -t aaa:0 -F 'G3' || exit 1 -wait_for 'bufa: G3' -$TMUX send-keys -t aaa:0 Enter +wait_count ': G3' 1 +exit_mode ': G3' Enter i=0 while [ "$i" -lt 50 ]; do - [ "$($TMUX display -p -t aaa:0 '#{pane_in_mode}')" = "0" ] && break - sleep 0.5 - i=$((i + 1)) -done -[ "$i" -lt 50 ] || fail "mode did not exit after Enter" -i=0 -while [ "$i" -lt 50 ]; do - $TMUX capture-pane -p -t aaa:0 | grep -q 'hello buffer' && break - sleep 0.5 + $TMUX capture-pane -p -t aaa:0 | grep -F -q 'hello buffer' && \ + break + sleep 0.2 i=$((i + 1)) done [ "$i" -lt 50 ] || fail "buffer not pasted into pane" -cleanup exit 0 diff --git a/regress/choose-client.sh b/regress/choose-client.sh index 1e8423415..d325f5335 100644 --- a/regress/choose-client.sh +++ b/regress/choose-client.sh @@ -16,18 +16,23 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMP=$(mktemp -d) || exit 1 +TMUX_TMPDIR="$TMP" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" cleanup() { $TMUX kill-server 2>/dev/null $TMUX2 kill-server 2>/dev/null + rm -rf "$TMP" } +trap cleanup EXIT + fail() { - echo "$1" - cleanup + echo "$1" >&2 exit 1 } @@ -45,16 +50,50 @@ wait_for() { i=0 while [ "$i" -lt 50 ]; do - if capture | grep -q "$1"; then - sleep 0.5 + CAPTURED=$(capture) + if printf '%s\n' "$CAPTURED" | grep -F -q "$1"; then return 0 fi - sleep 0.5 + sleep 0.2 i=$((i + 1)) done fail "timed out waiting for '$1'" } +# wait_gone $marker +# +# Wait (up to ~10s) until the rendered screen no longer contains $marker. +wait_gone() +{ + i=0 + while [ "$i" -lt 50 ]; do + CAPTURED=$(capture) + if ! printf '%s\n' "$CAPTURED" | grep -F -q "$1"; then + return 0 + fi + sleep 0.2 + i=$((i + 1)) + done + fail "timed out waiting for '$1' to disappear" +} + +# wait_count $marker $n +# +# Wait (up to ~10s) until exactly $n rendered lines contain $marker. The +# matching capture is left in CAPTURED. +wait_count() +{ + i=0 + while [ "$i" -lt 50 ]; do + CAPTURED=$(capture) + c=$(printf '%s\n' "$CAPTURED" | grep -F -c "$1") + [ "$c" -eq "$2" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "timed out waiting for $2 lines of '$1' (have $c)" +} + # wait_clients $n # # Wait (up to ~10s) until the test server has exactly $n clients attached. @@ -70,14 +109,40 @@ wait_clients() return 1 } -$TMUX kill-server 2>/dev/null -$TMUX2 kill-server 2>/dev/null +# wait_mode $target $state +# +# Wait (up to ~10s) until a pane enters or leaves mode. +wait_mode() +{ + t=$1 + want=$2 + + i=0 + while [ "$i" -lt 50 ]; do + got=$($TMUX display-message -p -t "$t" '#{pane_in_mode}' \ + 2>/dev/null) + [ "$got" = "$want" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "pane $t mode state is $got, expected $want" +} + +exit_mode() +{ + marker=$1 + shift + + $TMUX send-keys -t aaa:0 "$@" || fail "send-keys $* failed" + wait_mode aaa:0 0 + wait_gone "$marker" +} # One client attached to each of two sessions; the mode is displayed on the # client attached to aaa (in window 0 of the outer server) and the filters # tell the clients apart by their attached session. -$TMUX new-session -d -s aaa -x 80 -y 24 || exit 1 -$TMUX new-session -d -s bbb -x 80 -y 24 || exit 1 +$TMUX new-session -d -s aaa -x 80 -y 24 'cat' || exit 1 +$TMUX new-session -d -s bbb -x 80 -y 24 'cat' || exit 1 $TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t aaa" || exit 1 $TMUX2 new-window -d -t out: "$TMUX attach -t bbb" || exit 1 @@ -86,43 +151,55 @@ wait_clients 2 || fail "expected two clients attached to test server" # --- filter keeping only the aaa client ------------------------------------- $TMUX choose-client -t aaa:0 -F 'C1=#{client_session}' \ -f '#{==:#{client_session},aaa}' || exit 1 -wait_for 'C1=' -out=$(capture) -echo "$out" | grep -q 'C1=aaa' || fail "aaa client missing when it matches" -echo "$out" | grep -q 'C1=bbb' && fail "bbb client shown but does not match" -[ "$(echo "$out" | grep -c 'C1=')" -eq 1 ] || fail "expected 1 client" -$TMUX send-keys -t aaa:0 q +wait_count 'C1=' 1 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'C1=aaa' || \ + fail "aaa client missing when it matches" +printf '%s\n' "$out" | grep -F -q 'C1=bbb' && \ + fail "bbb client shown but does not match" +[ "$(printf '%s\n' "$out" | grep -F -c 'C1=')" -eq 1 ] || \ + fail "expected 1 client" +exit_mode 'C1=' q # --- filter keeping only the bbb client ------------------------------------- $TMUX choose-client -t aaa:0 -F 'C2=#{client_session}' \ -f '#{==:#{client_session},bbb}' || exit 1 -wait_for 'C2=' -out=$(capture) -echo "$out" | grep -q 'C2=bbb' || fail "bbb client missing when it matches" -echo "$out" | grep -q 'C2=aaa' && fail "aaa client shown but does not match" -[ "$(echo "$out" | grep -c 'C2=')" -eq 1 ] || fail "expected 1 client" -$TMUX send-keys -t aaa:0 q +wait_count 'C2=' 1 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'C2=bbb' || \ + fail "bbb client missing when it matches" +printf '%s\n' "$out" | grep -F -q 'C2=aaa' && \ + fail "aaa client shown but does not match" +[ "$(printf '%s\n' "$out" | grep -F -c 'C2=')" -eq 1 ] || \ + fail "expected 1 client" +exit_mode 'C2=' q # --- no filter shows both clients ------------------------------------------- $TMUX choose-client -t aaa:0 -F 'C3=#{client_session}' || exit 1 -wait_for 'C3=' -out=$(capture) -echo "$out" | grep -q 'C3=aaa' || fail "aaa client missing with no filter" -echo "$out" | grep -q 'C3=bbb' || fail "bbb client missing with no filter" -[ "$(echo "$out" | grep -c 'C3=')" -eq 2 ] || fail "expected 2 clients" -$TMUX send-keys -t aaa:0 q +wait_count 'C3=' 2 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'C3=aaa' || \ + fail "aaa client missing with no filter" +printf '%s\n' "$out" | grep -F -q 'C3=bbb' || \ + fail "bbb client missing with no filter" +[ "$(printf '%s\n' "$out" | grep -F -c 'C3=')" -eq 2 ] || \ + fail "expected 2 clients" +exit_mode 'C3=' q # --- filter matching nothing ------------------------------------------------ # # Everything is shown and the filter indicator reports no matches. $TMUX choose-client -t aaa:0 -F 'C4=#{client_session}' \ -f '#{==:#{client_session},nosuch}' || exit 1 -wait_for 'C4=' -out=$(capture) -echo "$out" | grep -q 'C4=aaa' || fail "aaa client missing with no-match filter" -echo "$out" | grep -q 'C4=bbb' || fail "bbb client missing with no-match filter" -echo "$out" | grep -q 'no matches' || fail "no matches indicator missing" -$TMUX send-keys -t aaa:0 q +wait_count 'C4=' 2 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'C4=aaa' || \ + fail "aaa client missing with no-match filter" +printf '%s\n' "$out" | grep -F -q 'C4=bbb' || \ + fail "bbb client missing with no-match filter" +printf '%s\n' "$out" | grep -F -q 'no matches' || \ + fail "no matches indicator missing" +exit_mode 'C4=' q # --- Enter runs the default command (detach-client) -------------------------- # @@ -131,10 +208,11 @@ $TMUX send-keys -t aaa:0 q $TMUX choose-client -t aaa:0 -F 'G1=#{client_session}' \ -f '#{==:#{client_session},bbb}' || exit 1 wait_for 'G1=bbb' -$TMUX send-keys -t aaa:0 Enter +$TMUX send-keys -t aaa:0 Enter || fail "send-keys Enter failed" wait_clients 1 || fail "bbb client did not detach" +wait_mode aaa:0 0 +wait_gone 'G1=' [ "$($TMUX list-clients -F '#{client_session}')" = "aaa" ] || \ fail "wrong client detached" -cleanup exit 0 diff --git a/regress/choose-tree.sh b/regress/choose-tree.sh index 15add9f3f..132890730 100644 --- a/regress/choose-tree.sh +++ b/regress/choose-tree.sh @@ -24,18 +24,23 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMP=$(mktemp -d) || exit 1 +TMUX_TMPDIR="$TMP" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" cleanup() { $TMUX kill-server 2>/dev/null $TMUX2 kill-server 2>/dev/null + rm -rf "$TMP" } +trap cleanup EXIT + fail() { - echo "$1" - cleanup + echo "$1" >&2 exit 1 } @@ -53,28 +58,30 @@ wait_for() { i=0 while [ "$i" -lt 50 ]; do - if capture | grep -q "$1"; then - sleep 0.5 + CAPTURED=$(capture) + if printf '%s\n' "$CAPTURED" | grep -F -q "$1"; then return 0 fi - sleep 0.5 + sleep 0.2 i=$((i + 1)) done fail "timed out waiting for '$1'" } -# wait_count $marker $n +# wait_count $row-marker $n # -# Wait (up to ~10s) until exactly $n rendered lines contain $marker. +# Wait (up to ~10s) until exactly $n rendered list rows contain $row-marker. wait_count() { i=0 while [ "$i" -lt 50 ]; do - [ "$(capture | grep -c "$1")" -eq "$2" ] && return 0 - sleep 0.5 + CAPTURED=$(capture) + c=$(printf '%s\n' "$CAPTURED" | grep -F -c "$1") + [ "$c" -eq "$2" ] && return 0 + sleep 0.2 i=$((i + 1)) done - fail "timed out waiting for $2 lines of '$1' (have $(capture | grep -c "$1"))" + fail "timed out waiting for $2 lines of '$1' (have $c)" } # wait_clients $n @@ -92,8 +99,30 @@ wait_clients() return 1 } -$TMUX kill-server 2>/dev/null -$TMUX2 kill-server 2>/dev/null +# wait_mode $target $state +# +# Wait (up to ~10s) until a pane enters or leaves mode. +wait_mode() +{ + t=$1 + want=$2 + + i=0 + while [ "$i" -lt 50 ]; do + got=$($TMUX display-message -p -t "$t" '#{pane_in_mode}' \ + 2>/dev/null) + [ "$got" = "$want" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "pane $t mode state is $got, expected $want" +} + +exit_mode() +{ + $TMUX send-keys -t aaa:0 "$@" || fail "send-keys $* failed" + wait_mode aaa:0 0 +} # Session zzz is created first, so it sorts first by index, and has a # two-pane window 0 and a single-pane window 1. Session aaa has one window @@ -102,10 +131,10 @@ $TMUX2 kill-server 2>/dev/null # # 0 zzz 1 window 0 2 pane 0 3 pane 1 4 window 1 5 pane 0 # 6 aaa 7 window 0 8 pane 0 -$TMUX new-session -d -s zzz -x 80 -y 24 || exit 1 -$TMUX split-window -t zzz:0 || exit 1 -$TMUX new-window -t zzz || exit 1 -$TMUX new-session -d -s aaa -x 80 -y 24 || exit 1 +$TMUX new-session -d -s zzz -x 80 -y 24 'cat' || exit 1 +$TMUX split-window -t zzz:0 'cat' || exit 1 +$TMUX new-window -t zzz 'cat' || exit 1 +$TMUX new-session -d -s aaa -x 80 -y 24 'cat' || exit 1 $TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t aaa" || exit 1 wait_clients 1 || fail "no client attached to test server" @@ -116,45 +145,55 @@ wait_clients 1 || fail "no client attached to test server" # regression - its two-pane window 0. aaa contributes exactly three lines # (session, window, pane). $TMUX choose-tree -t aaa:0 -F 'F1' -f '#{==:#{session_name},aaa}' || exit 1 -wait_count 'F1' 3 -out=$(capture) -echo "$out" | grep -q 'aaa: F1' || fail "aaa missing when filter matches it" -echo "$out" | grep -q 'zzz: F1' && fail "zzz shown but no pane matches" -$TMUX send-keys -t aaa:0 q +wait_count ': F1' 3 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'aaa: F1' || \ + fail "aaa missing when filter matches it" +printf '%s\n' "$out" | grep -F -q 'zzz: F1' && \ + fail "zzz shown but no pane matches" +exit_mode q # --- filter keeping only zzz ------------------------------------------------ # # zzz contributes six lines (session, two windows, three panes); aaa must # disappear. $TMUX choose-tree -t aaa:0 -F 'F2' -f '#{==:#{session_name},zzz}' || exit 1 -wait_count 'F2' 6 -out=$(capture) -echo "$out" | grep -q 'zzz: F2' || fail "zzz missing when filter matches it" -echo "$out" | grep -q 'aaa: F2' && fail "aaa shown but no pane matches" -$TMUX send-keys -t aaa:0 q +wait_count ': F2' 6 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'zzz: F2' || \ + fail "zzz missing when filter matches it" +printf '%s\n' "$out" | grep -F -q 'aaa: F2' && \ + fail "aaa shown but no pane matches" +exit_mode q # --- filter matching a single pane ------------------------------------------ # # Only pane 1 of zzz:0 matches, so the tree is exactly session zzz, window 0 # and that pane; zzz:1 and all of aaa must disappear. $TMUX choose-tree -t aaa:0 -F 'F3' -f '#{==:#{pane_index},1}' || exit 1 -wait_count 'F3' 3 -out=$(capture) -echo "$out" | grep -q 'zzz: F3' || fail "zzz missing when its pane matches" -echo "$out" | grep -q 'aaa: F3' && fail "aaa shown but no pane matches" -echo "$out" | grep -q '1: F3' || fail "matching pane missing" -$TMUX send-keys -t aaa:0 q +wait_count ': F3' 3 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'zzz: F3' || \ + fail "zzz missing when its pane matches" +printf '%s\n' "$out" | grep -F -q 'aaa: F3' && \ + fail "aaa shown but no pane matches" +printf '%s\n' "$out" | grep -F -q '1: F3' || \ + fail "matching pane missing" +exit_mode q # --- filter matching nothing ------------------------------------------------ # # Everything is shown and the filter indicator reports no matches. $TMUX choose-tree -t aaa:0 -F 'F4' -f '#{==:#{session_name},nosuch}' || exit 1 -wait_for 'F4' -out=$(capture) -echo "$out" | grep -q 'aaa: F4' || fail "aaa missing with no-match filter" -echo "$out" | grep -q 'zzz: F4' || fail "zzz missing with no-match filter" -echo "$out" | grep -q 'no matches' || fail "no matches indicator missing" -$TMUX send-keys -t aaa:0 q +wait_count ': F4' 9 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'aaa: F4' || \ + fail "aaa missing with no-match filter" +printf '%s\n' "$out" | grep -F -q 'zzz: F4' || \ + fail "zzz missing with no-match filter" +printf '%s\n' "$out" | grep -F -q 'no matches' || \ + fail "no matches indicator missing" +exit_mode q # --- -h with the tree pane as the only match -------------------------------- # @@ -162,86 +201,93 @@ $TMUX send-keys -t aaa:0 q # match, so session and window aaa stay listed: two lines, no pane line. $TMUX choose-tree -h -t aaa:0 -F 'F5' -f '#{==:#{session_name},aaa}' || \ exit 1 -wait_count 'F5' 2 -capture | grep -q 'aaa: F5' || fail "aaa missing with -h" -$TMUX send-keys -t aaa:0 q +wait_count ': F5' 2 +printf '%s\n' "$CAPTURED" | grep -F -q 'aaa: F5' || \ + fail "aaa missing with -h" +exit_mode q # --- sort orders ------------------------------------------------------------ # # By index zzz (created first) sorts first, by name aaa does, and -r reverses. $TMUX choose-tree -t aaa:0 -F 'F6' -O index || exit 1 -wait_for 'F6' -capture | grep 'F6' | head -1 | grep -q 'zzz: F6' || \ +wait_count ': F6' 9 +printf '%s\n' "$CAPTURED" | grep -F ': F6' | head -1 | \ + grep -F -q 'zzz: F6' || \ fail "zzz not first with -O index" -$TMUX send-keys -t aaa:0 q +exit_mode q $TMUX choose-tree -t aaa:0 -F 'F7' -O name || exit 1 -wait_for 'F7' -capture | grep 'F7' | head -1 | grep -q 'aaa: F7' || \ +wait_count ': F7' 9 +printf '%s\n' "$CAPTURED" | grep -F ': F7' | head -1 | \ + grep -F -q 'aaa: F7' || \ fail "aaa not first with -O name" -$TMUX send-keys -t aaa:0 q +exit_mode q $TMUX choose-tree -t aaa:0 -F 'F8' -O name -r || exit 1 -wait_for 'F8' -capture | grep 'F8' | head -1 | grep -q 'zzz: F8' || \ +wait_count ': F8' 9 +printf '%s\n' "$CAPTURED" | grep -F ': F8' | head -1 | \ + grep -F -q 'zzz: F8' || \ fail "zzz not first with -O name -r" -$TMUX send-keys -t aaa:0 q +exit_mode q # --- collapse and expand with h and l ----------------------------------------- # # g moves to the top (session zzz); h collapses it, hiding its five children; # l expands it again. $TMUX choose-tree -t aaa:0 -F 'G1' -O index || exit 1 -wait_count 'G1' 9 -$TMUX send-keys -t aaa:0 g h -wait_count 'G1' 4 -$TMUX send-keys -t aaa:0 l -wait_count 'G1' 9 -$TMUX send-keys -t aaa:0 q +wait_count ': G1' 9 +$TMUX send-keys -t aaa:0 g h || fail "send-keys collapse failed" +wait_count ': G1' 4 +$TMUX send-keys -t aaa:0 l || fail "send-keys expand failed" +wait_count ': G1' 9 +exit_mode q # --- filter entered at the prompt with f, cleared with c ---------------------- $TMUX choose-tree -t aaa:0 -F 'G2' -O index || exit 1 -wait_count 'G2' 9 -$TMUX send-keys -t aaa:0 f -$TMUX send-keys -t aaa:0 -l '#{==:#{session_name},aaa}' -$TMUX send-keys -t aaa:0 Enter -wait_count 'G2' 3 -out=$(capture) -echo "$out" | grep -q 'aaa: G2' || fail "aaa missing with prompt filter" -echo "$out" | grep -q 'zzz: G2' && fail "zzz shown with prompt filter" -$TMUX send-keys -t aaa:0 c -wait_count 'G2' 9 -$TMUX send-keys -t aaa:0 q +wait_count ': G2' 9 +$TMUX send-keys -t aaa:0 f || fail "send-keys f failed" +$TMUX send-keys -t aaa:0 -l '#{==:#{session_name},aaa}' || \ + fail "send-keys filter failed" +$TMUX send-keys -t aaa:0 Enter || fail "send-keys Enter failed" +wait_count ': G2' 3 +out=$CAPTURED +printf '%s\n' "$out" | grep -F -q 'aaa: G2' || \ + fail "aaa missing with prompt filter" +printf '%s\n' "$out" | grep -F -q 'zzz: G2' && \ + fail "zzz shown with prompt filter" +$TMUX send-keys -t aaa:0 c || fail "send-keys c failed" +wait_count ': G2' 9 +exit_mode q # --- Enter runs the default command (switch-client) ---------------------------- # # g selects session zzz and Enter switches the client to it. $TMUX choose-tree -t aaa:0 -F 'G3' -O index || exit 1 -wait_count 'G3' 9 -$TMUX send-keys -t aaa:0 g Enter +wait_count ': G3' 9 +$TMUX send-keys -t aaa:0 g Enter || fail "send-keys Enter failed" i=0 while [ "$i" -lt 50 ]; do [ "$($TMUX list-clients -F '#{client_session}')" = "zzz" ] && break - sleep 0.5 + sleep 0.2 i=$((i + 1)) done [ "$i" -lt 50 ] || fail "client did not switch to zzz" $TMUX switch-client -c "$($TMUX list-clients -F '#{client_name}')" -t aaa || \ exit 1 +wait_mode aaa:0 0 # --- x kills the current item after confirmation ------------------------------- # # g and four times j select window 1 of zzz; x asks for confirmation and y # kills it, leaving zzz with one window and the tree with seven lines. $TMUX choose-tree -t aaa:0 -F 'G4' -O index || exit 1 -wait_count 'G4' 9 -$TMUX send-keys -t aaa:0 g j j j j x +wait_count ': G4' 9 +$TMUX send-keys -t aaa:0 g j j j j x || fail "send-keys x failed" wait_for 'Kill window 1' -$TMUX send-keys -t aaa:0 y -wait_count 'G4' 7 +$TMUX send-keys -t aaa:0 y || fail "send-keys y failed" +wait_count ': G4' 7 [ "$($TMUX list-windows -t zzz -F x | grep -c x)" -eq 1 ] || \ fail "window 1 of zzz not killed" -$TMUX send-keys -t aaa:0 q +exit_mode q -cleanup exit 0 diff --git a/regress/combine-test.sh b/regress/combine-test.sh index a4d4f3681..ac9f800b0 100644 --- a/regress/combine-test.sh +++ b/regress/combine-test.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/command-alias.sh b/regress/command-alias.sh index 632e959a2..aae434cc0 100644 --- a/regress/command-alias.sh +++ b/regress/command-alias.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null $TMUX new-session -d -sfoo || exit 1 diff --git a/regress/command-order.sh b/regress/command-order.sh index 775768a6a..9c957246a 100644 --- a/regress/command-order.sh +++ b/regress/command-order.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/conf-syntax.sh b/regress/conf-syntax.sh index 49b9710b3..4dbc384b3 100644 --- a/regress/conf-syntax.sh +++ b/regress/conf-syntax.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null for i in conf/*.conf; do diff --git a/regress/control-client-sanity.sh b/regress/control-client-sanity.sh index 9f668275f..e2ac47da2 100644 --- a/regress/control-client-sanity.sh +++ b/regress/control-client-sanity.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/control-client-size.sh b/regress/control-client-size.sh index 7d5e1dc27..e6742608a 100644 --- a/regress/control-client-size.sh +++ b/regress/control-client-size.sh @@ -8,7 +8,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/control-subscriptions.sh b/regress/control-subscriptions.sh index f720b0650..8aebb2ea0 100644 --- a/regress/control-subscriptions.sh +++ b/regress/control-subscriptions.sh @@ -7,7 +7,7 @@ LANG=C.UTF-8 export TERM LC_ALL LANG [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest$$ -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMPDIR=$(mktemp -d) IN="$TMPDIR/in" diff --git a/regress/copy-mode-test-emacs.sh b/regress/copy-mode-test-emacs.sh index a5b69c675..3e9cbf50b 100644 --- a/regress/copy-mode-test-emacs.sh +++ b/regress/copy-mode-test-emacs.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null $TMUX new -d -x40 -y10 \ diff --git a/regress/copy-mode-test-vi.sh b/regress/copy-mode-test-vi.sh index 2d6bfc287..71810b006 100644 --- a/regress/copy-mode-test-vi.sh +++ b/regress/copy-mode-test-vi.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -f/dev/null -Ltest" +TMUX="$TEST_TMUX -f/dev/null -LtestA$$" $TMUX kill-server 2>/dev/null $TMUX new -d -x40 -y10 \ diff --git a/regress/cursor-test1.sh b/regress/cursor-test1.sh index 73a336be7..b098cdc15 100644 --- a/regress/cursor-test1.sh +++ b/regress/cursor-test1.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/cursor-test2.sh b/regress/cursor-test2.sh index 3de80fdac..8e9e7ec80 100644 --- a/regress/cursor-test2.sh +++ b/regress/cursor-test2.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/cursor-test3.sh b/regress/cursor-test3.sh index e4881c79a..213ed83ba 100644 --- a/regress/cursor-test3.sh +++ b/regress/cursor-test3.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/cursor-test4.sh b/regress/cursor-test4.sh index 8f4dfa5c3..0eb5f0c9a 100644 --- a/regress/cursor-test4.sh +++ b/regress/cursor-test4.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/decrqm-sync.sh b/regress/decrqm-sync.sh index 7f8c3f61d..f4daf36a9 100644 --- a/regress/decrqm-sync.sh +++ b/regress/decrqm-sync.sh @@ -9,7 +9,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null sleep 1 diff --git a/regress/environ-update.sh b/regress/environ-update.sh index 59f7d74c5..44e4e72c4 100644 --- a/regress/environ-update.sh +++ b/regress/environ-update.sh @@ -17,9 +17,9 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" # A second server on its own socket hosts the pane that runs the inner client. -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" cleanup() { diff --git a/regress/environ.sh b/regress/environ.sh index 244ec367d..2d5c0c031 100644 --- a/regress/environ.sh +++ b/regress/environ.sh @@ -19,7 +19,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null # check_value $args $expected @@ -151,7 +151,7 @@ cat > "$CONF" </dev/null $CTMUX new-session -d -s c -x 80 -y 24 || { rm -f "$CONF"; exit 1; } out=$($CTMUX show-environment -g CFGVAR 2>&1) diff --git a/regress/floating-pane-geometry.sh b/regress/floating-pane-geometry.sh index 7bd033ff6..7e80e8942 100644 --- a/regress/floating-pane-geometry.sh +++ b/regress/floating-pane-geometry.sh @@ -15,7 +15,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null fail() diff --git a/regress/format-fuzzy.sh b/regress/format-fuzzy.sh index 31a5b60cb..00bc28bd4 100644 --- a/regress/format-fuzzy.sh +++ b/regress/format-fuzzy.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" # test_format $format $expected_result test_format() diff --git a/regress/format-modifiers.sh b/regress/format-modifiers.sh index 21203b6a9..b6340b622 100644 --- a/regress/format-modifiers.sh +++ b/regress/format-modifiers.sh @@ -17,7 +17,7 @@ LC_ALL=C.UTF-8 export TZ LANG LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" ESC=$(printf '\033') diff --git a/regress/format-mouse.sh b/regress/format-mouse.sh index 8032f8a57..9a443d19c 100644 --- a/regress/format-mouse.sh +++ b/regress/format-mouse.sh @@ -18,8 +18,8 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" cleanup() { diff --git a/regress/format-render-contexts.sh b/regress/format-render-contexts.sh index a91fb88e1..2cceff2e3 100644 --- a/regress/format-render-contexts.sh +++ b/regress/format-render-contexts.sh @@ -9,8 +9,8 @@ export PATH TERM LANG LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Lformat-render-contexts-$$ -f/dev/null" -TMUX2="$TEST_TMUX -Lformat-render-contexts-outer-$$ -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" LIMIT=20000 cleanup() @@ -49,7 +49,13 @@ tmux_run() name=$1 shift - out=$(run_cmd $TMUX "$@" 2>&1) + out=$( + if command -v timeout >/dev/null 2>&1; then + timeout 10 $TMUX "$@" 2>&1 + else + $TMUX "$@" 2>&1 + fi + ) rc=$? bounded "$name" "$out" [ "$rc" -eq 0 ] || fail "$name failed: $out" @@ -58,7 +64,13 @@ tmux_run() capture() { - out=$(run_cmd $TMUX2 capture-pane -p -t out:0 2>/dev/null) + out=$( + if command -v timeout >/dev/null 2>&1; then + timeout 10 $TMUX2 capture-pane -p -t out:0 2>/dev/null + else + $TMUX2 capture-pane -p -t out:0 2>/dev/null + fi + ) rc=$? bounded "capture-pane" "$out" [ "$rc" -eq 0 ] || fail "capture-pane failed" @@ -67,7 +79,14 @@ capture() capture_esc() { - out=$(run_cmd $TMUX2 capture-pane -pe -t out:0 2>/dev/null) + out=$( + if command -v timeout >/dev/null 2>&1; then + timeout 10 $TMUX2 capture-pane -pe -t out:0 \ + 2>/dev/null + else + $TMUX2 capture-pane -pe -t out:0 2>/dev/null + fi + ) rc=$? bounded "capture-pane -e" "$out" [ "$rc" -eq 0 ] || fail "capture-pane -e failed" diff --git a/regress/format-strings.sh b/regress/format-strings.sh index bae66ab00..877e18fec 100644 --- a/regress/format-strings.sh +++ b/regress/format-strings.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" # test_format $format $expected_result test_format() diff --git a/regress/format-variables.sh b/regress/format-variables.sh index fa51e0216..9131c2cf4 100644 --- a/regress/format-variables.sh +++ b/regress/format-variables.sh @@ -15,10 +15,10 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" # A second server on its own socket provides a real terminal (an inner client # attached inside one of its panes) so client terminal variables are populated. -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" # Every variable name in format_table[]. Kept as a plain word list so it can be # iterated with normal shell word splitting. diff --git a/regress/has-session-return.sh b/regress/has-session-return.sh index a7d76ad85..8b259ac76 100644 --- a/regress/has-session-return.sh +++ b/regress/has-session-return.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null $TMUX -f/dev/null has -tfoo /dev/null && exit 1 diff --git a/regress/hooks-after.sh b/regress/hooks-after.sh new file mode 100644 index 000000000..37934db8d --- /dev/null +++ b/regress/hooks-after.sh @@ -0,0 +1,124 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +LANG=C.UTF-8 +export TERM LC_ALL LANG + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT=$(mktemp -d) +TMUX_TMPDIR="$OUT" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" + exit 1 +} + +cleanup() +{ + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" +} +trap cleanup EXIT + +wait_for() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 30 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] && return 0 + i=$((i + 1)) + sleep 0.2 + done + fail "expected $option to be '$expected' but got '$value'" +} + +assert_unchanged() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 10 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] || \ + fail "expected $option to remain '$expected' but got '$value'" + i=$((i + 1)) + sleep 0.2 + done +} + +$TMUX new -d -s one || fail "new-session one failed" +$TMUX new -d -s two || fail "new-session two failed" + +# An after hook fires with the command arguments as formats. +$TMUX set -g @after 0 || fail "set @after failed" +$TMUX set-hook -g after-rename-window \ + 'set -gF @after "#{hook}|#{hook_argument_0}|#{hook_flag_t}"' || + fail "set-hook -g after-rename-window failed" +$TMUX rename-window -t one:0 first || fail "rename-window first failed" +wait_for @after 'after-rename-window|first|one:0' + +# An appended hook command runs after the first. +$TMUX set -g @after2 0 || fail "set @after2 failed" +$TMUX set-hook -ga after-rename-window \ + 'set -gF @after2 "#{@after}+2"' || + fail "set-hook -ga after-rename-window failed" +$TMUX rename-window -t one:0 second || fail "rename-window second failed" +wait_for @after 'after-rename-window|second|one:0' +wait_for @after2 'after-rename-window|second|one:0+2' +$TMUX set-hook -gu after-rename-window || fail "set-hook -gu failed" + +# A session after hook only fires for commands targeting that session and +# the hook commands run with the command target as current state. +$TMUX set -g @safter 0 || fail "set @safter failed" +$TMUX set-hook -t two after-rename-window \ + 'set -gF @safter "#{hook}:#{session_name}"' || + fail "set-hook -t two after-rename-window failed" +$TMUX rename-window -t one:0 third || fail "rename-window third failed" +assert_unchanged @safter 0 +$TMUX rename-window -t two:0 fourth || fail "rename-window fourth failed" +wait_for @safter 'after-rename-window:two' +$TMUX set-hook -u -t two after-rename-window || + fail "set-hook -u -t two failed" + +# The command-error hook fires when a command fails. +$TMUX set -g @error 0 || fail "set @error failed" +$TMUX set-hook -g command-error 'set -gF @error "#{hook}"' || + fail "set-hook -g command-error failed" +if $TMUX rename-window -t nosuchsession:0 x 2>/dev/null; then + fail "rename-window to missing session succeeded" +fi +wait_for @error 'command-error' +$TMUX set-hook -gu command-error || fail "set-hook -gu command-error failed" + +# Commands run from a hook do not fire their own after hooks. +$TMUX set -g @copy 0 || fail "set @copy failed" +$TMUX set-hook -g after-copy-mode 'set -gF @copy "#{hook}"' || + fail "set-hook -g after-copy-mode failed" +$TMUX copy-mode -t one:0 || fail "copy-mode failed" +wait_for @copy 'after-copy-mode' +$TMUX send-keys -t one:0 -X cancel || fail "cancel failed" +$TMUX set -g @copy 0 || fail "reset @copy failed" +$TMUX set -g @ran 0 || fail "set @ran failed" +$TMUX set-hook -g after-rename-window 'copy-mode -t one:0' || + fail "set-hook -g after-rename-window nested failed" +$TMUX set-hook -ga after-rename-window 'set -g @ran 1' || + fail "set-hook -ga after-rename-window nested failed" +$TMUX rename-window -t one:0 fifth || fail "rename-window fifth failed" +wait_for @ran 1 +mode=$($TMUX display -pt one:0 '#{pane_in_mode}') || + fail "display pane_in_mode failed" +[ "$mode" = 1 ] || fail "hook did not enter copy mode" +assert_unchanged @copy 0 + +exit 0 diff --git a/regress/hooks-lifecycle.sh b/regress/hooks-lifecycle.sh new file mode 100644 index 000000000..e90f7ba3a --- /dev/null +++ b/regress/hooks-lifecycle.sh @@ -0,0 +1,162 @@ +#!/bin/sh + +# End-of-life hooks: pane-exited, pane-died, window-unlinked and +# session-closed when the pane, window or session they refer to is being +# or has been destroyed. Each hook appends to @log so the order hooks fire +# in is checked as well as the hook formats for the dead objects. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +LANG=C.UTF-8 +export TERM LC_ALL LANG + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT=$(mktemp -d) +TMUX_TMPDIR="$OUT" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" + exit 1 +} + +cleanup() +{ + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" +} +trap cleanup EXIT + +wait_for() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 30 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] && return 0 + i=$((i + 1)) + sleep 0.2 + done + fail "expected $option to be '$expected' but got '$value'" +} + +assert_unchanged() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 10 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] || \ + fail "expected $option to remain '$expected' but got '$value'" + i=$((i + 1)) + sleep 0.2 + done +} + +$TMUX new -d -s main || fail "new-session main failed" + +$TMUX set -g @log '' || fail "set @log failed" +$TMUX set-hook -g pane-exited \ + 'set -gF @log "#{@log}|pane-exited:#{hook_pane}"' || + fail "set-hook pane-exited failed" +$TMUX set-hook -g window-unlinked \ + 'set -gF @log "#{@log}|window-unlinked:#{hook_session_name}:#{hook_window_name}"' || + fail "set-hook window-unlinked failed" +$TMUX set-hook -g session-closed \ + 'set -gF @log "#{@log}|session-closed:#{hook_session_name}"' || + fail "set-hook session-closed failed" + +# The only pane of the only window of a session exits: pane-exited, then +# window-unlinked, then session-closed, each seeing the dead object in the +# hook formats. +pane=$($TMUX new -d -s doomed -n dwin -P -F '#{pane_id}' 'true') || + fail "new-session doomed failed" +wait_for @log \ + "|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed" +assert_unchanged @log \ + "|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed" + +# The dead pane, window and session cannot be used as targets but the +# server survives. +if $TMUX select-pane -t "$pane" 2>/dev/null; then + fail "dead pane still a valid target" +fi +if $TMUX list-windows -t doomed >/dev/null 2>&1; then + fail "dead session still a valid target" +fi +$TMUX list-panes -sat main >/dev/null || fail "list-panes failed" +$TMUX has -t main || fail "server died after pane exit chain" + +# kill-window on the last window: window-unlinked then session-closed but +# no pane-exited for the panes in the killed window. +$TMUX set -g @log '' || fail "reset @log failed" +$TMUX new -d -s doomed2 -n dwin2 || fail "new-session doomed2 failed" +$TMUX splitw -d -t doomed2:0 || fail "split-window doomed2 failed" +$TMUX kill-window -t doomed2:0 || fail "kill-window failed" +wait_for @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2' +assert_unchanged @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2' +$TMUX has -t main || fail "server died after kill-window chain" + +# kill-session: session-closed fires first, then window-unlinked for its +# windows. A window linked into another session survives. +$TMUX new -d -s shareA -n shared || fail "new-session shareA failed" +$TMUX new -d -s shareB -n bwin || fail "new-session shareB failed" +$TMUX link-window -s shareA:shared -t shareB:7 || fail "link-window failed" +$TMUX set -g @log '' || fail "reset @log failed" +$TMUX kill-session -t shareA || fail "kill-session shareA failed" +wait_for @log '|session-closed:shareA|window-unlinked:shareA:shared' +name=$($TMUX display -pt shareB:7 '#{window_name}') || + fail "shared window did not survive" +[ "$name" = shared ] || fail "expected window shared but got $name" + +# Killing the surviving session destroys the shared window for real while +# the session is being destroyed. +$TMUX set -g @log '' || fail "reset @log failed" +$TMUX kill-window -t shareB:0 || fail "kill-window bwin failed" +wait_for @log '|window-unlinked:shareB:bwin' +$TMUX set -g @log '' || fail "reset @log failed" +$TMUX kill-session -t shareB || fail "kill-session shareB failed" +wait_for @log '|session-closed:shareB|window-unlinked:shareB:shared' +$TMUX has -t main || fail "server died after kill-session chain" + +# A pane-died hook can kill its own dead pane (the hook runs with the dead +# pane as current target). The kill-pane runs without hooks so pane-exited +# does not fire. +$TMUX set -g @log '' || fail "reset @log failed" +$TMUX new -d -s roe -n rwin || fail "new-session roe failed" +$TMUX set -wt roe:0 remain-on-exit on || fail "set remain-on-exit failed" +$TMUX set-hook -g pane-died \ + 'set -gF @log "#{@log}|pane-died:#{hook_pane}" ; kill-pane' || + fail "set-hook pane-died failed" +pane=$($TMUX splitw -d -t roe:0 -P -F '#{pane_id}' 'true') || + fail "split-window roe failed" +wait_for @log "|pane-died:$pane" +assert_unchanged @log "|pane-died:$pane" +if $TMUX select-pane -t "$pane" 2>/dev/null; then + fail "pane-died hook did not kill its pane" +fi +$TMUX list-panes -t roe:0 >/dev/null || fail "surviving pane broken" +$TMUX has -t roe || fail "session roe died" +$TMUX set-hook -gu pane-died || fail "unset pane-died failed" +$TMUX kill-session -t roe || fail "kill-session roe failed" + +# Killing the last session with end-of-life hooks still set: the server +# runs the hooks and exits cleanly. +$TMUX kill-session -t main || fail "kill-session main failed" +i=0 +while $TMUX has 2>/dev/null; do + i=$((i + 1)) + [ $i -lt 30 ] || fail "server still running after last session killed" + sleep 0.2 +done + +exit 0 diff --git a/regress/hooks-notify.sh b/regress/hooks-notify.sh new file mode 100644 index 000000000..41916248c --- /dev/null +++ b/regress/hooks-notify.sh @@ -0,0 +1,199 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +LANG=C.UTF-8 +export TERM LC_ALL LANG + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT=$(mktemp -d) +TMUX_TMPDIR="$OUT" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" + exit 1 +} + +cleanup() +{ + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" +} +trap cleanup EXIT + +wait_for() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 30 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] && return 0 + i=$((i + 1)) + sleep 0.2 + done + fail "expected $option to be '$expected' but got '$value'" +} + +$TMUX new -d -s main || fail "new-session main failed" + +# session-created, session-renamed, session-closed. +$TMUX set -g @s 0 || fail "set @s failed" +$TMUX set-hook -g session-created \ + 'set -gF @s "#{hook}:#{hook_session_name}"' || + fail "set-hook session-created failed" +$TMUX new -d -s tmp || fail "new-session tmp failed" +wait_for @s 'session-created:tmp' +$TMUX set-hook -gu session-created || fail "unset session-created failed" + +$TMUX set-hook -g session-renamed \ + 'set -gF @s "#{hook}:#{hook_session_name}"' || + fail "set-hook session-renamed failed" +$TMUX rename-session -t tmp tmp2 || fail "rename-session failed" +wait_for @s 'session-renamed:tmp2' +$TMUX set-hook -gu session-renamed || fail "unset session-renamed failed" + +$TMUX set-hook -g session-closed \ + 'set -gF @s "#{hook}:#{hook_session_name}"' || + fail "set-hook session-closed failed" +$TMUX kill-session -t tmp2 || fail "kill-session failed" +wait_for @s 'session-closed:tmp2' +$TMUX set-hook -gu session-closed || fail "unset session-closed failed" + +# window-linked and window-unlinked from new-window and kill-window. +$TMUX set -g @w 0 || fail "set @w failed" +$TMUX set-hook -g window-linked \ + 'set -gF @w "#{hook}:#{hook_session_name}:#{hook_window_name}"' || + fail "set-hook window-linked failed" +$TMUX set-hook -g window-unlinked \ + 'set -gF @w "#{hook}:#{hook_session_name}:#{hook_window_name}"' || + fail "set-hook window-unlinked failed" +$TMUX neww -d -t main: -n mywin || fail "new-window failed" +wait_for @w 'window-linked:main:mywin' + +# window-linked and window-unlinked from link-window and unlink-window. +$TMUX new -d -s other || fail "new-session other failed" +$TMUX set -g @w 0 || fail "reset @w failed" +$TMUX link-window -s main:mywin -t other:5 || fail "link-window failed" +wait_for @w 'window-linked:other:mywin' +$TMUX set -g @w 0 || fail "reset @w failed" +$TMUX unlink-window -t other:5 || fail "unlink-window failed" +wait_for @w 'window-unlinked:other:mywin' +$TMUX set -g @w 0 || fail "reset @w failed" +$TMUX kill-window -t main:mywin || fail "kill-window failed" +wait_for @w 'window-unlinked:main:mywin' +$TMUX set-hook -gu window-linked || fail "unset window-linked failed" +$TMUX set-hook -gu window-unlinked || fail "unset window-unlinked failed" + +# window-renamed sees the window id. +window=$($TMUX display -pt main:0 '#{window_id}') || + fail "display window_id failed" +$TMUX set -g @r 0 || fail "set @r failed" +$TMUX set-hook -g window-renamed \ + 'set -gF @r "#{hook}:#{hook_window}:#{hook_window_name}"' || + fail "set-hook window-renamed failed" +$TMUX rename-window -t main:0 renamed || fail "rename-window failed" +wait_for @r "window-renamed:$window:renamed" +$TMUX set-hook -gu window-renamed || fail "unset window-renamed failed" + +# window-layout-changed from split-window. +$TMUX set -g @l 0 || fail "set @l failed" +$TMUX set-hook -g window-layout-changed \ + 'set -gF @l "#{hook}:#{hook_window}"' || + fail "set-hook window-layout-changed failed" +$TMUX splitw -d -t main:0 || fail "split-window failed" +wait_for @l "window-layout-changed:$window" +$TMUX set-hook -gu window-layout-changed || + fail "unset window-layout-changed failed" + +# window-pane-changed from select-pane. +$TMUX set -g @p 0 || fail "set @p failed" +$TMUX set-hook -g window-pane-changed \ + 'set -gF @p "#{hook}:#{hook_window}"' || + fail "set-hook window-pane-changed failed" +$TMUX selectp -t main:0.1 || fail "select-pane failed" +wait_for @p "window-pane-changed:$window" +$TMUX set-hook -gu window-pane-changed || + fail "unset window-pane-changed failed" + +# session-window-changed from select-window. +$TMUX neww -d -t main: -n w2 || fail "new-window w2 failed" +$TMUX set -g @c 0 || fail "set @c failed" +$TMUX set-hook -g session-window-changed \ + 'set -gF @c "#{hook}:#{hook_session_name}"' || + fail "set-hook session-window-changed failed" +$TMUX selectw -t main:w2 || fail "select-window failed" +wait_for @c 'session-window-changed:main' +$TMUX set-hook -gu session-window-changed || + fail "unset session-window-changed failed" + +# pane-mode-changed from entering and leaving copy mode. +$TMUX set -g @m 0 || fail "set @m failed" +pane=$($TMUX display -pt main:0.0 '#{pane_id}') || + fail "display pane_id failed" +$TMUX set-hook -g pane-mode-changed \ + 'set -gF @m "#{hook}:#{hook_pane}:#{pane_in_mode}"' || + fail "set-hook pane-mode-changed failed" +$TMUX copy-mode -t main:0.0 || fail "copy-mode failed" +wait_for @m "pane-mode-changed:$pane:1" +$TMUX send-keys -t main:0.0 -X cancel || fail "cancel failed" +wait_for @m "pane-mode-changed:$pane:0" +$TMUX set-hook -gu pane-mode-changed || fail "unset pane-mode-changed failed" + +# pane-exited when a pane's command exits with remain-on-exit off. +$TMUX set -g @x 0 || fail "set @x failed" +$TMUX set-hook -g pane-exited 'set -gF @x "#{hook}:#{hook_pane}"' || + fail "set-hook pane-exited failed" +pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' 'true') || + fail "split-window true failed" +wait_for @x "pane-exited:$pane" +$TMUX set-hook -gu pane-exited || fail "unset pane-exited failed" + +# pane-died when a pane's command exits with remain-on-exit on. +$TMUX set -g remain-on-exit on || fail "set remain-on-exit failed" +$TMUX set -g @d 0 || fail "set @d failed" +$TMUX set-hook -g pane-died 'set -gF @d "#{hook}:#{hook_pane}"' || + fail "set-hook pane-died failed" +pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' 'true') || + fail "split-window remain-on-exit failed" +wait_for @d "pane-died:$pane" +$TMUX set-hook -gu pane-died || fail "unset pane-died failed" +$TMUX killp -t "$pane" || fail "kill-pane failed" +$TMUX set -g remain-on-exit off || fail "reset remain-on-exit failed" + +# pane-title-changed when a pane sets its title. +$TMUX set -g @t 0 || fail "set @t failed" +$TMUX set-hook -g pane-title-changed \ + 'set -gF @t "#{hook}:#{hook_pane}:#{pane_title}"' || + fail "set-hook pane-title-changed failed" +pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \ + 'printf "\033]2;mytitle\007"; sleep 30') || + fail "split-window title failed" +wait_for @t "pane-title-changed:$pane:mytitle" +$TMUX set-hook -gu pane-title-changed || + fail "unset pane-title-changed failed" +$TMUX killp -t "$pane" || fail "kill-pane title failed" + +# client-attached and client-detached using a control client. +$TMUX set -g @a 0 || fail "set @a failed" +$TMUX set-hook -g client-attached 'set -gF @a "#{hook}"' || + fail "set-hook client-attached failed" +$TMUX set-hook -g client-detached 'set -gF @a "#{hook}"' || + fail "set-hook client-detached failed" +mkfifo "$OUT/fifo" || fail "mkfifo failed" +$TMUX -C attach -t main <"$OUT/fifo" >"$OUT/control.out" 2>&1 & +exec 3>"$OUT/fifo" +wait_for @a 'client-attached' +exec 3>&- +wait_for @a 'client-detached' +$TMUX set-hook -gu client-attached || fail "unset client-attached failed" +$TMUX set-hook -gu client-detached || fail "unset client-detached failed" + +exit 0 diff --git a/regress/hooks.sh b/regress/hooks.sh new file mode 100644 index 000000000..e1d75bf67 --- /dev/null +++ b/regress/hooks.sh @@ -0,0 +1,155 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +LANG=C.UTF-8 +export TERM LC_ALL LANG + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT=$(mktemp -d) +TMUX_TMPDIR="$OUT" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" + exit 1 +} + +cleanup() +{ + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" +} +trap cleanup EXIT + +wait_for() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 30 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] && return 0 + i=$((i + 1)) + sleep 0.2 + done + fail "expected $option to be '$expected' but got '$value'" +} + +assert_unchanged() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 10 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] || \ + fail "expected $option to remain '$expected' but got '$value'" + i=$((i + 1)) + sleep 0.2 + done +} + +$TMUX new -d -s one || fail "new-session one failed" + +# A global hook fires and sees the hook formats. +$TMUX set -g @created 0 || fail "set @created failed" +$TMUX set-hook -g session-created \ + 'set -gF @created "#{hook}:#{hook_session_name}"' || + fail "set-hook -g session-created failed" +$TMUX new -d -s two || fail "new-session two failed" +wait_for @created 'session-created:two' + +# Hooks are arrays: an appended command runs after the first. +$TMUX set -g @second 0 || fail "set @second failed" +$TMUX set-hook -ga session-created \ + 'set -gF @second "#{@created}+second"' || + fail "set-hook -ga session-created failed" +$TMUX new -d -s three || fail "new-session three failed" +wait_for @created 'session-created:three' +wait_for @second 'session-created:three+second' + +# show-hooks lists both commands. +shown=$($TMUX show-hooks -g session-created) || + fail "show-hooks -g failed" +[ "$(echo "$shown" | wc -l)" -eq 2 ] || + fail "unexpected show-hooks output: $shown" +echo "$shown" | grep -q '^session-created\[0\]' || + fail "missing first array item: $shown" +echo "$shown" | grep -q '^session-created\[1\]' || + fail "missing second array item: $shown" + +# Unsetting removes the whole hook. +$TMUX set-hook -gu session-created || fail "set-hook -gu failed" +shown=$($TMUX show-hooks -g session-created) || + fail "show-hooks -g after unset failed" +if echo "$shown" | grep -q '\['; then + fail "show-hooks showed removed hook: $shown" +fi +$TMUX set -g @created 0 || fail "reset @created failed" +$TMUX new -d -s four || fail "new-session four failed" +assert_unchanged @created 0 + +# An unknown hook name is rejected. +if $TMUX set-hook -g no-such-hook 'display x' 2>/dev/null; then + fail "unknown hook name was accepted" +fi + +# A session hook only fires for events in that session. +$TMUX set -g @renamed 0 || fail "set @renamed failed" +$TMUX set-hook -t one window-renamed \ + 'set -gF @renamed "#{hook}:#{hook_window_name}"' || + fail "set-hook -t one window-renamed failed" +$TMUX rename-window -t two:0 twoname || fail "rename-window two failed" +assert_unchanged @renamed 0 +$TMUX rename-window -t one:0 onename || fail "rename-window one failed" +wait_for @renamed 'window-renamed:onename' +$TMUX set-hook -u -t one window-renamed || fail "set-hook -u failed" + +# A pane hook only fires for events in that pane. +pane2=$($TMUX splitw -d -t one:0 -P -F '#{pane_id}') || + fail "split-window failed" +$TMUX set -g @mode 0 || fail "set @mode failed" +$TMUX set-hook -p -t "$pane2" pane-mode-changed \ + 'set -gF @mode "#{hook}:#{hook_pane}"' || + fail "set-hook -p pane-mode-changed failed" +$TMUX copy-mode -t one:0.0 || fail "copy-mode pane 0 failed" +assert_unchanged @mode 0 +$TMUX copy-mode -t "$pane2" || fail "copy-mode pane 1 failed" +wait_for @mode "pane-mode-changed:$pane2" +$TMUX send-keys -t "$pane2" -X cancel || fail "cancel failed" +$TMUX set-hook -pu -t "$pane2" pane-mode-changed || + fail "set-hook -pu failed" + +# A window hook only fires for events in that window. +$TMUX set -g @wmode 0 || fail "set @wmode failed" +$TMUX set-hook -w -t two:0 pane-mode-changed \ + 'set -gF @wmode "#{hook}:#{hook_window_name}"' || + fail "set-hook -w pane-mode-changed failed" +$TMUX copy-mode -t three:0.0 || fail "copy-mode three failed" +assert_unchanged @wmode 0 +$TMUX copy-mode -t two:0.0 || fail "copy-mode two failed" +wait_for @wmode 'pane-mode-changed:twoname' +$TMUX set-hook -wu -t two:0 pane-mode-changed || + fail "set-hook -wu failed" + +# Commands run from a hook do not fire hooks again. +$TMUX set -g @renames '' || fail "set @renames failed" +$TMUX set-hook -g window-renamed \ + 'set -gF @renames "#{@renames}x" ; rename-window -t three:0 inner' || + fail "set-hook -g window-renamed failed" +$TMUX rename-window -t three:0 outer || fail "rename-window outer failed" +wait_for @renames 'x' +assert_unchanged @renames 'x' +name=$($TMUX display -pt three:0 '#{window_name}') || + fail "display window_name failed" +[ "$name" = inner ] || fail "expected window name inner but got $name" + +exit 0 diff --git a/regress/if-shell-TERM.sh b/regress/if-shell-TERM.sh index 21846fd10..d1db52f45 100644 --- a/regress/if-shell-TERM.sh +++ b/regress/if-shell-TERM.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/if-shell-error.sh b/regress/if-shell-error.sh index 2eab8f7ca..3b2dac753 100644 --- a/regress/if-shell-error.sh +++ b/regress/if-shell-error.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/if-shell-nested.sh b/regress/if-shell-nested.sh index 434775c43..e10be41c6 100644 --- a/regress/if-shell-nested.sh +++ b/regress/if-shell-nested.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/input-common.inc b/regress/input-common.inc index 96eec1f09..100a7be0f 100644 --- a/regress/input-common.inc +++ b/regress/input-common.inc @@ -2,7 +2,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMP=$(mktemp) EXP=$(mktemp) diff --git a/regress/input-keys.sh b/regress/input-keys.sh index e2fc41f54..cb771d19a 100644 --- a/regress/input-keys.sh +++ b/regress/input-keys.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null sleep 1 $TMUX -f/dev/null new -x20 -y2 -d \; set -g escape-time 0 || exit 1 diff --git a/regress/input-reflow-stress.sh b/regress/input-reflow-stress.sh index aa1bbd717..926e8cdd0 100644 --- a/regress/input-reflow-stress.sh +++ b/regress/input-reflow-stress.sh @@ -8,8 +8,8 @@ export PATH TERM LANG LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Linput-reflow-stress-$$ -f/dev/null" -TMUX2="$TEST_TMUX -Linput-reflow-stress-outer-$$ -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" TMP=$(mktemp "${TMPDIR:-/tmp}/input-reflow-stress.XXXXXX") || exit 1 EXP=$(mktemp "${TMPDIR:-/tmp}/input-reflow-stress.XXXXXX") || exit 1 diff --git a/regress/input-replies.sh b/regress/input-replies.sh index 3435cd943..5fc77f843 100644 --- a/regress/input-replies.sh +++ b/regress/input-replies.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null sleep 0.5 diff --git a/regress/input-requests.sh b/regress/input-requests.sh index 5e7b3781a..094ce2a8a 100644 --- a/regress/input-requests.sh +++ b/regress/input-requests.sh @@ -15,7 +15,8 @@ import tempfile import time tmux = sys.argv[1] -server = [tmux, "-Ltest", "-f/dev/null"] +label = "testA%d" % os.getpid() +server = [tmux, "-L" + label, "-f/dev/null"] def run(*args, check=True): return subprocess.run(server + list(args), check=check, @@ -25,7 +26,7 @@ def attach(): pid, fd = os.forkpty() if pid == 0: os.environ["TERM"] = "xterm-256color" - os.execl(tmux, tmux, "-Ltest", "-f/dev/null", "attach-session", + os.execl(tmux, tmux, "-L" + label, "-f/dev/null", "attach-session", "-t", "requests") os.set_blocking(fd, False) return pid, fd diff --git a/regress/kill-session-process-exit.sh b/regress/kill-session-process-exit.sh index 04617ca2c..405449be2 100644 --- a/regress/kill-session-process-exit.sh +++ b/regress/kill-session-process-exit.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null sleep 1 diff --git a/regress/lifecycle-deferred.sh b/regress/lifecycle-deferred.sh index 2650f1827..d2669ff46 100644 --- a/regress/lifecycle-deferred.sh +++ b/regress/lifecycle-deferred.sh @@ -8,8 +8,8 @@ export TERM LC_ALL LANG [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Llifecycle-deferred -f/dev/null" -TMUX2="$TEST_TMUX -Llifecycle-deferred-outer -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" TMPDIR=$(mktemp -d) IN="$TMPDIR/in" diff --git a/regress/mode-mutation.sh b/regress/mode-mutation.sh index cab8e72c8..96927e995 100644 --- a/regress/mode-mutation.sh +++ b/regress/mode-mutation.sh @@ -8,20 +8,30 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMP=$(mktemp -d) || exit 1 +TMUX_TMPDIR="$TMP" +export TMUX_TMPDIR -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" -cleanup() +cleanup_servers() { $TMUX kill-server 2>/dev/null $TMUX2 kill-server 2>/dev/null sleep 0.5 } +cleanup() +{ + cleanup_servers + rm -rf "$TMP" +} +trap cleanup EXIT + fail() { - echo "$1" + echo "$1" >&2 cleanup exit 1 } @@ -53,7 +63,7 @@ wait_for() { i=0 while [ "$i" -lt 50 ]; do - capture | grep -q "$1" && return 0 + capture | grep -F -q "$1" && return 0 sleep 0.2 i=$((i + 1)) done @@ -95,7 +105,7 @@ start_client() s=$1 cmd=${2:-cat} - cleanup + cleanup_servers $TMUX new-session -d -s "$s" -n main -x 80 -y 24 "$cmd" || \ fail "$s: new-session failed" $TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t $s" || \ @@ -287,7 +297,7 @@ test_copy_mode() assert_alive "copy-mode exit" } -cleanup +cleanup_servers test_choose_tree test_choose_buffer test_choose_client diff --git a/regress/new-session-base-index.sh b/regress/new-session-base-index.sh index bb6dd5945..89fdb4de5 100644 --- a/regress/new-session-base-index.sh +++ b/regress/new-session-base-index.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/new-session-command.sh b/regress/new-session-command.sh index b2fc91db7..520c91e4f 100644 --- a/regress/new-session-command.sh +++ b/regress/new-session-command.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/new-session-environment.sh b/regress/new-session-environment.sh index e5404e67d..fc935687f 100644 --- a/regress/new-session-environment.sh +++ b/regress/new-session-environment.sh @@ -5,7 +5,7 @@ PATH=/bin:/usr/bin [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TERM=$($TMUX start \; show -gv default-terminal) diff --git a/regress/new-session-no-client.sh b/regress/new-session-no-client.sh index 84fddaecb..2f3da5abe 100644 --- a/regress/new-session-no-client.sh +++ b/regress/new-session-no-client.sh @@ -8,7 +8,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/new-session-size.sh b/regress/new-session-size.sh index 029bd6eed..6a35abbbd 100644 --- a/regress/new-session-size.sh +++ b/regress/new-session-size.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/new-window-command.sh b/regress/new-window-command.sh index 183491159..21a2661e8 100644 --- a/regress/new-window-command.sh +++ b/regress/new-window-command.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/options-array.sh b/regress/options-array.sh index 3b18ce977..d9751c94c 100644 --- a/regress/options-array.sh +++ b/regress/options-array.sh @@ -3,12 +3,14 @@ # Tests of array options in the options engine (options_array_* in options.c # and the array handling in cmd-set-option.c / cmd-show-options.c). # -# Array options are indexed by integer. This exercises: setting a whole array -# from a separator-delimited string; per-index set with option[N]; -a append -# (which lands at the next free index); show ordering by ascending index and -# preservation of gaps; per-index unset with -u; show -v of a single index and -# of a missing index; and per-option separators (user-keys splits only on -# comma, update-environment on space or comma). +# Array options are keyed by string, with numeric-looking keys kept compatible +# with the old numeric forms. This exercises: setting a whole array from a +# separator-delimited string; per-key set with option[key]; -a append (which +# lands at the next free numeric key); show ordering by numeric keys first in +# ascending order followed by string keys in strcmp order; preservation of gaps; +# per-key unset with -u; show -v of a single key and of a missing key; and +# per-option separators (user-keys splits only on comma, update-environment on +# space or comma). # # update-environment (session), status-format (session), user-keys (server) # and command-alias (server) are used as representative array options. @@ -20,7 +22,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null check_value() @@ -87,41 +89,59 @@ $TMUX new-session -d -s main -x 80 -y 24 || exit 1 # --- whole-array assignment splits on the separator ----------------------- # # update-environment has the default " ," separator, so a single string value -# is split into consecutive indices starting at 0. +# is split into consecutive numeric keys starting at 0. check_ok set -g update-environment "AAA BBB,CCC" check_array "-g update-environment" "update-environment[0] AAA update-environment[1] BBB update-environment[2] CCC" -# --- -a append goes to the next free index -------------------------------- +# --- -a append goes to the next free numeric key -------------------------- check_ok set -ga update-environment "DDD" check_array "-g update-environment" "update-environment[0] AAA update-environment[1] BBB update-environment[2] CCC update-environment[3] DDD" -# --- per-index unset leaves a gap; show preserves order and gaps ---------- +# --- per-key unset leaves a gap; show preserves order and gaps ------------ check_ok set -gu update-environment[1] check_array "-g update-environment" "update-environment[0] AAA update-environment[2] CCC update-environment[3] DDD" -# show -v of an existing index returns its value; a missing index is empty. +# show -v of an existing key returns its value; a missing key is empty. check_value "-gv update-environment[0]" "AAA" check_value "-gv update-environment[1]" "" +check_ok set -g update-environment[notify] "EEE" +check_ok set -ga update-environment "FFF" +check_array "-g update-environment" "update-environment[0] AAA +update-environment[1] FFF +update-environment[2] CCC +update-environment[3] DDD +update-environment[notify] EEE" -# --- explicit indexed set, including out-of-order and gaps ---------------- +# --- explicit keyed set, including out-of-order and gaps ------------------ # # status-format is a session array; assigning an empty string first clears its -# multi-index default, then set specific indices out of order and confirm show -# sorts by ascending index and keeps the gap at [1]. +# multi-index default, then set specific keys out of order and confirm show +# sorts by ascending numeric key and keeps the gap at [1]. check_ok set -g status-format "" check_array "-g status-format" "status-format" check_ok set -g status-format[5] "five" check_ok set -g status-format[0] "zero" check_ok set -g status-format[2] "two" +check_ok set -g status-format[01] "one" +check_ok set -g status-format[zoom] "zoom" +check_ok set -g status-format[foo-bar] "foo-bar" +check_ok set -g status-format[xterm-256color] "xterm" check_array "-g status-format" "status-format[0] zero +status-format[1] one status-format[2] two -status-format[5] five" +status-format[5] five +status-format[foo-bar] foo-bar +status-format[xterm-256color] xterm +status-format[zoom] zoom" +check_value "-gv status-format[01]" "one" +check_ok set -gu status-format[zoom] +check_value "-gv status-format[zoom]" "" # --- comma-only separator (user-keys) ------------------------------------- # @@ -133,11 +153,15 @@ user-keys[1] "Two Three"' # --- command-type array (a hook) ------------------------------------------ # -# Hooks are command arrays: an indexed value is parsed as a command when set +# Hooks are command arrays: a keyed value is parsed as a command when set # and re-printed from the parsed command list; a syntax error is reported. check_ok set -g alert-bell[0] "display-message hi" check_value "-gv alert-bell[0]" "display-message hi" check_fail "syntax error" set -g alert-bell[0] "if -x {" +check_ok set-hook -g window-renamed[notify] "display-message renamed" +check_value "-gv window-renamed[notify]" "display-message renamed" +check_ok set-hook -gu window-renamed[notify] +check_value "-gv window-renamed[notify]" "" # --- colour-type array ---------------------------------------------------- # @@ -146,11 +170,11 @@ check_ok set -w pane-colours[0] red check_value "-wv pane-colours[0]" "red" check_fail "bad colour: xxxyyy" set -w pane-colours[1] xxxyyy -# --- -o refuses to overwrite an already-set index ------------------------- +# --- -o refuses to overwrite an already-set key --------------------------- check_ok set -g command-alias[9] "x=list-keys" check_fail "already set: command-alias[9]" set -go command-alias[9] "y=list-keys" -# --- non-array option rejects index syntax -------------------------------- +# --- non-array option rejects key syntax ---------------------------------- # # status-left is a plain string; indexing it is an error. check_fail "not an array: status-left[0]" set -g status-left[0] "x" diff --git a/regress/options-scope.sh b/regress/options-scope.sh index bbc7168aa..ac6d62f77 100644 --- a/regress/options-scope.sh +++ b/regress/options-scope.sh @@ -17,7 +17,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null # check_value $args $expected diff --git a/regress/options-values.sh b/regress/options-values.sh index 28acd2b47..75da52e01 100644 --- a/regress/options-values.sh +++ b/regress/options-values.sh @@ -17,7 +17,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null check_value() diff --git a/regress/osc-11colours.sh b/regress/osc-11colours.sh index 2fba76d82..5cdc1be41 100644 --- a/regress/osc-11colours.sh +++ b/regress/osc-11colours.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null $TMUX new -d diff --git a/regress/pane-ops.sh b/regress/pane-ops.sh index 46984bc27..762bea200 100644 --- a/regress/pane-ops.sh +++ b/regress/pane-ops.sh @@ -30,7 +30,7 @@ LC_ALL=C.UTF-8 export TERM LANG LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null # check_ok $cmd... diff --git a/regress/prompt-keys.sh b/regress/prompt-keys.sh index 8c17581b1..6fde51b7b 100644 --- a/regress/prompt-keys.sh +++ b/regress/prompt-keys.sh @@ -7,8 +7,8 @@ LANG=C.UTF-8 export TERM LC_ALL LANG [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -OUT="$TEST_TMUX -Ltest -f/dev/null" # outer (host for the client) -IN="$TEST_TMUX -Ltest2 -f/dev/null" # inner (under test) +OUT="$TEST_TMUX -LtestA$$ -f/dev/null" # outer (host for the client) +IN="$TEST_TMUX -LtestB$$ -f/dev/null" # inner (under test) $OUT kill-server 2>/dev/null $IN kill-server 2>/dev/null diff --git a/regress/prompt-mechanics.sh b/regress/prompt-mechanics.sh index 6f3e5294e..55bd17a69 100644 --- a/regress/prompt-mechanics.sh +++ b/regress/prompt-mechanics.sh @@ -20,8 +20,8 @@ LANG=C.UTF-8 export TERM LC_ALL LANG [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -OUT="$TEST_TMUX -Ltest -f/dev/null" # outer (host for the client) -IN="$TEST_TMUX -Ltest2 -f/dev/null" # inner (under test) +OUT="$TEST_TMUX -LtestA$$ -f/dev/null" # outer (host for the client) +IN="$TEST_TMUX -LtestB$$ -f/dev/null" # inner (under test) $OUT kill-server 2>/dev/null $IN kill-server 2>/dev/null diff --git a/regress/run-shell-output.sh b/regress/run-shell-output.sh index 3b44504c2..926a520c9 100644 --- a/regress/run-shell-output.sh +++ b/regress/run-shell-output.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/screen-redraw-bidi.sh b/regress/screen-redraw-bidi.sh index e4e962950..ee9c2fa16 100644 --- a/regress/screen-redraw-bidi.sh +++ b/regress/screen-redraw-bidi.sh @@ -17,8 +17,8 @@ LC_ALL=C.UTF-8 export TERM LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" RESULTS=screen-redraw-results TMP=$(mktemp) diff --git a/regress/screen-redraw-cache.sh b/regress/screen-redraw-cache.sh index 27c98e3b4..0e0abf9db 100644 --- a/regress/screen-redraw-cache.sh +++ b/regress/screen-redraw-cache.sh @@ -18,8 +18,8 @@ LC_ALL=C.UTF-8 export TERM LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" RESULTS=screen-redraw-results TMP=$(mktemp) diff --git a/regress/screen-redraw-floating.sh b/regress/screen-redraw-floating.sh index 82c36d364..67d213699 100644 --- a/regress/screen-redraw-floating.sh +++ b/regress/screen-redraw-floating.sh @@ -16,8 +16,8 @@ LC_ALL=C.UTF-8 export TERM LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" RESULTS=screen-redraw-results TMP=$(mktemp) diff --git a/regress/screen-redraw-indicators.sh b/regress/screen-redraw-indicators.sh index 60bf652b2..28a1dee6c 100644 --- a/regress/screen-redraw-indicators.sh +++ b/regress/screen-redraw-indicators.sh @@ -18,8 +18,8 @@ LC_ALL=C.UTF-8 export TERM LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" RESULTS=screen-redraw-results TMP=$(mktemp) diff --git a/regress/screen-redraw-outside.sh b/regress/screen-redraw-outside.sh index 53a2a72b1..3eeb94938 100644 --- a/regress/screen-redraw-outside.sh +++ b/regress/screen-redraw-outside.sh @@ -18,8 +18,8 @@ LC_ALL=C.UTF-8 export TERM LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" RESULTS=screen-redraw-results TMP=$(mktemp) diff --git a/regress/screen-redraw-popups.sh b/regress/screen-redraw-popups.sh index 1eb7e5897..8ad3ba2eb 100644 --- a/regress/screen-redraw-popups.sh +++ b/regress/screen-redraw-popups.sh @@ -17,8 +17,8 @@ LC_ALL=C.UTF-8 export TERM LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" RESULTS=screen-redraw-results TMP=$(mktemp) diff --git a/regress/screen-redraw-scrollbars.sh b/regress/screen-redraw-scrollbars.sh index f8298b5e1..914fe542c 100644 --- a/regress/screen-redraw-scrollbars.sh +++ b/regress/screen-redraw-scrollbars.sh @@ -16,8 +16,8 @@ LC_ALL=C.UTF-8 export TERM LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" RESULTS=screen-redraw-results TMP=$(mktemp) diff --git a/regress/screen-redraw-status.sh b/regress/screen-redraw-status.sh index 12ca20bb1..46cb5fcad 100644 --- a/regress/screen-redraw-status.sh +++ b/regress/screen-redraw-status.sh @@ -20,8 +20,8 @@ LC_ALL=C.UTF-8 export TERM LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" RESULTS=screen-redraw-results TMP=$(mktemp) diff --git a/regress/screen-redraw-tiled.sh b/regress/screen-redraw-tiled.sh index 94353f5f8..3b32478f0 100644 --- a/regress/screen-redraw-tiled.sh +++ b/regress/screen-redraw-tiled.sh @@ -24,8 +24,8 @@ LC_ALL=C.UTF-8 export TERM LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" RESULTS=screen-redraw-results TMP=$(mktemp) diff --git a/regress/screen-redraw-window-style.sh b/regress/screen-redraw-window-style.sh index 1d2b2b281..69ac824f2 100644 --- a/regress/screen-redraw-window-style.sh +++ b/regress/screen-redraw-window-style.sh @@ -13,8 +13,8 @@ LC_ALL=C.UTF-8 export TERM LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" RESULTS=screen-redraw-results TMP=$(mktemp) diff --git a/regress/session-group-resize.sh b/regress/session-group-resize.sh index 28e7a85ed..f2b634a43 100644 --- a/regress/session-group-resize.sh +++ b/regress/session-group-resize.sh @@ -10,7 +10,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null TMP1=$(mktemp) diff --git a/regress/session-ops.sh b/regress/session-ops.sh index 4938ee22c..08c5dfbae 100644 --- a/regress/session-ops.sh +++ b/regress/session-ops.sh @@ -27,7 +27,7 @@ LC_ALL=C.UTF-8 export TERM LANG LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null # check_ok $cmd... diff --git a/regress/set-hook-B.sh b/regress/set-hook-B.sh index 0d8eed372..4947af70c 100755 --- a/regress/set-hook-B.sh +++ b/regress/set-hook-B.sh @@ -10,7 +10,7 @@ export TERM LC_ALL LANG OUT=$(mktemp -d) TMUX_TMPDIR="$OUT" export TMUX_TMPDIR -TMUX="$TEST_TMUX -Ltest-hook-B-$$ -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" fail() { diff --git a/regress/set-hook-R.sh b/regress/set-hook-R.sh new file mode 100644 index 000000000..49af68efc --- /dev/null +++ b/regress/set-hook-R.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +LANG=C.UTF-8 +export TERM LC_ALL LANG + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT=$(mktemp -d) +TMUX_TMPDIR="$OUT" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" + exit 1 +} + +cleanup() +{ + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" +} +trap cleanup EXIT + +wait_for() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 30 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] && return 0 + i=$((i + 1)) + sleep 0.2 + done + fail "expected $option to be '$expected' but got '$value'" +} + +$TMUX new -d -s one || fail "new-session one failed" +$TMUX new -d -s two || fail "new-session two failed" + +pane=$($TMUX display -pt two:0.0 '#{pane_id}') || + fail "display-message pane failed" + +$TMUX set -g @seen 0 || fail "set @seen failed" +$TMUX set-hook -g @manual \ + 'set -gF @seen "#{hook}:#{session_name}:#{window_index}:#{pane_id}"' || + fail "set-hook @manual failed" + +[ "$($TMUX show -gqv @seen)" = 0 ] || + fail "hook ran before set-hook -R" + +$TMUX set-hook -g -R -t two:0.0 @manual || + fail "set-hook -R @manual failed" +wait_for @seen "@manual:two:0:$pane" + +exit 0 diff --git a/regress/style-trim.sh b/regress/style-trim.sh index 5b03482d9..f42e0ffff 100644 --- a/regress/style-trim.sh +++ b/regress/style-trim.sh @@ -17,9 +17,9 @@ if command -v bash >/dev/null 2>&1; then fi [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" $TMUX2 kill-server 2>/dev/null $TMUX2 -f/dev/null new -d "$TMUX -f/dev/null new -- $shell" diff --git a/regress/targets-panes.sh b/regress/targets-panes.sh index db353403b..9ea936173 100644 --- a/regress/targets-panes.sh +++ b/regress/targets-panes.sh @@ -27,7 +27,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null # check $target $expected [format] diff --git a/regress/targets.sh b/regress/targets.sh index 346a86736..75ee3dbb8 100644 --- a/regress/targets.sh +++ b/regress/targets.sh @@ -29,7 +29,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null # check $target $expected [format] diff --git a/regress/tty-draw-line.sh b/regress/tty-draw-line.sh index b57de08cc..59331aa0d 100644 --- a/regress/tty-draw-line.sh +++ b/regress/tty-draw-line.sh @@ -10,8 +10,8 @@ LC_ALL=C.UTF-8 export TERM LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" fail() { echo "$*" >&2 diff --git a/regress/tty-keys.sh b/regress/tty-keys.sh index 849f6f336..1b300b995 100644 --- a/regress/tty-keys.sh +++ b/regress/tty-keys.sh @@ -4,9 +4,9 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null -TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" $TMUX2 kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/utf8-test.sh b/regress/utf8-test.sh index b3cf41bd0..1836cc459 100644 --- a/regress/utf8-test.sh +++ b/regress/utf8-test.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX kill-server 2>/dev/null diff --git a/regress/window-ops.sh b/regress/window-ops.sh index dde7fc957..bdb1b5fb9 100644 --- a/regress/window-ops.sh +++ b/regress/window-ops.sh @@ -30,7 +30,7 @@ LC_ALL=C.UTF-8 export TERM LANG LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest -f/dev/null" +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null # check_ok $cmd... diff --git a/resize.c b/resize.c index 6cbc938dd..19a940d61 100644 --- a/resize.c +++ b/resize.c @@ -46,13 +46,13 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) layout_resize(w, sx, sy); /* Resize the window, it can be no smaller than the layout. */ - if (sx < w->layout_root->sx) - sx = w->layout_root->sx; - if (sy < w->layout_root->sy) - sy = w->layout_root->sy; + if (sx < w->layout_root->g.sx) + sx = w->layout_root->g.sx; + if (sy < w->layout_root->g.sy) + sy = w->layout_root->g.sy; window_resize(w, sx, sy, xpixel, ypixel); log_debug("%s: @%u resized to %ux%u; layout %ux%u", __func__, w->id, - sx, sy, w->layout_root->sx, w->layout_root->sy); + sx, sy, w->layout_root->g.sx, w->layout_root->g.sy); /* Restore the window zoom state. */ if (zoomed) diff --git a/screen-write.c b/screen-write.c index e70d9390b..977f86829 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2401,6 +2401,10 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, if (wp != NULL && (wp->flags & (PANE_REDRAW|PANE_DROP))) goto discard; if (s->mode & MODE_SYNC) { + if (ctx->scrolled != 0) { + screen_write_should_draw_lines(ctx, s->rupper, + s->rlower + 1 - s->rupper); + } for (y = 0; y < screen_size_y(s); y++) { cl = &s->write_list[y]; if (!TAILQ_EMPTY(&cl->items)) diff --git a/spawn.c b/spawn.c index 3ae9153b8..e0cac4bc1 100644 --- a/spawn.c +++ b/spawn.c @@ -632,13 +632,13 @@ spawn_editor(struct client *c, const char *buf, size_t len, struct window *w = wl->window; struct window_pane *wp; struct layout_cell *lc; + struct layout_geometry lg; struct environ *env; FILE *f; char *cmd, *cause = NULL; char path[] = _PATH_TMP "tmux.XXXXXXXX"; const char *editor; int fd; - u_int px, py, sx, sy; editor = options_get_string(global_options, "editor"); fd = mkstemp(path); @@ -662,12 +662,12 @@ spawn_editor(struct client *c, const char *buf, size_t len, es->cb = cb; es->arg = arg; - sx = w->sx * 9 / 10; - sy = w->sy * 9 / 10; - px = w->sx / 2 - sx / 2; - py = w->sy / 2 - sy / 2; + lg.sx = w->sx * 9 / 10; + lg.sy = w->sy * 9 / 10; + lg.xoff = w->sx / 2 - lg.sx / 2; + lg.yoff = w->sy / 2 - lg.sy / 2; window_push_zoom(w, 1, 0); - lc = layout_floating_pane(w, NULL, sx, sy, px, py); + lc = layout_floating_pane(w, NULL, &lg); if (lc == NULL) { spawn_editor_free(es); return (NULL); diff --git a/status.c b/status.c index 0d9fc3e4e..8f7c209e1 100644 --- a/status.c +++ b/status.c @@ -274,7 +274,7 @@ status_redraw(struct client *c) for (i = 0; i < lines; i++) { screen_write_cursormove(&ctx, 0, i, 0); - ov = options_array_get(o, i); + ov = options_array_getv(o, "%u", i); if (ov == NULL) { for (n = 0; n < width; n++) screen_write_putc(&ctx, &gc, ' '); diff --git a/tmux.1 b/tmux.1 index 117c71644..fa65e38cb 100644 --- a/tmux.1 +++ b/tmux.1 @@ -365,13 +365,13 @@ Toggle zoom state of the current pane. .It Tab Choose a new window and session by fuzzy matching. .It { -Move floating pane to top-left corner. +Swap the current pane with the previous pane. .It } -Move floating pane to top-right corner. -.It M-{ -Move floating pane to bottom-left corner. -.It M-} -Move floating pane to bottom-right corner. +Swap the current pane with the next pane. +.It g 1, 2, 3, 4 +.It g Up, Down, Left, Right +Move floating pane to corners or edges. +Meta resizes the pane as well. .It \[ti] Show previous messages from .Nm , @@ -4634,6 +4634,10 @@ If .Fl g is given, the global session or window option is set. .Pp +Array options may be set by giving a key in square brackets after the option +name, for example +.Ql command-alias[zoom] . +.Pp .Fl F expands formats in the option value. The @@ -4745,7 +4749,7 @@ it is replaced with .Ar value . For example, after: .Pp -.Dl set \-s command\-alias[100] zoom=\[aq]resize\-pane \-Z\[aq] +.Dl set \-s command\-alias[zoom] zoom=\[aq]resize\-pane \-Z\[aq] .Pp Using: .Pp @@ -6346,11 +6350,11 @@ or .Fl H . The following two commands are equivalent: .Bd -literal -offset indent. -set\-hook \-g pane\-mode\-changed[42] \[aq]set \-g status\-left\-style bg=red\[aq] -set\-option \-g pane\-mode\-changed[42] \[aq]set \-g status\-left\-style bg=red\[aq] +set\-hook \-g pane\-mode\-changed[style] \[aq]display\-message changed\[aq] +set\-option \-g pane\-mode\-changed[style] \[aq]display\-message changed\[aq] .Ed .Pp -Setting a hook without specifying an array index clears the hook and sets the +Setting a hook without specifying an array key clears the hook and sets the first member of the array. .Pp A command's after @@ -7875,10 +7879,7 @@ This affects what completions are offered when is pressed. Available types are: .Ql command , -.Ql search , -.Ql target -and -.Ql window\-target . +.Ql search . .Pp The following keys have a special meaning in the command prompt, depending on the value of the @@ -8013,6 +8014,7 @@ Both may be a row or column number, or one of the following special values: .It Li "R" Ta Fl x Ta "The right side of the terminal" .It Li "P" Ta "Both" Ta "The bottom left of the pane" .It Li "M" Ta "Both" Ta "The mouse position" +.It Li "L" Ta "Both" Ta "The position of the last menu" .It Li "W" Ta "Both" Ta "The window position on the status line" .It Li "S" Ta Fl y Ta "The line above or below the status line" .El @@ -8023,6 +8025,8 @@ Or a format, which is expanded including the following additional variables: .It Li "popup_centre_x" Ta "Centered in the client" .It Li "popup_centre_y" Ta "Centered in the client" .It Li "popup_height" Ta "Height of menu or popup" +.It Li "popup_last_x" Ta "Left of the last menu" +.It Li "popup_last_y" Ta "Bottom of the last menu" .It Li "popup_mouse_bottom" Ta "Bottom of at the mouse" .It Li "popup_mouse_centre_x" Ta "Horizontal centre at the mouse" .It Li "popup_mouse_centre_y" Ta "Vertical centre at the mouse" diff --git a/tmux.h b/tmux.h index f93fbaf78..b1504bace 100644 --- a/tmux.h +++ b/tmux.h @@ -1265,6 +1265,7 @@ struct visible_ranges { /* Child window structure. */ struct window_pane { u_int id; + int references; u_int active_point; struct window *window; @@ -1296,6 +1297,7 @@ struct window_pane { #define PANE_THEMECHANGED 0x2000 #define PANE_UNSEENCHANGES 0x4000 #define PANE_REDRAWSCROLLBAR 0x8000 +#define PANE_DESTROYED 0x10000 bitstr_t *sync_dirty; u_int sync_dirty_size; @@ -1503,38 +1505,37 @@ enum layout_type { LAYOUT_WINDOWPANE }; +/* Layout cell sizes and position. */ +struct layout_geometry { + u_int sx; + u_int sy; + int xoff; + int yoff; +}; + /* Layout cells queue. */ TAILQ_HEAD(layout_cells, layout_cell); /* Layout cell. */ struct layout_cell { - enum layout_type type; + enum layout_type type; + int flags; #define LAYOUT_CELL_FLOATING 0x1 #define LAYOUT_CELL_HIDDEN 0x2 /* hidden in a serialized layout */ #define LAYOUT_CELL_ZOOMED 0x4 /* only while parsing a layout */ #define LAYOUT_CELL_X_RELATIVE 0x8 /* only while parsing a layout */ #define LAYOUT_CELL_Y_RELATIVE 0x10 /* only while parsing a layout */ - int flags; - u_int z_index; /* only while parsing a layout */ - u_int pane_id; /* only while parsing a layout */ + u_int z_index; /* only while parsing a layout */ + u_int pane_id; /* only while parsing a layout */ - struct layout_cell *parent; + struct layout_cell *parent; - u_int sx; - u_int sy; + struct layout_geometry g; + struct layout_geometry fg; /* saved floating pane */ - int xoff; - int yoff; - - u_int saved_sx; - u_int saved_sy; - - int saved_xoff; - int saved_yoff; - - struct window_pane *wp; - struct layout_cells cells; + struct window_pane *wp; + struct layout_cells cells; TAILQ_ENTRY(layout_cell) entry; }; @@ -2314,6 +2315,9 @@ struct client { void *overlay_data; struct event overlay_timer; + u_int menu_last_px; + u_int menu_last_py; + struct client_files files; u_int source_file_depth; @@ -2698,25 +2702,27 @@ const struct options_table_entry *options_table_entry(struct options_entry *); struct options_entry *options_get_only(struct options *, const char *); struct options_entry *options_get(struct options *, const char *); void options_array_clear(struct options_entry *); -union options_value *options_array_get(struct options_entry *, u_int); -int options_array_set(struct options_entry *, u_int, const char *, - int, char **); +union options_value *options_array_get(struct options_entry *, const char *); +union options_value * printflike(2, 3) options_array_getv( + struct options_entry *, const char *, ...); +int options_array_set(struct options_entry *, const char *, + const char *, int, char **); int options_array_assign(struct options_entry *, const char *, char **); struct options_array_item *options_array_first(struct options_entry *); struct options_array_item *options_array_next(struct options_array_item *); -u_int options_array_item_index(struct options_array_item *); +const char *options_array_item_key(struct options_array_item *); union options_value *options_array_item_value(struct options_array_item *); int options_is_array(struct options_entry *); int options_is_string(struct options_entry *); -char *options_to_string(struct options_entry *, int, int); -char *options_parse(const char *, int *); -struct options_entry *options_parse_get(struct options *, const char *, int *, - int); +char *options_to_string(struct options_entry *, const char *, int); +char *options_parse(const char *, char **); +struct options_entry *options_parse_get(struct options *, const char *, + char **, int); const struct options_table_entry *options_search(const char *); -char *options_match(const char *, int *, int *); -struct options_entry *options_match_get(struct options *, const char *, int *, - int, int *); +char *options_match(const char *, char **, int *); +struct options_entry *options_match_get(struct options *, const char *, + char **, int, int *); const char *options_get_string(struct options *, const char *); long long options_get_number(struct options *, const char *); struct cmd_list *options_get_command(struct options *, const char *); @@ -2739,7 +2745,7 @@ int options_from_string(struct options *, int options_find_choice(const struct options_table_entry *, const char *, char **); void options_push_changes(const char *); -int options_remove_or_default(struct options_entry *, int, +int options_remove_or_default(struct options_entry *, const char *, char **); /* options-table.c */ @@ -3659,6 +3665,8 @@ void window_pane_stack_remove(struct window_panes *, void window_set_name(struct window *, const char *, int); void window_add_ref(struct window *, const char *); void window_remove_ref(struct window *, const char *); +void window_pane_add_ref(struct window_pane *, const char *); +void window_pane_remove_ref(struct window_pane *, const char *); void winlink_clear_flags(struct winlink *); int winlink_shuffle_up(struct session *, struct winlink *, int); int window_pane_start_input(struct window_pane *, @@ -3711,7 +3719,6 @@ struct visible_ranges *window_visible_ranges(struct window_pane *, int, int, /* layout.c */ u_int layout_count_cells(struct layout_cell *); -int layout_has_tiled(struct layout_cell *); struct layout_cell *layout_create_cell(struct layout_cell *); void layout_free_cell(struct layout_cell *, int); void layout_print_cell(struct layout_cell *, const char *, u_int); @@ -3720,11 +3727,12 @@ void layout_destroy_cell(struct window *, struct layout_cell *, void layout_resize_layout(struct window *, struct layout_cell *, enum layout_type, int, int); struct layout_cell *layout_search_by_border(struct layout_cell *, u_int, u_int); -void layout_set_size(struct layout_cell *, u_int, u_int, int, int); +void layout_set_size(struct layout_cell *, u_int, u_int, int, int); void layout_make_leaf(struct layout_cell *, struct window_pane *); void layout_make_node(struct layout_cell *, enum layout_type); void layout_fix_zindexes(struct window *, struct layout_cell *); int layout_cell_is_tiled(struct layout_cell *); +int layout_has_tiled(struct layout_cell *); void layout_fix_offsets(struct window *); void layout_fix_panes(struct window *, struct window_pane *); void layout_resize_adjust(struct window *, struct layout_cell *, @@ -3754,7 +3762,7 @@ struct layout_cell *layout_replace_with_node(struct window *, struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type, int, int); struct layout_cell *layout_floating_pane(struct window *, struct window_pane *, - u_int, u_int, int, int); + struct layout_geometry *); void layout_close_pane(struct window_pane *); int layout_spread_cell(struct window *, struct layout_cell *); void layout_spread_out(struct window_pane *); @@ -3762,17 +3770,17 @@ struct layout_cell *layout_get_tiled_cell(struct cmdq_item *, struct args *, struct window *, struct window_pane *, int, char **); struct layout_cell *layout_get_floating_cell(struct cmdq_item *, struct args *, enum pane_lines, struct window *, struct window_pane *, - char **cause); + char **); int layout_floating_args_parse(struct cmdq_item *, struct args *, - enum pane_lines, struct window *, u_int *, u_int *, int *, - int *, char **); + enum pane_lines, struct window *, struct layout_geometry *, + char **); int layout_remove_tile(struct window *, struct layout_cell *); int layout_insert_tile(struct window *, struct layout_cell *); /* layout-custom.c */ -struct layout_prepared; char *layout_dump(struct window *, struct layout_cell *); char *layout_dump_legacy(struct layout_cell *); +struct layout_prepared; struct layout_prepared *layout_prepare(struct window *, const char *, char **); void layout_free_prepared(struct layout_prepared *); void layout_apply_prepared(struct window *, struct layout_prepared *); diff --git a/tty-keys.c b/tty-keys.c index 209bbeeab..d6a2610ea 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -495,7 +495,6 @@ tty_keys_build(struct tty *tty) u_int i, j; const char *s; struct options_entry *o; - struct options_array_item *a; union options_value *ov; char copy[16]; key_code key; @@ -532,12 +531,10 @@ tty_keys_build(struct tty *tty) o = options_get(global_options, "user-keys"); if (o != NULL) { - a = options_array_first(o); - while (a != NULL) { - i = options_array_item_index(a); - ov = options_array_item_value(a); - tty_keys_add(tty, ov->string, KEYC_USER + i); - a = options_array_next(a); + for (i = 0; i <= KEYC_NUSER; i++) { + ov = options_array_getv(o, "%u", i); + if (ov != NULL) + tty_keys_add(tty, ov->string, KEYC_USER + i); } } } diff --git a/window-customize.c b/window-customize.c index 32caae9a3..09da37f2e 100644 --- a/window-customize.c +++ b/window-customize.c @@ -82,15 +82,15 @@ enum window_customize_change { }; struct window_customize_itemdata { - struct window_customize_modedata *data; - enum window_customize_scope scope; + struct window_customize_modedata *data; + enum window_customize_scope scope; - char *table; - key_code key; + char *table; + key_code key; - struct options *oo; - char *name; - int idx; + struct options *oo; + char *name; + char *array_key; }; struct window_customize_modedata { @@ -111,15 +111,17 @@ struct window_customize_modedata { }; static uint64_t -window_customize_get_tag(struct options_entry *o, int idx, +window_customize_get_tag(struct options_entry *o, struct options_array_item *a, const struct options_table_entry *oe) { uint64_t offset; + if (a != NULL) + return ((uint64_t)(uintptr_t)a); if (oe == NULL) return ((uint64_t)o); offset = ((char *)oe - (char *)options_table) / sizeof *options_table; - return ((2ULL << 62)|(offset << 32)|((idx + 1) << 1)|1); + return ((2ULL << 62)|(offset << 32)|1); } static struct options * @@ -224,6 +226,7 @@ window_customize_free_item(struct window_customize_itemdata *item) { free(item->table); free(item->name); + free(item->array_key); free(item); } @@ -237,26 +240,26 @@ window_customize_build_array(struct window_customize_modedata *data, struct window_customize_itemdata *item; struct options_array_item *ai; char *name, *value, *text; - u_int idx; uint64_t tag; + const char *array_key; ai = options_array_first(o); while (ai != NULL) { - idx = options_array_item_index(ai); + array_key = options_array_item_key(ai); - xasprintf(&name, "%s[%u]", options_name(o), idx); + xasprintf(&name, "%s[%s]", options_name(o), array_key); format_add(ft, "option_name", "%s", name); - value = options_to_string(o, idx, 0); + value = options_to_string(o, array_key, 0); format_add(ft, "option_value", "%s", value); item = window_customize_add_item(data); item->scope = scope; item->oo = oo; item->name = xstrdup(options_name(o)); - item->idx = idx; + item->array_key = xstrdup(array_key); text = format_expand(ft, data->format); - tag = window_customize_get_tag(o, idx, oe); + tag = window_customize_get_tag(o, ai, oe); mode_tree_add(data->data, top, item, tag, name, text, -1); free(text); @@ -307,7 +310,7 @@ window_customize_build_option(struct window_customize_modedata *data, format_add(ft, "option_unit", "%s", ""); if (!array) { - value = options_to_string(o, -1, 0); + value = options_to_string(o, NULL, 0); format_add(ft, "option_value", "%s", value); free(value); } @@ -324,13 +327,12 @@ window_customize_build_option(struct window_customize_modedata *data, item->oo = oo; item->scope = scope; item->name = xstrdup(name); - item->idx = -1; if (array) text = NULL; else text = format_expand(ft, data->format); - tag = window_customize_get_tag(o, -1, oe); + tag = window_customize_get_tag(o, NULL, oe); top = mode_tree_add(data->data, top, item, tag, name, text, 0); free(text); @@ -483,7 +485,6 @@ window_customize_build_keys(struct window_customize_modedata *data, item->table = xstrdup(kt->name); item->key = bd->key; item->name = xstrdup(key_string_lookup_key(item->key, 0)); - item->idx = -1; expanded = format_expand(ft, data->format); child = mode_tree_add(data->data, top, item, (uint64_t)bd, @@ -653,12 +654,12 @@ window_customize_draw_option(struct window_customize_modedata *data, { struct screen *s = ctx->s; u_int cx = s->cx, cy = s->cy; - int idx; struct options_entry *o, *parent; struct options *go, *wo; const struct options_table_entry *oe; struct grid_cell gc; const char **choice, *text, *name; + const char *array_key; const char *space = "", *unit = ""; char *value = NULL, *expanded; char *default_value = NULL; @@ -669,7 +670,7 @@ window_customize_draw_option(struct window_customize_modedata *data, if (!window_customize_check_item(data, item, &fs)) return; name = item->name; - idx = item->idx; + array_key = item->array_key; o = options_get(item->oo, name); if (o == NULL) @@ -708,25 +709,25 @@ window_customize_draw_option(struct window_customize_modedata *data, &grid_default_cell, "This is a %s option.", text)) goto out; if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { - if (idx != -1) { + if (array_key != NULL) { if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &grid_default_cell, - "This is an array option, index %u.", idx)) + "This is an array option, key %s.", array_key)) goto out; } else { if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &grid_default_cell, "This is an array option.")) goto out; } - if (idx == -1) + if (array_key == NULL) goto out; } screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ if (s->cy >= cy + sy - 1) goto out; - value = options_to_string(o, idx, 0); - if (oe != NULL && idx == -1) { + value = options_to_string(o, array_key, 0); + if (oe != NULL && array_key == NULL) { default_value = options_default_to_string(oe); if (strcmp(default_value, value) == 0) { free(default_value); @@ -820,7 +821,7 @@ window_customize_draw_option(struct window_customize_modedata *data, if (wo != NULL && options_owner(o) != wo) { parent = options_get_only(wo, name); if (parent != NULL) { - value = options_to_string(parent, -1 , 0); + value = options_to_string(parent, NULL, 0); if (!screen_write_text(ctx, s->cx, sx, sy - (s->cy - cy), 0, &grid_default_cell, "Window value (from window %u): %s%s%s", fs.wl->idx, @@ -831,7 +832,7 @@ window_customize_draw_option(struct window_customize_modedata *data, if (go != NULL && options_owner(o) != go) { parent = options_get_only(go, name); if (parent != NULL) { - value = options_to_string(parent, -1 , 0); + value = options_to_string(parent, NULL, 0); if (!screen_write_text(ctx, s->cx, sx, sy - (s->cy - cy), 0, &grid_default_cell, "Global value: %s%s%s", value, space, unit)) @@ -896,6 +897,8 @@ static const char* window_customize_help_lines[] = { "#[fg=themelightgrey]" " U #[#{E:tree-mode-border-style},acs]x#[default] Unset tagged %1s", "#[fg=themelightgrey]" + " a #[#{E:tree-mode-border-style},acs]x#[default] Change array key", + "#[fg=themelightgrey]" " f #[#{E:tree-mode-border-style},acs]x#[default] Enter a filter", "#[fg=themelightgrey]" " v #[#{E:tree-mode-border-style},acs]x#[default] Toggle information", @@ -1007,8 +1010,10 @@ window_customize_set_option_callback(struct client *c, void *itemdata, const struct options_table_entry *oe; struct options *oo = item->oo; const char *name = item->name; + const char *array_key = item->array_key; char *cause; - int idx = item->idx; + u_int idx; + char keybuf[32]; if (s == NULL || *s == '\0' || data->dead) return (PROMPT_CLOSE); @@ -1020,13 +1025,15 @@ window_customize_set_option_callback(struct client *c, void *itemdata, oe = options_table_entry(o); if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { - if (idx == -1) { + if (array_key == NULL) { for (idx = 0; idx < INT_MAX; idx++) { - if (options_array_get(o, idx) == NULL) + if (options_array_getv(o, "%u", idx) == NULL) break; } + xsnprintf(keybuf, sizeof keybuf, "%u", idx); + array_key = keybuf; } - if (options_array_set(o, idx, s, 0, &cause) != 0) + if (options_array_set(o, array_key, s, 0, &cause) != 0) goto fail; } else { if (options_from_string(oo, oe, name, s, 0, &cause) != 0) @@ -1056,10 +1063,11 @@ window_customize_set_option(struct client *c, const struct options_table_entry *oe; struct options *oo; struct window_customize_itemdata *new_item; - int flag, idx = item->idx; + int flag; enum window_customize_scope scope = WINDOW_CUSTOMIZE_NONE; u_int choice; const char *name = item->name, *space = ""; + const char *array_key = item->array_key; char *prompt, *value, *text; struct cmd_find_state fs; @@ -1142,25 +1150,26 @@ window_customize_set_option(struct client *c, else if (scope != WINDOW_CUSTOMIZE_SERVER) space = ", global"; if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { - if (idx == -1) { + if (array_key == NULL) { xasprintf(&prompt, "(%s[+]%s%s) ", name, space, text); } else { - xasprintf(&prompt, "(%s[%d]%s%s) ", name, idx, - space, text); + xasprintf(&prompt, "(%s[%s]%s%s) ", name, + array_key, space, text); } } else xasprintf(&prompt, "(%s%s%s) ", name, space, text); free(text); - value = options_to_string(o, idx, 0); + value = options_to_string(o, array_key, 0); new_item = xcalloc(1, sizeof *new_item); new_item->data = data; new_item->scope = scope; new_item->oo = oo; new_item->name = xstrdup(name); - new_item->idx = idx; + if (array_key != NULL) + new_item->array_key = xstrdup(array_key); data->references++; mode_tree_set_prompt(data->data, c, prompt, value, @@ -1173,6 +1182,84 @@ window_customize_set_option(struct client *c, } } +static enum prompt_result +window_customize_set_array_key_callback(struct client *c, void *itemdata, + const char *s, __unused enum prompt_key_result key) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data; + struct options_entry *o; + const char *name, *array_key; + char *value, *cause; + + if (item == NULL) + return (PROMPT_CLOSE); + data = item->data; + if (s == NULL || *s == '\0' || data->dead) + return (PROMPT_CLOSE); + name = item->name; + array_key = item->array_key; + if (array_key == NULL || !window_customize_check_item(data, item, NULL)) + return (PROMPT_CLOSE); + + o = options_get(item->oo, name); + if (o == NULL) + return (PROMPT_CLOSE); + if (options_array_get(o, s) != NULL) + return (PROMPT_CLOSE); + + value = options_to_string(o, array_key, 0); + if (options_array_set(o, s, value, 0, &cause) != 0) + goto fail; + free(value); + + options_array_set(o, array_key, NULL, 0, NULL); + options_push_changes(item->name); + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (PROMPT_CLOSE); + +fail: + free(value); + *cause = toupper((u_char)*cause); + status_message_set(c, -1, 1, 0, 0, "%s", cause); + free(cause); + return (PROMPT_CLOSE); +} + +static void +window_customize_set_array_key(struct client *c, + struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct window_customize_itemdata *new_item; + char *prompt; + + if (item == NULL || + item->array_key == NULL || + !window_customize_check_item(data, item, NULL)) + return; + + xasprintf(&prompt, "(%s[%s]) ", item->name, item->array_key); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->scope = item->scope; + new_item->oo = item->oo; + new_item->name = xstrdup(item->name); + new_item->array_key = xstrdup(item->array_key); + + data->references++; + mode_tree_set_prompt(data->data, c, prompt, item->array_key, + PROMPT_TYPE_COMMAND, PROMPT_NOFORMAT, + window_customize_set_array_key_callback, + window_customize_free_item_callback, new_item); + + free(prompt); +} + static void window_customize_unset_option(struct window_customize_modedata *data, struct window_customize_itemdata *item) @@ -1185,9 +1272,10 @@ window_customize_unset_option(struct window_customize_modedata *data, o = options_get(item->oo, item->name); if (o == NULL) return; - if (item->idx != -1 && item == mode_tree_get_current(data->data)) + if (item->array_key != NULL && + item == mode_tree_get_current(data->data)) mode_tree_up(data->data, 0); - options_remove_or_default(o, item->idx, NULL); + options_remove_or_default(o, item->array_key, NULL); } static void @@ -1199,14 +1287,14 @@ window_customize_reset_option(struct window_customize_modedata *data, if (item == NULL || !window_customize_check_item(data, item, NULL)) return; - if (item->idx != -1) + if (item->array_key != NULL) return; oo = item->oo; while (oo != NULL) { o = options_get_only(item->oo, item->name); if (o != NULL) - options_remove_or_default(o, -1, NULL); + options_remove_or_default(o, NULL, NULL); oo = options_get_parent(oo); } } @@ -1452,7 +1540,7 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, struct window_pane *wp = wme->wp; struct window_customize_modedata *data = wme->data; struct window_customize_itemdata *item, *new_item; - int finished, idx; + int finished; char *prompt; u_int tagged; @@ -1462,6 +1550,11 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, item = new_item; switch (key) { + case 'a': + if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY) + break; + window_customize_set_array_key(c, data, item); + break; case '\r': case 's': if (item == NULL) @@ -1490,7 +1583,7 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, mode_tree_build(data->data); break; case 'd': - if (item == NULL || item->idx != -1) + if (item == NULL || item->array_key != NULL) break; xasprintf(&prompt, "Reset %s to default? ", item->name); data->references++; @@ -1519,10 +1612,10 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, case 'u': if (item == NULL) break; - idx = item->idx; - if (idx != -1) - xasprintf(&prompt, "Unset %s[%d]? ", item->name, idx); - else + if (item->array_key != NULL) { + xasprintf(&prompt, "Unset %s[%s]? ", item->name, + item->array_key); + } else xasprintf(&prompt, "Unset %s? ", item->name); data->references++; data->change = WINDOW_CUSTOMIZE_UNSET; diff --git a/window.c b/window.c index 435d15bbb..acdcb2efa 100644 --- a/window.c +++ b/window.c @@ -71,6 +71,7 @@ struct window_pane_input_data { static struct window_pane *window_pane_create(struct window *, u_int, u_int, u_int); static void window_pane_destroy(struct window_pane *); +static void window_pane_free(struct window_pane *); static void window_pane_scrollbar_timer(int, short, void *); static void window_pane_full_size_offset(struct window_pane *wp, int *xoff, int *yoff, u_int *sx, u_int *sy); @@ -331,6 +332,8 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel) w->ypixel = ypixel; w->options = options_create(global_w_options); + w->sb = options_get_number(w->options, "pane-scrollbars"); + w->sb_pos = options_get_number(w->options, "pane-scrollbars-position"); w->references = 0; TAILQ_INIT(&w->winlinks); @@ -419,6 +422,25 @@ window_remove_ref(struct window *w, const char *from) window_destroy(w); } +void +window_pane_add_ref(struct window_pane *wp, const char *from) +{ + wp->references++; + log_debug("%s: %%%u %s, now %d", __func__, wp->id, from, + wp->references); +} + +void +window_pane_remove_ref(struct window_pane *wp, const char *from) +{ + wp->references--; + log_debug("%s: %%%u %s, now %d", __func__, wp->id, from, + wp->references); + + if (wp->references == 0) + window_pane_free(wp); +} + void window_set_name(struct window *w, const char *new_name, int untrusted) { @@ -1080,6 +1102,7 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) char host[HOST_NAME_MAX + 1]; wp = xcalloc(1, sizeof *wp); + wp->references = 1; wp->window = w; wp->options = options_create(w->options); wp->flags = PANE_STYLECHANGED; @@ -1208,10 +1231,11 @@ window_pane_destroy(struct window_pane *wp) spawn_editor_finish(wp); window_pane_clear_prompt(wp); + RB_REMOVE(window_pane_tree, &all_window_panes, wp); + wp->flags |= PANE_DESTROYED; window_pane_free_modes(wp); screen_write_clear_dirty(wp); - free(wp->searchstr); if (wp->fd != -1) { #ifdef HAVE_UTEMPTER @@ -1219,18 +1243,20 @@ window_pane_destroy(struct window_pane *wp) kill(getpid(), SIGCHLD); #endif bufferevent_free(wp->event); + wp->event = NULL; close(wp->fd); + wp->fd = -1; } - if (wp->ictx != NULL) + if (wp->ictx != NULL) { input_free(wp->ictx); - - screen_free(&wp->status_screen); - - screen_free(&wp->base); + wp->ictx = NULL; + } if (wp->pipe_fd != -1) { bufferevent_free(wp->pipe_event); + wp->pipe_event = NULL; close(wp->pipe_fd); + wp->pipe_fd = -1; } if (event_initialized(&wp->resize_timer)) @@ -1241,7 +1267,18 @@ window_pane_destroy(struct window_pane *wp) event_del(&wp->sb_auto_timer); window_pane_clear_resizes(wp, NULL); - RB_REMOVE(window_pane_tree, &all_window_panes, wp); + window_pane_remove_ref(wp, __func__); +} + +static void +window_pane_free(struct window_pane *wp) +{ + log_debug("pane %%%u freed (%d references)", wp->id, wp->references); + + free(wp->searchstr); + + screen_free(&wp->status_screen); + screen_free(&wp->base); options_free(wp->options); free((void *)wp->cwd);