From 968f4396729ef432b69ba06ea92135acdc471b65 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sat, 4 Oct 2025 09:15:51 +0100 Subject: [PATCH 001/167] Initial commit. Add new new-floating-window command to create panes without a layout_cell indicating they are floating panes. --- cmd-split-window.c | 153 +++++++++++++++++++++++++++++++++++++++++++++ cmd.c | 2 + layout.c | 3 + screen-write.c | 3 +- spawn.c | 9 ++- tmux.h | 6 ++ 6 files changed, 174 insertions(+), 2 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 3d366a0049..515bd78940 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -34,6 +34,8 @@ static enum cmd_retval cmd_split_window_exec(struct cmd *, struct cmdq_item *); +static enum cmd_retval cmd_new_floating_window_exec(struct cmd *, + struct cmdq_item *); const struct cmd_entry cmd_split_window_entry = { .name = "split-window", @@ -50,6 +52,22 @@ const struct cmd_entry cmd_split_window_entry = { .exec = cmd_split_window_exec }; +const struct cmd_entry cmd_new_floating_window_entry = { + .name = "new-floating-window", + .alias = "floatw", + + .args = { "bc:de:fF:hIl:p:Pt:vZ", 0, -1, NULL }, + .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " + "[-F format] [-l size] " CMD_TARGET_PANE_USAGE + " [shell-command [argument ...]]", + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = 0, + .exec = cmd_new_floating_window_exec +}; + + static enum cmd_retval cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) { @@ -197,3 +215,138 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_WAIT); return (CMD_RETURN_NORMAL); } + +static enum cmd_retval +cmd_new_floating_window_exec(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct spawn_context sc = { 0 }; + struct client *tc = cmdq_get_target_client(item); + struct session *s = target->s; + struct winlink *wl = target->wl; + struct window *w = wl->window; + struct window_pane *wp = target->wp, *new_wp; + struct cmd_find_state fs; + int flags, input; + const char *template; + char *cause = NULL, *cp; + struct args_value *av; + u_int count = args_count(args); + u_int sx, sy, pct; + + if (args_has(args, 'f')) { + sx = w->sx; + sy = w->sy; + } else { + if (args_has(args, 'l')) { + sx = args_percentage_and_expand(args, 'l', 0, INT_MAX, w->sx, + item, &cause); + sy = args_percentage_and_expand(args, 'l', 0, INT_MAX, w->sy, + item, &cause); + } else if (args_has(args, 'p')) { + pct = args_strtonum_and_expand(args, 'p', 0, 100, item, + &cause); + if (cause == NULL) { + sx = w->sx * pct / 100; + sy = w->sy * pct / 100; + } + } else { + sx = w->sx / 2; + sy = w->sy / 2; + } + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + sc.xoff = 0; + sc.yoff = 0; + sc.sx = sx; + sc.sy = sy; + + input = (args_has(args, 'I') && count == 0); + + flags = SPAWN_FLOATING; + if (args_has(args, 'b')) + flags |= SPAWN_BEFORE; + if (args_has(args, 'f')) + flags |= SPAWN_FULLSIZE; + if (input || (count == 1 && *args_string(args, 0) == '\0')) + flags |= SPAWN_EMPTY; + + sc.item = item; + sc.s = s; + sc.wl = wl; + + sc.wp0 = wp; + sc.lc = NULL; + + args_to_vector(args, &sc.argc, &sc.argv); + sc.environ = environ_create(); + + av = args_first_value(args, 'e'); + while (av != NULL) { + environ_put(sc.environ, av->string, 0); + av = args_next_value(av); + } + + sc.idx = -1; + sc.cwd = args_get(args, 'c'); + + sc.flags = flags; + if (args_has(args, 'd')) + sc.flags |= SPAWN_DETACHED; + if (args_has(args, 'Z')) + sc.flags |= SPAWN_ZOOM; + + if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { + cmdq_error(item, "create pane failed: %s", cause); + free(cause); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); + return (CMD_RETURN_ERROR); + } + if (input) { + switch (window_pane_start_input(new_wp, item, &cause)) { + case -1: + server_client_remove_pane(new_wp); + window_remove_pane(wp->window, new_wp); + cmdq_error(item, "%s", cause); + free(cause); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); + return (CMD_RETURN_ERROR); + case 1: + input = 0; + break; + } + } + if (!args_has(args, 'd')) + cmd_find_from_winlink_pane(current, wl, new_wp, 0); + window_pop_zoom(wp->window); + server_redraw_window(wp->window); + server_status_session(s); + + if (args_has(args, 'P')) { + if ((template = args_get(args, 'F')) == NULL) + template = SPLIT_WINDOW_TEMPLATE; + cp = format_single(item, template, tc, s, wl, new_wp); + cmdq_print(item, "%s", cp); + free(cp); + } + + cmd_find_from_winlink_pane(&fs, wl, new_wp, 0); + cmdq_insert_hook(s, item, &fs, "after-split-window"); + + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); + if (input) + return (CMD_RETURN_WAIT); + return (CMD_RETURN_NORMAL); +} diff --git a/cmd.c b/cmd.c index 02d2ac41bc..b9d6e0e927 100644 --- a/cmd.c +++ b/cmd.c @@ -109,6 +109,7 @@ extern const struct cmd_entry cmd_show_prompt_history_entry; extern const struct cmd_entry cmd_show_window_options_entry; extern const struct cmd_entry cmd_source_file_entry; extern const struct cmd_entry cmd_split_window_entry; +extern const struct cmd_entry cmd_new_floating_window_entry; extern const struct cmd_entry cmd_start_server_entry; extern const struct cmd_entry cmd_suspend_client_entry; extern const struct cmd_entry cmd_swap_pane_entry; @@ -201,6 +202,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_show_window_options_entry, &cmd_source_file_entry, &cmd_split_window_entry, + &cmd_new_floating_window_entry, &cmd_start_server_entry, &cmd_suspend_client_entry, &cmd_swap_pane_entry, diff --git a/layout.c b/layout.c index 2832039c7b..8087a9e723 100644 --- a/layout.c +++ b/layout.c @@ -1085,6 +1085,9 @@ layout_close_pane(struct window_pane *wp) { struct window *w = wp->window; + if (wp->layout_cell == NULL) + return; + /* Remove the cell. */ layout_destroy_cell(w, wp->layout_cell, &w->layout_root); diff --git a/screen-write.c b/screen-write.c index 5ef9ce4600..221ebf0668 100644 --- a/screen-write.c +++ b/screen-write.c @@ -139,9 +139,10 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c) if (c->session->curw->window != wp->window) return (0); + /* if (wp->layout_cell == NULL) return (0); - + */ if (wp->flags & (PANE_REDRAW|PANE_DROP)) return (-1); if (c->flags & CLIENT_REDRAWPANES) { diff --git a/spawn.c b/spawn.c index 90cda75724..69dbb27865 100644 --- a/spawn.c +++ b/spawn.c @@ -265,7 +265,14 @@ spawn_pane(struct spawn_context *sc, char **cause) new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN); } else if (sc->lc == NULL) { new_wp = window_add_pane(w, NULL, hlimit, sc->flags); - layout_init(w, new_wp); + if (sc->flags & SPAWN_FLOATING) { + new_wp->flags |= PANE_FLOATING; + window_pane_resize(new_wp, sc->sx, sc->sy); + new_wp->xoff = sc->xoff; + new_wp->yoff = sc->yoff; + } else { + layout_init(w, new_wp); + } } else { new_wp = window_add_pane(w, sc->wp0, hlimit, sc->flags); if (sc->flags & SPAWN_ZOOM) diff --git a/tmux.h b/tmux.h index 4276ce8ba5..7a4b68bb53 100644 --- a/tmux.h +++ b/tmux.h @@ -1174,6 +1174,7 @@ struct window_pane { #define PANE_THEMECHANGED 0x2000 #define PANE_UNSEENCHANGES 0x4000 #define PANE_REDRAWSCROLLBAR 0x8000 +#define PANE_FLOATING 0x10000 u_int sb_slider_y; u_int sb_slider_h; @@ -2201,6 +2202,10 @@ struct spawn_context { struct window_pane *wp0; struct layout_cell *lc; + u_int xoff; + u_int yoff; + u_int sx; + u_int sy; const char *name; char **argv; @@ -2219,6 +2224,7 @@ struct spawn_context { #define SPAWN_FULLSIZE 0x20 #define SPAWN_EMPTY 0x40 #define SPAWN_ZOOM 0x80 +#define SPAWN_FLOATING 0x100 }; /* Mode tree sort order. */ From 7634daa83416f8b5596d4ee23721e3cf604bcfa4 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 6 Oct 2025 22:19:25 +0100 Subject: [PATCH 002/167] Add function screen_redraw_get_visual_ranges to figure out what parts of floating panes obscure a target pane being redrawn. --- screen-redraw.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++++- tmux.h | 1 + 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/screen-redraw.c b/screen-redraw.c index 9608183951..23488abbbb 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -885,6 +885,76 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) } } +struct visual_range { + u_int px; + u_int nx; +}; + +static void +screen_redraw_get_visual_ranges(u_int px, u_int py, u_int width, + struct window_pane *base_wp, struct window *w, + struct visual_range **vr_p, int *vr_count_p) { + struct visual_range *vr; + int found_self, r, s; + int vr_len, vr_count; + struct window_pane *wp; + + vr = xcalloc(4, sizeof(struct visual_range *)); + vr_len = 4; // 4 initial slots + vr[0].px = px; // initialise first slot + vr[0].nx = width; + vr_count = 1; + + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == base_wp) { // wp == current pane + found_self = 1; + continue; + } + if (found_self && wp->layout_cell == NULL && + !(wp->flags & PANE_MINIMISED) && + (wp->yoff >= py && py <= wp->yoff + wp->sy)) { + for (r=0; rxoff > vr[r].px && + wp->xoff < vr[r].px + vr[r].nx && + wp->xoff + wp->sx > vr[r].px + vr[r].nx) + vr[r].nx = wp->xoff; + /* else if the right edge of wp + falls inside of this range and left + edge is less than the start of the range, + then move px forward */ + else if (wp->xoff + wp->sx > vr[r].px && + wp->xoff + wp->sx < vr[r].px + vr[r].nx && + wp->xoff < vr[r].px + vr[r].nx) + vr[r].px = wp->xoff + wp->sx; + /* else if wp fully inside range + then split range into 2 ranges */ + else if (wp->xoff > vr[r].px && + wp->xoff + wp->sx < vr[r].px + vr[r].nx) { + /* make space */ + if (vr_len == vr_count) { + vr = xreallocarray(vr, vr_len + + 4, sizeof *vr); + vr_len += 4; + } + for (s=vr_count; s>r; s--) { + vr[s].px = vr[s-1].px; + vr[s].nx = vr[s-1].nx; + } + vr[r].nx = wp->xoff; + vr[r+1].px = wp->xoff + wp->sx; + } + } + } + } + *vr_p = vr; + *vr_count_p = vr_count; +} + + /* Draw one pane. */ static void screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) @@ -895,6 +965,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct screen *s = wp->screen; struct colour_palette *palette = &wp->palette; struct grid_cell defaults; + struct visual_range *vr; + int vr_count, r; u_int i, j, top, x, y, width; log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); @@ -905,6 +977,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) top = ctx->statuslines; else top = 0; + for (j = 0; j < wp->sy; j++) { if (wp->yoff + j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy) continue; @@ -936,8 +1009,13 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", __func__, c->name, wp->id, i, j, x, y, width); + screen_redraw_get_visual_ranges(x, y, width, wp, w, &vr, &vr_count); + tty_default_colours(&defaults, wp); - tty_draw_line(tty, s, i, j, width, x, y, &defaults, palette); + + for (r=0; r Date: Fri, 10 Oct 2025 23:59:47 +0100 Subject: [PATCH 003/167] Add support in screen-write.c to properly display cmd output when there are floating panes on the screen. --- cmd-split-window.c | 4 +- screen-redraw.c | 68 +++++++++++--------- screen-write.c | 153 ++++++++++++++++++++++++++++++++++++--------- tmux.h | 8 +++ 4 files changed, 172 insertions(+), 61 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 515bd78940..564f7daf6a 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -262,8 +262,8 @@ cmd_new_floating_window_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } } - sc.xoff = 0; - sc.yoff = 0; + sc.xoff = 10; + sc.yoff = 10; sc.sx = sx; sc.sy = sy; diff --git a/screen-redraw.c b/screen-redraw.c index 23488abbbb..5df141bacf 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -885,34 +885,41 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) } } -struct visual_range { - u_int px; - u_int nx; -}; - -static void -screen_redraw_get_visual_ranges(u_int px, u_int py, u_int width, - struct window_pane *base_wp, struct window *w, - struct visual_range **vr_p, int *vr_count_p) { - struct visual_range *vr; - int found_self, r, s; - int vr_len, vr_count; - struct window_pane *wp; - - vr = xcalloc(4, sizeof(struct visual_range *)); - vr_len = 4; // 4 initial slots - vr[0].px = px; // initialise first slot +/* Construct ranges of line at px,py of width cells of base_wp that are + unobsructed. */ +void +screen_redraw_get_visible_ranges(u_int px, u_int py, u_int width, + struct window_pane *base_wp, struct visible_range **vr_p, + int *vr_count_p) { + struct window_pane *wp; + struct window *w; + struct visible_range *vr; + int found_self, r, s; + int vr_len, vr_count; + + /* Caller must call free(vr). */ + vr = xcalloc(4, sizeof(struct visible_range *)); + vr_len = 4; + vr[0].px = px; vr[0].nx = width; vr_count = 1; + if (base_wp == NULL) { + *vr_p = vr; + *vr_count_p = vr_count; + return; + } + + w = base_wp->window; + TAILQ_FOREACH(wp, &w->panes, entry) { - if (wp == base_wp) { // wp == current pane + if (wp == base_wp) { found_self = 1; continue; } if (found_self && wp->layout_cell == NULL && !(wp->flags & PANE_MINIMISED) && - (wp->yoff >= py && py <= wp->yoff + wp->sy)) { + (py >= wp->yoff && py <= wp->yoff + wp->sy)) { for (r=0; rxoff > vr[r].px && wp->xoff < vr[r].px + vr[r].nx && - wp->xoff + wp->sx > vr[r].px + vr[r].nx) + wp->xoff + wp->sx > vr[r].px + vr[r].nx) { vr[r].nx = wp->xoff; /* else if the right edge of wp falls inside of this range and left edge is less than the start of the range, then move px forward */ - else if (wp->xoff + wp->sx > vr[r].px && + } else if (wp->xoff + wp->sx > vr[r].px && wp->xoff + wp->sx < vr[r].px + vr[r].nx && - wp->xoff < vr[r].px + vr[r].nx) - vr[r].px = wp->xoff + wp->sx; + wp->xoff < vr[r].px) { + vr[r].px = vr[r].px + (wp->xoff + wp->sx); + vr[r].nx = vr[r].nx - (wp->xoff + wp->sx); /* else if wp fully inside range then split range into 2 ranges */ - else if (wp->xoff > vr[r].px && + } else if (wp->xoff > vr[r].px && wp->xoff + wp->sx < vr[r].px + vr[r].nx) { /* make space */ if (vr_len == vr_count) { @@ -946,7 +954,8 @@ screen_redraw_get_visual_ranges(u_int px, u_int py, u_int width, } vr[r].nx = wp->xoff; vr[r+1].px = wp->xoff + wp->sx; - } + vr_count++; + } /* XXX what if it's completely obscured? */ } } } @@ -965,7 +974,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct screen *s = wp->screen; struct colour_palette *palette = &wp->palette; struct grid_cell defaults; - struct visual_range *vr; + struct visible_range *vr; int vr_count, r; u_int i, j, top, x, y, width; @@ -1009,12 +1018,15 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", __func__, c->name, wp->id, i, j, x, y, width); - screen_redraw_get_visual_ranges(x, y, width, wp, w, &vr, &vr_count); + /* Get visible ranges of line before we draw it. */ + screen_redraw_get_visible_ranges(x, y, width, wp, + &vr, &vr_count); tty_default_colours(&defaults, wp); for (r=0; rs; - struct screen_write_cline *cl; - u_int y; - char *saved; - struct screen_write_citem *ci; + struct screen *s = ctx->s; + struct screen_write_cline *cl_src, *cl_dst; + u_int y, r_start, r_end; + u_int ci_start, ci_end, new_end; + char *saved; + struct screen_write_citem *ci, *new_ci; + struct window_pane *wp = ctx->wp; + struct visible_range *vr; + int vr_count, r; log_debug("%s: at %u,%u (region %u-%u)", __func__, s->cx, s->cy, s->rupper, s->rlower); screen_write_collect_clear(ctx, s->rupper, 1); - saved = ctx->s->write_list[s->rupper].data; + saved = s->write_list[s->rupper].data; + + /* For each line being scrolled, copy visible ranges from the line + below to the line above. */ for (y = s->rupper; y < s->rlower; y++) { - cl = &ctx->s->write_list[y + 1]; - TAILQ_CONCAT(&ctx->s->write_list[y].items, &cl->items, entry); - ctx->s->write_list[y].data = cl->data; + cl_src = &s->write_list[y + 1]; + cl_dst = &s->write_list[y]; + vr = NULL; + vr_count = 0; + + screen_redraw_get_visible_ranges(0, y, screen_size_x(s), wp, + &vr, &vr_count); + + TAILQ_INIT(&cl_dst->items); + + if (cl_dst->data == NULL) + cl_dst->data = xmalloc(screen_size_x(s)); + + /* For each visible range, copy corresponding items from cl_src + to cl_dst. */ + for (r = 0; r < vr_count; r++) { + r_start = vr[r].px; + r_end = vr[r].px + vr[r].nx; + + TAILQ_FOREACH(ci, &cl_src->items, entry) { + ci_start = ci->x; + ci_end = ci->x + ci->used; + + if (ci_end <= r_start || ci_start >= r_end) + continue; + + new_ci = screen_write_get_citem(); + new_ci->x = (ci_start < r_start) ? r_start : + ci_start; + new_end = (ci_end > r_end) ? r_end : ci_end; + new_ci->used = new_end - new_ci->x; + new_ci->type = ci->type; + new_ci->wrapped = ci->wrapped; + new_ci->bg = bg; + memcpy(&new_ci->gc, &ci->gc, sizeof(ci->gc)); + if (ci->type == TEXT) + memcpy(cl_dst->data, cl_src->data, + screen_size_x(s)); + TAILQ_INSERT_TAIL(&cl_dst->items, new_ci, + entry); + } + } + free(vr); } - ctx->s->write_list[s->rlower].data = saved; + s->write_list[s->rlower].data = saved; - ci = screen_write_get_citem(); - ci->x = 0; - ci->used = screen_size_x(s); - ci->type = CLEAR; - ci->bg = bg; - TAILQ_INSERT_TAIL(&ctx->s->write_list[s->rlower].items, ci, entry); + screen_write_collect_clear(ctx, s->rlower, 1); } /* Flush collected lines. */ @@ -1780,7 +1822,12 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct screen_write_citem *ci, *tmp; struct screen_write_cline *cl; u_int y, cx, cy, last, items = 0; + u_int r_start, r_end, ci_start, ci_end; + u_int wr_start, wr_end, wr_length; struct tty_ctx ttyctx; + struct visible_range *vr = NULL; + int vr_count = 0; + struct window_pane *wp = ctx->wp; if (ctx->scrolled != 0) { log_debug("%s: scrolled %u (region %u-%u)", __func__, @@ -1803,34 +1850,57 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, return; cx = s->cx; cy = s->cy; + for (y = 0; y < screen_size_y(s); y++) { cl = &ctx->s->write_list[y]; + + screen_redraw_get_visible_ranges(0, y, screen_size_x(s), wp, + &vr, &vr_count); + last = UINT_MAX; TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { if (last != UINT_MAX && ci->x <= last) { fatalx("collect list not in order: %u <= %u", ci->x, last); } - screen_write_set_cursor(ctx, ci->x, y); - if (ci->type == CLEAR) { - screen_write_initctx(ctx, &ttyctx, 1); - ttyctx.bg = ci->bg; - ttyctx.num = ci->used; - tty_write(tty_cmd_clearcharacter, &ttyctx); - } else { - screen_write_initctx(ctx, &ttyctx, 0); - ttyctx.cell = &ci->gc; - ttyctx.wrapped = ci->wrapped; - ttyctx.ptr = cl->data + ci->x; - ttyctx.num = ci->used; - tty_write(tty_cmd_cells, &ttyctx); + for (int r = 0; r < vr_count; r++) { + r_start = vr[r].px; + r_end = vr[r].px + vr[r].nx; + ci_start = ci->x; + ci_end = ci->x + ci->used; + + if (ci_end <= r_start || ci_start >= r_end) + continue; + + wr_start = (ci_start < r_start) ? r_start : + ci_start; + wr_end = (ci_end > r_end) ? r_end : ci_end; + wr_length = wr_end - wr_start; + if (wr_length == 0) + continue; + + screen_write_set_cursor(ctx, wr_start, y); + if (ci->type == CLEAR) { + screen_write_initctx(ctx, &ttyctx, 1); + ttyctx.bg = ci->bg; + ttyctx.num = wr_length; + tty_write(tty_cmd_clearcharacter, + &ttyctx); + } else { + screen_write_initctx(ctx, &ttyctx, 0); + ttyctx.cell = &ci->gc; + ttyctx.wrapped = ci->wrapped; + ttyctx.ptr = cl->data + wr_start; + ttyctx.num = wr_length; + tty_write(tty_cmd_cells, &ttyctx); + } + items++; } - items++; - TAILQ_REMOVE(&cl->items, ci, entry); screen_write_free_citem(ci); last = ci->x; } + free(vr); } s->cx = cx; s->cy = cy; @@ -1967,6 +2037,27 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) u_int sx = screen_size_x(s), sy = screen_size_y(s); u_int width = ud->width, xx, not_wrap; int selected, skip = 1; + struct window_pane *base_wp = ctx->wp, *wp; + struct window *w; + u_int found_self, px, py; + + + if (base_wp != NULL) { + w = base_wp->window; + px = ctx->s->cx; + py = ctx->s->cy; + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == base_wp) { + found_self = 1; + continue; + } + if (found_self && wp->layout_cell == NULL && + !(wp->flags & PANE_MINIMISED) && + (py >= wp->yoff && py <= wp->yoff + wp->sy) && + (px >= wp->xoff && px <= wp->xoff + wp->sx)) + return; + } + } /* Ignore padding cells. */ if (gc->flags & GRID_FLAG_PADDING) diff --git a/tmux.h b/tmux.h index 79a2bd6c7b..8a95cbb14c 100644 --- a/tmux.h +++ b/tmux.h @@ -2234,6 +2234,11 @@ struct mode_tree_sort_criteria { int reversed; }; +struct visible_range { + u_int px; + u_int nx; +}; + /* tmux.c */ extern struct options *global_options; extern struct options *global_s_options; @@ -3164,6 +3169,9 @@ void screen_write_alternateoff(struct screen_write_ctx *, /* screen-redraw.c */ void screen_redraw_screen(struct client *); void screen_redraw_pane(struct client *, struct window_pane *, int); +void screen_redraw_get_visible_ranges(u_int, u_int, u_int, + struct window_pane *, struct visible_range **, int *); + /* screen.c */ void screen_init(struct screen *, u_int, u_int, u_int); From ce03f1abeaa2aea53a1ae9cbc117ec9ccf667be1 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sat, 11 Oct 2025 18:07:22 +0100 Subject: [PATCH 004/167] Cleanup - screen_redraw_get_visible_ranges returns a value rather than pass addr of arg. Bugfix to redraw code. --- screen-redraw.c | 147 +++++++++++++++++++++++++++--------------------- screen-write.c | 30 +++++----- tmux.h | 13 +++-- 3 files changed, 106 insertions(+), 84 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 5df141bacf..a026339da3 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -887,28 +887,30 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) /* Construct ranges of line at px,py of width cells of base_wp that are unobsructed. */ -void +struct visible_ranges * screen_redraw_get_visible_ranges(u_int px, u_int py, u_int width, - struct window_pane *base_wp, struct visible_range **vr_p, - int *vr_count_p) { - struct window_pane *wp; - struct window *w; - struct visible_range *vr; - int found_self, r, s; - int vr_len, vr_count; + struct window_pane *base_wp) { + struct window_pane *wp; + struct window *w; + static struct visible_ranges ranges = { NULL, 0, 0 }; + static struct visible_range *vr; + int found_self; + u_int r, s; + + /* For efficiency ranges is static and space reused. */ + if (ranges.array == NULL) { + ranges.array = xcalloc(4, sizeof(struct visible_range *)); + ranges.size = 4; + } - /* Caller must call free(vr). */ - vr = xcalloc(4, sizeof(struct visible_range *)); - vr_len = 4; + /* Start with the entire width of the range. */ + vr = ranges.array; vr[0].px = px; vr[0].nx = width; - vr_count = 1; + ranges.n = 1; - if (base_wp == NULL) { - *vr_p = vr; - *vr_count_p = vr_count; - return; - } + if (base_wp == NULL) + return (&ranges); w = base_wp->window; @@ -917,50 +919,59 @@ screen_redraw_get_visible_ranges(u_int px, u_int py, u_int width, found_self = 1; continue; } - if (found_self && wp->layout_cell == NULL && - !(wp->flags & PANE_MINIMISED) && - (py >= wp->yoff && py <= wp->yoff + wp->sy)) { - for (r=0; rxoff > vr[r].px && - wp->xoff < vr[r].px + vr[r].nx && - wp->xoff + wp->sx > vr[r].px + vr[r].nx) { - vr[r].nx = wp->xoff; - /* else if the right edge of wp - falls inside of this range and left - edge is less than the start of the range, - then move px forward */ - } else if (wp->xoff + wp->sx > vr[r].px && - wp->xoff + wp->sx < vr[r].px + vr[r].nx && - wp->xoff < vr[r].px) { - vr[r].px = vr[r].px + (wp->xoff + wp->sx); - vr[r].nx = vr[r].nx - (wp->xoff + wp->sx); - /* else if wp fully inside range - then split range into 2 ranges */ - } else if (wp->xoff > vr[r].px && - wp->xoff + wp->sx < vr[r].px + vr[r].nx) { - /* make space */ - if (vr_len == vr_count) { - vr = xreallocarray(vr, vr_len + - 4, sizeof *vr); - vr_len += 4; - } - for (s=vr_count; s>r; s--) { - vr[s].px = vr[s-1].px; - vr[s].nx = vr[s-1].nx; - } - vr[r].nx = wp->xoff; - vr[r+1].px = wp->xoff + wp->sx; - vr_count++; - } /* XXX what if it's completely obscured? */ + + if (!found_self || wp->layout_cell != NULL || + (wp->flags & PANE_MINIMISED) || + (py < wp->yoff || py > wp->yoff + wp->sy)) + continue; + + for (r=0; rxoff > vr[r].px && + wp->xoff < vr[r].px + vr[r].nx && + wp->xoff + wp->sx >= vr[r].px + vr[r].nx) { + vr[r].nx = wp->xoff; + } + /* Else if the right edge of floating wp + falls inside of this range and left + edge covers the left of range, + then move px forward to right edge of wp. */ + else if (wp->xoff + wp->sx > vr[r].px && + wp->xoff + wp->sx < vr[r].px + vr[r].nx && + wp->xoff <= vr[r].px) { + vr[r].px = vr[r].px + (wp->xoff + wp->sx); + vr[r].nx = vr[r].nx - (wp->xoff + wp->sx); + } + /* Else if wp fully inside range + then split range into 2 ranges. */ + else if (wp->xoff > vr[r].px && + wp->xoff + wp->sx < vr[r].px + vr[r].nx) { + if (ranges.size == ranges.n) { + ranges.array = xreallocarray(vr, + ranges.size += 4, sizeof *vr); + ranges.array = vr; + } + for (s=ranges.n; s>r; s--) { + vr[s].px = vr[s-1].px; + vr[s].nx = vr[s-1].nx; + } + vr[r].nx = wp->xoff; + vr[r+1].px = wp->xoff + wp->sx; + ranges.n++; } + /* If floating wp completely covers this range + then delete it (make it 0 length). */ + else if (wp->xoff <= vr[r].px && + wp->xoff+wp->sx >= vr[r].px+vr[r].nx) { + vr[r].nx = 0; + } + /* Else the range is already obscured, do nothing. */ } } - *vr_p = vr; - *vr_count_p = vr_count; + return (&ranges); } @@ -974,9 +985,9 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct screen *s = wp->screen; struct colour_palette *palette = &wp->palette; struct grid_cell defaults; + struct visible_ranges *visible_ranges; struct visible_range *vr; - int vr_count, r; - u_int i, j, top, x, y, width; + u_int i, j, top, x, y, width, r; log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); @@ -1019,15 +1030,21 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) __func__, c->name, wp->id, i, j, x, y, width); /* Get visible ranges of line before we draw it. */ - screen_redraw_get_visible_ranges(x, y, width, wp, - &vr, &vr_count); + visible_ranges = screen_redraw_get_visible_ranges(x, y, width, + wp); + vr = visible_ranges->array; tty_default_colours(&defaults, wp); - for (r=0; rn; r++) { + if (vr[r].nx == 0) + continue; + /* i is px of cell, add px of region, sub the + pane offset. If you don't sub offset, + contents of pane shifted. */ + tty_draw_line(tty, s, i+vr[r].px-wp->xoff, j, vr[r].nx, vr[r].px, y, &defaults, palette); - free(vr); + } } #ifdef ENABLE_SIXEL diff --git a/screen-write.c b/screen-write.c index 4b3a709869..b31a813cc3 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1747,13 +1747,13 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) { struct screen *s = ctx->s; struct screen_write_cline *cl_src, *cl_dst; - u_int y, r_start, r_end; + u_int y, r, r_start, r_end; u_int ci_start, ci_end, new_end; char *saved; struct screen_write_citem *ci, *new_ci; struct window_pane *wp = ctx->wp; + struct visible_ranges *visible_ranges; struct visible_range *vr; - int vr_count, r; log_debug("%s: at %u,%u (region %u-%u)", __func__, s->cx, s->cy, s->rupper, s->rlower); @@ -1766,11 +1766,10 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) for (y = s->rupper; y < s->rlower; y++) { cl_src = &s->write_list[y + 1]; cl_dst = &s->write_list[y]; - vr = NULL; - vr_count = 0; - screen_redraw_get_visible_ranges(0, y, screen_size_x(s), wp, - &vr, &vr_count); + visible_ranges = screen_redraw_get_visible_ranges(0, y, + screen_size_x(s), wp); + vr = visible_ranges->array; TAILQ_INIT(&cl_dst->items); @@ -1779,7 +1778,8 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) /* For each visible range, copy corresponding items from cl_src to cl_dst. */ - for (r = 0; r < vr_count; r++) { + for (r = 0; r < visible_ranges->n; r++) { + if (vr[r].nx == 0) continue; r_start = vr[r].px; r_end = vr[r].px + vr[r].nx; @@ -1806,7 +1806,6 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) entry); } } - free(vr); } s->write_list[s->rlower].data = saved; @@ -1821,12 +1820,12 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct screen *s = ctx->s; struct screen_write_citem *ci, *tmp; struct screen_write_cline *cl; - u_int y, cx, cy, last, items = 0; + u_int y, cx, cy, last, items = 0, r; u_int r_start, r_end, ci_start, ci_end; u_int wr_start, wr_end, wr_length; struct tty_ctx ttyctx; - struct visible_range *vr = NULL; - int vr_count = 0; + struct visible_ranges *visible_ranges; + struct visible_range *vr; struct window_pane *wp = ctx->wp; if (ctx->scrolled != 0) { @@ -1854,8 +1853,9 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, for (y = 0; y < screen_size_y(s); y++) { cl = &ctx->s->write_list[y]; - screen_redraw_get_visible_ranges(0, y, screen_size_x(s), wp, - &vr, &vr_count); + visible_ranges = screen_redraw_get_visible_ranges(0, y, + screen_size_x(s), wp); + vr = visible_ranges->array; last = UINT_MAX; TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { @@ -1863,7 +1863,8 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, fatalx("collect list not in order: %u <= %u", ci->x, last); } - for (int r = 0; r < vr_count; r++) { + for (r = 0; r < visible_ranges->n; r++) { + if (vr[r].nx == 0) continue; r_start = vr[r].px; r_end = vr[r].px + vr[r].nx; ci_start = ci->x; @@ -1900,7 +1901,6 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, screen_write_free_citem(ci); last = ci->x; } - free(vr); } s->cx = cx; s->cy = cy; diff --git a/tmux.h b/tmux.h index 8a95cbb14c..fbb9f42119 100644 --- a/tmux.h +++ b/tmux.h @@ -2235,8 +2235,13 @@ struct mode_tree_sort_criteria { }; struct visible_range { - u_int px; - u_int nx; + u_int px; /* Start */ + u_int nx; /* Length */ +}; +struct visible_ranges { + struct visible_range *array; + size_t n; /* Elements used */ + size_t size; /* Array size */ }; /* tmux.c */ @@ -3169,8 +3174,8 @@ void screen_write_alternateoff(struct screen_write_ctx *, /* screen-redraw.c */ void screen_redraw_screen(struct client *); void screen_redraw_pane(struct client *, struct window_pane *, int); -void screen_redraw_get_visible_ranges(u_int, u_int, u_int, - struct window_pane *, struct visible_range **, int *); +struct visible_ranges *screen_redraw_get_visible_ranges(u_int, u_int, u_int, + struct window_pane *); /* screen.c */ From 1b959d0effb54969c78a21fbfd4c875739042498 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 12 Oct 2025 05:48:30 +0100 Subject: [PATCH 005/167] Cleanup before reworking collect_scroll. --- screen-write.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/screen-write.c b/screen-write.c index b31a813cc3..ead065c09f 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1773,9 +1773,6 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) TAILQ_INIT(&cl_dst->items); - if (cl_dst->data == NULL) - cl_dst->data = xmalloc(screen_size_x(s)); - /* For each visible range, copy corresponding items from cl_src to cl_dst. */ for (r = 0; r < visible_ranges->n; r++) { @@ -1799,11 +1796,11 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) new_ci->wrapped = ci->wrapped; new_ci->bg = bg; memcpy(&new_ci->gc, &ci->gc, sizeof(ci->gc)); - if (ci->type == TEXT) - memcpy(cl_dst->data, cl_src->data, - screen_size_x(s)); - TAILQ_INSERT_TAIL(&cl_dst->items, new_ci, - entry); + TAILQ_INSERT_TAIL(&cl_dst->items, new_ci, entry); + if (cl_dst->data == NULL) + cl_dst->data = xmalloc(screen_size_x(s)); + memcpy(cl_dst->data, cl_src->data, + screen_size_x(s)); } } } @@ -1873,9 +1870,10 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, if (ci_end <= r_start || ci_start >= r_end) continue; - wr_start = (ci_start < r_start) ? r_start : - ci_start; - wr_end = (ci_end > r_end) ? r_end : ci_end; + wr_start = (ci_start < r_start) ? + r_start : ci_start; + wr_end = (ci_end > r_end) ? + r_end : ci_end; wr_length = wr_end - wr_start; if (wr_length == 0) continue; From a37db556897bd441cf8492645033f096073df729 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 12 Oct 2025 10:38:47 +0100 Subject: [PATCH 006/167] Bugfix. --- screen-write.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/screen-write.c b/screen-write.c index ead065c09f..6bba95ce3c 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1750,7 +1750,7 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) u_int y, r, r_start, r_end; u_int ci_start, ci_end, new_end; char *saved; - struct screen_write_citem *ci, *new_ci; + struct screen_write_citem *ci, *ci_tmp, *new_ci; struct window_pane *wp = ctx->wp; struct visible_ranges *visible_ranges; struct visible_range *vr; @@ -1760,9 +1760,6 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, s->rupper, 1); saved = s->write_list[s->rupper].data; - - /* For each line being scrolled, copy visible ranges from the line - below to the line above. */ for (y = s->rupper; y < s->rlower; y++) { cl_src = &s->write_list[y + 1]; cl_dst = &s->write_list[y]; @@ -1771,8 +1768,6 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) screen_size_x(s), wp); vr = visible_ranges->array; - TAILQ_INIT(&cl_dst->items); - /* For each visible range, copy corresponding items from cl_src to cl_dst. */ for (r = 0; r < visible_ranges->n; r++) { @@ -1780,7 +1775,7 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) r_start = vr[r].px; r_end = vr[r].px + vr[r].nx; - TAILQ_FOREACH(ci, &cl_src->items, entry) { + TAILQ_FOREACH_SAFE(ci, &cl_src->items, entry, ci_tmp) { ci_start = ci->x; ci_end = ci->x + ci->used; @@ -1788,25 +1783,31 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) continue; new_ci = screen_write_get_citem(); - new_ci->x = (ci_start < r_start) ? r_start : - ci_start; - new_end = (ci_end > r_end) ? r_end : ci_end; + new_ci->x = (ci_start < r_start) ? + r_start : ci_start; + new_end = (ci_end > r_end) ? + r_end : ci_end; new_ci->used = new_end - new_ci->x; new_ci->type = ci->type; new_ci->wrapped = ci->wrapped; new_ci->bg = bg; memcpy(&new_ci->gc, &ci->gc, sizeof(ci->gc)); + TAILQ_INSERT_TAIL(&cl_dst->items, new_ci, entry); - if (cl_dst->data == NULL) - cl_dst->data = xmalloc(screen_size_x(s)); - memcpy(cl_dst->data, cl_src->data, - screen_size_x(s)); + TAILQ_REMOVE(&cl_src->items, ci, entry); } } + ctx->s->write_list[y].data = cl_src->data; } s->write_list[s->rlower].data = saved; - screen_write_collect_clear(ctx, s->rlower, 1); + /* Also worked without this clear, is this needed? */ + ci = screen_write_get_citem(); + ci->x = 0; + ci->used = screen_size_x(s); + ci->type = CLEAR; + ci->bg = bg; + TAILQ_INSERT_TAIL(&ctx->s->write_list[s->rlower].items, ci, entry); } /* Flush collected lines. */ From b5f9293014da642c21dcf8bfdb15db65a62ecbd1 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 13 Oct 2025 08:45:32 +0100 Subject: [PATCH 007/167] Possible fixes leak --- cmd-split-window.c | 4 ++-- screen-write.c | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 564f7daf6a..82bdbfdc1e 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -262,8 +262,8 @@ cmd_new_floating_window_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } } - sc.xoff = 10; - sc.yoff = 10; + sc.xoff = 20; + sc.yoff = 20; sc.sx = sx; sc.sy = sy; diff --git a/screen-write.c b/screen-write.c index 6bba95ce3c..ac6415cb06 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1797,10 +1797,14 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) TAILQ_REMOVE(&cl_src->items, ci, entry); } } + if (! TAILQ_EMPTY(&cl_src->items)) { + screen_write_collect_clear(ctx, y+1, 1); + TAILQ_INIT(&cl_src->items); + } ctx->s->write_list[y].data = cl_src->data; } s->write_list[s->rlower].data = saved; - + return; /* Also worked without this clear, is this needed? */ ci = screen_write_get_citem(); ci->x = 0; From 379e4d976c101064c69044975d3a4d475b7098ee Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 19 Oct 2025 14:11:08 +0100 Subject: [PATCH 008/167] Fix scrolling, redraw, and borders of floating panes. --- cmd-split-window.c | 4 +- menu.c | 2 +- popup.c | 2 +- screen-redraw.c | 109 ++++++++++++++++++++++++++++++++------------- screen-write.c | 80 +++++++++------------------------ tmux.h | 7 +-- tty.c | 98 +++++++++++++++++++++++++++++++++++----- 7 files changed, 193 insertions(+), 109 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 82bdbfdc1e..564f7daf6a 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -262,8 +262,8 @@ cmd_new_floating_window_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } } - sc.xoff = 20; - sc.yoff = 20; + sc.xoff = 10; + sc.yoff = 10; sc.sx = sx; sc.sy = sy; diff --git a/menu.c b/menu.c index fd3a9fe400..b43bfd8dd9 100644 --- a/menu.c +++ b/menu.c @@ -217,7 +217,7 @@ menu_draw_cb(struct client *c, void *data, for (i = 0; i < screen_size_y(&md->s); i++) { tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i, - &grid_default_cell, NULL); + &grid_default_cell, NULL, NULL); } } diff --git a/popup.c b/popup.c index 4d837a9b12..1011c3c22a 100644 --- a/popup.c +++ b/popup.c @@ -250,7 +250,7 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) } for (i = 0; i < pd->sy; i++) { tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults, - palette); + palette, NULL); } screen_free(&s); if (pd->md != NULL) { diff --git a/screen-redraw.c b/screen-redraw.c index a026339da3..01b73c38f5 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -104,7 +104,11 @@ screen_redraw_two_panes(struct window *w, int direction) { struct window_pane *wp; - wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); + wp = TAILQ_FIRST(&w->panes); + do { + wp = TAILQ_NEXT(wp, entry); + } while (wp && wp->layout_cell == NULL); + if (wp == NULL) return (0); /* one pane */ if (TAILQ_NEXT(wp, entry) != NULL) @@ -230,7 +234,7 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, u_int px, u_int py) return (1); /* Check all the panes. */ - TAILQ_FOREACH(wp, &w->panes, entry) { + TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) { if (!window_pane_visible(wp)) continue; switch (screen_redraw_pane_border(ctx, wp, px, py)) { @@ -335,7 +339,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, { struct client *c = ctx->c; struct window *w = c->session->curw->window; - struct window_pane *wp, *active; + struct window_pane *wp, *start; int pane_status = ctx->pane_status; u_int sx = w->sx, sy = w->sy; int border, pane_scrollbars = ctx->pane_scrollbars; @@ -351,7 +355,18 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, return (screen_redraw_type_of_cell(ctx, px, py)); if (pane_status != PANE_STATUS_OFF) { - active = wp = server_client_get_pane(c); + /* Look for higest z-index window at px,py. xxxx scrollbars? */ + TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) { + if (! (wp->flags & PANE_MINIMISED) && + (px >= wp->xoff - 1 && px<= wp->xoff + wp->sx + 1) && + (py >= wp->yoff - 1 && py<= wp->yoff + wp->sy + 1)) + break; + } + if (wp != NULL) + start = wp; + else + start = wp = server_client_get_pane(c); + do { if (!window_pane_visible(wp)) goto next1; @@ -369,10 +384,22 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, wp = TAILQ_NEXT(wp, entry); if (wp == NULL) wp = TAILQ_FIRST(&w->panes); - } while (wp != active); + } while (wp != start); } - active = wp = server_client_get_pane(c); + + /* Look for higest z-index window at px,py. xxxx scrollbars? */ + TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) { + if (! (wp->flags & PANE_MINIMISED) && + (px >= wp->xoff-1 && px<= wp->xoff+wp->sx+1) && + (py >= wp->yoff-1 && py<= wp->yoff+wp->sy+1)) + break; + } + if (wp == NULL) + start = wp = server_client_get_pane(c); + else + start = wp; + do { if (!window_pane_visible(wp)) goto next2; @@ -422,7 +449,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, wp = TAILQ_NEXT(wp, entry); if (wp == NULL) wp = TAILQ_FIRST(&w->panes); - } while (wp != active); + } while (wp != start); return (CELL_OUTSIDE); } @@ -516,6 +543,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) struct tty *tty = &c->tty; struct window_pane *wp; struct screen *s; + struct visible_ranges *visible_ranges; u_int i, x, width, xoff, yoff, size; log_debug("%s: %s @%u", __func__, c->name, w->id); @@ -562,8 +590,11 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) if (ctx->statustop) yoff += ctx->statuslines; + visible_ranges = screen_redraw_get_visible_ranges(wp, i, 0, + width); + tty_draw_line(tty, s, i, 0, width, x, yoff - ctx->oy, - &grid_default_cell, NULL); + &grid_default_cell, NULL, visible_ranges); } tty_cursor(tty, 0, 0); } @@ -722,6 +753,7 @@ screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x, wp->border_gc_set = 1; ft = format_create_defaults(NULL, c, s, s->curw, wp); + if (screen_redraw_check_is(ctx, x, y, active)) style_apply(&wp->border_gc, oo, "pane-active-border-style", ft); else @@ -881,21 +913,22 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) y = c->tty.sy - ctx->statuslines; for (i = 0; i < ctx->statuslines; i++) { tty_draw_line(tty, s, 0, i, UINT_MAX, 0, y + i, - &grid_default_cell, NULL); + &grid_default_cell, NULL, NULL); } } /* Construct ranges of line at px,py of width cells of base_wp that are unobsructed. */ struct visible_ranges * -screen_redraw_get_visible_ranges(u_int px, u_int py, u_int width, - struct window_pane *base_wp) { +screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, + u_int py, u_int width) { struct window_pane *wp; struct window *w; static struct visible_ranges ranges = { NULL, 0, 0 }; static struct visible_range *vr; - int found_self; - u_int r, s; + int found_self, sb_w; + u_int r, s, lb, rb, tb, bb; + int pane_scrollbars; /* For efficiency ranges is static and space reused. */ if (ranges.array == NULL) { @@ -913,6 +946,7 @@ screen_redraw_get_visible_ranges(u_int px, u_int py, u_int width, return (&ranges); w = base_wp->window; + pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); TAILQ_FOREACH(wp, &w->panes, entry) { if (wp == base_wp) { @@ -920,35 +954,43 @@ screen_redraw_get_visible_ranges(u_int px, u_int py, u_int width, continue; } + tb = wp->yoff-1; + bb = wp->yoff + wp->sy; if (!found_self || wp->layout_cell != NULL || (wp->flags & PANE_MINIMISED) || - (py < wp->yoff || py > wp->yoff + wp->sy)) + (py < tb || py > bb)) continue; + /* Are scrollbars enabled? */ + if (window_pane_show_scrollbar(wp, pane_scrollbars)) + sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; + for (r=0; rxoff - 1; + rb = wp->xoff + wp->sx + sb_w + 1; /* If the left edge of floating wp falls inside this range and right edge covers up to right of range, then shrink left edge of range. */ - if (wp->xoff > vr[r].px && - wp->xoff < vr[r].px + vr[r].nx && - wp->xoff + wp->sx >= vr[r].px + vr[r].nx) { - vr[r].nx = wp->xoff; + if (lb > vr[r].px && + lb < vr[r].px + vr[r].nx && + rb >= vr[r].px + vr[r].nx) { + vr[r].nx = lb; } /* Else if the right edge of floating wp falls inside of this range and left edge covers the left of range, then move px forward to right edge of wp. */ - else if (wp->xoff + wp->sx > vr[r].px && - wp->xoff + wp->sx < vr[r].px + vr[r].nx && - wp->xoff <= vr[r].px) { - vr[r].px = vr[r].px + (wp->xoff + wp->sx); - vr[r].nx = vr[r].nx - (wp->xoff + wp->sx); + else if (rb > vr[r].px && + rb < vr[r].px + vr[r].nx && + lb <= vr[r].px) { + vr[r].px = vr[r].px + rb; + vr[r].nx = vr[r].nx - rb; } /* Else if wp fully inside range then split range into 2 ranges. */ - else if (wp->xoff > vr[r].px && - wp->xoff + wp->sx < vr[r].px + vr[r].nx) { + else if (lb > vr[r].px && + rb < vr[r].px + vr[r].nx) { if (ranges.size == ranges.n) { ranges.array = xreallocarray(vr, ranges.size += 4, sizeof *vr); @@ -958,14 +1000,14 @@ screen_redraw_get_visible_ranges(u_int px, u_int py, u_int width, vr[s].px = vr[s-1].px; vr[s].nx = vr[s-1].nx; } - vr[r].nx = wp->xoff; - vr[r+1].px = wp->xoff + wp->sx; + vr[r].nx = lb; + vr[r+1].px = rb; ranges.n++; } /* If floating wp completely covers this range then delete it (make it 0 length). */ - else if (wp->xoff <= vr[r].px && - wp->xoff+wp->sx >= vr[r].px+vr[r].nx) { + else if (lb <= vr[r].px && + rb >= vr[r].px+vr[r].nx) { vr[r].nx = 0; } /* Else the range is already obscured, do nothing. */ @@ -1030,11 +1072,13 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) __func__, c->name, wp->id, i, j, x, y, width); /* Get visible ranges of line before we draw it. */ - visible_ranges = screen_redraw_get_visible_ranges(x, y, width, - wp); + visible_ranges = screen_redraw_get_visible_ranges(wp, x, y, + width); vr = visible_ranges->array; tty_default_colours(&defaults, wp); + /* xxxx tty_draw_line should drawn only visible ranges. see xxxx commment in tty_draw_line. */ + /*tty_draw_line(tty, s, i, j, width, x, y, &defaults, palette, visible_ranges); */ for (r=0; rn; r++) { if (vr[r].nx == 0) @@ -1043,7 +1087,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) pane offset. If you don't sub offset, contents of pane shifted. */ tty_draw_line(tty, s, i+vr[r].px-wp->xoff, j, - vr[r].nx, vr[r].px, y, &defaults, palette); + vr[r].nx, vr[r].px, y, &defaults, palette, + visible_ranges); } } diff --git a/screen-write.c b/screen-write.c index ac6415cb06..099a9754c7 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1745,67 +1745,24 @@ screen_write_collect_clear(struct screen_write_ctx *ctx, u_int y, u_int n) static void screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct screen_write_cline *cl_src, *cl_dst; - u_int y, r, r_start, r_end; - u_int ci_start, ci_end, new_end; - char *saved; - struct screen_write_citem *ci, *ci_tmp, *new_ci; - struct window_pane *wp = ctx->wp; - struct visible_ranges *visible_ranges; - struct visible_range *vr; + struct screen *s = ctx->s; + struct screen_write_cline *cl; + u_int y; + char *saved; + struct screen_write_citem *ci; log_debug("%s: at %u,%u (region %u-%u)", __func__, s->cx, s->cy, s->rupper, s->rlower); screen_write_collect_clear(ctx, s->rupper, 1); - saved = s->write_list[s->rupper].data; + saved = ctx->s->write_list[s->rupper].data; for (y = s->rupper; y < s->rlower; y++) { - cl_src = &s->write_list[y + 1]; - cl_dst = &s->write_list[y]; - - visible_ranges = screen_redraw_get_visible_ranges(0, y, - screen_size_x(s), wp); - vr = visible_ranges->array; - - /* For each visible range, copy corresponding items from cl_src - to cl_dst. */ - for (r = 0; r < visible_ranges->n; r++) { - if (vr[r].nx == 0) continue; - r_start = vr[r].px; - r_end = vr[r].px + vr[r].nx; - - TAILQ_FOREACH_SAFE(ci, &cl_src->items, entry, ci_tmp) { - ci_start = ci->x; - ci_end = ci->x + ci->used; - - if (ci_end <= r_start || ci_start >= r_end) - continue; - - new_ci = screen_write_get_citem(); - new_ci->x = (ci_start < r_start) ? - r_start : ci_start; - new_end = (ci_end > r_end) ? - r_end : ci_end; - new_ci->used = new_end - new_ci->x; - new_ci->type = ci->type; - new_ci->wrapped = ci->wrapped; - new_ci->bg = bg; - memcpy(&new_ci->gc, &ci->gc, sizeof(ci->gc)); - - TAILQ_INSERT_TAIL(&cl_dst->items, new_ci, entry); - TAILQ_REMOVE(&cl_src->items, ci, entry); - } - } - if (! TAILQ_EMPTY(&cl_src->items)) { - screen_write_collect_clear(ctx, y+1, 1); - TAILQ_INIT(&cl_src->items); - } - ctx->s->write_list[y].data = cl_src->data; + cl = &ctx->s->write_list[y + 1]; + TAILQ_CONCAT(&ctx->s->write_list[y].items, &cl->items, entry); + ctx->s->write_list[y].data = cl->data; } - s->write_list[s->rlower].data = saved; - return; - /* Also worked without this clear, is this needed? */ + ctx->s->write_list[s->rlower].data = saved; + ci = screen_write_get_citem(); ci->x = 0; ci->used = screen_size_x(s); @@ -1855,8 +1812,8 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, for (y = 0; y < screen_size_y(s); y++) { cl = &ctx->s->write_list[y]; - visible_ranges = screen_redraw_get_visible_ranges(0, y, - screen_size_x(s), wp); + visible_ranges = screen_redraw_get_visible_ranges(wp, 0, y, + screen_size_x(s)); vr = visible_ranges->array; last = UINT_MAX; @@ -1865,6 +1822,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, fatalx("collect list not in order: %u <= %u", ci->x, last); } + wr_length = 0; for (r = 0; r < visible_ranges->n; r++) { if (vr[r].nx == 0) continue; r_start = vr[r].px; @@ -1880,6 +1838,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, wr_end = (ci_end > r_end) ? r_end : ci_end; wr_length = wr_end - wr_start; + if (wr_length == 0) continue; @@ -1889,7 +1848,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ttyctx.bg = ci->bg; ttyctx.num = wr_length; tty_write(tty_cmd_clearcharacter, - &ttyctx); + &ttyctx); } else { screen_write_initctx(ctx, &ttyctx, 0); ttyctx.cell = &ci->gc; @@ -1899,10 +1858,11 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, tty_write(tty_cmd_cells, &ttyctx); } items++; + + TAILQ_REMOVE(&cl->items, ci, entry); + screen_write_free_citem(ci); + last = ci->x; } - TAILQ_REMOVE(&cl->items, ci, entry); - screen_write_free_citem(ci); - last = ci->x; } } s->cx = cx; s->cy = cy; diff --git a/tmux.h b/tmux.h index fbb9f42119..cb9fee2417 100644 --- a/tmux.h +++ b/tmux.h @@ -2531,7 +2531,8 @@ void tty_set_title(struct tty *, const char *); void tty_set_path(struct tty *, const char *); void tty_update_mode(struct tty *, int, struct screen *); void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, - u_int, u_int, const struct grid_cell *, struct colour_palette *); + u_int, u_int, const struct grid_cell *, struct colour_palette *, + struct visible_ranges *); #ifdef ENABLE_SIXEL void tty_draw_images(struct client *, struct window_pane *, struct screen *); @@ -3174,8 +3175,8 @@ void screen_write_alternateoff(struct screen_write_ctx *, /* screen-redraw.c */ void screen_redraw_screen(struct client *); void screen_redraw_pane(struct client *, struct window_pane *, int); -struct visible_ranges *screen_redraw_get_visible_ranges(u_int, u_int, u_int, - struct window_pane *); +struct visible_ranges *screen_redraw_get_visible_ranges(struct window_pane *, + u_int, u_int, u_int); /* screen.c */ diff --git a/tty.c b/tty.c index 86cfb31623..40b019f905 100644 --- a/tty.c +++ b/tty.c @@ -1365,22 +1365,30 @@ tty_clear_pane_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, tty_clear_area(tty, &ctx->defaults, y, ry, x, rx, bg); } +/* Redraw a line at py of a screen taking into account obscured ranges. + * Menus and popups are always on top, ctx->arg == NULL. + */ static void tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) { - struct screen *s = ctx->s; - u_int nx = ctx->sx, i, x, rx, ry; + struct screen *s = ctx->s; + struct window_pane *wp = ctx->arg; + struct visible_ranges *visible_ranges = NULL; + u_int nx = ctx->sx, i, x, rx, ry; log_debug("%s: %s %u %d", __func__, tty->client->name, py, ctx->bigger); + if (wp) + visible_ranges = screen_redraw_get_visible_ranges(wp, 0, py, nx); + if (!ctx->bigger) { tty_draw_line(tty, s, 0, py, nx, ctx->xoff, ctx->yoff + py, - &ctx->defaults, ctx->palette); + &ctx->defaults, ctx->palette, visible_ranges); return; } if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry)) { tty_draw_line(tty, s, i, py, rx, x, ry, &ctx->defaults, - ctx->palette); + ctx->palette, visible_ranges); } } @@ -1457,10 +1465,38 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, c->overlay_check(c, c->overlay_data, px, py, nx, r); } +/* Check if a single character is within a visible range (not obscured by a + * floating window pane). Returns a boolean. + */ +static int +tty_check_in_visible_range(struct visible_ranges *visible_ranges, u_int px) +{ + struct visible_range *vr; + u_int r; + + /* No visible_ranges if called from a popup or menu. Always on top. */ + if (visible_ranges == NULL) + return (0); + + vr = visible_ranges->array; + + /* Verify if px is in a visible range. */ + for (r=0; rn; r++) { + if (vr[r].nx == 0) + continue; + if (px >= vr[r].px && + px <= (vr[r].px + vr[r].nx)) + return (0); + } + + /* px not found in any visible range, it is obscured by a pane. */ + return (1); +} + void tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, u_int atx, u_int aty, const struct grid_cell *defaults, - struct colour_palette *palette) + struct colour_palette *palette, struct visible_ranges *visible_ranges) { struct grid *gd = s->grid; struct grid_cell gc, last; @@ -1517,6 +1553,10 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, atx != 0 || tty->cx < tty->sx || nx < tty->sx) { + /* Do I need to check + !tty_is_obstructed(c->session->curw->window->active + here too? It's not certain that the active pane is + the one being drawn in. (delete this comment) */ if (nx < tty->sx && atx == 0 && px + sx != nx && @@ -1543,6 +1583,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, gcp = tty_check_codeset(tty, &gc); if (len != 0 && (!tty_check_overlay(tty, atx + ux + width, aty) || + !tty_check_in_visible_range(visible_ranges, + atx + ux + width) || (gcp->attr & GRID_ATTR_CHARSET) || gcp->flags != last.flags || gcp->attr != last.attr || @@ -1577,7 +1619,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, tty_check_overlay_range(tty, atx + ux, aty, gcp->data.width, &r); - hidden = 0; + hidden = 0; /* need to check visible_ranges too? xxxx */ for (j = 0; j < OVERLAY_MAX_RANGES; j++) hidden += r.nx[j]; hidden = gcp->data.width - hidden; @@ -1997,11 +2039,40 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) tty_putc(tty, '\n'); } +/* Return 1 if there is a floating window pane overlapping this pane. */ +static int +tty_is_obscured(const struct tty_ctx *ctx) +{ + struct window_pane *base_wp = ctx->arg, *wp; + struct window *w; + int found_self = 0; + + if (base_wp == NULL) + return(0); + w = base_wp->window; + + /* Check if there is a floating pane. xxxx borders? scrollbars? */ + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == base_wp) { + found_self = 1; + continue; + } + if (found_self && wp->layout_cell == NULL && + ! (wp->flags & PANE_MINIMISED) && + (wp->yoff > base_wp->yoff && + wp->yoff + wp->sy < base_wp->yoff + base_wp->sy) && + (wp->xoff > base_wp->xoff && + wp->xoff + wp->sx < base_wp->xoff + base_wp->sx)) + return (1); + } + return (0); +} + void tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) { - struct client *c = tty->client; - u_int i; + struct client *c = tty->client; + u_int i; if (ctx->bigger || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || @@ -2009,7 +2080,8 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) !tty_term_has(tty->term, TTYC_CSR) || ctx->sx == 1 || ctx->sy == 1 || - c->overlay_check != NULL) { + c->overlay_check != NULL || + tty_is_obscured(ctx)) { tty_redraw_region(tty, ctx); return; } @@ -2168,6 +2240,8 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) const struct grid_cell *gcp = ctx->cell; struct screen *s = ctx->s; struct overlay_ranges r; + struct window_pane *wp = ctx->arg; + struct visible_ranges *visible_ranges; u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2176,6 +2250,9 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) (gcp->data.width == 1 && !tty_check_overlay(tty, px, py))) return; + if (wp) + visible_ranges = screen_redraw_get_visible_ranges(wp, px, py, 1); + /* Handle partially obstructed wide characters. */ if (gcp->data.width > 1) { tty_check_overlay_range(tty, px, py, gcp->data.width, &r); @@ -2183,7 +2260,8 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) vis += r.nx[i]; if (vis < gcp->data.width) { tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, - px, py, &ctx->defaults, ctx->palette); + px, py, &ctx->defaults, ctx->palette, + visible_ranges); return; } } From 72dbbfedce3c4336209895cbbafed5d9b5954e67 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 21 Oct 2025 09:13:33 +0100 Subject: [PATCH 009/167] Manage visible ranges when drawing scrollbars. --- screen-redraw.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/screen-redraw.c b/screen-redraw.c index 01b73c38f5..48b0c5118c 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -917,6 +917,22 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) } } +static int +screen_redraw_is_visible(struct visible_ranges *ranges, u_int px) +{ + struct visible_range *vr; + u_int r; + + vr = ranges->array; + for (r=0; rn; r++) { + if (vr[r].nx == 0) + continue; + if ((px >= vr[r].px) && (px <= vr[r].px + vr[r].nx)) + return (1); + } + return (0); +} + /* Construct ranges of line at px,py of width cells of base_wp that are unobsructed. */ struct visible_ranges * @@ -1179,6 +1195,7 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, int px, py, ox = ctx->ox, oy = ctx->oy; int sx = ctx->sx, sy = ctx->sy, xoff = wp->xoff; int yoff = wp->yoff; + struct visible_ranges *visible_ranges; /* Set up style for slider. */ gc = sb_style->gc; @@ -1195,12 +1212,15 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, for (j = 0; j < jmax; j++) { py = sb_y + j; + visible_ranges = screen_redraw_get_visible_ranges(wp, sb_x, py, + imax); for (i = 0; i < imax; i++) { px = sb_x + i; if (px < xoff - ox - (int)sb_w - (int)sb_pad || px >= sx || px < 0 || py < yoff - oy - 1 || - py >= sy || py < 0) + py >= sy || py < 0 || + ! screen_redraw_is_visible(visible_ranges, px)) continue; tty_cursor(tty, px, py); if ((sb_pos == PANE_SCROLLBARS_LEFT && From 26362dfc72169187ef599944dd61cba9092895c9 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 22 Oct 2025 22:07:42 +0100 Subject: [PATCH 010/167] Bugfix display of 2 side-by-side panes with overlapping floating pane. --- screen-redraw.c | 13 ++++++++++--- screen-write.c | 30 ++++++++++++++++++++++-------- tmux.h | 1 + tty.c | 42 +++++++++--------------------------------- 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 48b0c5118c..c0c34bc4fb 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -917,12 +917,19 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) } } -static int +/* Check if a single character is within a visible range (not obscured by a + * floating window pane). Returns a boolean. + */ +int screen_redraw_is_visible(struct visible_ranges *ranges, u_int px) { struct visible_range *vr; u_int r; + /* No visible_ranges if called from a popup or menu. Always visible. */ + if (ranges == NULL) + return (1); + vr = ranges->array; for (r=0; rn; r++) { if (vr[r].nx == 0) @@ -1000,8 +1007,8 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, else if (rb > vr[r].px && rb < vr[r].px + vr[r].nx && lb <= vr[r].px) { - vr[r].px = vr[r].px + rb; - vr[r].nx = vr[r].nx - rb; + vr[r].px = vr[r].px + (rb - vr[r].px); + vr[r].nx = vr[r].nx - (rb - vr[r].px); } /* Else if wp fully inside range then split range into 2 ranges. */ diff --git a/screen-write.c b/screen-write.c index 099a9754c7..9ab54a14f6 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1781,7 +1781,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct screen_write_cline *cl; u_int y, cx, cy, last, items = 0, r; u_int r_start, r_end, ci_start, ci_end; - u_int wr_start, wr_end, wr_length; + u_int wr_start, wr_end, wr_length, sx, xoff; struct tty_ctx ttyctx; struct visible_ranges *visible_ranges; struct visible_range *vr; @@ -1809,11 +1809,21 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, cx = s->cx; cy = s->cy; + /* The xoff and width of window pane relative to the window we + * are writing to relative to the visible_ranges array. */ + if (wp != NULL) { + sx = wp->window->sx; + xoff = wp->xoff; + } else { + sx = screen_size_x(s); + xoff = 0; + } + for (y = 0; y < screen_size_y(s); y++) { cl = &ctx->s->write_list[y]; visible_ranges = screen_redraw_get_visible_ranges(wp, 0, y, - screen_size_x(s)); + sx); vr = visible_ranges->array; last = UINT_MAX; @@ -1830,16 +1840,20 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ci_start = ci->x; ci_end = ci->x + ci->used; - if (ci_end <= r_start || ci_start >= r_end) + if (ci_start + xoff > r_end || ci_end + xoff < r_start) continue; - wr_start = (ci_start < r_start) ? - r_start : ci_start; - wr_end = (ci_end > r_end) ? - r_end : ci_end; + if (r_start > ci_start + xoff) + wr_start = ci_start + (r_start - (ci_start + xoff)); + else + wr_start = ci_start; + if (ci_end + xoff > r_end) + wr_end = ci_end - ((ci_end + xoff) - r_end); + else + wr_end = ci_end; wr_length = wr_end - wr_start; - if (wr_length == 0) + if (wr_length <= 0) continue; screen_write_set_cursor(ctx, wr_start, y); diff --git a/tmux.h b/tmux.h index cb9fee2417..23e7036ac5 100644 --- a/tmux.h +++ b/tmux.h @@ -3175,6 +3175,7 @@ void screen_write_alternateoff(struct screen_write_ctx *, /* screen-redraw.c */ void screen_redraw_screen(struct client *); void screen_redraw_pane(struct client *, struct window_pane *, int); +int screen_redraw_is_visible(struct visible_ranges *ranges, u_int px); struct visible_ranges *screen_redraw_get_visible_ranges(struct window_pane *, u_int, u_int, u_int); diff --git a/tty.c b/tty.c index 40b019f905..da8eafa3d8 100644 --- a/tty.c +++ b/tty.c @@ -1465,34 +1465,6 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, c->overlay_check(c, c->overlay_data, px, py, nx, r); } -/* Check if a single character is within a visible range (not obscured by a - * floating window pane). Returns a boolean. - */ -static int -tty_check_in_visible_range(struct visible_ranges *visible_ranges, u_int px) -{ - struct visible_range *vr; - u_int r; - - /* No visible_ranges if called from a popup or menu. Always on top. */ - if (visible_ranges == NULL) - return (0); - - vr = visible_ranges->array; - - /* Verify if px is in a visible range. */ - for (r=0; rn; r++) { - if (vr[r].nx == 0) - continue; - if (px >= vr[r].px && - px <= (vr[r].px + vr[r].nx)) - return (0); - } - - /* px not found in any visible range, it is obscured by a pane. */ - return (1); -} - void tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, u_int atx, u_int aty, const struct grid_cell *defaults, @@ -1583,7 +1555,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, gcp = tty_check_codeset(tty, &gc); if (len != 0 && (!tty_check_overlay(tty, atx + ux + width, aty) || - !tty_check_in_visible_range(visible_ranges, + screen_redraw_is_visible(visible_ranges, atx + ux + width) || (gcp->attr & GRID_ATTR_CHARSET) || gcp->flags != last.flags || @@ -2059,10 +2031,14 @@ tty_is_obscured(const struct tty_ctx *ctx) } if (found_self && wp->layout_cell == NULL && ! (wp->flags & PANE_MINIMISED) && - (wp->yoff > base_wp->yoff && - wp->yoff + wp->sy < base_wp->yoff + base_wp->sy) && - (wp->xoff > base_wp->xoff && - wp->xoff + wp->sx < base_wp->xoff + base_wp->sx)) + ((wp->yoff >= base_wp->yoff && + wp->yoff <= base_wp->yoff + base_wp->sy) || + (wp->yoff + wp->sy >= base_wp->yoff && + wp->yoff + wp->sy <= base_wp->yoff + base_wp->sy)) && + ((wp->xoff >= base_wp->xoff && + wp->xoff <= base_wp->xoff + base_wp->sx) || + (wp->xoff + wp->sx >= base_wp->xoff && + wp->xoff + wp->sx <= base_wp->xoff + base_wp->sx))) return (1); } return (0); From 3197b715b30ae7ac3ee4150b9f5fc9e0c7b11f23 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 22 Oct 2025 22:50:10 +0100 Subject: [PATCH 011/167] Bugfix display of above-below windows with a floating pane. --- screen-write.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/screen-write.c b/screen-write.c index 9ab54a14f6..0057594218 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1781,7 +1781,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct screen_write_cline *cl; u_int y, cx, cy, last, items = 0, r; u_int r_start, r_end, ci_start, ci_end; - u_int wr_start, wr_end, wr_length, sx, xoff; + u_int wr_start, wr_end, wr_length, sx, xoff, yoff; struct tty_ctx ttyctx; struct visible_ranges *visible_ranges; struct visible_range *vr; @@ -1814,15 +1814,17 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, if (wp != NULL) { sx = wp->window->sx; xoff = wp->xoff; + yoff = wp->yoff; } else { sx = screen_size_x(s); xoff = 0; + yoff = 0; } for (y = 0; y < screen_size_y(s); y++) { cl = &ctx->s->write_list[y]; - visible_ranges = screen_redraw_get_visible_ranges(wp, 0, y, + visible_ranges = screen_redraw_get_visible_ranges(wp, 0, y + yoff, sx); vr = visible_ranges->array; From 65fc09fac930b45b53e10056096c78d012db3a0e Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 22 Oct 2025 23:29:21 +0100 Subject: [PATCH 012/167] Hide cursor behind floating panes. --- server-client.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server-client.c b/server-client.c index 0309a3b074..d9f4418da5 100644 --- a/server-client.c +++ b/server-client.c @@ -2953,6 +2953,10 @@ server_client_reset_state(struct client *c) if (status_at_line(c) == 0) cy += status_line_size(c); } + if (!screen_redraw_is_visible( + screen_redraw_get_visible_ranges(wp, cx, cy, 1), cx)) + cursor = 0; + if (!cursor) mode &= ~MODE_CURSOR; } From 38724f2e866d01dde1e121662d3b792e598f69ef Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 22 Oct 2025 23:37:58 +0100 Subject: [PATCH 013/167] Return error if you try to split a floating pane. --- cmd-split-window.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd-split-window.c b/cmd-split-window.c index 564f7daf6a..27e588b188 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -89,6 +89,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) struct args_value *av; u_int count = args_count(args), curval = 0; + if (wp->layout_cell == NULL) { + cmdq_error(item, "can't split a floating pane"); + return (CMD_RETURN_ERROR); + } + type = LAYOUT_TOPBOTTOM; if (args_has(args, 'h')) type = LAYOUT_LEFTRIGHT; From e3b7bf9b31a499febb7470cc88ba16b770afa3fa Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 23 Oct 2025 00:12:54 +0100 Subject: [PATCH 014/167] Add -x, -y, -w, -h args to set the xoff, yoff, sx, and sy of the pane. --- cmd-split-window.c | 49 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 27e588b188..f1a2246a6f 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -56,7 +56,7 @@ const struct cmd_entry cmd_new_floating_window_entry = { .name = "new-floating-window", .alias = "floatw", - .args = { "bc:de:fF:hIl:p:Pt:vZ", 0, -1, NULL }, + .args = { "bc:de:fF:h:Il:p:Pt:w:x:y:Z", 0, -1, NULL }, .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " "[-F format] [-l size] " CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]", @@ -239,7 +239,7 @@ cmd_new_floating_window_exec(struct cmd *self, struct cmdq_item *item) char *cause = NULL, *cp; struct args_value *av; u_int count = args_count(args); - u_int sx, sy, pct; + u_int sx, sy, pct, x, y; if (args_has(args, 'f')) { sx = w->sx; @@ -257,7 +257,7 @@ cmd_new_floating_window_exec(struct cmd *self, struct cmdq_item *item) sx = w->sx * pct / 100; sy = w->sy * pct / 100; } - } else { + } else if (cause == NULL) { sx = w->sx / 2; sy = w->sy / 2; } @@ -267,8 +267,47 @@ cmd_new_floating_window_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } } - sc.xoff = 10; - sc.yoff = 10; + if (args_has(args, 'w')) { + sx = args_strtonum_and_expand(args, 'w', 0, w->sx, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + if (args_has(args, 'h')) { + sy = args_strtonum_and_expand(args, 'h', 0, w->sy, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + if (args_has(args, 'x')) { + x = args_strtonum_and_expand(args, 'x', 0, w->sx, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } else + x = 10; + if (args_has(args, 'y')) { + y = args_strtonum_and_expand(args, 'y', 0, w->sx, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } else + y = 10; + + sc.xoff = x; + sc.yoff = y; sc.sx = sx; sc.sy = sy; From 8db76e90573a6cc63b045821d15f15c22c04f335 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 23 Oct 2025 18:05:04 +0100 Subject: [PATCH 015/167] Bugfix fix redraw of overlapping floating panes. --- screen-redraw.c | 14 ++++++++++++-- window.c | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index c0c34bc4fb..3807d17f93 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -940,6 +940,15 @@ screen_redraw_is_visible(struct visible_ranges *ranges, u_int px) return (0); } +static void +screen_redraw_print_panes(struct window *w) { + struct window_pane *wp; + + TAILQ_FOREACH(wp, &w->panes, entry) { + printf("id=%u %ux%u @%u,%u\n", wp->id, wp->sx, wp->sy, wp->xoff, wp->yoff); + } +} + /* Construct ranges of line at px,py of width cells of base_wp that are unobsructed. */ struct visible_ranges * @@ -971,6 +980,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, w = base_wp->window; pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); + found_self = 0; TAILQ_FOREACH(wp, &w->panes, entry) { if (wp == base_wp) { found_self = 1; @@ -979,7 +989,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, tb = wp->yoff-1; bb = wp->yoff + wp->sy; - if (!found_self || wp->layout_cell != NULL || + if (!found_self || (wp->flags & PANE_MINIMISED) || (py < tb || py > bb)) continue; @@ -1007,8 +1017,8 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, else if (rb > vr[r].px && rb < vr[r].px + vr[r].nx && lb <= vr[r].px) { - vr[r].px = vr[r].px + (rb - vr[r].px); vr[r].nx = vr[r].nx - (rb - vr[r].px); + vr[r].px = vr[r].px + (rb - vr[r].px); } /* Else if wp fully inside range then split range into 2 ranges. */ diff --git a/window.c b/window.c index 6f2c19fe4c..3b92019a15 100644 --- a/window.c +++ b/window.c @@ -745,7 +745,7 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, TAILQ_INSERT_BEFORE(other, wp, entry); } else { log_debug("%s: @%u after %%%u", __func__, w->id, wp->id); - if (flags & SPAWN_FULLSIZE) + if (flags & SPAWN_FULLSIZE|SPAWN_FLOATING) TAILQ_INSERT_TAIL(&w->panes, wp, entry); else TAILQ_INSERT_AFTER(&w->panes, other, wp, entry); From baf642b7d294345b42d27151fca796a6199fd833 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 23 Oct 2025 20:30:32 +0100 Subject: [PATCH 016/167] window_redraw_active_switch now brings floating pane to front. Fix compile warning in window_add_pane. --- window.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/window.c b/window.c index 3b92019a15..64288dc69e 100644 --- a/window.c +++ b/window.c @@ -585,6 +585,11 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) } if (wp == w->active) break; + if (wp->layout_cell == NULL) { + TAILQ_REMOVE(&w->panes, wp, entry); + TAILQ_INSERT_TAIL(&w->panes, wp, entry); + wp->flags |= PANE_REDRAW; + } wp = w->active; } } @@ -595,7 +600,7 @@ window_get_active_at(struct window *w, u_int x, u_int y) struct window_pane *wp; u_int xoff, yoff, sx, sy; - TAILQ_FOREACH(wp, &w->panes, entry) { + TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) { if (!window_pane_visible(wp)) continue; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); @@ -745,7 +750,7 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, TAILQ_INSERT_BEFORE(other, wp, entry); } else { log_debug("%s: @%u after %%%u", __func__, w->id, wp->id); - if (flags & SPAWN_FULLSIZE|SPAWN_FLOATING) + if (flags & (SPAWN_FULLSIZE|SPAWN_FLOATING)) TAILQ_INSERT_TAIL(&w->panes, wp, entry); else TAILQ_INSERT_AFTER(&w->panes, other, wp, entry); From b315a6c3d122d7495dcfc517d68ff191a57e9551 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 23 Oct 2025 23:25:48 +0100 Subject: [PATCH 017/167] Add mouse detection on top & left borders of floating panes. --- server-client.c | 41 +++++++++++++++++++++++++++++++++-------- window.c | 18 ++++++++++++++---- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/server-client.c b/server-client.c index d9f4418da5..27c555de3c 100644 --- a/server-client.c +++ b/server-client.c @@ -592,6 +592,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, struct window_pane *fwp; int pane_status, sb, sb_pos, sb_w, sb_pad; u_int line, sl_top, sl_bottom; + u_int bdr_bottom, bdr_top, bdr_left, bdr_right; sb = options_get_number(wo, "pane-scrollbars"); sb_pos = options_get_number(wo, "pane-scrollbars-position"); @@ -643,16 +644,40 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, } else if (~w->flags & WINDOW_ZOOMED) { /* Try the pane borders if not zoomed. */ TAILQ_FOREACH(fwp, &w->panes, entry) { - if ((((sb_pos == PANE_SCROLLBARS_RIGHT && - fwp->xoff + fwp->sx + sb_pad + sb_w == px) || - (sb_pos == PANE_SCROLLBARS_LEFT && - fwp->xoff + fwp->sx == px)) && - fwp->yoff <= 1 + py && - fwp->yoff + fwp->sy >= py) || + if (sb_pos == PANE_SCROLLBARS_LEFT) + bdr_right = fwp->xoff + fwp->sx; + else + /* PANE_SCROLLBARS_RIGHT or none. */ + bdr_right = fwp->xoff + fwp->sx + sb_pad + sb_w; + if (py >= fwp->yoff - 1 && py <= fwp->yoff + fwp->sy) { + if (px == bdr_right) + break; + if (wp->layout_cell == NULL) { + /* Floating pane, check if left border. */ + bdr_left = fwp->xoff - 1; + if (px == bdr_left) + break; + } + } + if (px >= fwp->xoff - 1 && px <= fwp->xoff + fwp->sx) { + bdr_bottom = fwp->yoff + fwp->sy; + if (py == bdr_bottom) + break; + if (wp->layout_cell == NULL) { + /* Floating pane, check if top border. */ + bdr_top = fwp->yoff - 1; + if (py == bdr_top) + break; + } + } + /* + if ((((sb_pos == PANE_SCROLLBARS_RIGHT && fwp->xoff + fwp->sx + sb_pad + sb_w == px) || + (sb_pos == PANE_SCROLLBARS_LEFT && fwp->xoff + fwp->sx == px)) && + fwp->yoff <= 1 + py && fwp->yoff + fwp->sy >= py) || (fwp->yoff + fwp->sy == py && - fwp->xoff <= 1 + px && - fwp->xoff + fwp->sx >= px)) + fwp->xoff <= 1 + px && fwp->xoff + fwp->sx >= px)) break; + */ } if (fwp != NULL) return (BORDER); diff --git a/window.c b/window.c index 64288dc69e..461d672ef1 100644 --- a/window.c +++ b/window.c @@ -604,10 +604,20 @@ window_get_active_at(struct window *w, u_int x, u_int y) if (!window_pane_visible(wp)) continue; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); - if (x < xoff || x > xoff + sx) - continue; - if (y < yoff || y > yoff + sy) - continue; + if (wp->layout_cell != NULL) { + /* Tiled, select up to including bottom or + right border. */ + if (x < xoff || x > xoff + sx) + continue; + if (y < yoff || y > yoff + sy) + continue; + } else { + /* Floating, include top or or left border. */ + if (x < xoff - 1 || x > xoff + sx) + continue; + if (y < yoff - 1 || y > yoff + sy) + continue; + } return (wp); } return (NULL); From 8ce0af3fc9f805215f2b54af269e845658040c74 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 23 Oct 2025 23:59:49 +0100 Subject: [PATCH 018/167] Fix a fencepost error (not sure why wp->sy+1 isn't the border). --- screen-redraw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 3807d17f93..ef1e3d0db9 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -391,8 +391,8 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, /* Look for higest z-index window at px,py. xxxx scrollbars? */ TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) { if (! (wp->flags & PANE_MINIMISED) && - (px >= wp->xoff-1 && px<= wp->xoff+wp->sx+1) && - (py >= wp->yoff-1 && py<= wp->yoff+wp->sy+1)) + (px >= wp->xoff - 1 && px <= wp->xoff + wp->sx + 1) && + (py >= wp->yoff - 1 && py <= wp->yoff + wp->sy)) /* + 1? */ break; } if (wp == NULL) From b2226d1608880eba59cb5c370bd7e6123d79fcdd Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 24 Oct 2025 00:06:18 +0100 Subject: [PATCH 019/167] Add scrollbar width to right border calculation. --- screen-redraw.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index ef1e3d0db9..18b78ef506 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -388,11 +388,13 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, } - /* Look for higest z-index window at px,py. xxxx scrollbars? */ + /* Look for higest z-index window at px,py. */ TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) { + sb_w = wp->scrollbar_style.width + + wp->scrollbar_style.pad; if (! (wp->flags & PANE_MINIMISED) && - (px >= wp->xoff - 1 && px <= wp->xoff + wp->sx + 1) && - (py >= wp->yoff - 1 && py <= wp->yoff + wp->sy)) /* + 1? */ + (px >= wp->xoff - 1 && px <= wp->xoff + wp->sx + sb_w) && + (py >= wp->yoff - 1 && py <= wp->yoff + wp->sy)) break; } if (wp == NULL) @@ -940,15 +942,6 @@ screen_redraw_is_visible(struct visible_ranges *ranges, u_int px) return (0); } -static void -screen_redraw_print_panes(struct window *w) { - struct window_pane *wp; - - TAILQ_FOREACH(wp, &w->panes, entry) { - printf("id=%u %ux%u @%u,%u\n", wp->id, wp->sx, wp->sy, wp->xoff, wp->yoff); - } -} - /* Construct ranges of line at px,py of width cells of base_wp that are unobsructed. */ struct visible_ranges * From cc1324e2d9fbdf8d08a11c21a661d7d9693bb193 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 24 Oct 2025 09:53:12 +0100 Subject: [PATCH 020/167] Cleanup and simplification. Array of visual ranges now just simple array. --- screen-redraw.c | 83 ++++++++++++++++++++++++------------------------- screen-write.c | 6 ++-- server-client.c | 8 ----- tmux.h | 16 ++++------ tty.c | 20 ++++++------ 5 files changed, 57 insertions(+), 76 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 18b78ef506..8088aabd74 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -545,7 +545,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) struct tty *tty = &c->tty; struct window_pane *wp; struct screen *s; - struct visible_ranges *visible_ranges; + struct visible_range *vr; u_int i, x, width, xoff, yoff, size; log_debug("%s: %s @%u", __func__, c->name, w->id); @@ -592,11 +592,10 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) if (ctx->statustop) yoff += ctx->statuslines; - visible_ranges = screen_redraw_get_visible_ranges(wp, i, 0, - width); + vr = screen_redraw_get_visible_ranges(wp, i, 0, width); tty_draw_line(tty, s, i, 0, width, x, yoff - ctx->oy, - &grid_default_cell, NULL, visible_ranges); + &grid_default_cell, NULL, vr); } tty_cursor(tty, 0, 0); } @@ -923,17 +922,15 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) * floating window pane). Returns a boolean. */ int -screen_redraw_is_visible(struct visible_ranges *ranges, u_int px) +screen_redraw_is_visible(struct visible_range *vr, u_int px) { - struct visible_range *vr; u_int r; /* No visible_ranges if called from a popup or menu. Always visible. */ - if (ranges == NULL) + if (vr == NULL) return (1); - vr = ranges->array; - for (r=0; rn; r++) { + for (r=0; vr[r].nx != -1; r++) { if (vr[r].nx == 0) continue; if ((px >= vr[r].px) && (px <= vr[r].px + vr[r].nx)) @@ -942,33 +939,32 @@ screen_redraw_is_visible(struct visible_ranges *ranges, u_int px) return (0); } -/* Construct ranges of line at px,py of width cells of base_wp that are - unobsructed. */ -struct visible_ranges * +/* Construct ranges array for the line at starting at px,py of width + cells of base_wp that are unobsructed. */ +struct visible_range * screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, u_int py, u_int width) { struct window_pane *wp; struct window *w; - static struct visible_ranges ranges = { NULL, 0, 0 }; - static struct visible_range *vr; - int found_self, sb_w; - u_int r, s, lb, rb, tb, bb; - int pane_scrollbars; + static struct visible_range *vr = NULL; + static size_t size = 0, last; + int found_self, sb_w; + u_int r, s, lb, rb, tb, bb; + int pane_scrollbars; /* For efficiency ranges is static and space reused. */ - if (ranges.array == NULL) { - ranges.array = xcalloc(4, sizeof(struct visible_range *)); - ranges.size = 4; + if (vr == NULL) { + vr = xcalloc(4, sizeof(struct visible_range *)); + size = 4; } /* Start with the entire width of the range. */ - vr = ranges.array; vr[0].px = px; vr[0].nx = width; - ranges.n = 1; + last = 1; if (base_wp == NULL) - return (&ranges); + return (vr); w = base_wp->window; pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); @@ -991,7 +987,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, if (window_pane_show_scrollbar(wp, pane_scrollbars)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - for (r=0; rxoff - 1; rb = wp->xoff + wp->sx + sb_w + 1; /* If the left edge of floating wp @@ -1017,18 +1013,17 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, then split range into 2 ranges. */ else if (lb > vr[r].px && rb < vr[r].px + vr[r].nx) { - if (ranges.size == ranges.n) { - ranges.array = xreallocarray(vr, - ranges.size += 4, sizeof *vr); - ranges.array = vr; + if (size == last) { + vr = xreallocarray(vr, + size += 4, sizeof *vr); } - for (s=ranges.n; s>r; s--) { + for (s=last; s>r; s--) { vr[s].px = vr[s-1].px; vr[s].nx = vr[s-1].nx; } + last++; vr[r].nx = lb; vr[r+1].px = rb; - ranges.n++; } /* If floating wp completely covers this range then delete it (make it 0 length). */ @@ -1039,7 +1034,15 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, /* Else the range is already obscured, do nothing. */ } } - return (&ranges); + + /* Tie off array. */ + if (size == last) { + vr = xreallocarray(vr, size += 4, sizeof *vr); + } + vr[last].px = 0; + vr[last].nx = -1; + + return (vr); } @@ -1053,7 +1056,6 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct screen *s = wp->screen; struct colour_palette *palette = &wp->palette; struct grid_cell defaults; - struct visible_ranges *visible_ranges; struct visible_range *vr; u_int i, j, top, x, y, width, r; @@ -1098,15 +1100,11 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) __func__, c->name, wp->id, i, j, x, y, width); /* Get visible ranges of line before we draw it. */ - visible_ranges = screen_redraw_get_visible_ranges(wp, x, y, - width); - vr = visible_ranges->array; + vr = screen_redraw_get_visible_ranges(wp, x, y, width); tty_default_colours(&defaults, wp); - /* xxxx tty_draw_line should drawn only visible ranges. see xxxx commment in tty_draw_line. */ - /*tty_draw_line(tty, s, i, j, width, x, y, &defaults, palette, visible_ranges); */ - for (r=0; rn; r++) { + for (r=0; vr[r].nx != -1; r++) { if (vr[r].nx == 0) continue; /* i is px of cell, add px of region, sub the @@ -1114,7 +1112,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) contents of pane shifted. */ tty_draw_line(tty, s, i+vr[r].px-wp->xoff, j, vr[r].nx, vr[r].px, y, &defaults, palette, - visible_ranges); + vr); } } @@ -1205,7 +1203,7 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, int px, py, ox = ctx->ox, oy = ctx->oy; int sx = ctx->sx, sy = ctx->sy, xoff = wp->xoff; int yoff = wp->yoff; - struct visible_ranges *visible_ranges; + struct visible_range *vr; /* Set up style for slider. */ gc = sb_style->gc; @@ -1222,15 +1220,14 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, for (j = 0; j < jmax; j++) { py = sb_y + j; - visible_ranges = screen_redraw_get_visible_ranges(wp, sb_x, py, - imax); + vr = screen_redraw_get_visible_ranges(wp, sb_x, py, imax); for (i = 0; i < imax; i++) { px = sb_x + i; if (px < xoff - ox - (int)sb_w - (int)sb_pad || px >= sx || px < 0 || py < yoff - oy - 1 || py >= sy || py < 0 || - ! screen_redraw_is_visible(visible_ranges, px)) + ! screen_redraw_is_visible(vr, px)) continue; tty_cursor(tty, px, py); if ((sb_pos == PANE_SCROLLBARS_LEFT && diff --git a/screen-write.c b/screen-write.c index 0057594218..bccbdedea0 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1783,7 +1783,6 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, u_int r_start, r_end, ci_start, ci_end; u_int wr_start, wr_end, wr_length, sx, xoff, yoff; struct tty_ctx ttyctx; - struct visible_ranges *visible_ranges; struct visible_range *vr; struct window_pane *wp = ctx->wp; @@ -1824,9 +1823,8 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, for (y = 0; y < screen_size_y(s); y++) { cl = &ctx->s->write_list[y]; - visible_ranges = screen_redraw_get_visible_ranges(wp, 0, y + yoff, + vr = screen_redraw_get_visible_ranges(wp, 0, y + yoff, sx); - vr = visible_ranges->array; last = UINT_MAX; TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { @@ -1835,7 +1833,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ci->x, last); } wr_length = 0; - for (r = 0; r < visible_ranges->n; r++) { + for (r = 0; vr[r].nx != -1; r++) { if (vr[r].nx == 0) continue; r_start = vr[r].px; r_end = vr[r].px + vr[r].nx; diff --git a/server-client.c b/server-client.c index 27c555de3c..d8d516b568 100644 --- a/server-client.c +++ b/server-client.c @@ -670,14 +670,6 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, break; } } - /* - if ((((sb_pos == PANE_SCROLLBARS_RIGHT && fwp->xoff + fwp->sx + sb_pad + sb_w == px) || - (sb_pos == PANE_SCROLLBARS_LEFT && fwp->xoff + fwp->sx == px)) && - fwp->yoff <= 1 + py && fwp->yoff + fwp->sy >= py) || - (fwp->yoff + fwp->sy == py && - fwp->xoff <= 1 + px && fwp->xoff + fwp->sx >= px)) - break; - */ } if (fwp != NULL) return (BORDER); diff --git a/tmux.h b/tmux.h index 23e7036ac5..4bb92a6593 100644 --- a/tmux.h +++ b/tmux.h @@ -2234,14 +2234,10 @@ struct mode_tree_sort_criteria { int reversed; }; +/* Visible range array element. nx==-1 is end of array mark. */ struct visible_range { - u_int px; /* Start */ - u_int nx; /* Length */ -}; -struct visible_ranges { - struct visible_range *array; - size_t n; /* Elements used */ - size_t size; /* Array size */ + u_int px; /* Start */ + int nx; /* Length */ }; /* tmux.c */ @@ -2532,7 +2528,7 @@ void tty_set_path(struct tty *, const char *); void tty_update_mode(struct tty *, int, struct screen *); void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, u_int, u_int, const struct grid_cell *, struct colour_palette *, - struct visible_ranges *); + struct visible_range *); #ifdef ENABLE_SIXEL void tty_draw_images(struct client *, struct window_pane *, struct screen *); @@ -3175,8 +3171,8 @@ void screen_write_alternateoff(struct screen_write_ctx *, /* screen-redraw.c */ void screen_redraw_screen(struct client *); void screen_redraw_pane(struct client *, struct window_pane *, int); -int screen_redraw_is_visible(struct visible_ranges *ranges, u_int px); -struct visible_ranges *screen_redraw_get_visible_ranges(struct window_pane *, +int screen_redraw_is_visible(struct visible_range *, u_int px); +struct visible_range *screen_redraw_get_visible_ranges(struct window_pane *, u_int, u_int, u_int); diff --git a/tty.c b/tty.c index da8eafa3d8..07b89334ca 100644 --- a/tty.c +++ b/tty.c @@ -1373,22 +1373,22 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) { struct screen *s = ctx->s; struct window_pane *wp = ctx->arg; - struct visible_ranges *visible_ranges = NULL; + struct visible_range *vr = NULL; u_int nx = ctx->sx, i, x, rx, ry; log_debug("%s: %s %u %d", __func__, tty->client->name, py, ctx->bigger); if (wp) - visible_ranges = screen_redraw_get_visible_ranges(wp, 0, py, nx); + vr = screen_redraw_get_visible_ranges(wp, 0, py, nx); if (!ctx->bigger) { tty_draw_line(tty, s, 0, py, nx, ctx->xoff, ctx->yoff + py, - &ctx->defaults, ctx->palette, visible_ranges); + &ctx->defaults, ctx->palette, vr); return; } if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry)) { tty_draw_line(tty, s, i, py, rx, x, ry, &ctx->defaults, - ctx->palette, visible_ranges); + ctx->palette, vr); } } @@ -1468,7 +1468,7 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, void tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, u_int atx, u_int aty, const struct grid_cell *defaults, - struct colour_palette *palette, struct visible_ranges *visible_ranges) + struct colour_palette *palette, struct visible_range *vr) { struct grid *gd = s->grid; struct grid_cell gc, last; @@ -1555,8 +1555,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, gcp = tty_check_codeset(tty, &gc); if (len != 0 && (!tty_check_overlay(tty, atx + ux + width, aty) || - screen_redraw_is_visible(visible_ranges, - atx + ux + width) || + screen_redraw_is_visible(vr, atx + ux + width) || (gcp->attr & GRID_ATTR_CHARSET) || gcp->flags != last.flags || gcp->attr != last.attr || @@ -2217,7 +2216,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) struct screen *s = ctx->s; struct overlay_ranges r; struct window_pane *wp = ctx->arg; - struct visible_ranges *visible_ranges; + struct visible_range *vr; u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2227,7 +2226,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) return; if (wp) - visible_ranges = screen_redraw_get_visible_ranges(wp, px, py, 1); + vr = screen_redraw_get_visible_ranges(wp, px, py, 1); /* Handle partially obstructed wide characters. */ if (gcp->data.width > 1) { @@ -2236,8 +2235,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) vis += r.nx[i]; if (vis < gcp->data.width) { tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, - px, py, &ctx->defaults, ctx->palette, - visible_ranges); + px, py, &ctx->defaults, ctx->palette, vr); return; } } From 8a9e2fccbd01abd286d76ca5f40f4e63e6bc4fd3 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 24 Oct 2025 10:25:18 +0100 Subject: [PATCH 021/167] Move floating window stuff to new file: cmd-new-pane.c. --- Makefile.am | 1 + cmd-new-pane.c | 222 +++++++++++++++++++++++++++++++++++++++++++++ cmd-split-window.c | 192 --------------------------------------- 3 files changed, 223 insertions(+), 192 deletions(-) create mode 100644 cmd-new-pane.c diff --git a/Makefile.am b/Makefile.am index ff0de9f84d..79d4458a70 100644 --- a/Makefile.am +++ b/Makefile.am @@ -107,6 +107,7 @@ dist_tmux_SOURCES = \ cmd-load-buffer.c \ cmd-lock-server.c \ cmd-move-window.c \ + cmd-new-pane.c \ cmd-new-session.c \ cmd-new-window.c \ cmd-parse.y \ diff --git a/cmd-new-pane.c b/cmd-new-pane.c new file mode 100644 index 0000000000..d1dffaca0d --- /dev/null +++ b/cmd-new-pane.c @@ -0,0 +1,222 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include + +#include "tmux.h" + +#define NEW_WINDOW_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}" + +static enum cmd_retval cmd_new_floating_window_exec(struct cmd *, + struct cmdq_item *); + +const struct cmd_entry cmd_new_floating_window_entry = { + .name = "new-floating-window", + .alias = "floatw", + + .args = { "bc:de:fF:h:Il:p:Pt:w:x:y:Z", 0, -1, NULL }, + .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " + "[-F format] [-l size] " CMD_TARGET_PANE_USAGE + " [shell-command [argument ...]]", + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = 0, + .exec = cmd_new_floating_window_exec +}; + + +static enum cmd_retval +cmd_new_floating_window_exec(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct spawn_context sc = { 0 }; + struct client *tc = cmdq_get_target_client(item); + struct session *s = target->s; + struct winlink *wl = target->wl; + struct window *w = wl->window; + struct window_pane *wp = target->wp, *new_wp; + struct cmd_find_state fs; + int flags, input; + const char *template; + char *cause = NULL, *cp; + struct args_value *av; + u_int count = args_count(args); + u_int sx, sy, pct, x, y; + + if (args_has(args, 'f')) { + sx = w->sx; + sy = w->sy; + } else { + if (args_has(args, 'l')) { + sx = args_percentage_and_expand(args, 'l', 0, INT_MAX, w->sx, + item, &cause); + sy = args_percentage_and_expand(args, 'l', 0, INT_MAX, w->sy, + item, &cause); + } else if (args_has(args, 'p')) { + pct = args_strtonum_and_expand(args, 'p', 0, 100, item, + &cause); + if (cause == NULL) { + sx = w->sx * pct / 100; + sy = w->sy * pct / 100; + } + } else if (cause == NULL) { + sx = w->sx / 2; + sy = w->sy / 2; + } + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + if (args_has(args, 'w')) { + sx = args_strtonum_and_expand(args, 'w', 0, w->sx, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + if (args_has(args, 'h')) { + sy = args_strtonum_and_expand(args, 'h', 0, w->sy, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + if (args_has(args, 'x')) { + x = args_strtonum_and_expand(args, 'x', 0, w->sx, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } else + x = 10; + if (args_has(args, 'y')) { + y = args_strtonum_and_expand(args, 'y', 0, w->sx, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } else + y = 10; + + sc.xoff = x; + sc.yoff = y; + sc.sx = sx; + sc.sy = sy; + + input = (args_has(args, 'I') && count == 0); + + flags = SPAWN_FLOATING; + if (args_has(args, 'b')) + flags |= SPAWN_BEFORE; + if (args_has(args, 'f')) + flags |= SPAWN_FULLSIZE; + if (input || (count == 1 && *args_string(args, 0) == '\0')) + flags |= SPAWN_EMPTY; + + sc.item = item; + sc.s = s; + sc.wl = wl; + + sc.wp0 = wp; + sc.lc = NULL; + + args_to_vector(args, &sc.argc, &sc.argv); + sc.environ = environ_create(); + + av = args_first_value(args, 'e'); + while (av != NULL) { + environ_put(sc.environ, av->string, 0); + av = args_next_value(av); + } + + sc.idx = -1; + sc.cwd = args_get(args, 'c'); + + sc.flags = flags; + if (args_has(args, 'd')) + sc.flags |= SPAWN_DETACHED; + if (args_has(args, 'Z')) + sc.flags |= SPAWN_ZOOM; + + if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { + cmdq_error(item, "create pane failed: %s", cause); + free(cause); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); + return (CMD_RETURN_ERROR); + } + if (input) { + switch (window_pane_start_input(new_wp, item, &cause)) { + case -1: + server_client_remove_pane(new_wp); + window_remove_pane(wp->window, new_wp); + cmdq_error(item, "%s", cause); + free(cause); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); + return (CMD_RETURN_ERROR); + case 1: + input = 0; + break; + } + } + if (!args_has(args, 'd')) + cmd_find_from_winlink_pane(current, wl, new_wp, 0); + window_pop_zoom(wp->window); + server_redraw_window(wp->window); + server_status_session(s); + + if (args_has(args, 'P')) { + if ((template = args_get(args, 'F')) == NULL) + template = NEW_WINDOW_TEMPLATE; + cp = format_single(item, template, tc, s, wl, new_wp); + cmdq_print(item, "%s", cp); + free(cp); + } + + cmd_find_from_winlink_pane(&fs, wl, new_wp, 0); + cmdq_insert_hook(s, item, &fs, "after-split-window"); + + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); + if (input) + return (CMD_RETURN_WAIT); + return (CMD_RETURN_NORMAL); +} diff --git a/cmd-split-window.c b/cmd-split-window.c index f1a2246a6f..ca8354a76a 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -34,8 +34,6 @@ static enum cmd_retval cmd_split_window_exec(struct cmd *, struct cmdq_item *); -static enum cmd_retval cmd_new_floating_window_exec(struct cmd *, - struct cmdq_item *); const struct cmd_entry cmd_split_window_entry = { .name = "split-window", @@ -52,22 +50,6 @@ const struct cmd_entry cmd_split_window_entry = { .exec = cmd_split_window_exec }; -const struct cmd_entry cmd_new_floating_window_entry = { - .name = "new-floating-window", - .alias = "floatw", - - .args = { "bc:de:fF:h:Il:p:Pt:w:x:y:Z", 0, -1, NULL }, - .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " - "[-F format] [-l size] " CMD_TARGET_PANE_USAGE - " [shell-command [argument ...]]", - - .target = { 't', CMD_FIND_PANE, 0 }, - - .flags = 0, - .exec = cmd_new_floating_window_exec -}; - - static enum cmd_retval cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) { @@ -220,177 +202,3 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_WAIT); return (CMD_RETURN_NORMAL); } - -static enum cmd_retval -cmd_new_floating_window_exec(struct cmd *self, struct cmdq_item *item) -{ - struct args *args = cmd_get_args(self); - struct cmd_find_state *current = cmdq_get_current(item); - struct cmd_find_state *target = cmdq_get_target(item); - struct spawn_context sc = { 0 }; - struct client *tc = cmdq_get_target_client(item); - struct session *s = target->s; - struct winlink *wl = target->wl; - struct window *w = wl->window; - struct window_pane *wp = target->wp, *new_wp; - struct cmd_find_state fs; - int flags, input; - const char *template; - char *cause = NULL, *cp; - struct args_value *av; - u_int count = args_count(args); - u_int sx, sy, pct, x, y; - - if (args_has(args, 'f')) { - sx = w->sx; - sy = w->sy; - } else { - if (args_has(args, 'l')) { - sx = args_percentage_and_expand(args, 'l', 0, INT_MAX, w->sx, - item, &cause); - sy = args_percentage_and_expand(args, 'l', 0, INT_MAX, w->sy, - item, &cause); - } else if (args_has(args, 'p')) { - pct = args_strtonum_and_expand(args, 'p', 0, 100, item, - &cause); - if (cause == NULL) { - sx = w->sx * pct / 100; - sy = w->sy * pct / 100; - } - } else if (cause == NULL) { - sx = w->sx / 2; - sy = w->sy / 2; - } - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - } - if (args_has(args, 'w')) { - sx = args_strtonum_and_expand(args, 'w', 0, w->sx, item, - &cause); - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - } - if (args_has(args, 'h')) { - sy = args_strtonum_and_expand(args, 'h', 0, w->sy, item, - &cause); - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - } - if (args_has(args, 'x')) { - x = args_strtonum_and_expand(args, 'x', 0, w->sx, item, - &cause); - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - } else - x = 10; - if (args_has(args, 'y')) { - y = args_strtonum_and_expand(args, 'y', 0, w->sx, item, - &cause); - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - } else - y = 10; - - sc.xoff = x; - sc.yoff = y; - sc.sx = sx; - sc.sy = sy; - - input = (args_has(args, 'I') && count == 0); - - flags = SPAWN_FLOATING; - if (args_has(args, 'b')) - flags |= SPAWN_BEFORE; - if (args_has(args, 'f')) - flags |= SPAWN_FULLSIZE; - if (input || (count == 1 && *args_string(args, 0) == '\0')) - flags |= SPAWN_EMPTY; - - sc.item = item; - sc.s = s; - sc.wl = wl; - - sc.wp0 = wp; - sc.lc = NULL; - - args_to_vector(args, &sc.argc, &sc.argv); - sc.environ = environ_create(); - - av = args_first_value(args, 'e'); - while (av != NULL) { - environ_put(sc.environ, av->string, 0); - av = args_next_value(av); - } - - sc.idx = -1; - sc.cwd = args_get(args, 'c'); - - sc.flags = flags; - if (args_has(args, 'd')) - sc.flags |= SPAWN_DETACHED; - if (args_has(args, 'Z')) - sc.flags |= SPAWN_ZOOM; - - if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { - cmdq_error(item, "create pane failed: %s", cause); - free(cause); - if (sc.argv != NULL) - cmd_free_argv(sc.argc, sc.argv); - environ_free(sc.environ); - return (CMD_RETURN_ERROR); - } - if (input) { - switch (window_pane_start_input(new_wp, item, &cause)) { - case -1: - server_client_remove_pane(new_wp); - window_remove_pane(wp->window, new_wp); - cmdq_error(item, "%s", cause); - free(cause); - if (sc.argv != NULL) - cmd_free_argv(sc.argc, sc.argv); - environ_free(sc.environ); - return (CMD_RETURN_ERROR); - case 1: - input = 0; - break; - } - } - if (!args_has(args, 'd')) - cmd_find_from_winlink_pane(current, wl, new_wp, 0); - window_pop_zoom(wp->window); - server_redraw_window(wp->window); - server_status_session(s); - - if (args_has(args, 'P')) { - if ((template = args_get(args, 'F')) == NULL) - template = SPLIT_WINDOW_TEMPLATE; - cp = format_single(item, template, tc, s, wl, new_wp); - cmdq_print(item, "%s", cp); - free(cp); - } - - cmd_find_from_winlink_pane(&fs, wl, new_wp, 0); - cmdq_insert_hook(s, item, &fs, "after-split-window"); - - if (sc.argv != NULL) - cmd_free_argv(sc.argc, sc.argv); - environ_free(sc.environ); - if (input) - return (CMD_RETURN_WAIT); - return (CMD_RETURN_NORMAL); -} From 04fc6ea11b19c140f921c93351be5f339d5e4ba2 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 24 Oct 2025 14:24:26 +0100 Subject: [PATCH 022/167] Renamed floating window panes simple new-pane. --- cmd-new-pane.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd-new-pane.c b/cmd-new-pane.c index d1dffaca0d..b73f2c744e 100644 --- a/cmd-new-pane.c +++ b/cmd-new-pane.c @@ -26,14 +26,14 @@ #include "tmux.h" -#define NEW_WINDOW_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}" +#define NEW_PANE_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}" -static enum cmd_retval cmd_new_floating_window_exec(struct cmd *, +static enum cmd_retval cmd_new_pane_exec(struct cmd *, struct cmdq_item *); -const struct cmd_entry cmd_new_floating_window_entry = { - .name = "new-floating-window", - .alias = "floatw", +const struct cmd_entry cmd_new_pane_entry = { + .name = "new-pane", + .alias = "newp", .args = { "bc:de:fF:h:Il:p:Pt:w:x:y:Z", 0, -1, NULL }, .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " @@ -43,12 +43,12 @@ const struct cmd_entry cmd_new_floating_window_entry = { .target = { 't', CMD_FIND_PANE, 0 }, .flags = 0, - .exec = cmd_new_floating_window_exec + .exec = cmd_new_pane_exec }; static enum cmd_retval -cmd_new_floating_window_exec(struct cmd *self, struct cmdq_item *item) +cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); struct cmd_find_state *current = cmdq_get_current(item); @@ -204,7 +204,7 @@ cmd_new_floating_window_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) - template = NEW_WINDOW_TEMPLATE; + template = NEW_PANE_TEMPLATE; cp = format_single(item, template, tc, s, wl, new_wp); cmdq_print(item, "%s", cp); free(cp); From cc83ca66288b4b9763a4e63818461da4c4f42075 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 26 Oct 2025 14:02:57 +0000 Subject: [PATCH 023/167] Add new newp command. --- cmd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd.c b/cmd.c index b9d6e0e927..ea27c851ca 100644 --- a/cmd.c +++ b/cmd.c @@ -109,7 +109,7 @@ extern const struct cmd_entry cmd_show_prompt_history_entry; extern const struct cmd_entry cmd_show_window_options_entry; extern const struct cmd_entry cmd_source_file_entry; extern const struct cmd_entry cmd_split_window_entry; -extern const struct cmd_entry cmd_new_floating_window_entry; +extern const struct cmd_entry cmd_new_pane_entry; extern const struct cmd_entry cmd_start_server_entry; extern const struct cmd_entry cmd_suspend_client_entry; extern const struct cmd_entry cmd_swap_pane_entry; @@ -202,7 +202,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_show_window_options_entry, &cmd_source_file_entry, &cmd_split_window_entry, - &cmd_new_floating_window_entry, + &cmd_new_pane_entry, &cmd_start_server_entry, &cmd_suspend_client_entry, &cmd_swap_pane_entry, From 984fbacccf6d20f8590e82c41dc5b657e4944631 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 26 Oct 2025 14:18:27 +0000 Subject: [PATCH 024/167] Change the visible_ranges array to work more like the overlay_ranges array, except be able to grow. --- screen-redraw.c | 104 +++++++++++++++++++++++------------------------- screen-write.c | 10 ++--- tmux.h | 14 ++++--- 3 files changed, 63 insertions(+), 65 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 8088aabd74..96023fce3c 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -545,7 +545,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) struct tty *tty = &c->tty; struct window_pane *wp; struct screen *s; - struct visible_range *vr; + struct visible_ranges *vr; u_int i, x, width, xoff, yoff, size; log_debug("%s: %s @%u", __func__, c->name, w->id); @@ -922,7 +922,7 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) * floating window pane). Returns a boolean. */ int -screen_redraw_is_visible(struct visible_range *vr, u_int px) +screen_redraw_is_visible(struct visible_ranges *vr, u_int px) { u_int r; @@ -930,10 +930,10 @@ screen_redraw_is_visible(struct visible_range *vr, u_int px) if (vr == NULL) return (1); - for (r=0; vr[r].nx != -1; r++) { - if (vr[r].nx == 0) + for (r=0; r < vr->used; r++) { + if (vr->nx[r] == 0) continue; - if ((px >= vr[r].px) && (px <= vr[r].px + vr[r].nx)) + if ((px >= vr->px[r]) && (px <= vr->px[r] + vr->nx[r])) return (1); } return (0); @@ -941,30 +941,30 @@ screen_redraw_is_visible(struct visible_range *vr, u_int px) /* Construct ranges array for the line at starting at px,py of width cells of base_wp that are unobsructed. */ -struct visible_range * +struct visible_ranges * screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, u_int py, u_int width) { struct window_pane *wp; struct window *w; - static struct visible_range *vr = NULL; - static size_t size = 0, last; + static struct visible_ranges vr = {NULL, NULL, 0, 0}; int found_self, sb_w; u_int r, s, lb, rb, tb, bb; int pane_scrollbars; - /* For efficiency ranges is static and space reused. */ - if (vr == NULL) { - vr = xcalloc(4, sizeof(struct visible_range *)); - size = 4; + /* For efficiency vr is static and space reused. */ + if (vr.size == 0) { + vr.px = xcalloc(1, sizeof(u_int)); + vr.nx = xcalloc(1, sizeof(u_int)); + vr.size = 1; } /* Start with the entire width of the range. */ - vr[0].px = px; - vr[0].nx = width; - last = 1; + vr.px[0] = px; + vr.nx[0] = width; + vr.used = 1; if (base_wp == NULL) - return (vr); + return (&vr); w = base_wp->window; pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); @@ -987,62 +987,58 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, if (window_pane_show_scrollbar(wp, pane_scrollbars)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - for (r=0; rxoff - 1; rb = wp->xoff + wp->sx + sb_w + 1; /* If the left edge of floating wp falls inside this range and right edge covers up to right of range, then shrink left edge of range. */ - if (lb > vr[r].px && - lb < vr[r].px + vr[r].nx && - rb >= vr[r].px + vr[r].nx) { - vr[r].nx = lb; + if (lb > vr.px[r] && + lb < vr.px[r] + vr.nx[r] && + rb >= vr.px[r] + vr.nx[r]) { + vr.nx[r] = lb; } /* Else if the right edge of floating wp falls inside of this range and left edge covers the left of range, then move px forward to right edge of wp. */ - else if (rb > vr[r].px && - rb < vr[r].px + vr[r].nx && - lb <= vr[r].px) { - vr[r].nx = vr[r].nx - (rb - vr[r].px); - vr[r].px = vr[r].px + (rb - vr[r].px); + else if (rb > vr.px[r] && + rb < vr.px[r] + vr.nx[r] && + lb <= vr.px[r]) { + vr.nx[r] = vr.nx[r] - (rb - vr.px[r]); + vr.px[r] = vr.px[r] + (rb - vr.px[r]); } /* Else if wp fully inside range then split range into 2 ranges. */ - else if (lb > vr[r].px && - rb < vr[r].px + vr[r].nx) { - if (size == last) { - vr = xreallocarray(vr, - size += 4, sizeof *vr); + else if (lb > vr.px[r] && + rb < vr.px[r] + vr.nx[r]) { + if (vr.size == vr.used) { + vr.size++; + vr.px = xreallocarray(vr.px, + vr.size, sizeof (u_int)); + vr.nx = xreallocarray(vr.nx, + vr.size, sizeof (u_int)); } - for (s=last; s>r; s--) { - vr[s].px = vr[s-1].px; - vr[s].nx = vr[s-1].nx; + for (s=vr.used; s>r; s--) { + vr.px[s] = vr.px[s-1]; + vr.nx[s] = vr.nx[s-1]; } - last++; - vr[r].nx = lb; - vr[r+1].px = rb; + vr.used++; + vr.nx[r] = lb; + vr.px[r+1] = rb; } /* If floating wp completely covers this range then delete it (make it 0 length). */ - else if (lb <= vr[r].px && - rb >= vr[r].px+vr[r].nx) { - vr[r].nx = 0; + else if (lb <= vr.px[r] && + rb >= vr.px[r] + vr.nx[r]) { + vr.nx[r] = 0; } /* Else the range is already obscured, do nothing. */ } } - /* Tie off array. */ - if (size == last) { - vr = xreallocarray(vr, size += 4, sizeof *vr); - } - vr[last].px = 0; - vr[last].nx = -1; - - return (vr); + return (&vr); } @@ -1056,7 +1052,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct screen *s = wp->screen; struct colour_palette *palette = &wp->palette; struct grid_cell defaults; - struct visible_range *vr; + struct visible_ranges *vr; u_int i, j, top, x, y, width, r; log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); @@ -1104,14 +1100,14 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) tty_default_colours(&defaults, wp); - for (r=0; vr[r].nx != -1; r++) { - if (vr[r].nx == 0) + for (r=0; r < vr->used; r++) { + if (vr->nx[r] == 0) continue; /* i is px of cell, add px of region, sub the pane offset. If you don't sub offset, contents of pane shifted. */ - tty_draw_line(tty, s, i+vr[r].px-wp->xoff, j, - vr[r].nx, vr[r].px, y, &defaults, palette, + tty_draw_line(tty, s, i + vr->px[r] - wp->xoff, j, + vr->nx[r], vr->px[r], y, &defaults, palette, vr); } } @@ -1203,7 +1199,7 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, int px, py, ox = ctx->ox, oy = ctx->oy; int sx = ctx->sx, sy = ctx->sy, xoff = wp->xoff; int yoff = wp->yoff; - struct visible_range *vr; + struct visible_ranges *vr; /* Set up style for slider. */ gc = sb_style->gc; diff --git a/screen-write.c b/screen-write.c index bccbdedea0..a987f81cfe 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1783,7 +1783,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, u_int r_start, r_end, ci_start, ci_end; u_int wr_start, wr_end, wr_length, sx, xoff, yoff; struct tty_ctx ttyctx; - struct visible_range *vr; + struct visible_ranges *vr; struct window_pane *wp = ctx->wp; if (ctx->scrolled != 0) { @@ -1833,10 +1833,10 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ci->x, last); } wr_length = 0; - for (r = 0; vr[r].nx != -1; r++) { - if (vr[r].nx == 0) continue; - r_start = vr[r].px; - r_end = vr[r].px + vr[r].nx; + for (r = 0; r < vr->used; r++) { + if (vr->nx[r] == 0) continue; + r_start = vr->px[r]; + r_end = vr->px[r] + vr->nx[r]; ci_start = ci->x; ci_end = ci->x + ci->used; diff --git a/tmux.h b/tmux.h index 4bb92a6593..78d219454a 100644 --- a/tmux.h +++ b/tmux.h @@ -2235,9 +2235,11 @@ struct mode_tree_sort_criteria { }; /* Visible range array element. nx==-1 is end of array mark. */ -struct visible_range { - u_int px; /* Start */ - int nx; /* Length */ +struct visible_ranges { + u_int *px; /* Start */ + u_int *nx; /* Length */ + size_t used; + size_t size; }; /* tmux.c */ @@ -2528,7 +2530,7 @@ void tty_set_path(struct tty *, const char *); void tty_update_mode(struct tty *, int, struct screen *); void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, u_int, u_int, const struct grid_cell *, struct colour_palette *, - struct visible_range *); + struct visible_ranges *); #ifdef ENABLE_SIXEL void tty_draw_images(struct client *, struct window_pane *, struct screen *); @@ -3171,8 +3173,8 @@ void screen_write_alternateoff(struct screen_write_ctx *, /* screen-redraw.c */ void screen_redraw_screen(struct client *); void screen_redraw_pane(struct client *, struct window_pane *, int); -int screen_redraw_is_visible(struct visible_range *, u_int px); -struct visible_range *screen_redraw_get_visible_ranges(struct window_pane *, +int screen_redraw_is_visible(struct visible_ranges *, u_int px); +struct visible_ranges *screen_redraw_get_visible_ranges(struct window_pane *, u_int, u_int, u_int); From 6344bab6ccdc05c8af7506df398af5e11ac85e79 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 27 Oct 2025 19:54:21 +0000 Subject: [PATCH 025/167] Fix typo with visible_ranges struct name. Add support for checking visual ranges to tty_draw_pane. --- tty.c | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/tty.c b/tty.c index 07b89334ca..4ad746ce26 100644 --- a/tty.c +++ b/tty.c @@ -1373,22 +1373,40 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) { struct screen *s = ctx->s; struct window_pane *wp = ctx->arg; - struct visible_range *vr = NULL; - u_int nx = ctx->sx, i, x, rx, ry; + struct visible_ranges *vr = NULL; + u_int nx = ctx->sx, i, x, rx, ry, r; log_debug("%s: %s %u %d", __func__, tty->client->name, py, ctx->bigger); - if (wp) - vr = screen_redraw_get_visible_ranges(wp, 0, py, nx); - if (!ctx->bigger) { - tty_draw_line(tty, s, 0, py, nx, ctx->xoff, ctx->yoff + py, - &ctx->defaults, ctx->palette, vr); + if (wp) { + vr = screen_redraw_get_visible_ranges(wp, 0, py, nx); + for (r=0; r < vr->used; r++) { + if (vr->nx[r] == 0) + continue; + tty_draw_line(tty, s, vr->px[r], py, vr->nx[r], + ctx->xoff + vr->px[r], ctx->yoff + py, + &ctx->defaults, ctx->palette, vr); + } + } else { + tty_draw_line(tty, s, 0, py, nx, ctx->xoff, ctx->yoff + py, + &ctx->defaults, ctx->palette, vr); + } return; } if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry)) { - tty_draw_line(tty, s, i, py, rx, x, ry, &ctx->defaults, - ctx->palette, vr); + if (wp) { + vr = screen_redraw_get_visible_ranges(wp, i, py, rx); + for (r=0; r < vr->used; r++) { + if (vr->nx[r] == 0) + continue; + tty_draw_line(tty, s, i, py, vr->nx[r], x + vr->px[r], ry, &ctx->defaults, + ctx->palette, vr); + } + } else { + tty_draw_line(tty, s, i, py, rx, x, ry, &ctx->defaults, + ctx->palette, vr); + } } } @@ -1468,7 +1486,7 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, void tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, u_int atx, u_int aty, const struct grid_cell *defaults, - struct colour_palette *palette, struct visible_range *vr) + struct colour_palette *palette, struct visible_ranges *vr) { struct grid *gd = s->grid; struct grid_cell gc, last; @@ -2216,7 +2234,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) struct screen *s = ctx->s; struct overlay_ranges r; struct window_pane *wp = ctx->arg; - struct visible_range *vr; + struct visible_ranges *vr; u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; From 10dc308e6b04a3cb74980a73ce8d8d9300a07db6 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 27 Oct 2025 21:28:31 +0000 Subject: [PATCH 026/167] Add checking the redraw of floating panes and the pane border status. --- screen-redraw.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 96023fce3c..b9ec6ce89e 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -546,7 +546,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) struct window_pane *wp; struct screen *s; struct visible_ranges *vr; - u_int i, x, width, xoff, yoff, size; + u_int i, x, width, xoff, yoff, size, r; log_debug("%s: %s @%u", __func__, c->name, w->id); @@ -592,10 +592,14 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) if (ctx->statustop) yoff += ctx->statuslines; - vr = screen_redraw_get_visible_ranges(wp, i, 0, width); + vr = screen_redraw_get_visible_ranges(wp, x, yoff - ctx->oy, width); - tty_draw_line(tty, s, i, 0, width, x, yoff - ctx->oy, - &grid_default_cell, NULL, vr); + for (r=0; r < vr->used; r++) { + if (vr->nx[r] == 0) + continue; + tty_draw_line(tty, s, i + (vr->px[r] - x), 0, vr->nx[r], vr->px[r], yoff - ctx->oy, + &grid_default_cell, NULL, vr); + } } tty_cursor(tty, 0, 0); } From 28d038fb161639e1c083cad3828d76ac6ff74a1f Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 27 Oct 2025 22:18:07 +0000 Subject: [PATCH 027/167] Bugfix calculation error on bottom border when pane border status on and scrollbar enabled. --- screen-redraw.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index b9ec6ce89e..523f51031c 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -410,10 +410,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, /* Check if CELL_SCROLLBAR */ if (window_pane_show_scrollbar(wp, pane_scrollbars)) { - if (pane_status == PANE_STATUS_TOP) - line = wp->yoff - 1; - else - line = wp->yoff + wp->sy; + line = wp->yoff + wp->sy; /* * Check if py could lie within a scrollbar. If the From 0cd3ab629907f541fcadbf9f0290dedaf6b59099 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 27 Oct 2025 23:35:53 +0000 Subject: [PATCH 028/167] Fix active border colour if only floating panes in the window. --- screen-redraw.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 523f51031c..82e8ea7dc2 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -371,6 +371,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, if (!window_pane_visible(wp)) goto next1; + /* xxxx Isn't this only true if pane at the top of the window? */ if (pane_status == PANE_STATUS_TOP) line = wp->yoff - 1; else @@ -419,8 +420,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, */ sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - if ((pane_status && py != line) || - (wp->yoff == 0 && py < wp->sy) || + if ((wp->yoff == 0 && py < wp->sy) || (py >= wp->yoff && py < wp->yoff + wp->sy)) { /* Check if px lies within a scrollbar. */ if ((sb_pos == PANE_SCROLLBARS_RIGHT && @@ -792,7 +792,7 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) if (cell_type == CELL_INSIDE || cell_type == CELL_SCROLLBAR) return; - if (wp == NULL) { + if (wp == NULL || cell_type == CELL_OUTSIDE) { if (!ctx->no_pane_gc_set) { ft = format_create_defaults(NULL, c, s, s->curw, NULL); memcpy(&ctx->no_pane_gc, &grid_default_cell, sizeof gc); From 25ce5b4281eb877008860068eefb630f7a63860d Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 28 Oct 2025 08:07:32 +0000 Subject: [PATCH 029/167] Add separate z-index list. Each window has its own z-order list of panes now. --- screen-redraw.c | 12 ++++++------ screen-write.c | 4 ++-- tmux.h | 3 +++ tty.c | 2 +- window.c | 10 +++++++--- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 82e8ea7dc2..c439eff86a 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -104,9 +104,9 @@ screen_redraw_two_panes(struct window *w, int direction) { struct window_pane *wp; - wp = TAILQ_FIRST(&w->panes); + wp = TAILQ_FIRST(&w->z_index); do { - wp = TAILQ_NEXT(wp, entry); + wp = TAILQ_NEXT(wp, zentry); } while (wp && wp->layout_cell == NULL); if (wp == NULL) @@ -234,7 +234,7 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, u_int px, u_int py) return (1); /* Check all the panes. */ - TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) { + TAILQ_FOREACH(wp, &w->z_index, zentry) { if (!window_pane_visible(wp)) continue; switch (screen_redraw_pane_border(ctx, wp, px, py)) { @@ -356,7 +356,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, if (pane_status != PANE_STATUS_OFF) { /* Look for higest z-index window at px,py. xxxx scrollbars? */ - TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) { + TAILQ_FOREACH(wp, &w->z_index, zentry) { if (! (wp->flags & PANE_MINIMISED) && (px >= wp->xoff - 1 && px<= wp->xoff + wp->sx + 1) && (py >= wp->yoff - 1 && py<= wp->yoff + wp->sy + 1)) @@ -390,7 +390,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, /* Look for higest z-index window at px,py. */ - TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) { + TAILQ_FOREACH(wp, &w->z_index, zentry) { sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; if (! (wp->flags & PANE_MINIMISED) && @@ -971,7 +971,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); found_self = 0; - TAILQ_FOREACH(wp, &w->panes, entry) { + TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { if (wp == base_wp) { found_self = 1; continue; diff --git a/screen-write.c b/screen-write.c index a987f81cfe..f0b3542b49 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1783,7 +1783,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, u_int r_start, r_end, ci_start, ci_end; u_int wr_start, wr_end, wr_length, sx, xoff, yoff; struct tty_ctx ttyctx; - struct visible_ranges *vr; + struct visible_ranges *vr; struct window_pane *wp = ctx->wp; if (ctx->scrolled != 0) { @@ -2023,7 +2023,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) w = base_wp->window; px = ctx->s->cx; py = ctx->s->cy; - TAILQ_FOREACH(wp, &w->panes, entry) { + TAILQ_FOREACH(wp, &w->panes, zentry) { if (wp == base_wp) { found_self = 1; continue; diff --git a/tmux.h b/tmux.h index 78d219454a..a9ef147b9f 100644 --- a/tmux.h +++ b/tmux.h @@ -1230,9 +1230,11 @@ struct window_pane { TAILQ_ENTRY(window_pane) entry; /* link in list of all panes */ TAILQ_ENTRY(window_pane) sentry; /* link in list of last visited */ + TAILQ_ENTRY(window_pane) zentry; /* z-index link in list of all panes */ RB_ENTRY(window_pane) tree_entry; }; TAILQ_HEAD(window_panes, window_pane); +TAILQ_HEAD(window_panes_zindex, window_pane); RB_HEAD(window_pane_tree, window_pane); /* Window structure. */ @@ -1251,6 +1253,7 @@ struct window { struct window_pane *active; struct window_panes last_panes; + struct window_panes z_index; struct window_panes panes; int lastlayout; diff --git a/tty.c b/tty.c index 4ad746ce26..148b761544 100644 --- a/tty.c +++ b/tty.c @@ -2041,7 +2041,7 @@ tty_is_obscured(const struct tty_ctx *ctx) w = base_wp->window; /* Check if there is a floating pane. xxxx borders? scrollbars? */ - TAILQ_FOREACH(wp, &w->panes, entry) { + TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { if (wp == base_wp) { found_self = 1; continue; diff --git a/window.c b/window.c index 461d672ef1..62a53fbb90 100644 --- a/window.c +++ b/window.c @@ -306,6 +306,7 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel) w->flags = 0; TAILQ_INIT(&w->panes); + TAILQ_INIT(&w->z_index); TAILQ_INIT(&w->last_panes); w->active = NULL; @@ -586,8 +587,8 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) if (wp == w->active) break; if (wp->layout_cell == NULL) { - TAILQ_REMOVE(&w->panes, wp, entry); - TAILQ_INSERT_TAIL(&w->panes, wp, entry); + TAILQ_REMOVE(&w->z_index, wp, zentry); + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); wp->flags |= PANE_REDRAW; } wp = w->active; @@ -600,7 +601,7 @@ window_get_active_at(struct window *w, u_int x, u_int y) struct window_pane *wp; u_int xoff, yoff, sx, sy; - TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) { + TAILQ_FOREACH(wp, &w->z_index, zentry) { if (!window_pane_visible(wp)) continue; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); @@ -765,6 +766,7 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, else TAILQ_INSERT_AFTER(&w->panes, other, wp, entry); } + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); return (wp); } @@ -799,6 +801,7 @@ window_remove_pane(struct window *w, struct window_pane *wp) window_lost_pane(w, wp); TAILQ_REMOVE(&w->panes, wp, entry); + TAILQ_REMOVE(&w->z_index, wp, zentry); window_pane_destroy(wp); } @@ -882,6 +885,7 @@ window_destroy_panes(struct window *w) while (!TAILQ_EMPTY(&w->panes)) { wp = TAILQ_FIRST(&w->panes); TAILQ_REMOVE(&w->panes, wp, entry); + TAILQ_REMOVE(&w->z_index, wp, zentry); window_pane_destroy(wp); } } From 7980d00e8c54f096b5c9e2fde9f61351c565d1b2 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 28 Oct 2025 11:01:27 +0000 Subject: [PATCH 030/167] Attempt to take care of case of partially obscured wide characters by floating panes. (Not yet tested!) --- tty.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tty.c b/tty.c index 148b761544..24a6fab2b9 100644 --- a/tty.c +++ b/tty.c @@ -2235,7 +2235,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) struct overlay_ranges r; struct window_pane *wp = ctx->arg; struct visible_ranges *vr; - u_int px, py, i, vis = 0; + u_int px, py, i, vis = 0, vis2 = 0; px = ctx->xoff + ctx->ocx - ctx->wox; py = ctx->yoff + ctx->ocy - ctx->woy; @@ -2243,15 +2243,17 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) (gcp->data.width == 1 && !tty_check_overlay(tty, px, py))) return; - if (wp) - vr = screen_redraw_get_visible_ranges(wp, px, py, 1); - /* Handle partially obstructed wide characters. */ if (gcp->data.width > 1) { + vr = screen_redraw_get_visible_ranges(wp, px, py, + gcp->data.width); + for (i = 0; i < vr->used; i++) + vis2 += vr->nx[i]; tty_check_overlay_range(tty, px, py, gcp->data.width, &r); for (i = 0; i < OVERLAY_MAX_RANGES; i++) vis += r.nx[i]; - if (vis < gcp->data.width) { + if (vis < gcp->data.width || + vis2 < gcp->data.width) { tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, px, py, &ctx->defaults, ctx->palette, vr); return; From 39d2839e37c1c6e95ab12445fb1be86eb728ccd0 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 28 Oct 2025 12:50:13 +0000 Subject: [PATCH 031/167] Bugfix visible_ranges calculation, no longer need to inject vr into tty_draw_line. --- menu.c | 2 +- popup.c | 2 +- screen-redraw.c | 10 +++++----- tmux.h | 3 +-- tty.c | 20 ++++++++++---------- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/menu.c b/menu.c index b43bfd8dd9..fd3a9fe400 100644 --- a/menu.c +++ b/menu.c @@ -217,7 +217,7 @@ menu_draw_cb(struct client *c, void *data, for (i = 0; i < screen_size_y(&md->s); i++) { tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i, - &grid_default_cell, NULL, NULL); + &grid_default_cell, NULL); } } diff --git a/popup.c b/popup.c index 1011c3c22a..4d837a9b12 100644 --- a/popup.c +++ b/popup.c @@ -250,7 +250,7 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) } for (i = 0; i < pd->sy; i++) { tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults, - palette, NULL); + palette); } screen_free(&s); if (pd->md != NULL) { diff --git a/screen-redraw.c b/screen-redraw.c index c439eff86a..9dc7e4538f 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -595,7 +595,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) if (vr->nx[r] == 0) continue; tty_draw_line(tty, s, i + (vr->px[r] - x), 0, vr->nx[r], vr->px[r], yoff - ctx->oy, - &grid_default_cell, NULL, vr); + &grid_default_cell, NULL); } } tty_cursor(tty, 0, 0); @@ -915,7 +915,7 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) y = c->tty.sy - ctx->statuslines; for (i = 0; i < ctx->statuslines; i++) { tty_draw_line(tty, s, 0, i, UINT_MAX, 0, y + i, - &grid_default_cell, NULL, NULL); + &grid_default_cell, NULL); } } @@ -990,7 +990,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, for (r=0; r < vr.used; r++) { lb = wp->xoff - 1; - rb = wp->xoff + wp->sx + sb_w + 1; + rb = wp->xoff + wp->sx + sb_w; /* If the left edge of floating wp falls inside this range and right edge covers up to right of range, @@ -1028,6 +1028,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, vr.used++; vr.nx[r] = lb; vr.px[r+1] = rb; + vr.nx[r+1] = vr.nx[r+1] - rb - 1; } /* If floating wp completely covers this range then delete it (make it 0 length). */ @@ -1108,8 +1109,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) pane offset. If you don't sub offset, contents of pane shifted. */ tty_draw_line(tty, s, i + vr->px[r] - wp->xoff, j, - vr->nx[r], vr->px[r], y, &defaults, palette, - vr); + vr->nx[r], vr->px[r], y, &defaults, palette); } } diff --git a/tmux.h b/tmux.h index a9ef147b9f..6db37de879 100644 --- a/tmux.h +++ b/tmux.h @@ -2532,8 +2532,7 @@ void tty_set_title(struct tty *, const char *); void tty_set_path(struct tty *, const char *); void tty_update_mode(struct tty *, int, struct screen *); void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, - u_int, u_int, const struct grid_cell *, struct colour_palette *, - struct visible_ranges *); + u_int, u_int, const struct grid_cell *, struct colour_palette *); #ifdef ENABLE_SIXEL void tty_draw_images(struct client *, struct window_pane *, struct screen *); diff --git a/tty.c b/tty.c index 24a6fab2b9..a169eab999 100644 --- a/tty.c +++ b/tty.c @@ -1385,12 +1385,12 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) if (vr->nx[r] == 0) continue; tty_draw_line(tty, s, vr->px[r], py, vr->nx[r], - ctx->xoff + vr->px[r], ctx->yoff + py, - &ctx->defaults, ctx->palette, vr); + ctx->xoff + vr->px[r], ctx->yoff + py, + &ctx->defaults, ctx->palette); } } else { - tty_draw_line(tty, s, 0, py, nx, ctx->xoff, ctx->yoff + py, - &ctx->defaults, ctx->palette, vr); + tty_draw_line(tty, s, 0, py, nx, ctx->xoff, + ctx->yoff + py, &ctx->defaults, ctx->palette); } return; } @@ -1400,12 +1400,13 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) for (r=0; r < vr->used; r++) { if (vr->nx[r] == 0) continue; - tty_draw_line(tty, s, i, py, vr->nx[r], x + vr->px[r], ry, &ctx->defaults, - ctx->palette, vr); + tty_draw_line(tty, s, i, py, vr->nx[r], + x + vr->px[r], ry, &ctx->defaults, + ctx->palette); } } else { tty_draw_line(tty, s, i, py, rx, x, ry, &ctx->defaults, - ctx->palette, vr); + ctx->palette); } } } @@ -1486,7 +1487,7 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, void tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, u_int atx, u_int aty, const struct grid_cell *defaults, - struct colour_palette *palette, struct visible_ranges *vr) + struct colour_palette *palette) { struct grid *gd = s->grid; struct grid_cell gc, last; @@ -1573,7 +1574,6 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, gcp = tty_check_codeset(tty, &gc); if (len != 0 && (!tty_check_overlay(tty, atx + ux + width, aty) || - screen_redraw_is_visible(vr, atx + ux + width) || (gcp->attr & GRID_ATTR_CHARSET) || gcp->flags != last.flags || gcp->attr != last.attr || @@ -2255,7 +2255,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) if (vis < gcp->data.width || vis2 < gcp->data.width) { tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, - px, py, &ctx->defaults, ctx->palette, vr); + px, py, &ctx->defaults, ctx->palette); return; } } From 34e858ea05f428441cfcf01d7db6ce4850f1cf39 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 30 Oct 2025 21:42:32 +0100 Subject: [PATCH 032/167] Add support to drag and resize floating window panes. --- cmd-resize-pane.c | 133 ++++++++++++++++++++++++++++++++++++++++++++-- server-client.c | 9 ++++ tmux.h | 3 +- window.c | 12 +++++ 4 files changed, 152 insertions(+), 5 deletions(-) diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index b2c167f59f..706c78dee1 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -29,7 +29,9 @@ static enum cmd_retval cmd_resize_pane_exec(struct cmd *, struct cmdq_item *); -static void cmd_resize_pane_mouse_update(struct client *, +static void cmd_resize_pane_mouse_update_floating(struct client *, + struct mouse_event *); +static void cmd_resize_pane_mouse_update_tiled(struct client *, struct mouse_event *); const struct cmd_entry cmd_resize_pane_entry = { @@ -80,8 +82,13 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); if (c == NULL || c->session != s) return (CMD_RETURN_NORMAL); - c->tty.mouse_drag_update = cmd_resize_pane_mouse_update; - cmd_resize_pane_mouse_update(c, &event->m); + if (c->tty.mouse_wp->layout_cell != NULL) { + c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_tiled; + cmd_resize_pane_mouse_update_tiled(c, &event->m); + } else { + c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_floating; + cmd_resize_pane_mouse_update_floating(c, &event->m); + } return (CMD_RETURN_NORMAL); } @@ -149,7 +156,125 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) } static void -cmd_resize_pane_mouse_update(struct client *c, struct mouse_event *m) +cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) +{ + struct winlink *wl; + struct window *w; + struct window_pane *wp; + u_int y, ly, x, lx, new_sx, new_sy; + + wl = cmd_mouse_window(m, NULL); + if (wl == NULL) { + c->tty.mouse_drag_update = NULL; + return; + } + w = wl->window; + + y = m->y + m->oy; x = m->x + m->ox; + if (m->statusat == 0 && y >= m->statuslines) + y -= m->statuslines; + else if (m->statusat > 0 && y >= (u_int)m->statusat) + y = m->statusat - 1; + ly = m->ly + m->oy; lx = m->lx + m->ox; + if (m->statusat == 0 && ly >= m->statuslines) + ly -= m->statuslines; + else if (m->statusat > 0 && ly >= (u_int)m->statusat) + ly = m->statusat - 1; + + wp = c->tty.mouse_wp; + + log_debug("%s: %%%u resize_pane xoff=%u sx=%u xy=%ux%u lxy=%ux%u", + __func__, wp->id, wp->xoff, wp->sx, x, y, lx, ly); + if (((m->lx == wp->xoff - 1) || (m->lx == wp->xoff)) && + (m->ly == wp->yoff - 1)) { + /* Top left border */ + new_sx = wp->sx + (lx - x); + if (new_sx < PANE_MINIMUM) + new_sx = PANE_MINIMUM; + new_sy = wp->sy + (ly - y); + if (new_sy < PANE_MINIMUM) + new_sy = PANE_MINIMUM; + window_pane_move(wp, x + 1, y + 1); + window_pane_resize(wp, new_sx, new_sy); + server_redraw_window(w); + } else if (((m->lx == wp->xoff + wp->sx + 1) || + (m->lx == wp->xoff + wp->sx)) && + (m->ly == wp->yoff - 1)) { + /* Top right border */ + new_sx = x - wp->xoff - 1; + if (new_sx < PANE_MINIMUM) + new_sx = PANE_MINIMUM; + new_sy = wp->sy + (ly - y); + if (new_sy < PANE_MINIMUM) + new_sy = PANE_MINIMUM; + window_pane_move(wp, wp->xoff, y + 1); + window_pane_resize(wp, new_sx, new_sy); + server_redraw_window(w); + } else if (((m->lx == wp->xoff - 1) || (m->lx == wp->xoff)) && + (m->ly == wp->yoff + wp->sy)) { + /* Bottom left border */ + new_sx = wp->sx + (lx - x); + if (new_sx < PANE_MINIMUM) + new_sx = PANE_MINIMUM; + new_sy = y - wp->yoff; + if (new_sy < PANE_MINIMUM) + return; + window_pane_move(wp, x + 1, wp->yoff); + window_pane_resize(wp, new_sx, new_sy); + server_redraw_window(w); + } else if (((m->lx == wp->xoff + wp->sx + 1) || + (m->lx == wp->xoff + wp->sx)) && + (m->ly == wp->yoff + wp->sy)) { + /* Bottom right corner */ + new_sx = x - wp->xoff - 1; + if (new_sx < PANE_MINIMUM) + new_sx = PANE_MINIMUM; + new_sy = y - wp->yoff; + if (new_sy < PANE_MINIMUM) + new_sy = PANE_MINIMUM; + window_pane_resize(wp, new_sx, new_sy); + server_redraw_window(w); + } else if (m->lx == wp->xoff + wp->sx + 1) { + /* Right border */ + new_sx = x - wp->xoff - 1; + if (new_sx < PANE_MINIMUM) + return; + window_pane_resize(wp, new_sx, wp->sy); + server_redraw_window(w); + } else if (m->lx == wp->xoff - 1) { + /* Left border */ + new_sx = wp->sx + (lx - x); + if (new_sx < PANE_MINIMUM) + return; + window_pane_move(wp, x + 1, wp->yoff); + window_pane_resize(wp, new_sx, wp->sy); + server_redraw_window(w); + } else if (m->ly == wp->yoff + wp->sy) { + /* Bottom border */ + new_sy = y - wp->yoff; + if (new_sy < PANE_MINIMUM) + return; + window_pane_resize(wp, wp->sx, new_sy); + server_redraw_window(w); + } else if (m->ly == wp->yoff - 1) { + /* Top border */ + window_pane_move(wp, wp->xoff + (x - lx), y + 1); + /* + new_sy = wp->sy + (ly - y); + if (new_sy < PANE_MINIMUM) + return; + window_pane_move(wp, wp->xoff, y + 1); + window_pane_resize(wp, wp->sx, new_sy); + */ + server_redraw_window(w); + } else { + log_debug("%s: %%%u resize_pane xoff=%u sx=%u xy=%ux%u lxy=%ux%u ", + __func__, wp->id, wp->xoff, wp->sx, x, y, lx, ly); + } +} + +static void +cmd_resize_pane_mouse_update_tiled(struct client *c, struct mouse_event *m) { struct winlink *wl; struct window *w; diff --git a/server-client.c b/server-client.c index d8d516b568..02ab0e7893 100644 --- a/server-client.c +++ b/server-client.c @@ -637,6 +637,10 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, return (SCROLLBAR_SLIDER); } else /* py > sl_bottom */ return (SCROLLBAR_DOWN); + } else if (wp->layout_cell == NULL && + (px == wp->xoff - 1 || py == wp->yoff -1)) { + /* Floating pane left or top border. */ + return (BORDER); } else { /* Must be inside the pane. */ return (PANE); @@ -1063,6 +1067,7 @@ server_client_check_mouse(struct client *c, struct key_event *event) break; } c->tty.mouse_drag_flag = 0; + c->tty.mouse_wp = NULL; c->tty.mouse_slider_mpos = -1; goto out; } @@ -1281,6 +1286,10 @@ server_client_check_mouse(struct client *c, struct key_event *event) * where the user grabbed. */ c->tty.mouse_drag_flag = MOUSE_BUTTONS(b) + 1; + /* Only change pane if not already dragging a pane border. */ + if (c->tty.mouse_wp == NULL) { + c->tty.mouse_wp = wp; + } if (c->tty.mouse_scrolling_flag == 0 && where == SCROLLBAR_SLIDER) { c->tty.mouse_scrolling_flag = 1; diff --git a/tmux.h b/tmux.h index 6db37de879..2d85d0112c 100644 --- a/tmux.h +++ b/tmux.h @@ -1596,7 +1596,7 @@ struct tty { int mouse_drag_flag; int mouse_scrolling_flag; int mouse_slider_mpos; - + struct window_pane *mouse_wp; void (*mouse_drag_update)(struct client *, struct mouse_event *); void (*mouse_drag_release)(struct client *, @@ -3266,6 +3266,7 @@ struct window_pane *window_pane_find_by_id_str(const char *); struct window_pane *window_pane_find_by_id(u_int); int window_pane_destroy_ready(struct window_pane *); void window_pane_resize(struct window_pane *, u_int, u_int); +void window_pane_move(struct window_pane *, u_int, u_int); int window_pane_set_mode(struct window_pane *, struct window_pane *, const struct window_mode *, struct cmd_find_state *, struct args *); diff --git a/window.c b/window.c index 62a53fbb90..753f3abb8b 100644 --- a/window.c +++ b/window.c @@ -1117,6 +1117,18 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) wme->mode->resize(wme, sx, sy); } +void +window_pane_move(struct window_pane *wp, u_int xoff, u_int yoff) +{ + if (xoff == wp->xoff && yoff == wp->yoff) + return; + + wp->xoff = xoff; + wp->yoff = yoff; + + log_debug("%s: %%%u resize %ux%u", __func__, wp->id, xoff, yoff); +} + int window_pane_set_mode(struct window_pane *wp, struct window_pane *swp, const struct window_mode *mode, struct cmd_find_state *fs, From 6dd552d68974c4c72c86f0757269f99de7dfd0c2 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sat, 1 Nov 2025 21:47:54 +0100 Subject: [PATCH 033/167] Fix floating pane redraw bugs. Allow floating panes to be partly out of the window. This required changing xoff and yoff from u_int to int and it required a fair bit of casting for example when xoff is added to sx or comparing px to xoff. It makes sense for px and sx to be u_int since they refers to things which should never be negative. --- cmd-display-panes.c | 8 ++-- cmd-resize-pane.c | 52 ++++++++++++---------- cmd.c | 4 +- screen-redraw.c | 106 ++++++++++++++++++++++++++++---------------- screen-write.c | 4 +- server-client.c | 12 ++--- tmux.h | 10 ++--- tty.c | 8 ++-- window.c | 2 +- 9 files changed, 119 insertions(+), 87 deletions(-) diff --git a/cmd-display-panes.c b/cmd-display-panes.c index 25556f3d58..cd68808125 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -70,10 +70,10 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, char buf[16], lbuf[16], rbuf[16], *ptr; size_t len, llen, rlen; - if (wp->xoff + wp->sx <= ctx->ox || - wp->xoff >= ctx->ox + ctx->sx || - wp->yoff + wp->sy <= ctx->oy || - wp->yoff >= ctx->oy + ctx->sy) + if (wp->xoff + (int)wp->sx <= ctx->ox || + wp->xoff >= ctx->ox + (int)ctx->sx || + wp->yoff + (int)wp->sy <= ctx->oy || + wp->yoff >= ctx->oy + (int)ctx->sy) return; if (wp->xoff >= ctx->ox && wp->xoff + wp->sx <= ctx->ox + ctx->sx) { diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 706c78dee1..f34062a03a 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -161,7 +161,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) struct winlink *wl; struct window *w; struct window_pane *wp; - u_int y, ly, x, lx, new_sx, new_sy; + u_int y, ly, x, lx, new_sx, new_sy, resizes = 0; wl = cmd_mouse_window(m, NULL); if (wl == NULL) { @@ -176,7 +176,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) else if (m->statusat > 0 && y >= (u_int)m->statusat) y = m->statusat - 1; ly = m->ly + m->oy; lx = m->lx + m->ox; - if (m->statusat == 0 && ly >= m->statuslines) + if (m->statusat == 0 && ly >= (u_int)m->statuslines) ly -= m->statuslines; else if (m->statusat > 0 && ly >= (u_int)m->statusat) ly = m->statusat - 1; @@ -185,8 +185,8 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) log_debug("%s: %%%u resize_pane xoff=%u sx=%u xy=%ux%u lxy=%ux%u", __func__, wp->id, wp->xoff, wp->sx, x, y, lx, ly); - if (((m->lx == wp->xoff - 1) || (m->lx == wp->xoff)) && - (m->ly == wp->yoff - 1)) { + if ((((int)lx == wp->xoff - 1) || ((int)lx == wp->xoff)) && + ((int)ly == wp->yoff - 1)) { /* Top left border */ new_sx = wp->sx + (lx - x); if (new_sx < PANE_MINIMUM) @@ -196,10 +196,10 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) new_sy = PANE_MINIMUM; window_pane_move(wp, x + 1, y + 1); window_pane_resize(wp, new_sx, new_sy); - server_redraw_window(w); - } else if (((m->lx == wp->xoff + wp->sx + 1) || - (m->lx == wp->xoff + wp->sx)) && - (m->ly == wp->yoff - 1)) { + resizes++; + } else if ((((int)lx == wp->xoff + (int)wp->sx + 1) || + ((int)lx == wp->xoff + (int)wp->sx)) && + ((int)ly == wp->yoff - 1)) { /* Top right border */ new_sx = x - wp->xoff - 1; if (new_sx < PANE_MINIMUM) @@ -209,9 +209,9 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) new_sy = PANE_MINIMUM; window_pane_move(wp, wp->xoff, y + 1); window_pane_resize(wp, new_sx, new_sy); - server_redraw_window(w); - } else if (((m->lx == wp->xoff - 1) || (m->lx == wp->xoff)) && - (m->ly == wp->yoff + wp->sy)) { + resizes++; + } else if ((((int)lx == wp->xoff - 1) || ((int)lx == wp->xoff)) && + ((int)ly == wp->yoff + (int)wp->sy)) { /* Bottom left border */ new_sx = wp->sx + (lx - x); if (new_sx < PANE_MINIMUM) @@ -221,10 +221,10 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) return; window_pane_move(wp, x + 1, wp->yoff); window_pane_resize(wp, new_sx, new_sy); - server_redraw_window(w); - } else if (((m->lx == wp->xoff + wp->sx + 1) || - (m->lx == wp->xoff + wp->sx)) && - (m->ly == wp->yoff + wp->sy)) { + resizes++; + } else if ((((int)lx == wp->xoff + (int)wp->sx + 1) || + ((int)lx == wp->xoff + (int)wp->sx)) && + ((int)ly == wp->yoff + (int)wp->sy)) { /* Bottom right corner */ new_sx = x - wp->xoff - 1; if (new_sx < PANE_MINIMUM) @@ -233,30 +233,30 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) if (new_sy < PANE_MINIMUM) new_sy = PANE_MINIMUM; window_pane_resize(wp, new_sx, new_sy); - server_redraw_window(w); - } else if (m->lx == wp->xoff + wp->sx + 1) { + resizes++; + } else if ((int)lx == wp->xoff + (int)wp->sx + 1) { /* Right border */ new_sx = x - wp->xoff - 1; if (new_sx < PANE_MINIMUM) return; window_pane_resize(wp, new_sx, wp->sy); - server_redraw_window(w); - } else if (m->lx == wp->xoff - 1) { + resizes++; + } else if ((int)lx == wp->xoff - 1) { /* Left border */ new_sx = wp->sx + (lx - x); if (new_sx < PANE_MINIMUM) return; window_pane_move(wp, x + 1, wp->yoff); window_pane_resize(wp, new_sx, wp->sy); - server_redraw_window(w); - } else if (m->ly == wp->yoff + wp->sy) { + resizes++; + } else if ((int)ly == wp->yoff + (int)wp->sy) { /* Bottom border */ new_sy = y - wp->yoff; if (new_sy < PANE_MINIMUM) return; window_pane_resize(wp, wp->sx, new_sy); - server_redraw_window(w); - } else if (m->ly == wp->yoff - 1) { + resizes++; + } else if ((int)ly == wp->yoff - 1) { /* Top border */ window_pane_move(wp, wp->xoff + (x - lx), y + 1); /* @@ -266,11 +266,15 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) window_pane_move(wp, wp->xoff, y + 1); window_pane_resize(wp, wp->sx, new_sy); */ - server_redraw_window(w); + resizes++; } else { log_debug("%s: %%%u resize_pane xoff=%u sx=%u xy=%ux%u lxy=%ux%u ", __func__, wp->id, wp->xoff, wp->sx, x, y, lx, ly); } + if (resizes != 0) { + server_redraw_window(w); + server_redraw_window_borders(w); + } } static void diff --git a/cmd.c b/cmd.c index ea27c851ca..97233e0ee0 100644 --- a/cmd.c +++ b/cmd.c @@ -766,9 +766,9 @@ cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp, if (m->statusat == 0 && y >= m->statuslines) y -= m->statuslines; - if (x < wp->xoff || x >= wp->xoff + wp->sx) + if ((int)x < wp->xoff || (int)x >= wp->xoff + (int)wp->sx) return (-1); - if (y < wp->yoff || y >= wp->yoff + wp->sy) + if ((int)y < wp->yoff || (int)y >= wp->yoff + (int)wp->sy) return (-1); if (xp != NULL) diff --git a/screen-redraw.c b/screen-redraw.c index 9dc7e4538f..f9a8d96a8b 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -137,7 +137,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, sb_pos = 0; /* Inside pane. */ - if (px >= wp->xoff && px < ex && py >= wp->yoff && py < ey) + if ((int)px >= wp->xoff && px < ex && (int)py >= wp->yoff && py < ey) return (SCREEN_REDRAW_INSIDE); /* Get pane indicator. */ @@ -157,16 +157,16 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, * Left/right borders. The wp->sy / 2 test is to colour only half the * active window's border when there are two panes. */ - if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) { + if ((wp->yoff == 0 || (int)py >= wp->yoff - 1) && py <= ey) { if (sb_pos == PANE_SCROLLBARS_LEFT) { if (wp->xoff - sb_w == 0 && px == wp->sx + sb_w) if (!hsplit || (hsplit && py <= wp->sy / 2)) return (SCREEN_REDRAW_BORDER_RIGHT); if (wp->xoff - sb_w != 0) { - if (px == wp->xoff - sb_w - 1 && + if ((int)px == wp->xoff - sb_w - 1 && (!hsplit || (hsplit && py > wp->sy / 2))) return (SCREEN_REDRAW_BORDER_LEFT); - if (px == wp->xoff + wp->sx + sb_w - 1) + if ((int)px == wp->xoff + (int)wp->sx + sb_w - 1) return (SCREEN_REDRAW_BORDER_RIGHT); } } else { /* sb_pos == PANE_SCROLLBARS_RIGHT or disabled*/ @@ -174,7 +174,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if (!hsplit || (hsplit && py <= wp->sy / 2)) return (SCREEN_REDRAW_BORDER_RIGHT); if (wp->xoff != 0) { - if (px == wp->xoff - 1 && + if ((int)px == wp->xoff - 1 && (!hsplit || (hsplit && py > wp->sy / 2))) return (SCREEN_REDRAW_BORDER_LEFT); if (px == wp->xoff + wp->sx + sb_w) @@ -187,21 +187,21 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if (vsplit && pane_status == PANE_STATUS_OFF && sb_w == 0) { if (wp->yoff == 0 && py == wp->sy && px <= wp->sx / 2) return (SCREEN_REDRAW_BORDER_BOTTOM); - if (wp->yoff != 0 && py == wp->yoff - 1 && px > wp->sx / 2) + if (wp->yoff != 0 && (int)py == wp->yoff - 1 && px > wp->sx / 2) return (SCREEN_REDRAW_BORDER_TOP); } else { if (sb_pos == PANE_SCROLLBARS_LEFT) { - if ((wp->xoff - sb_w == 0 || px >= wp->xoff - sb_w) && + if ((wp->xoff - sb_w == 0 || (int)px >= wp->xoff - sb_w) && (px <= ex || (sb_w != 0 && px < ex + sb_w))) { - if (wp->yoff != 0 && py == wp->yoff - 1) + if (wp->yoff != 0 && (int)py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); if (py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); } } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ - if ((wp->xoff == 0 || px >= wp->xoff) && + if ((wp->xoff == 0 || (int)px >= wp->xoff) && (px <= ex || (sb_w != 0 && px < ex + sb_w))) { - if (wp->yoff != 0 && py == wp->yoff - 1) + if (wp->yoff != 0 && (int)py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); if (py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); @@ -358,8 +358,8 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, /* Look for higest z-index window at px,py. xxxx scrollbars? */ TAILQ_FOREACH(wp, &w->z_index, zentry) { if (! (wp->flags & PANE_MINIMISED) && - (px >= wp->xoff - 1 && px<= wp->xoff + wp->sx + 1) && - (py >= wp->yoff - 1 && py<= wp->yoff + wp->sy + 1)) + ((int)px >= wp->xoff - 1 && (int)px <= wp->xoff + (int)wp->sx + 1) && + ((int)py >= wp->yoff - 1 && (int)py <= wp->yoff + (int)wp->sy + 1)) break; } if (wp != NULL) @@ -378,7 +378,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, line = wp->yoff + sy; right = wp->xoff + 2 + wp->status_size - 1; - if (py == line && px >= wp->xoff + 2 && px <= right) + if (py == line && (int)px >= wp->xoff + 2 && px <= right) return (CELL_INSIDE); next1: @@ -394,8 +394,8 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; if (! (wp->flags & PANE_MINIMISED) && - (px >= wp->xoff - 1 && px <= wp->xoff + wp->sx + sb_w) && - (py >= wp->yoff - 1 && py <= wp->yoff + wp->sy)) + ((int)px >= wp->xoff - 1 && (int)px <= wp->xoff + (int)wp->sx + sb_w) && + ((int)py >= wp->yoff - 1 && (int)py <= wp->yoff + (int)wp->sy)) break; } if (wp == NULL) @@ -421,14 +421,14 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; if ((wp->yoff == 0 && py < wp->sy) || - (py >= wp->yoff && py < wp->yoff + wp->sy)) { + ((int)py >= wp->yoff && (int)py < wp->yoff + (int)wp->sy)) { /* Check if px lies within a scrollbar. */ if ((sb_pos == PANE_SCROLLBARS_RIGHT && - (px >= wp->xoff + wp->sx && - px < wp->xoff + wp->sx + sb_w)) || + ((int)px >= wp->xoff + (int)wp->sx && + (int)px < wp->xoff + (int)wp->sx + sb_w)) || (sb_pos == PANE_SCROLLBARS_LEFT && - (px >= wp->xoff - sb_w && - px < wp->xoff))) + ((int)px >= wp->xoff - sb_w && + (int)px < wp->xoff))) return (CELL_SCROLLBAR); } } @@ -543,7 +543,8 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) struct window_pane *wp; struct screen *s; struct visible_ranges *vr; - u_int i, x, width, xoff, yoff, size, r; + u_int i, x, width, size, r; + int xoff, yoff; log_debug("%s: %s @%u", __func__, c->name, w->id); @@ -559,10 +560,10 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) yoff = wp->yoff + wp->sy; xoff = wp->xoff + 2; - if (xoff + size <= ctx->ox || - xoff >= ctx->ox + ctx->sx || + if (xoff + (int)size <= ctx->ox || + xoff >= ctx->ox + (int)ctx->sx || yoff < ctx->oy || - yoff >= ctx->oy + ctx->sy) + yoff >= ctx->oy + (int)ctx->sy) continue; if (xoff >= ctx->ox && xoff + size <= ctx->ox + ctx->sx) { @@ -837,13 +838,13 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) if (wp != NULL && arrows) { border = screen_redraw_pane_border(ctx, active, x, y); - if (((i == wp->xoff + 1 && + if ((((int)i == wp->xoff + 1 && (cell_type == CELL_LEFTRIGHT || (cell_type == CELL_TOPJOIN && border == SCREEN_REDRAW_BORDER_BOTTOM) || (cell_type == CELL_BOTTOMJOIN && border == SCREEN_REDRAW_BORDER_TOP))) || - (j == wp->yoff + 1 && + ((int)j == wp->yoff + 1 && (cell_type == CELL_TOPBOTTOM || (cell_type == CELL_LEFTJOIN && border == SCREEN_REDRAW_BORDER_RIGHT) || @@ -989,8 +990,13 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; for (r=0; r < vr.used; r++) { - lb = wp->xoff - 1; + if (wp->xoff > 0) + lb = wp->xoff - 1; + else + lb = 0; rb = wp->xoff + wp->sx + sb_w; + if (rb > w->sx) + rb = w->sx - 1; /* If the left edge of floating wp falls inside this range and right edge covers up to right of range, @@ -998,7 +1004,8 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, if (lb > vr.px[r] && lb < vr.px[r] + vr.nx[r] && rb >= vr.px[r] + vr.nx[r]) { - vr.nx[r] = lb; + /* vr.nx[r] = vr.nx[r] - ((vr.px[r] + vr.nx[r]) - lb); */ + vr.nx[r] = lb - vr.px[r]; } /* Else if the right edge of floating wp falls inside of this range and left @@ -1007,8 +1014,8 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, else if (rb > vr.px[r] && rb < vr.px[r] + vr.nx[r] && lb <= vr.px[r]) { - vr.nx[r] = vr.nx[r] - (rb - vr.px[r]); - vr.px[r] = vr.px[r] + (rb - vr.px[r]); + vr.nx[r] = vr.nx[r] - (rb + 1 - vr.px[r]); + vr.px[r] = vr.px[r] + (rb + 1 - vr.px[r]); } /* Else if wp fully inside range then split range into 2 ranges. */ @@ -1025,10 +1032,11 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, vr.px[s] = vr.px[s-1]; vr.nx[s] = vr.nx[s-1]; } + vr.px[r+1] = rb + 1; + vr.nx[r+1] = (vr.px[r] + vr.nx[r]) - (rb + 1); + /* vr.px[r] was copied, unchanged. */ + vr.nx[r] = lb - vr.px[r]; vr.used++; - vr.nx[r] = lb; - vr.px[r+1] = rb; - vr.nx[r+1] = vr.nx[r+1] - rb - 1; } /* If floating wp completely covers this range then delete it (make it 0 length). */ @@ -1039,6 +1047,11 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, /* Else the range is already obscured, do nothing. */ } } + for (r=0; rid, py, r, vr.px[r], vr.nx[r]); + + } return (&vr); } @@ -1059,7 +1072,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); - if (wp->xoff + wp->sx <= ctx->ox || wp->xoff >= ctx->ox + ctx->sx) + if (wp->xoff + (int)wp->sx <= ctx->ox || wp->xoff >= ctx->ox + (int)ctx->sx) return; if (ctx->statustop) top = ctx->statuslines; @@ -1067,7 +1080,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) top = 0; for (j = 0; j < wp->sy; j++) { - if (wp->yoff + j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy) + if (wp->yoff + (int)j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy) continue; y = top + wp->yoff + j - ctx->oy; @@ -1195,7 +1208,7 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, struct tty *tty = &c->tty; struct grid_cell gc, slgc, *gcp; struct style *sb_style = &wp->scrollbar_style; - u_int i, j, imax, jmax; + u_int i, j, imin = 0, jmin = 0, imax, jmax; u_int sb_w = sb_style->width, sb_pad = sb_style->pad; int px, py, ox = ctx->ox, oy = ctx->oy; int sx = ctx->sx, sy = ctx->sy, xoff = wp->xoff; @@ -1208,17 +1221,32 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, slgc.fg = gc.bg; slgc.bg = gc.fg; + if (sb_x + (int)sb_w < 0) + /* Whole sb off screen. */ + return; + if (sb_x < 0) + imin = - sb_x; imax = sb_w + sb_pad; - if ((int)imax + sb_x > sx) + if ((int)imax + sb_x > sx) { + if (sb_x > sx) + /* Whole sb off screen. */ + return; imax = sx - sb_x; + } + if (sb_y > oy + sy) + return; + if (sb_y < 0) + jmin = -sb_y; + if ((int)sb_h < oy) + return; jmax = sb_h; if ((int)jmax + sb_y > sy) jmax = sy - sb_y; - for (j = 0; j < jmax; j++) { + for (j = jmin; j < jmax; j++) { py = sb_y + j; vr = screen_redraw_get_visible_ranges(wp, sb_x, py, imax); - for (i = 0; i < imax; i++) { + for (i = imin; i < imax; i++) { px = sb_x + i; if (px < xoff - ox - (int)sb_w - (int)sb_pad || px >= sx || px < 0 || diff --git a/screen-write.c b/screen-write.c index f0b3542b49..c2c9121efa 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2030,8 +2030,8 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) } if (found_self && wp->layout_cell == NULL && !(wp->flags & PANE_MINIMISED) && - (py >= wp->yoff && py <= wp->yoff + wp->sy) && - (px >= wp->xoff && px <= wp->xoff + wp->sx)) + ((int)py >= wp->yoff && (int)py <= wp->yoff + (int)wp->sy) && + ((int)px >= wp->xoff && (int)px <= wp->xoff + (int)wp->sx)) return; } } diff --git a/server-client.c b/server-client.c index 02ab0e7893..87f8196f70 100644 --- a/server-client.c +++ b/server-client.c @@ -613,7 +613,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, /* Check if point is within the pane or scrollbar. */ if (((pane_status != PANE_STATUS_OFF && py != line) || (wp->yoff == 0 && py < wp->sy) || - (py >= wp->yoff && py < wp->yoff + wp->sy)) && + ((int)py >= wp->yoff && py < wp->yoff + wp->sy)) && ((sb_pos == PANE_SCROLLBARS_RIGHT && px < wp->xoff + wp->sx + sb_pad + sb_w) || (sb_pos == PANE_SCROLLBARS_LEFT && @@ -623,8 +623,8 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, (px >= wp->xoff + wp->sx + sb_pad && px < wp->xoff + wp->sx + sb_pad + sb_w)) || (sb_pos == PANE_SCROLLBARS_LEFT && - (px >= wp->xoff - sb_pad - sb_w && - px < wp->xoff - sb_pad))) { + ((int)px >= wp->xoff - sb_pad - sb_w && + (int)px < wp->xoff - sb_pad))) { /* Check where inside the scrollbar. */ sl_top = wp->yoff + wp->sb_slider_y; sl_bottom = (wp->yoff + wp->sb_slider_y + @@ -638,7 +638,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, } else /* py > sl_bottom */ return (SCROLLBAR_DOWN); } else if (wp->layout_cell == NULL && - (px == wp->xoff - 1 || py == wp->yoff -1)) { + ((int)px == wp->xoff - 1 || (int)py == wp->yoff -1)) { /* Floating pane left or top border. */ return (BORDER); } else { @@ -653,7 +653,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, else /* PANE_SCROLLBARS_RIGHT or none. */ bdr_right = fwp->xoff + fwp->sx + sb_pad + sb_w; - if (py >= fwp->yoff - 1 && py <= fwp->yoff + fwp->sy) { + if ((int)py >= fwp->yoff - 1 && py <= fwp->yoff + fwp->sy) { if (px == bdr_right) break; if (wp->layout_cell == NULL) { @@ -663,7 +663,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, break; } } - if (px >= fwp->xoff - 1 && px <= fwp->xoff + fwp->sx) { + if ((int)px >= fwp->xoff - 1 && px <= fwp->xoff + fwp->sx) { bdr_bottom = fwp->yoff + fwp->sy; if (py == bdr_bottom) break; diff --git a/tmux.h b/tmux.h index 2d85d0112c..974ce6006a 100644 --- a/tmux.h +++ b/tmux.h @@ -1058,8 +1058,8 @@ struct screen_redraw_ctx { u_int sx; u_int sy; - u_int ox; - u_int oy; + int ox; + int oy; }; /* Screen size. */ @@ -1154,8 +1154,8 @@ struct window_pane { u_int sx; u_int sy; - u_int xoff; - u_int yoff; + int xoff; + int yoff; int flags; #define PANE_REDRAW 0x1 @@ -3266,7 +3266,7 @@ struct window_pane *window_pane_find_by_id_str(const char *); struct window_pane *window_pane_find_by_id(u_int); int window_pane_destroy_ready(struct window_pane *); void window_pane_resize(struct window_pane *, u_int, u_int); -void window_pane_move(struct window_pane *, u_int, u_int); +void window_pane_move(struct window_pane *, int, int); int window_pane_set_mode(struct window_pane *, struct window_pane *, const struct window_mode *, struct cmd_find_state *, struct args *); diff --git a/tty.c b/tty.c index a169eab999..d31246ce8d 100644 --- a/tty.c +++ b/tty.c @@ -2049,12 +2049,12 @@ tty_is_obscured(const struct tty_ctx *ctx) if (found_self && wp->layout_cell == NULL && ! (wp->flags & PANE_MINIMISED) && ((wp->yoff >= base_wp->yoff && - wp->yoff <= base_wp->yoff + base_wp->sy) || - (wp->yoff + wp->sy >= base_wp->yoff && + wp->yoff <= base_wp->yoff + (int)base_wp->sy) || + (wp->yoff + (int)wp->sy >= base_wp->yoff && wp->yoff + wp->sy <= base_wp->yoff + base_wp->sy)) && ((wp->xoff >= base_wp->xoff && - wp->xoff <= base_wp->xoff + base_wp->sx) || - (wp->xoff + wp->sx >= base_wp->xoff && + wp->xoff <= base_wp->xoff + (int)base_wp->sx) || + (wp->xoff + (int)wp->sx >= base_wp->xoff && wp->xoff + wp->sx <= base_wp->xoff + base_wp->sx))) return (1); } diff --git a/window.c b/window.c index 753f3abb8b..c355530ca3 100644 --- a/window.c +++ b/window.c @@ -1118,7 +1118,7 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) } void -window_pane_move(struct window_pane *wp, u_int xoff, u_int yoff) +window_pane_move(struct window_pane *wp, int xoff, int yoff) { if (xoff == wp->xoff && yoff == wp->yoff) return; From 9a061a2fee18c810beed625d6dc70da08e15bfc3 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 2 Nov 2025 09:56:57 +0100 Subject: [PATCH 034/167] Bugfix floating panes display when off window edge. --- screen-redraw.c | 5 +++-- screen-write.c | 4 ++-- window.c | 11 ++++++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index f9a8d96a8b..7cc4f955e0 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1084,6 +1084,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) continue; y = top + wp->yoff + j - ctx->oy; + /* xxx i apparenty unneeded now that the vr array returns where in s to read from. */ if (wp->xoff >= ctx->ox && wp->xoff + wp->sx <= ctx->ox + ctx->sx) { /* All visible. */ @@ -1120,8 +1121,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) continue; /* i is px of cell, add px of region, sub the pane offset. If you don't sub offset, - contents of pane shifted. */ - tty_draw_line(tty, s, i + vr->px[r] - wp->xoff, j, + contents of pane shifted. note: i apparently unnec. */ + tty_draw_line(tty, s, /* i + */ vr->px[r] - wp->xoff, j, vr->nx[r], vr->px[r], y, &defaults, palette); } } diff --git a/screen-write.c b/screen-write.c index c2c9121efa..083290b3da 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2018,7 +2018,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) struct window *w; u_int found_self, px, py; - + /* early attempt. if (base_wp != NULL) { w = base_wp->window; px = ctx->s->cx; @@ -2035,7 +2035,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) return; } } - + */ /* Ignore padding cells. */ if (gc->flags & GRID_FLAG_PADDING) return; diff --git a/window.c b/window.c index c355530ca3..94a6898589 100644 --- a/window.c +++ b/window.c @@ -599,7 +599,8 @@ struct window_pane * window_get_active_at(struct window *w, u_int x, u_int y) { struct window_pane *wp; - u_int xoff, yoff, sx, sy; + int xoff, yoff; + u_int sx, sy; TAILQ_FOREACH(wp, &w->z_index, zentry) { if (!window_pane_visible(wp)) @@ -608,15 +609,15 @@ window_get_active_at(struct window *w, u_int x, u_int y) if (wp->layout_cell != NULL) { /* Tiled, select up to including bottom or right border. */ - if (x < xoff || x > xoff + sx) + if ((int)x < xoff || x > xoff + sx) continue; - if (y < yoff || y > yoff + sy) + if ((int)y < yoff || y > yoff + sy) continue; } else { /* Floating, include top or or left border. */ - if (x < xoff - 1 || x > xoff + sx) + if ((int)x < xoff - 1 || x > xoff + sx) continue; - if (y < yoff - 1 || y > yoff + sy) + if ((int)y < yoff - 1 || y > yoff + sy) continue; } return (wp); From 6b462474f0094e88bc5178457f9c7315f3b1a258 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 2 Nov 2025 11:52:21 +0100 Subject: [PATCH 035/167] Bugfix so floating wp can be moved to top of window. --- window.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/window.c b/window.c index 94a6898589..87ed736394 100644 --- a/window.c +++ b/window.c @@ -1121,9 +1121,6 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) void window_pane_move(struct window_pane *wp, int xoff, int yoff) { - if (xoff == wp->xoff && yoff == wp->yoff) - return; - wp->xoff = xoff; wp->yoff = yoff; From 6c5cc400394005459ee61b32a49bbbeb4baef432 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 3 Nov 2025 21:56:15 +0100 Subject: [PATCH 036/167] Bugfix, more changes to allow xoff and yoff to be int. --- screen-redraw.c | 17 +++++++++-------- screen-write.c | 42 ++++++++++++++---------------------------- server-client.c | 1 + tmux.h | 8 ++++---- tty.c | 30 +++++++++++++++++++++--------- 5 files changed, 49 insertions(+), 49 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 7cc4f955e0..c467b95159 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -126,7 +126,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, u_int px, u_int py) { struct options *oo = wp->window->options; - u_int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy; + int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy; int hsplit = 0, vsplit = 0, pane_status = ctx->pane_status; int pane_scrollbars = ctx->pane_scrollbars, sb_w = 0; int sb_pos; @@ -137,7 +137,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, sb_pos = 0; /* Inside pane. */ - if ((int)px >= wp->xoff && px < ex && (int)py >= wp->yoff && py < ey) + if ((int)px >= wp->xoff && (int)px < ex && (int)py >= wp->yoff && (int)py < ey) return (SCREEN_REDRAW_INSIDE); /* Get pane indicator. */ @@ -157,7 +157,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, * Left/right borders. The wp->sy / 2 test is to colour only half the * active window's border when there are two panes. */ - if ((wp->yoff == 0 || (int)py >= wp->yoff - 1) && py <= ey) { + if ((wp->yoff == 0 || (int)py >= wp->yoff - 1) && (int)py <= ey) { if (sb_pos == PANE_SCROLLBARS_LEFT) { if (wp->xoff - sb_w == 0 && px == wp->sx + sb_w) if (!hsplit || (hsplit && py <= wp->sy / 2)) @@ -192,18 +192,18 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, } else { if (sb_pos == PANE_SCROLLBARS_LEFT) { if ((wp->xoff - sb_w == 0 || (int)px >= wp->xoff - sb_w) && - (px <= ex || (sb_w != 0 && px < ex + sb_w))) { + ((int)px <= ex || (sb_w != 0 && (int)px < ex + sb_w))) { if (wp->yoff != 0 && (int)py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); - if (py == ey) + if ((int)py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); } } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ if ((wp->xoff == 0 || (int)px >= wp->xoff) && - (px <= ex || (sb_w != 0 && px < ex + sb_w))) { + ((int)px <= ex || (sb_w != 0 && (int)px < ex + sb_w))) { if (wp->yoff != 0 && (int)py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); - if (py == ey) + if ((int)py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); } } @@ -950,7 +950,8 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, struct window *w; static struct visible_ranges vr = {NULL, NULL, 0, 0}; int found_self, sb_w; - u_int r, s, lb, rb, tb, bb; + u_int lb, rb, tb, bb; + u_int r, s; int pane_scrollbars; /* For efficiency vr is static and space reused. */ diff --git a/screen-write.c b/screen-write.c index 083290b3da..c9ff217874 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1780,8 +1780,9 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct screen_write_citem *ci, *tmp; struct screen_write_cline *cl; u_int y, cx, cy, last, items = 0, r; - u_int r_start, r_end, ci_start, ci_end; - u_int wr_start, wr_end, wr_length, sx, xoff, yoff; + u_int wr_start, wr_end, wr_length, wsx, wsy; + int r_start, r_end, ci_start, ci_end; + int xoff, yoff; struct tty_ctx ttyctx; struct visible_ranges *vr; struct window_pane *wp = ctx->wp; @@ -1793,6 +1794,8 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ctx->scrolled = s->rlower - s->rupper + 1; screen_write_initctx(ctx, &ttyctx, 1); + if (wp->yoff + wp->sy > wp->window->sy) + ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy); ttyctx.num = ctx->scrolled; ttyctx.bg = ctx->bg; tty_write(tty_cmd_scrollup, &ttyctx); @@ -1811,20 +1814,24 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, /* The xoff and width of window pane relative to the window we * are writing to relative to the visible_ranges array. */ if (wp != NULL) { - sx = wp->window->sx; + wsx = wp->window->sx; + wsy = wp->window->sy; xoff = wp->xoff; yoff = wp->yoff; } else { - sx = screen_size_x(s); + wsx = screen_size_x(s); + wsy = screen_size_y(s); xoff = 0; yoff = 0; } for (y = 0; y < screen_size_y(s); y++) { + if (y + yoff >= wsy) + continue; cl = &ctx->s->write_list[y]; vr = screen_redraw_get_visible_ranges(wp, 0, y + yoff, - sx); + wsx); last = UINT_MAX; TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { @@ -1855,7 +1862,6 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, if (wr_length <= 0) continue; - screen_write_set_cursor(ctx, wr_start, y); if (ci->type == CLEAR) { screen_write_initctx(ctx, &ttyctx, 1); @@ -1879,6 +1885,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, } } } + s->cx = cx; s->cy = cy; log_debug("%s: flushed %u items (%s)", __func__, items, from); @@ -2014,28 +2021,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) u_int sx = screen_size_x(s), sy = screen_size_y(s); u_int width = ud->width, xx, not_wrap; int selected, skip = 1; - struct window_pane *base_wp = ctx->wp, *wp; - struct window *w; - u_int found_self, px, py; - - /* early attempt. - if (base_wp != NULL) { - w = base_wp->window; - px = ctx->s->cx; - py = ctx->s->cy; - TAILQ_FOREACH(wp, &w->panes, zentry) { - if (wp == base_wp) { - found_self = 1; - continue; - } - if (found_self && wp->layout_cell == NULL && - !(wp->flags & PANE_MINIMISED) && - ((int)py >= wp->yoff && (int)py <= wp->yoff + (int)wp->sy) && - ((int)px >= wp->xoff && (int)px <= wp->xoff + (int)wp->sx)) - return; - } - } - */ + /* Ignore padding cells. */ if (gc->flags & GRID_FLAG_PADDING) return; diff --git a/server-client.c b/server-client.c index 87f8196f70..60a6ddb356 100644 --- a/server-client.c +++ b/server-client.c @@ -605,6 +605,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, sb_w = 0; sb_pad = 0; } + /* xxxx isn't this only for tiled panes at the top or bottom of the window? */ if (pane_status == PANE_STATUS_TOP) line = wp->yoff - 1; else if (pane_status == PANE_STATUS_BOTTOM) diff --git a/tmux.h b/tmux.h index 974ce6006a..ad071260e5 100644 --- a/tmux.h +++ b/tmux.h @@ -1642,10 +1642,10 @@ struct tty_ctx { u_int orlower; /* Target region (usually pane) offset and size. */ - u_int xoff; - u_int yoff; - u_int rxoff; - u_int ryoff; + int xoff; + int yoff; + int rxoff; + int ryoff; u_int sx; u_int sy; diff --git a/tty.c b/tty.c index d31246ce8d..064bb4b70f 100644 --- a/tty.c +++ b/tty.c @@ -1121,23 +1121,23 @@ static int tty_clamp_line(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, u_int nx, u_int *i, u_int *x, u_int *rx, u_int *ry) { - u_int xoff = ctx->rxoff + px; + int xoff = ctx->rxoff + px; if (!tty_is_visible(tty, ctx, px, py, nx, 1)) return (0); *ry = ctx->yoff + py - ctx->woy; - if (xoff >= ctx->wox && xoff + nx <= ctx->wox + ctx->wsx) { + if (xoff >= (int)ctx->wox && xoff + nx <= ctx->wox + ctx->wsx) { /* All visible. */ *i = 0; *x = ctx->xoff + px - ctx->wox; *rx = nx; - } else if (xoff < ctx->wox && xoff + nx > ctx->wox + ctx->wsx) { + } else if (xoff < (int)ctx->wox && xoff + nx > ctx->wox + ctx->wsx) { /* Both left and right not visible. */ *i = ctx->wox; *x = 0; *rx = ctx->wsx; - } else if (xoff < ctx->wox) { + } else if (xoff < (int)ctx->wox) { /* Left not visible. */ *i = ctx->wox - (ctx->xoff + px); *x = 0; @@ -2065,7 +2065,7 @@ void tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - u_int i; + u_int i, rlower; if (ctx->bigger || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || @@ -2085,11 +2085,16 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); + if (tty->rlower < ctx->wsy) + rlower = tty->rlower; + else + rlower = ctx->wsy - 1; + if (ctx->num == 1 || !tty_term_has(tty->term, TTYC_INDN)) { if (!tty_use_margin(tty)) - tty_cursor(tty, 0, tty->rlower); + tty_cursor(tty, 0, rlower); else - tty_cursor(tty, tty->rright, tty->rlower); + tty_cursor(tty, tty->rright, rlower); for (i = 0; i < ctx->num; i++) tty_putc(tty, '\n'); } else { @@ -2558,8 +2563,15 @@ tty_margin_off(struct tty *tty) static void tty_margin_pane(struct tty *tty, const struct tty_ctx *ctx) { - tty_margin(tty, ctx->xoff - ctx->wox, - ctx->xoff + ctx->sx - 1 - ctx->wox); + int l, r; + + l = ctx->xoff - ctx->wox; + r = ctx->xoff + ctx->sx - 1 - ctx->wox; + + if (l < 0) l = 0; + if (r < 0) r = 0; + + tty_margin(tty, l, r); } /* Set margin at absolute position. */ From 04792d06567c6c8c956a437c9da2127a2fa717ac Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 3 Nov 2025 22:38:35 +0100 Subject: [PATCH 037/167] Bugfix, max margin should be right side of window. --- tty.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tty.c b/tty.c index 064bb4b70f..a04a5414ac 100644 --- a/tty.c +++ b/tty.c @@ -2569,7 +2569,9 @@ tty_margin_pane(struct tty *tty, const struct tty_ctx *ctx) r = ctx->xoff + ctx->sx - 1 - ctx->wox; if (l < 0) l = 0; + if (l > (int)ctx->wsx) l = ctx->wsx; if (r < 0) r = 0; + if (r > (int)ctx->wsx) r = ctx->wsx; tty_margin(tty, l, r); } From 7e0038c691a9202e78bc401acedb16e38a05fbd5 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 4 Nov 2025 15:48:23 +0100 Subject: [PATCH 038/167] Bugfix split pane border colours. --- screen-redraw.c | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index c467b95159..6198b425a2 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -100,23 +100,22 @@ screen_redraw_border_set(struct window *w, struct window_pane *wp, /* Return if window has only two panes. */ static int -screen_redraw_two_panes(struct window *w, int direction) +screen_redraw_two_panes(struct window *w, enum layout_type *type) { struct window_pane *wp; + u_int count = 0; - wp = TAILQ_FIRST(&w->z_index); - do { - wp = TAILQ_NEXT(wp, zentry); - } while (wp && wp->layout_cell == NULL); - - if (wp == NULL) - return (0); /* one pane */ - if (TAILQ_NEXT(wp, entry) != NULL) - return (0); /* more than two panes */ - if (direction == 0 && wp->xoff == 0) - return (0); - if (direction == 1 && wp->yoff == 0) + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->layout_cell == NULL) + continue; + count++; + if (count > 2 || wp->layout_cell->parent == NULL) + return (0); + *type = wp->layout_cell->parent->type; + } + if (count <= 1) return (0); + return (1); } @@ -130,6 +129,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, int hsplit = 0, vsplit = 0, pane_status = ctx->pane_status; int pane_scrollbars = ctx->pane_scrollbars, sb_w = 0; int sb_pos; + enum layout_type split_type; if (pane_scrollbars != 0) sb_pos = ctx->pane_scrollbars_pos; @@ -144,8 +144,10 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, switch (options_get_number(oo, "pane-border-indicators")) { case PANE_BORDER_COLOUR: case PANE_BORDER_BOTH: - hsplit = screen_redraw_two_panes(wp->window, 0); - vsplit = screen_redraw_two_panes(wp->window, 1); + if (screen_redraw_two_panes(wp->window, &split_type)) { + hsplit = (split_type == LAYOUT_LEFTRIGHT); + vsplit = (split_type == LAYOUT_TOPBOTTOM); + } break; } @@ -371,7 +373,9 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, if (!window_pane_visible(wp)) goto next1; - /* xxxx Isn't this only true if pane at the top of the window? */ + /* Pane border status inside top/bottom border + * is CELL_INSIDE so it doesn't get overdrawn. + */ if (pane_status == PANE_STATUS_TOP) line = wp->yoff - 1; else From 61ca158de1400880316ba72c6eedbc84c08932a1 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 6 Nov 2025 12:16:45 +0100 Subject: [PATCH 039/167] Fix pane borders, including fixing 2 side-by-side or top-bottom panes with split coulering. --- screen-redraw.c | 228 +++++++++++++++++++++++++++++------------------- window.c | 12 ++- 2 files changed, 151 insertions(+), 89 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 6198b425a2..b4128f880f 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -137,9 +137,30 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, sb_pos = 0; /* Inside pane. */ - if ((int)px >= wp->xoff && (int)px < ex && (int)py >= wp->yoff && (int)py < ey) + if ((int)px >= wp->xoff && (int)px < ex && + (int)py >= wp->yoff && (int)py < ey) return (SCREEN_REDRAW_INSIDE); + /* Are scrollbars enabled? */ + if (window_pane_show_scrollbar(wp, pane_scrollbars)) + sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; + + /* Floating pane borders */ + if (wp->layout_cell == NULL) { + if ((int)px == wp->xoff - 1 && + (int)py >= wp->yoff - 1 && (int)py <= wp->yoff + (int)wp->sy) + return (SCREEN_REDRAW_BORDER_LEFT); + if ((int)px == wp->xoff + (int)wp->sx + sb_w && + (int)py >= wp->yoff && (int)py <= wp->yoff + (int)wp->sy) + return (SCREEN_REDRAW_BORDER_RIGHT); + if ((int)py == wp->yoff - 1 && + (int)px >= wp->xoff && (int)px <= wp->xoff + (int)wp->sx) + return (SCREEN_REDRAW_BORDER_TOP); + if ((int)py == wp->yoff + (int)wp->sy && + (int)px >= wp->xoff && (int)px <= wp->xoff + (int)wp->sx) + return (SCREEN_REDRAW_BORDER_BOTTOM); + } + /* Get pane indicator. */ switch (options_get_number(oo, "pane-border-indicators")) { case PANE_BORDER_COLOUR: @@ -151,10 +172,6 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, break; } - /* Are scrollbars enabled? */ - if (window_pane_show_scrollbar(wp, pane_scrollbars)) - sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - /* * Left/right borders. The wp->sy / 2 test is to colour only half the * active window's border when there are two panes. @@ -177,7 +194,8 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, return (SCREEN_REDRAW_BORDER_RIGHT); if (wp->xoff != 0) { if ((int)px == wp->xoff - 1 && - (!hsplit || (hsplit && py > wp->sy / 2))) + (!hsplit || + (hsplit && py > wp->sy / 2))) return (SCREEN_REDRAW_BORDER_LEFT); if (px == wp->xoff + wp->sx + sb_w) return (SCREEN_REDRAW_BORDER_RIGHT); @@ -193,8 +211,10 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, return (SCREEN_REDRAW_BORDER_TOP); } else { if (sb_pos == PANE_SCROLLBARS_LEFT) { - if ((wp->xoff - sb_w == 0 || (int)px >= wp->xoff - sb_w) && - ((int)px <= ex || (sb_w != 0 && (int)px < ex + sb_w))) { + if ((wp->xoff - sb_w == 0 || + (int)px >= wp->xoff - sb_w) && + ((int)px <= ex || + (sb_w != 0 && (int)px < ex + sb_w))) { if (wp->yoff != 0 && (int)py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); if ((int)py == ey) @@ -202,7 +222,8 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, } } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ if ((wp->xoff == 0 || (int)px >= wp->xoff) && - ((int)px <= ex || (sb_w != 0 && (int)px < ex + sb_w))) { + ((int)px <= ex || + (sb_w != 0 && (int)px < ex + sb_w))) { if (wp->yoff != 0 && (int)py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); if ((int)py == ey) @@ -217,12 +238,17 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, /* Check if a cell is on a border. */ static int -screen_redraw_cell_border(struct screen_redraw_ctx *ctx, u_int px, u_int py) +screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, + u_int px, u_int py) { struct client *c = ctx->c; struct window *w = c->session->curw->window; - struct window_pane *wp; + struct window_pane *wp2; u_int sy = w->sy; + int sb_w, floating = 0; + + sb_w = wp->scrollbar_style.width + + wp->scrollbar_style.pad; if (ctx->pane_status == PANE_STATUS_BOTTOM) sy--; @@ -235,11 +261,29 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, u_int px, u_int py) if (px == w->sx || py == sy) return (1); + /* If checking a cell from a tiled pane, ignore floating panes + * because 2 side-by-side or top-bottom panes share a border + * which is used to do split coulering. Essentially treat all + * non-floating panes as being in a single z-index. + * + * If checking a cell from a floating pane, only check cells + * from this floating pane, again, essentially only this z-index. + */ + floating = (wp->layout_cell == NULL); + /* Check all the panes. */ - TAILQ_FOREACH(wp, &w->z_index, zentry) { - if (!window_pane_visible(wp)) + TAILQ_FOREACH(wp2, &w->z_index, zentry) { + if (!window_pane_visible(wp2) || + (wp->flags & PANE_MINIMISED) || + (!floating && wp2->layout_cell==NULL) || + (floating && wp2 != wp)) continue; - switch (screen_redraw_pane_border(ctx, wp, px, py)) { + if (((int)px < wp2->xoff - 1 || + (int)px > wp2->xoff + (int)wp2->sx + sb_w) && + ((int)py < wp2->yoff - 1 || + (int)py > wp2->yoff + (int)wp2->sy)) + continue; + switch (screen_redraw_pane_border(ctx, wp2, px, py)) { case SCREEN_REDRAW_INSIDE: return (0); case SCREEN_REDRAW_OUTSIDE: @@ -254,7 +298,8 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, u_int px, u_int py) /* Work out type of border cell from surrounding cells. */ static int -screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py) +screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx, + struct window_pane *wp, u_int px, u_int py) { struct client *c = ctx->c; int pane_status = ctx->pane_status; @@ -277,28 +322,28 @@ screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py) * 8 + 4 * 1 */ - if (px == 0 || screen_redraw_cell_border(ctx, px - 1, py)) + if (px == 0 || screen_redraw_cell_border(ctx, wp, px - 1, py)) borders |= 8; - if (px <= sx && screen_redraw_cell_border(ctx, px + 1, py)) + if (px <= sx && screen_redraw_cell_border(ctx, wp, px + 1, py)) borders |= 4; if (pane_status == PANE_STATUS_TOP) { if (py != 0 && - screen_redraw_cell_border(ctx, px, py - 1)) + screen_redraw_cell_border(ctx, wp, px, py - 1)) borders |= 2; - if (screen_redraw_cell_border(ctx, px, py + 1)) + if (screen_redraw_cell_border(ctx, wp, px, py + 1)) borders |= 1; } else if (pane_status == PANE_STATUS_BOTTOM) { if (py == 0 || - screen_redraw_cell_border(ctx, px, py - 1)) + screen_redraw_cell_border(ctx, wp, px, py - 1)) borders |= 2; if (py != sy && - screen_redraw_cell_border(ctx, px, py + 1)) + screen_redraw_cell_border(ctx, wp, px, py + 1)) borders |= 1; } else { if (py == 0 || - screen_redraw_cell_border(ctx, px, py - 1)) + screen_redraw_cell_border(ctx, wp, px, py - 1)) borders |= 2; - if (screen_redraw_cell_border(ctx, px, py + 1)) + if (screen_redraw_cell_border(ctx, wp, px, py + 1)) borders |= 1; } @@ -345,61 +390,24 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, int pane_status = ctx->pane_status; u_int sx = w->sx, sy = w->sy; int border, pane_scrollbars = ctx->pane_scrollbars; - u_int right, line; + u_int pane_status_line; int sb_pos = ctx->pane_scrollbars_pos; - int sb_w; + int sb_w, left, right, tiled_only=0; *wpp = NULL; if (px > sx || py > sy) return (CELL_OUTSIDE); - if (px == sx || py == sy) /* window border */ - return (screen_redraw_type_of_cell(ctx, px, py)); - - if (pane_status != PANE_STATUS_OFF) { - /* Look for higest z-index window at px,py. xxxx scrollbars? */ - TAILQ_FOREACH(wp, &w->z_index, zentry) { - if (! (wp->flags & PANE_MINIMISED) && - ((int)px >= wp->xoff - 1 && (int)px <= wp->xoff + (int)wp->sx + 1) && - ((int)py >= wp->yoff - 1 && (int)py <= wp->yoff + (int)wp->sy + 1)) - break; - } - if (wp != NULL) - start = wp; - else - start = wp = server_client_get_pane(c); - - do { - if (!window_pane_visible(wp)) - goto next1; - - /* Pane border status inside top/bottom border - * is CELL_INSIDE so it doesn't get overdrawn. - */ - if (pane_status == PANE_STATUS_TOP) - line = wp->yoff - 1; - else - line = wp->yoff + sy; - right = wp->xoff + 2 + wp->status_size - 1; - - if (py == line && (int)px >= wp->xoff + 2 && px <= right) - return (CELL_INSIDE); - - next1: - wp = TAILQ_NEXT(wp, entry); - if (wp == NULL) - wp = TAILQ_FIRST(&w->panes); - } while (wp != start); - } - - /* Look for higest z-index window at px,py. */ + /* Look through panes in z-index order starting with active pane. */ TAILQ_FOREACH(wp, &w->z_index, zentry) { sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; if (! (wp->flags & PANE_MINIMISED) && - ((int)px >= wp->xoff - 1 && (int)px <= wp->xoff + (int)wp->sx + sb_w) && - ((int)py >= wp->yoff - 1 && (int)py <= wp->yoff + (int)wp->sy)) + ((int)px >= wp->xoff - 1 && + (int)px <= wp->xoff + (int)wp->sx + sb_w) && + ((int)py >= wp->yoff - 1 && + (int)py <= wp->yoff + (int)wp->sy)) break; } if (wp == NULL) @@ -407,25 +415,63 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, else start = wp; - do { - if (!window_pane_visible(wp)) - goto next2; + if (px == sx || py == sy) /* window border */ + return (screen_redraw_type_of_cell(ctx, wp, px, py)); + + /* If this is a tiled window, then only check other tiled + * windows. This is necessary if there are 2 side-by-side or + * top-bottom windows with a shared border and half the shared + * border is the active border. + */ + if (wp->layout_cell != NULL) + tiled_only = 1; + + do { /* Loop until back to wp==start.*/ + + if (!window_pane_visible(wp) || + (wp->flags & PANE_MINIMISED) || + (tiled_only && wp->layout_cell==NULL)) + goto next; *wpp = wp; - /* Check if CELL_SCROLLBAR */ - if (window_pane_show_scrollbar(wp, pane_scrollbars)) { + sb_w = wp->scrollbar_style.width + + wp->scrollbar_style.pad; + + if (((int)px < wp->xoff - 1 || + (int)px > wp->xoff + (int)wp->sx + sb_w) && + ((int)py < wp->yoff - 1 || + (int)py > wp->yoff + (int)wp->sy)) + goto next; - line = wp->yoff + wp->sy; + if (pane_status != PANE_STATUS_OFF) { + /* Pane border status inside top/bottom border is + * CELL_INSIDE so it doesn't get overdrawn by a border + * line. + */ + if (pane_status == PANE_STATUS_TOP) + pane_status_line = wp->yoff - 1; + else + pane_status_line = wp->yoff + sy; + left = wp->xoff + 2; + right = wp->xoff + 2 + wp->status_size - 1; + if (py == pane_status_line && + (int)px >= left && (int)px <= right) + return (CELL_INSIDE); + } + + /* Check if CELL_SCROLLBAR */ + if (window_pane_show_scrollbar(wp, pane_scrollbars)) { /* * Check if py could lie within a scrollbar. If the * pane is at the top then py == 0 to sy; if the pane * is not at the top, then yoff to yoff + sy. */ sb_w = wp->scrollbar_style.width + - wp->scrollbar_style.pad; + wp->scrollbar_style.pad; if ((wp->yoff == 0 && py < wp->sy) || - ((int)py >= wp->yoff && (int)py < wp->yoff + (int)wp->sy)) { + ((int)py >= wp->yoff && + (int)py < wp->yoff + (int)wp->sy)) { /* Check if px lies within a scrollbar. */ if ((sb_pos == PANE_SCROLLBARS_RIGHT && ((int)px >= wp->xoff + (int)wp->sx && @@ -445,13 +491,13 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, if (border == SCREEN_REDRAW_INSIDE) return (CELL_INSIDE); if (border == SCREEN_REDRAW_OUTSIDE) - goto next2; - return (screen_redraw_type_of_cell(ctx, px, py)); + goto next; + return (screen_redraw_type_of_cell(ctx, wp, px, py)); - next2: - wp = TAILQ_NEXT(wp, entry); + next: + wp = TAILQ_NEXT(wp, zentry); if (wp == NULL) - wp = TAILQ_FIRST(&w->panes); + wp = TAILQ_FIRST(&w->z_index); } while (wp != start); return (CELL_OUTSIDE); @@ -516,7 +562,7 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, py = wp->yoff - 1; else py = wp->yoff + wp->sy; - cell_type = screen_redraw_type_of_cell(rctx, px, py); + cell_type = screen_redraw_type_of_cell(rctx, wp, px, py); screen_redraw_border_set(w, wp, pane_lines, cell_type, &gc); screen_write_cell(&ctx, &gc); } @@ -594,12 +640,14 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) if (ctx->statustop) yoff += ctx->statuslines; - vr = screen_redraw_get_visible_ranges(wp, x, yoff - ctx->oy, width); + vr = screen_redraw_get_visible_ranges(wp, x, yoff - ctx->oy, + width); for (r=0; r < vr->used; r++) { if (vr->nx[r] == 0) continue; - tty_draw_line(tty, s, i + (vr->px[r] - x), 0, vr->nx[r], vr->px[r], yoff - ctx->oy, + tty_draw_line(tty, s, i + (vr->px[r] - x), 0, vr->nx[r], + vr->px[r], yoff - ctx->oy, &grid_default_cell, NULL); } } @@ -992,7 +1040,8 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, /* Are scrollbars enabled? */ if (window_pane_show_scrollbar(wp, pane_scrollbars)) - sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; + sb_w = wp->scrollbar_style.width + + wp->scrollbar_style.pad; for (r=0; r < vr.used; r++) { if (wp->xoff > 0) @@ -1009,7 +1058,6 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, if (lb > vr.px[r] && lb < vr.px[r] + vr.nx[r] && rb >= vr.px[r] + vr.nx[r]) { - /* vr.nx[r] = vr.nx[r] - ((vr.px[r] + vr.nx[r]) - lb); */ vr.nx[r] = lb - vr.px[r]; } /* Else if the right edge of floating wp @@ -1077,7 +1125,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); - if (wp->xoff + (int)wp->sx <= ctx->ox || wp->xoff >= ctx->ox + (int)ctx->sx) + if (wp->xoff + (int)wp->sx <= ctx->ox || + wp->xoff >= ctx->ox + (int)ctx->sx) return; if (ctx->statustop) top = ctx->statuslines; @@ -1085,11 +1134,14 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) top = 0; for (j = 0; j < wp->sy; j++) { - if (wp->yoff + (int)j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy) + if (wp->yoff + (int)j < ctx->oy || + wp->yoff + j >= ctx->oy + ctx->sy) continue; y = top + wp->yoff + j - ctx->oy; - /* xxx i apparenty unneeded now that the vr array returns where in s to read from. */ + /* Note: i is apparenty not used now that the vr array + * returns where in s to read from. + */ if (wp->xoff >= ctx->ox && wp->xoff + wp->sx <= ctx->ox + ctx->sx) { /* All visible. */ diff --git a/window.c b/window.c index 87ed736394..9b10fa684b 100644 --- a/window.c +++ b/window.c @@ -586,11 +586,17 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) } if (wp == w->active) break; + + /* If you want tiled planes to be able to bury + * floating planes then do this regardless of + * wp->layout_cell==NULL or not. A new option? + */ if (wp->layout_cell == NULL) { TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); wp->flags |= PANE_REDRAW; } + wp = w->active; } } @@ -767,7 +773,11 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, else TAILQ_INSERT_AFTER(&w->panes, other, wp, entry); } - TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); + /* Floating panes are created above tiled planes. */ + if (flags & (SPAWN_FLOATING)) + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); + else + TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); return (wp); } From cc671e449536dc1c381310bee1ca6d106db4e567 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 7 Nov 2025 01:24:19 +0100 Subject: [PATCH 040/167] Fix mouse drag in copy-mode to properly select around floating panes. --- tty.c | 10 ++++++---- window-copy.c | 5 ++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tty.c b/tty.c index a04a5414ac..cf84927cf3 100644 --- a/tty.c +++ b/tty.c @@ -2248,10 +2248,11 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) (gcp->data.width == 1 && !tty_check_overlay(tty, px, py))) return; + vr = screen_redraw_get_visible_ranges(wp, px, py, + gcp->data.width); + /* Handle partially obstructed wide characters. */ if (gcp->data.width > 1) { - vr = screen_redraw_get_visible_ranges(wp, px, py, - gcp->data.width); for (i = 0; i < vr->used; i++) vis2 += vr->nx[i]; tty_check_overlay_range(tty, px, py, gcp->data.width, &r); @@ -2273,8 +2274,9 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette, - ctx->s->hyperlinks); + if (screen_redraw_is_visible(vr, px)) + tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette, + ctx->s->hyperlinks); if (ctx->num == 1) tty_invalidate(tty); diff --git a/window-copy.c b/window-copy.c index f93e548b9b..0f7e223d21 100644 --- a/window-copy.c +++ b/window-copy.c @@ -5810,7 +5810,10 @@ window_copy_drag_update(struct client *c, struct mouse_event *m) if (c == NULL) return; - wp = cmd_mouse_pane(m, NULL, NULL); + if (c->tty.mouse_wp != NULL) + wp = c->tty.mouse_wp; + else + wp = cmd_mouse_pane(m, NULL, NULL); if (wp == NULL) return; wme = TAILQ_FIRST(&wp->modes); From 9c45dd693b8c17137b3283dd13a7f0509d6d9bd9 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 7 Nov 2025 01:24:39 +0100 Subject: [PATCH 041/167] Comment cleanup. --- screen-redraw.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index b4128f880f..9d0e28bd6d 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1092,7 +1092,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, vr.used++; } /* If floating wp completely covers this range - then delete it (make it 0 length). */ + * then delete it (make it 0 length). */ else if (lb <= vr.px[r] && rb >= vr.px[r] + vr.nx[r]) { vr.nx[r] = 0; @@ -1177,8 +1177,9 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) if (vr->nx[r] == 0) continue; /* i is px of cell, add px of region, sub the - pane offset. If you don't sub offset, - contents of pane shifted. note: i apparently unnec. */ + * pane offset. If you don't sub offset, + * contents of pane shifted. note: i apparently unnec. + */ tty_draw_line(tty, s, /* i + */ vr->px[r] - wp->xoff, j, vr->nx[r], vr->px[r], y, &defaults, palette); } From 329e9d54ab727548ed4e68a51a41f57d63b95492 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 7 Nov 2025 11:43:41 +0100 Subject: [PATCH 042/167] Add support for clicking on a floating pane border to make it active. Including bugfix to click bottom border of floating panes. --- cmd-resize-pane.c | 2 ++ cmd-select-pane.c | 4 ++++ key-bindings.c | 3 +++ server-client.c | 8 +++++--- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index f34062a03a..75ef9e0fd0 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -86,6 +86,8 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_tiled; cmd_resize_pane_mouse_update_tiled(c, &event->m); } else { + window_redraw_active_switch(w, c->tty.mouse_wp); + window_set_active_pane(w, c->tty.mouse_wp, 1); c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_floating; cmd_resize_pane_mouse_update_floating(c, &event->m); } diff --git a/cmd-select-pane.c b/cmd-select-pane.c index 3cabe07e63..b01388933e 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -160,6 +160,10 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) server_redraw_window_borders(markedwp->window); server_status_window(markedwp->window); } + if (wp->layout_cell == NULL) { + window_redraw_active_switch(w, wp); + window_set_active_pane(w, wp, 1); + } return (CMD_RETURN_NORMAL); } diff --git a/key-bindings.c b/key-bindings.c index fb766a02f3..63305f2eba 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -455,6 +455,9 @@ key_bindings_init(void) /* Mouse button 1 triple click on pane. */ "bind -n TripleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel } }", + /* Mouse button 1 on border. */ + "bind -n MouseDown1Border { select-pane -M }", + /* Mouse button 1 drag on border. */ "bind -n MouseDrag1Border { resize-pane -M }", diff --git a/server-client.c b/server-client.c index 60a6ddb356..7be6babd1a 100644 --- a/server-client.c +++ b/server-client.c @@ -606,7 +606,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, sb_pad = 0; } /* xxxx isn't this only for tiled panes at the top or bottom of the window? */ - if (pane_status == PANE_STATUS_TOP) + if (pane_status == PANE_STATUS_TOP && wp->layout_cell != NULL) line = wp->yoff - 1; else if (pane_status == PANE_STATUS_BOTTOM) line = wp->yoff + wp->sy; @@ -639,8 +639,10 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, } else /* py > sl_bottom */ return (SCROLLBAR_DOWN); } else if (wp->layout_cell == NULL && - ((int)px == wp->xoff - 1 || (int)py == wp->yoff -1)) { - /* Floating pane left or top border. */ + ((int)px == wp->xoff - 1 || + (int)py == wp->yoff -1 || + (int)py == wp->yoff + (int)wp->sy)) { + /* Floating pane left, bottom or top border. */ return (BORDER); } else { /* Must be inside the pane. */ From 3bb4f72a4becf9727fa033645c55a4e80693e376 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 7 Nov 2025 12:25:59 +0100 Subject: [PATCH 043/167] Add new key binding ctrl-b * to create new floating pane. New panes created at increasing offsets. --- cmd-new-pane.c | 27 ++++++++++++++++++++++----- key-bindings.c | 1 + 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/cmd-new-pane.c b/cmd-new-pane.c index b73f2c744e..77be1baa75 100644 --- a/cmd-new-pane.c +++ b/cmd-new-pane.c @@ -65,7 +65,8 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) char *cause = NULL, *cp; struct args_value *av; u_int count = args_count(args); - u_int sx, sy, pct, x, y; + u_int x, y, sx, sy, pct; + static u_int last_x = 0, last_y = 0; if (args_has(args, 'f')) { sx = w->sx; @@ -119,8 +120,15 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) free(cause); return (CMD_RETURN_ERROR); } - } else - x = 10; + } else { + if (last_x == 0) { + x = 5; + } else { + x = (last_x += 5); + if (last_x > w->sx) + x = 5; + } + } if (args_has(args, 'y')) { y = args_strtonum_and_expand(args, 'y', 0, w->sx, item, &cause); @@ -129,11 +137,20 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) free(cause); return (CMD_RETURN_ERROR); } - } else - y = 10; + } else { + if (last_y == 0) { + y = 5; + } else { + y = (last_y += 5); + if (last_y > w->sy) + y = 5; + } + } sc.xoff = x; sc.yoff = y; + last_x = x; + last_y = y; sc.sx = sx; sc.sy = sy; diff --git a/key-bindings.c b/key-bindings.c index 63305f2eba..ea8d4ebaff 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -358,6 +358,7 @@ key_bindings_init(void) "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 'New floating pane' * { new-pane }", "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 -- '%%' } }", From 466e79d572827ea723ba286363065cea2fdbcd10 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 7 Nov 2025 22:53:42 +0100 Subject: [PATCH 044/167] Bugfix for size of pane_border status not properly clipping. --- screen-redraw.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/screen-redraw.c b/screen-redraw.c index 9d0e28bd6d..2838fd1aac 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -528,6 +528,7 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, char *expanded; int pane_status = rctx->pane_status, sb_w = 0; int pane_scrollbars = rctx->pane_scrollbars; + int max_width; u_int width, i, cell_type, px, py; struct screen_write_ctx ctx; struct screen old; @@ -549,6 +550,9 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, wp->status_size = width = 0; else wp->status_size = width = wp->sx + sb_w - 2; + max_width = (int)w->sx - (wp->xoff + 2) - sb_w; + if (max_width < 0) max_width = 0; + if (width > (u_int)max_width) width = (u_int)max_width; memcpy(&old, &wp->status_screen, sizeof old); screen_init(&wp->status_screen, width, 1, 0); From 31d09450590aaf4369666a22adb20574e6c1ee54 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 10 Nov 2025 23:04:03 +0100 Subject: [PATCH 045/167] Fix dragging a window to y==0 when pane border status enabled. window_get_active_at() needs to return the pane at the top of the window when called with y==0, otherwise it returns null as if there is no pane at the top line. --- window.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/window.c b/window.c index 9b10fa684b..f7fe866c67 100644 --- a/window.c +++ b/window.c @@ -605,9 +605,11 @@ struct window_pane * window_get_active_at(struct window *w, u_int x, u_int y) { struct window_pane *wp; - int xoff, yoff; + int status, xoff, yoff; u_int sx, sy; + status = options_get_number(w->options, "pane-border-status"); + TAILQ_FOREACH(wp, &w->z_index, zentry) { if (!window_pane_visible(wp)) continue; @@ -617,8 +619,13 @@ window_get_active_at(struct window *w, u_int x, u_int y) right border. */ if ((int)x < xoff || x > xoff + sx) continue; - if ((int)y < yoff || y > yoff + sy) - continue; + if (status == PANE_STATUS_TOP) { + if ((int)y < yoff - 1 || y > yoff + sy) + continue; + } else { + if ((int)y < yoff || y > yoff + sy) + continue; + } } else { /* Floating, include top or or left border. */ if ((int)x < xoff - 1 || x > xoff + sx) From 7194fdca38b73f459cb14799def572968dd2ed82 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 10 Nov 2025 23:07:06 +0100 Subject: [PATCH 046/167] Fix many display problems with floating windows up against the window edge and borders of other windows and many off-by-one errors. --- screen-redraw.c | 108 +++++++++++++++++++++++++++++++----------------- tty.c | 2 +- 2 files changed, 72 insertions(+), 38 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 2838fd1aac..71cdf54293 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -247,29 +247,32 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, u_int sy = w->sy; int sb_w, floating = 0; + floating = (wp->layout_cell == NULL); + sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; if (ctx->pane_status == PANE_STATUS_BOTTOM) sy--; - /* Outside the window? */ - if (px > w->sx || py > sy) - return (0); + if (! floating) { + /* Outside the window? */ + if (px > w->sx || py > sy) + return (0); - /* On the window border? */ - if (px == w->sx || py == sy) - return (1); + /* On the window border? */ + if (px == w->sx || py == sy) + return (1); + } /* If checking a cell from a tiled pane, ignore floating panes * because 2 side-by-side or top-bottom panes share a border - * which is used to do split coulering. Essentially treat all + * which is used to do split colouring. Essentially treat all * non-floating panes as being in a single z-index. * * If checking a cell from a floating pane, only check cells * from this floating pane, again, essentially only this z-index. */ - floating = (wp->layout_cell == NULL); /* Check all the panes. */ TAILQ_FOREACH(wp2, &w->z_index, zentry) { @@ -305,7 +308,7 @@ screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx, int pane_status = ctx->pane_status; struct window *w = c->session->curw->window; u_int sx = w->sx, sy = w->sy; - int borders = 0; + int borders = 0, floating; if (pane_status == PANE_STATUS_BOTTOM) sy--; @@ -314,6 +317,8 @@ screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx, if (px > sx || py > sy) return (CELL_OUTSIDE); + floating = (wp->layout_cell == NULL); + /* * Construct a bitmask of whether the cells to the left (bit 8), right, * top, and bottom (bit 1) of this cell are borders. @@ -322,29 +327,54 @@ screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx, * 8 + 4 * 1 */ - if (px == 0 || screen_redraw_cell_border(ctx, wp, px - 1, py)) - borders |= 8; - if (px <= sx && screen_redraw_cell_border(ctx, wp, px + 1, py)) - borders |= 4; - if (pane_status == PANE_STATUS_TOP) { - if (py != 0 && - screen_redraw_cell_border(ctx, wp, px, py - 1)) - borders |= 2; - if (screen_redraw_cell_border(ctx, wp, px, py + 1)) - borders |= 1; - } else if (pane_status == PANE_STATUS_BOTTOM) { - if (py == 0 || - screen_redraw_cell_border(ctx, wp, px, py - 1)) - borders |= 2; - if (py != sy && - screen_redraw_cell_border(ctx, wp, px, py + 1)) - borders |= 1; + if (! floating) { + if (px == 0 || screen_redraw_cell_border(ctx, wp, px - 1, py)) + borders |= 8; + if (px <= sx && screen_redraw_cell_border(ctx, wp, px + 1, py)) + borders |= 4; + if (pane_status == PANE_STATUS_TOP) { + if (py != 0 && + screen_redraw_cell_border(ctx, wp, px, py - 1)) + borders |= 2; + if (screen_redraw_cell_border(ctx, wp, px, py + 1)) + borders |= 1; + } else if (pane_status == PANE_STATUS_BOTTOM) { + if (py == 0 || + screen_redraw_cell_border(ctx, wp, px, py - 1)) + borders |= 2; + if (py != sy && + screen_redraw_cell_border(ctx, wp, px, py + 1)) + borders |= 1; + } else { + if (py == 0 || + screen_redraw_cell_border(ctx, wp, px, py - 1)) + borders |= 2; + if (screen_redraw_cell_border(ctx, wp, px, py + 1)) + borders |= 1; + } } else { - if (py == 0 || - screen_redraw_cell_border(ctx, wp, px, py - 1)) - borders |= 2; - if (screen_redraw_cell_border(ctx, wp, px, py + 1)) - borders |= 1; + if (screen_redraw_cell_border(ctx, wp, px - 1, py)) + borders |= 8; + if (px <= sx && screen_redraw_cell_border(ctx, wp, px + 1, py)) + borders |= 4; + if (pane_status == PANE_STATUS_TOP) { + if (py != 0 && + screen_redraw_cell_border(ctx, wp, px, py - 1)) + borders |= 2; + if (screen_redraw_cell_border(ctx, wp, px, py + 1)) + borders |= 1; + } else if (pane_status == PANE_STATUS_BOTTOM) { + if (screen_redraw_cell_border(ctx, wp, px, py - 1)) + borders |= 2; + if (py != sy && + screen_redraw_cell_border(ctx, wp, px, py + 1)) + borders |= 1; + } else { + if (screen_redraw_cell_border(ctx, wp, px, py - 1)) + borders |= 2; + if (screen_redraw_cell_border(ctx, wp, px, py + 1)) + borders |= 1; + } } /* @@ -399,7 +429,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, if (px > sx || py > sy) return (CELL_OUTSIDE); - /* Look through panes in z-index order starting with active pane. */ + /* Find pane higest in z-index at this point. */ TAILQ_FOREACH(wp, &w->z_index, zentry) { sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; @@ -547,12 +577,13 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, expanded = format_expand_time(ft, fmt); if (wp->sx < 4) - wp->status_size = width = 0; + width = 0; else - wp->status_size = width = wp->sx + sb_w - 2; + width = wp->sx + sb_w - 2; max_width = (int)w->sx - (wp->xoff + 2) - sb_w; if (max_width < 0) max_width = 0; if (width > (u_int)max_width) width = (u_int)max_width; + wp->status_size = width; memcpy(&old, &wp->status_screen, sizeof old); screen_init(&wp->status_screen, width, 1, 0); @@ -644,6 +675,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) if (ctx->statustop) yoff += ctx->statuslines; + vr = screen_redraw_get_visible_ranges(wp, x, yoff - ctx->oy, width); @@ -1038,8 +1070,10 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, tb = wp->yoff-1; bb = wp->yoff + wp->sy; if (!found_self || - (wp->flags & PANE_MINIMISED) || - (py < tb || py > bb)) + (wp->flags & PANE_MINIMISED) || + py < tb || + (wp->layout_cell == NULL && py > bb) || + (wp->layout_cell != NULL && py >= bb)) continue; /* Are scrollbars enabled? */ @@ -1068,7 +1102,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, falls inside of this range and left edge covers the left of range, then move px forward to right edge of wp. */ - else if (rb > vr.px[r] && + else if (rb >= vr.px[r] && rb < vr.px[r] + vr.nx[r] && lb <= vr.px[r]) { vr.nx[r] = vr.nx[r] - (rb + 1 - vr.px[r]); diff --git a/tty.c b/tty.c index cf84927cf3..d9ec003259 100644 --- a/tty.c +++ b/tty.c @@ -2259,7 +2259,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) for (i = 0; i < OVERLAY_MAX_RANGES; i++) vis += r.nx[i]; if (vis < gcp->data.width || - vis2 < gcp->data.width) { + vis2 < gcp->data.width) { /* xxxx check visible range */ tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, px, py, &ctx->defaults, ctx->palette); return; From c8d4d8cb66de373987298a6b88d165f35315de34 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 14 Nov 2025 11:02:12 +0100 Subject: [PATCH 047/167] Bugfix fix some uninitialised variable warnings. --- server-client.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/server-client.c b/server-client.c index 463f1be263..1e79a49c19 100644 --- a/server-client.c +++ b/server-client.c @@ -594,7 +594,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, struct options *wo = w->options; struct window_pane *fwp; int pane_status, sb, sb_pos, sb_w, sb_pad; - u_int line, sl_top, sl_bottom; + u_int pane_status_line, sl_top, sl_bottom; u_int bdr_bottom, bdr_top, bdr_left, bdr_right; sb = options_get_number(wo, "pane-scrollbars"); @@ -608,14 +608,16 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, sb_w = 0; sb_pad = 0; } - /* xxxx isn't this only for tiled panes at the top or bottom of the window? */ - if (pane_status == PANE_STATUS_TOP && wp->layout_cell != NULL) - line = wp->yoff - 1; + + if (pane_status == PANE_STATUS_TOP) + pane_status_line = wp->yoff - 1; else if (pane_status == PANE_STATUS_BOTTOM) - line = wp->yoff + wp->sy; + pane_status_line = wp->yoff + wp->sy; + else + pane_status_line = -1; /* not used */ /* Check if point is within the pane or scrollbar. */ - if (((pane_status != PANE_STATUS_OFF && py != line) || + if (((pane_status != PANE_STATUS_OFF && py != pane_status_line) || (wp->yoff == 0 && py < wp->sy) || ((int)py >= wp->yoff && py < wp->yoff + wp->sy)) && ((sb_pos == PANE_SCROLLBARS_RIGHT && @@ -1294,6 +1296,7 @@ server_client_check_mouse(struct client *c, struct key_event *event) c->tty.mouse_drag_flag = MOUSE_BUTTONS(b) + 1; /* Only change pane if not already dragging a pane border. */ if (c->tty.mouse_wp == NULL) { + wp = window_get_active_at(w, x, y); c->tty.mouse_wp = wp; } if (c->tty.mouse_scrolling_flag == 0 && From eaa467618b42845ad4a3e0571e334977733ba340 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 8 Dec 2025 14:28:17 +0000 Subject: [PATCH 048/167] 1. Rework floating panes to have a stub layout_cell, 2. Add new <..> format to list-windows & select-layout for floating anes, 3. Fix zooming to work with floating panes, 4. Fix several display issues. --- cmd-new-pane.c | 22 ++++-- cmd-resize-pane.c | 77 ++++++++++--------- cmd-select-layout.c | 2 +- cmd-select-pane.c | 2 +- cmd-split-window.c | 2 +- format.c | 6 +- layout-custom.c | 180 +++++++++++++++++++++++++++++++++----------- layout.c | 96 ++++++++++++++++++----- screen-redraw.c | 38 +++++----- screen-write.c | 3 +- server-client.c | 17 +++-- spawn.c | 16 ++-- tmux.h | 9 +-- tty.c | 4 +- window.c | 43 ++++++++--- 15 files changed, 357 insertions(+), 160 deletions(-) diff --git a/cmd-new-pane.c b/cmd-new-pane.c index 77be1baa75..a014aec6b5 100644 --- a/cmd-new-pane.c +++ b/cmd-new-pane.c @@ -59,6 +59,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) struct winlink *wl = target->wl; struct window *w = wl->window; struct window_pane *wp = target->wp, *new_wp; + struct layout_cell *lc; struct cmd_find_state fs; int flags, input; const char *template; @@ -147,13 +148,6 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) } } - sc.xoff = x; - sc.yoff = y; - last_x = x; - last_y = y; - sc.sx = sx; - sc.sy = sy; - input = (args_has(args, 'I') && count == 0); flags = SPAWN_FLOATING; @@ -169,7 +163,19 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) sc.wl = wl; sc.wp0 = wp; - sc.lc = NULL; + + /* Floating panes sit in layout cells which are not in the layout_root + * tree so we call it with parent == NULL. + */ + lc = layout_create_cell(NULL); + lc->xoff = x; + lc->yoff = y; + lc->sx = sx; + lc->sy = sy; + sc.lc = lc; + + last_x = x; /* Statically save last xoff & yoff so that new */ + last_y = y; /* floating panes offset so they don't overlap. */ args_to_vector(args, &sc.argc, &sc.argv); sc.environ = environ_create(); diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 75ef9e0fd0..5abbaa3cae 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -82,14 +82,14 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); if (c == NULL || c->session != s) return (CMD_RETURN_NORMAL); - if (c->tty.mouse_wp->layout_cell != NULL) { - c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_tiled; - cmd_resize_pane_mouse_update_tiled(c, &event->m); - } else { + if (c->tty.mouse_wp->flags & PANE_FLOATING) { window_redraw_active_switch(w, c->tty.mouse_wp); window_set_active_pane(w, c->tty.mouse_wp, 1); c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_floating; cmd_resize_pane_mouse_update_floating(c, &event->m); + } else { + c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_tiled; + cmd_resize_pane_mouse_update_tiled(c, &event->m); } return (CMD_RETURN_NORMAL); } @@ -163,7 +163,9 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) struct winlink *wl; struct window *w; struct window_pane *wp; - u_int y, ly, x, lx, new_sx, new_sy, resizes = 0; + struct layout_cell *lc; + u_int y, ly, x, lx, new_sx, new_sy; + int new_xoff, new_yoff, resizes = 0; wl = cmd_mouse_window(m, NULL); if (wl == NULL) { @@ -184,89 +186,93 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) ly = m->statusat - 1; wp = c->tty.mouse_wp; + lc = wp->layout_cell; log_debug("%s: %%%u resize_pane xoff=%u sx=%u xy=%ux%u lxy=%ux%u", __func__, wp->id, wp->xoff, wp->sx, x, y, lx, ly); if ((((int)lx == wp->xoff - 1) || ((int)lx == wp->xoff)) && ((int)ly == wp->yoff - 1)) { - /* Top left border */ - new_sx = wp->sx + (lx - x); + /* Top left corner */ + new_sx = lc->sx + (lx - x); if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = wp->sy + (ly - y); + new_sy = lc->sy + (ly - y); if (new_sy < PANE_MINIMUM) new_sy = PANE_MINIMUM; - window_pane_move(wp, x + 1, y + 1); - window_pane_resize(wp, new_sx, new_sy); + new_xoff = x + 1; /* Because mouse is on border at xoff - 1 */ + new_yoff = y + 1; + layout_set_size(lc, new_sx, new_sy, new_xoff, new_yoff); resizes++; } else if ((((int)lx == wp->xoff + (int)wp->sx + 1) || ((int)lx == wp->xoff + (int)wp->sx)) && ((int)ly == wp->yoff - 1)) { - /* Top right border */ - new_sx = x - wp->xoff - 1; + /* Top right corner */ + new_sx = x - lc->xoff; if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = wp->sy + (ly - y); + new_sy = lc->sy + (ly - y); if (new_sy < PANE_MINIMUM) new_sy = PANE_MINIMUM; - window_pane_move(wp, wp->xoff, y + 1); - window_pane_resize(wp, new_sx, new_sy); + new_yoff = y + 1; + layout_set_size(lc, new_sx, new_sy, lc->xoff, new_yoff); resizes++; } else if ((((int)lx == wp->xoff - 1) || ((int)lx == wp->xoff)) && ((int)ly == wp->yoff + (int)wp->sy)) { - /* Bottom left border */ - new_sx = wp->sx + (lx - x); + /* Bottom left corner */ + new_sx = lc->sx + (lx - x); if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = y - wp->yoff; + new_sy = y - lc->yoff; if (new_sy < PANE_MINIMUM) return; - window_pane_move(wp, x + 1, wp->yoff); - window_pane_resize(wp, new_sx, new_sy); + new_xoff = x + 1; + layout_set_size(lc, new_sx, new_sy, new_xoff, lc->yoff); resizes++; } else if ((((int)lx == wp->xoff + (int)wp->sx + 1) || ((int)lx == wp->xoff + (int)wp->sx)) && ((int)ly == wp->yoff + (int)wp->sy)) { /* Bottom right corner */ - new_sx = x - wp->xoff - 1; + new_sx = x - lc->xoff; if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = y - wp->yoff; + new_sy = y - lc->yoff; if (new_sy < PANE_MINIMUM) new_sy = PANE_MINIMUM; - window_pane_resize(wp, new_sx, new_sy); + layout_set_size(lc, new_sx, new_sy, lc->xoff, lc->yoff); resizes++; } else if ((int)lx == wp->xoff + (int)wp->sx + 1) { /* Right border */ - new_sx = x - wp->xoff - 1; + new_sx = x - lc->xoff; if (new_sx < PANE_MINIMUM) return; - window_pane_resize(wp, new_sx, wp->sy); + layout_set_size(lc, new_sx, lc->sy, lc->xoff, lc->yoff); resizes++; } else if ((int)lx == wp->xoff - 1) { /* Left border */ - new_sx = wp->sx + (lx - x); + new_sx = lc->sx + (lx - x); if (new_sx < PANE_MINIMUM) return; - window_pane_move(wp, x + 1, wp->yoff); - window_pane_resize(wp, new_sx, wp->sy); + new_xoff = x + 1; + layout_set_size(lc, new_sx, lc->sy, new_xoff, lc->yoff); resizes++; } else if ((int)ly == wp->yoff + (int)wp->sy) { /* Bottom border */ - new_sy = y - wp->yoff; + new_sy = y - lc->yoff; if (new_sy < PANE_MINIMUM) return; - window_pane_resize(wp, wp->sx, new_sy); + layout_set_size(lc, lc->sx, new_sy, lc->xoff, lc->yoff); resizes++; } else if ((int)ly == wp->yoff - 1) { - /* Top border */ - window_pane_move(wp, wp->xoff + (x - lx), y + 1); - /* + /* Top border (move instead of resize) */ + new_xoff = lc->xoff + (x - lx); + new_yoff = y + 1; + layout_set_size(lc, lc->sx, lc->sy, new_xoff, new_yoff); + /* To resize instead of move: new_sy = wp->sy + (ly - y); if (new_sy < PANE_MINIMUM) return; - window_pane_move(wp, wp->xoff, y + 1); - window_pane_resize(wp, wp->sx, new_sy); + new_yoff = y + 1; + layout_set_size(lc, lc->sx, new_sy, lc->xoff, new_yoff); */ resizes++; } else { @@ -274,6 +280,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) __func__, wp->id, wp->xoff, wp->sx, x, y, lx, ly); } if (resizes != 0) { + layout_fix_panes(w, NULL); server_redraw_window(w); server_redraw_window_borders(w); } diff --git a/cmd-select-layout.c b/cmd-select-layout.c index 6dfe2b6a80..4177974721 100644 --- a/cmd-select-layout.c +++ b/cmd-select-layout.c @@ -90,7 +90,7 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item) previous = 1; oldlayout = w->old_layout; - w->old_layout = layout_dump(w->layout_root); + w->old_layout = layout_dump(w, w->layout_root); if (next || previous) { if (next) diff --git a/cmd-select-pane.c b/cmd-select-pane.c index b01388933e..99b0841c12 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -160,7 +160,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) server_redraw_window_borders(markedwp->window); server_status_window(markedwp->window); } - if (wp->layout_cell == NULL) { + if (wp->flags & PANE_FLOATING) { window_redraw_active_switch(w, wp); window_set_active_pane(w, wp, 1); } diff --git a/cmd-split-window.c b/cmd-split-window.c index ca8354a76a..bd2a4b48f2 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -71,7 +71,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) struct args_value *av; u_int count = args_count(args), curval = 0; - if (wp->layout_cell == NULL) { + if (wp->flags & PANE_FLOATING) { cmdq_error(item, "can't split a floating pane"); return (CMD_RETURN_ERROR); } diff --git a/format.c b/format.c index 17a9dd53ac..36799d27ba 100644 --- a/format.c +++ b/format.c @@ -821,8 +821,8 @@ format_cb_window_layout(struct format_tree *ft) return (NULL); if (w->saved_layout_root != NULL) - return (layout_dump(w->saved_layout_root)); - return (layout_dump(w->layout_root)); + return (layout_dump(w, w->saved_layout_root)); + return (layout_dump(w, w->layout_root)); } /* Callback for window_visible_layout. */ @@ -834,7 +834,7 @@ format_cb_window_visible_layout(struct format_tree *ft) if (w == NULL) return (NULL); - return (layout_dump(w->layout_root)); + return (layout_dump(w, w->layout_root)); } /* Callback for pane_start_command. */ diff --git a/layout-custom.c b/layout-custom.c index 2bfb6d8964..30f8909b01 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -27,10 +27,11 @@ static struct layout_cell *layout_find_bottomright(struct layout_cell *); static u_short layout_checksum(const char *); static int layout_append(struct layout_cell *, char *, size_t); -static struct layout_cell *layout_construct(struct layout_cell *, - const char **); +static int layout_construct(struct layout_cell *, + const char **, struct layout_cell **, + struct layout_cell **); static void layout_assign(struct window_pane **, - struct layout_cell *); + struct layout_cell *, int); /* Find the bottom-right cell. */ static struct layout_cell * @@ -58,14 +59,33 @@ layout_checksum(const char *layout) /* Dump layout as a string. */ char * -layout_dump(struct layout_cell *root) +layout_dump(struct window *w, struct layout_cell *root) { - char layout[8192], *out; + char layout[8192], *out; + int braket; + struct window_pane *wp; *layout = '\0'; if (layout_append(root, layout, sizeof layout) != 0) return (NULL); + braket = 0; + TAILQ_FOREACH(wp, &w->z_index, zentry) { + if (~wp->flags & PANE_FLOATING) + break; + if (!braket) { + strcat(layout, "<"); + braket = 1; + } + if (layout_append(wp->layout_cell, layout, sizeof layout) != 0) + return (NULL); + strcat(layout, ","); + } + if (braket) { + /* Overwrite the trailing ','. */ + layout[strlen(layout) - 1] = '>'; + } + xasprintf(&out, "%04hx,%s", layout_checksum(layout), layout); return (out); } @@ -81,7 +101,8 @@ layout_append(struct layout_cell *lc, char *buf, size_t len) if (len == 0) return (-1); - + if (lc == NULL) + return (0); if (lc->wp != NULL) { tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%u,%u,%u", lc->sx, lc->sy, lc->xoff, lc->yoff, lc->wp->id); @@ -109,6 +130,7 @@ layout_append(struct layout_cell *lc, char *buf, size_t len) } buf[strlen(buf) - 1] = brackets[0]; break; + case LAYOUT_FLOATING: case LAYOUT_WINDOWPANE: break; } @@ -125,6 +147,7 @@ layout_check(struct layout_cell *lc) switch (lc->type) { case LAYOUT_WINDOWPANE: + case LAYOUT_FLOATING: break; case LAYOUT_LEFTRIGHT: TAILQ_FOREACH(lcchild, &lc->cells, entry) { @@ -156,7 +179,7 @@ layout_check(struct layout_cell *lc) int layout_parse(struct window *w, const char *layout, char **cause) { - struct layout_cell *lc, *lcchild; + struct layout_cell *lcchild, *tiled_lc = NULL, *floating_lc = NULL; struct window_pane *wp; u_int npanes, ncells, sx = 0, sy = 0; u_short csum; @@ -173,11 +196,16 @@ layout_parse(struct window *w, const char *layout, char **cause) } /* Build the layout. */ - lc = layout_construct(NULL, &layout); - if (lc == NULL) { + if (layout_construct(NULL, &layout, &tiled_lc, &floating_lc) != 0) { *cause = xstrdup("invalid layout"); return (-1); } + if (tiled_lc == NULL) { + /* A stub layout cell for an empty window. */ + tiled_lc = layout_create_cell(NULL); + tiled_lc->type = LAYOUT_LEFTRIGHT; + layout_set_size(tiled_lc, w->sx, w->sy, 0, 0); + } if (*layout != '\0') { *cause = xstrdup("invalid layout"); goto fail; @@ -186,8 +214,10 @@ layout_parse(struct window *w, const char *layout, char **cause) /* Check this window will fit into the layout. */ for (;;) { npanes = window_count_panes(w); - ncells = layout_count_cells(lc); + ncells = layout_count_cells(tiled_lc); + ncells += layout_count_cells(floating_lc); if (npanes > ncells) { + /* Modify this to open a new pane */ xasprintf(cause, "have %u panes but need %u", npanes, ncells); goto fail; @@ -195,9 +225,17 @@ layout_parse(struct window *w, const char *layout, char **cause) if (npanes == ncells) break; - /* Fewer panes than cells - close the bottom right. */ - lcchild = layout_find_bottomright(lc); - layout_destroy_cell(w, lcchild, &lc); + /* + * Fewer panes than cells - close floating panes first + * then close the bottom right until. + */ + if (floating_lc && ! TAILQ_EMPTY(&floating_lc->cells)) { + lcchild = TAILQ_FIRST(&floating_lc->cells); + layout_destroy_cell(w, lcchild, &floating_lc); + } else { + lcchild = layout_find_bottomright(tiled_lc); + layout_destroy_cell(w, lcchild, &tiled_lc); + } } /* @@ -205,85 +243,112 @@ layout_parse(struct window *w, const char *layout, char **cause) * an incorrect top cell size - if it is larger than the top child then * correct that (if this is still wrong the check code will catch it). */ - switch (lc->type) { + + switch (tiled_lc->type) { case LAYOUT_WINDOWPANE: break; case LAYOUT_LEFTRIGHT: - TAILQ_FOREACH(lcchild, &lc->cells, entry) { + TAILQ_FOREACH(lcchild, &tiled_lc->cells, entry) { sy = lcchild->sy + 1; sx += lcchild->sx + 1; } break; case LAYOUT_TOPBOTTOM: - TAILQ_FOREACH(lcchild, &lc->cells, entry) { + TAILQ_FOREACH(lcchild, &tiled_lc->cells, entry) { sx = lcchild->sx + 1; sy += lcchild->sy + 1; } break; + case LAYOUT_FLOATING: + *cause = xstrdup("invalid layout"); + goto fail; } - if (lc->type != LAYOUT_WINDOWPANE && (lc->sx != sx || lc->sy != sy)) { - log_debug("fix layout %u,%u to %u,%u", lc->sx, lc->sy, sx,sy); - layout_print_cell(lc, __func__, 0); - lc->sx = sx - 1; lc->sy = sy - 1; + if (tiled_lc->type != LAYOUT_WINDOWPANE && + (tiled_lc->sx != sx || tiled_lc->sy != sy)) { + log_debug("fix layout %u,%u to %u,%u", tiled_lc->sx, + tiled_lc->sy, sx,sy); + layout_print_cell(tiled_lc, __func__, 0); + tiled_lc->sx = sx - 1; tiled_lc->sy = sy - 1; } /* Check the new layout. */ - if (!layout_check(lc)) { + if (!layout_check(tiled_lc)) { *cause = xstrdup("size mismatch after applying layout"); goto fail; } - /* Resize to the layout size. */ - window_resize(w, lc->sx, lc->sy, -1, -1); + /* Resize window to the layout size. */ + if (sx != 0 && sy != 0) + window_resize(w, tiled_lc->sx, tiled_lc->sy, -1, -1); /* Destroy the old layout and swap to the new. */ layout_free_cell(w->layout_root); - w->layout_root = lc; + w->layout_root = tiled_lc; /* Assign the panes into the cells. */ wp = TAILQ_FIRST(&w->panes); - layout_assign(&wp, lc); + layout_assign(&wp, tiled_lc, 0); + layout_assign(&wp, floating_lc, 1); + + /* Fix z_indexes. */ + while (!TAILQ_EMPTY(&w->z_index)) { + wp = TAILQ_FIRST(&w->z_index); + TAILQ_REMOVE(&w->z_index, wp, zentry); + } + layout_fix_zindexes(w, floating_lc); + layout_fix_zindexes(w, tiled_lc); /* Update pane offsets and sizes. */ layout_fix_offsets(w); layout_fix_panes(w, NULL); recalculate_sizes(); - layout_print_cell(lc, __func__, 0); + layout_print_cell(tiled_lc, __func__, 0); + layout_print_cell(floating_lc, __func__, 0); + + /* Free the floating layout cell, no longer needed. */ + layout_free_cell(floating_lc); notify_window("window-layout-changed", w); return (0); fail: - layout_free_cell(lc); + layout_free_cell(tiled_lc); + layout_free_cell(floating_lc); return (-1); } /* Assign panes into cells. */ static void -layout_assign(struct window_pane **wp, struct layout_cell *lc) +layout_assign(struct window_pane **wp, struct layout_cell *lc, int floating) { struct layout_cell *lcchild; + if (lc == NULL) + return; + switch (lc->type) { case LAYOUT_WINDOWPANE: layout_make_leaf(lc, *wp); + if (floating) { + (*wp)->flags |= PANE_FLOATING; + } *wp = TAILQ_NEXT(*wp, entry); return; case LAYOUT_LEFTRIGHT: case LAYOUT_TOPBOTTOM: + case LAYOUT_FLOATING: TAILQ_FOREACH(lcchild, &lc->cells, entry) - layout_assign(wp, lcchild); + layout_assign(wp, lcchild, 1); return; } } -/* Construct a cell from all or part of a layout tree. */ static struct layout_cell * -layout_construct(struct layout_cell *lcparent, const char **layout) +layout_construct_cell(struct layout_cell *lcparent, const char **layout) { - struct layout_cell *lc, *lcchild; + struct layout_cell *lc; u_int sx, sy, xoff, yoff; const char *saved; @@ -324,17 +389,42 @@ layout_construct(struct layout_cell *lcparent, const char **layout) lc->xoff = xoff; lc->yoff = yoff; + return (lc); +} + + +/* + * Given a character string layout, recursively construct cells. + * Possible return values: + * lc LAYOUT_WINDOWPANE, no children + * lc LAYOUT_LEFTRIGHT or LAYOUT_TOPBOTTOM, with children + * floating_lc LAYOUT_FLOATING, with children + */ +static int +layout_construct(struct layout_cell *lcparent, const char **layout, + struct layout_cell **lc, struct layout_cell **floating_lc) +{ + struct layout_cell *lcchild, *saved_lc; + + *lc = layout_construct_cell(lcparent, layout); + switch (**layout) { case ',': case '}': case ']': + case '>': case '\0': - return (lc); + return (0); case '{': - lc->type = LAYOUT_LEFTRIGHT; + (*lc)->type = LAYOUT_LEFTRIGHT; break; case '[': - lc->type = LAYOUT_TOPBOTTOM; + (*lc)->type = LAYOUT_TOPBOTTOM; + break; + case '<': + saved_lc = *lc; + *lc = layout_create_cell(lcparent); + (*lc)->type = LAYOUT_FLOATING; break; default: goto fail; @@ -342,13 +432,12 @@ layout_construct(struct layout_cell *lcparent, const char **layout) do { (*layout)++; - lcchild = layout_construct(lc, layout); - if (lcchild == NULL) + if (layout_construct(*lc, layout, &lcchild, floating_lc) != 0) goto fail; - TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry); + TAILQ_INSERT_TAIL(&(*lc)->cells, lcchild, entry); } while (**layout == ','); - switch (lc->type) { + switch ((*lc)->type) { case LAYOUT_LEFTRIGHT: if (**layout != '}') goto fail; @@ -357,14 +446,21 @@ layout_construct(struct layout_cell *lcparent, const char **layout) if (**layout != ']') goto fail; break; + case LAYOUT_FLOATING: + if (**layout != '>') + goto fail; + *floating_lc = *lc; + *lc = saved_lc; + break; default: goto fail; } (*layout)++; - return (lc); + return (0); fail: - layout_free_cell(lc); - return (NULL); + layout_free_cell(*lc); + layout_free_cell(*floating_lc); + return (-1); } diff --git a/layout.c b/layout.c index 8087a9e723..b3d8362298 100644 --- a/layout.c +++ b/layout.c @@ -83,9 +83,24 @@ layout_free_cell(struct layout_cell *lc) layout_free_cell(lcchild); } break; + case LAYOUT_FLOATING: + /* A Floating layout cell is only used temporarily + * while select-layout constructs a layout. + * Cleave the children from the temp layout, then + * free temp floating layout cell. Each floating + * pane has stub layout. + */ + while (!TAILQ_EMPTY(&lc->cells)) { + lcchild = TAILQ_FIRST(&lc->cells); + TAILQ_REMOVE(&lc->cells, lcchild, entry); + lcchild->parent = NULL; + } + break; case LAYOUT_WINDOWPANE: - if (lc->wp != NULL) + if (lc->wp != NULL) { + lc->wp->layout_cell->parent = NULL; lc->wp->layout_cell = NULL; + } break; } @@ -98,6 +113,9 @@ layout_print_cell(struct layout_cell *lc, const char *hdr, u_int n) struct layout_cell *lcchild; const char *type; + if (lc == NULL) + return; + switch (lc->type) { case LAYOUT_LEFTRIGHT: type = "LEFTRIGHT"; @@ -105,6 +123,9 @@ layout_print_cell(struct layout_cell *lc, const char *hdr, u_int n) case LAYOUT_TOPBOTTOM: type = "TOPBOTTOM"; break; + case LAYOUT_FLOATING: + type = "FLOATING"; + break; case LAYOUT_WINDOWPANE: type = "WINDOWPANE"; break; @@ -118,6 +139,7 @@ layout_print_cell(struct layout_cell *lc, const char *hdr, u_int n) switch (lc->type) { case LAYOUT_LEFTRIGHT: case LAYOUT_TOPBOTTOM: + case LAYOUT_FLOATING: TAILQ_FOREACH(lcchild, &lc->cells, entry) layout_print_cell(lcchild, hdr, n + 1); break; @@ -153,6 +175,7 @@ layout_search_by_border(struct layout_cell *lc, u_int x, u_int y) return (last); break; case LAYOUT_WINDOWPANE: + case LAYOUT_FLOATING: break; } @@ -198,6 +221,29 @@ layout_make_node(struct layout_cell *lc, enum layout_type type) lc->wp = NULL; } +void +layout_fix_zindexes(struct window *w, struct layout_cell *lc) +{ + struct layout_cell *lcchild; + + if (lc == NULL) + return; + + switch (lc->type) { + case LAYOUT_WINDOWPANE: + TAILQ_INSERT_TAIL(&w->z_index, lc->wp, zentry); + break; + case LAYOUT_LEFTRIGHT: + case LAYOUT_TOPBOTTOM: + case LAYOUT_FLOATING: + TAILQ_FOREACH(lcchild, &lc->cells, entry) + layout_fix_zindexes(w, lcchild); + return; + default: + fatalx("bad layout type"); + } +} + /* Fix cell offsets for a child cell. */ static void layout_fix_offsets1(struct layout_cell *lc) @@ -307,7 +353,8 @@ layout_fix_panes(struct window *w, struct window_pane *skip) sx = lc->sx; sy = lc->sy; - if (layout_add_horizontal_border(w, lc, status)) { + if (~wp->flags & PANE_FLOATING && + layout_add_horizontal_border(w, lc, status)) { if (status == PANE_STATUS_TOP) wp->yoff++; sy--; @@ -353,6 +400,7 @@ layout_count_cells(struct layout_cell *lc) return (1); case LAYOUT_LEFTRIGHT: case LAYOUT_TOPBOTTOM: + case LAYOUT_FLOATING: count = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) count += layout_count_cells(lcchild); @@ -462,7 +510,7 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, } } -/* Destroy a cell and redistribute the space. */ +/* Destroy a cell and redistribute the space in tiled cells. */ void layout_destroy_cell(struct window *w, struct layout_cell *lc, struct layout_cell **lcroot) @@ -470,36 +518,44 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, struct layout_cell *lcother, *lcparent; /* - * If no parent, this is the last pane so window close is imminent and - * there is no need to resize anything. + * If no parent, this is either a floating pane or the last + * pane so window close is imminent and there is no need to + * resize anything. */ lcparent = lc->parent; if (lcparent == NULL) { - layout_free_cell(lc); - *lcroot = NULL; + if (lc->wp != NULL && ~lc->wp->flags & PANE_FLOATING) + *lcroot = NULL; + /* xxx if (lc->type == LAYOUT_WINDOWPANE) */ + layout_free_cell(lc); return; } - /* Merge the space into the previous or next cell. */ - if (lc == TAILQ_FIRST(&lcparent->cells)) - lcother = TAILQ_NEXT(lc, entry); - else - lcother = TAILQ_PREV(lc, layout_cells, entry); - if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) - layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); - else if (lcother != NULL) - layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1); + /* In tiled layouts, merge the space into the previous or next cell. */ + if (lcparent->type != LAYOUT_FLOATING) { + if (lc == TAILQ_FIRST(&lcparent->cells)) + lcother = TAILQ_NEXT(lc, entry); + else + lcother = TAILQ_PREV(lc, layout_cells, entry); + if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) + layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); + else if (lcother != NULL) + layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1); + } /* Remove this from the parent's list. */ TAILQ_REMOVE(&lcparent->cells, lc, entry); layout_free_cell(lc); + if (lcparent->type == LAYOUT_FLOATING) + return; + /* - * If the parent now has one cell, remove the parent from the tree and - * replace it by that cell. + * In tiled layouts, if the parent now has one cell, remove + * the parent from the tree and replace it by that cell. */ lc = TAILQ_FIRST(&lcparent->cells); - if (TAILQ_NEXT(lc, entry) == NULL) { + if (lc != NULL && TAILQ_NEXT(lc, entry) == NULL) { TAILQ_REMOVE(&lcparent->cells, lc, entry); lc->parent = lcparent->parent; @@ -741,7 +797,7 @@ layout_resize_pane_shrink(struct window *w, struct layout_cell *lc, return (size); } -/* Assign window pane to newly split cell. */ +/* Assign window pane to new cell. */ void layout_assign_pane(struct layout_cell *lc, struct window_pane *wp, int do_not_resize) diff --git a/screen-redraw.c b/screen-redraw.c index 71cdf54293..9244348f18 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -106,7 +106,7 @@ screen_redraw_two_panes(struct window *w, enum layout_type *type) u_int count = 0; TAILQ_FOREACH(wp, &w->panes, entry) { - if (wp->layout_cell == NULL) + if (wp->flags & PANE_FLOATING || wp->layout_cell == NULL) continue; count++; if (count > 2 || wp->layout_cell->parent == NULL) @@ -146,7 +146,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; /* Floating pane borders */ - if (wp->layout_cell == NULL) { + if (wp->flags & PANE_FLOATING) { if ((int)px == wp->xoff - 1 && (int)py >= wp->yoff - 1 && (int)py <= wp->yoff + (int)wp->sy) return (SCREEN_REDRAW_BORDER_LEFT); @@ -247,7 +247,7 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, u_int sy = w->sy; int sb_w, floating = 0; - floating = (wp->layout_cell == NULL); + floating = (wp->flags & PANE_FLOATING); sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; @@ -268,7 +268,7 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, /* If checking a cell from a tiled pane, ignore floating panes * because 2 side-by-side or top-bottom panes share a border * which is used to do split colouring. Essentially treat all - * non-floating panes as being in a single z-index. + * tiled panes as being in a single z-index. * * If checking a cell from a floating pane, only check cells * from this floating pane, again, essentially only this z-index. @@ -277,8 +277,7 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, /* Check all the panes. */ TAILQ_FOREACH(wp2, &w->z_index, zentry) { if (!window_pane_visible(wp2) || - (wp->flags & PANE_MINIMISED) || - (!floating && wp2->layout_cell==NULL) || + (!floating && (wp2->flags & PANE_FLOATING)) || (floating && wp2 != wp)) continue; if (((int)px < wp2->xoff - 1 || @@ -317,7 +316,7 @@ screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx, if (px > sx || py > sy) return (CELL_OUTSIDE); - floating = (wp->layout_cell == NULL); + floating = (wp->flags & PANE_FLOATING); /* * Construct a bitmask of whether the cells to the left (bit 8), right, @@ -433,7 +432,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, TAILQ_FOREACH(wp, &w->z_index, zentry) { sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - if (! (wp->flags & PANE_MINIMISED) && + if (~wp->flags & PANE_MINIMISED && ((int)px >= wp->xoff - 1 && (int)px <= wp->xoff + (int)wp->sx + sb_w) && ((int)py >= wp->yoff - 1 && @@ -453,14 +452,13 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, * top-bottom windows with a shared border and half the shared * border is the active border. */ - if (wp->layout_cell != NULL) + if (~wp->flags & PANE_FLOATING) tiled_only = 1; do { /* Loop until back to wp==start.*/ if (!window_pane_visible(wp) || - (wp->flags & PANE_MINIMISED) || - (tiled_only && wp->layout_cell==NULL)) + (tiled_only && (wp->flags & PANE_FLOATING))) goto next; *wpp = wp; @@ -1067,13 +1065,13 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, continue; } - tb = wp->yoff-1; - bb = wp->yoff + wp->sy; + tb = wp->yoff - 1; + bb = wp->yoff + wp->sy + 1; if (!found_self || - (wp->flags & PANE_MINIMISED) || - py < tb || - (wp->layout_cell == NULL && py > bb) || - (wp->layout_cell != NULL && py >= bb)) + !window_pane_visible(wp) || + py < tb || + ((wp->flags & PANE_FLOATING) && py > bb) || + (~(wp->flags & PANE_FLOATING) && py >= bb)) continue; /* Are scrollbars enabled? */ @@ -1283,6 +1281,9 @@ screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx, else sb_x = xoff + wp->sx - ox; + if (ctx->statustop) + sb_y += ctx->statuslines; + if (slider_h < 1) slider_h = 1; if (slider_y >= sb_h) @@ -1312,6 +1313,9 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, int yoff = wp->yoff; struct visible_ranges *vr; + if (ctx->statustop) + sy += ctx->statuslines; + /* Set up style for slider. */ gc = sb_style->gc; memcpy(&slgc, &gc, sizeof slgc); diff --git a/screen-write.c b/screen-write.c index 96cdeab4a1..48ee3d152e 100644 --- a/screen-write.c +++ b/screen-write.c @@ -139,10 +139,9 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c) if (c->session->curw->window != wp->window) return (0); - /* if (wp->layout_cell == NULL) return (0); - */ + if (wp->flags & (PANE_REDRAW|PANE_DROP)) return (-1); if (c->flags & CLIENT_REDRAWPANES) { diff --git a/server-client.c b/server-client.c index 1e79a49c19..6c6e193029 100644 --- a/server-client.c +++ b/server-client.c @@ -643,9 +643,9 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, return (SCROLLBAR_SLIDER); } else /* py > sl_bottom */ return (SCROLLBAR_DOWN); - } else if (wp->layout_cell == NULL && + } else if (wp->flags & PANE_FLOATING && ((int)px == wp->xoff - 1 || - (int)py == wp->yoff -1 || + (int)py == wp->yoff - 1 || (int)py == wp->yoff + (int)wp->sy)) { /* Floating pane left, bottom or top border. */ return (BORDER); @@ -664,7 +664,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, if ((int)py >= fwp->yoff - 1 && py <= fwp->yoff + fwp->sy) { if (px == bdr_right) break; - if (wp->layout_cell == NULL) { + if (wp->flags & PANE_FLOATING) { /* Floating pane, check if left border. */ bdr_left = fwp->xoff - 1; if (px == bdr_left) @@ -675,7 +675,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, bdr_bottom = fwp->yoff + fwp->sy; if (py == bdr_bottom) break; - if (wp->layout_cell == NULL) { + if (wp->flags & PANE_FLOATING) { /* Floating pane, check if top border. */ bdr_top = fwp->yoff - 1; if (py == bdr_top) @@ -885,8 +885,13 @@ server_client_check_mouse(struct client *c, struct key_event *event) px = px + m->ox; py = py + m->oy; - /* Try inside the pane. */ - wp = window_get_active_at(w, px, py); + if (type == DRAG && + c->tty.mouse_wp != NULL) + /* Use pane from last mouse event. */ + wp = c->tty.mouse_wp; + else + /* Try inside the pane. */ + wp = window_get_active_at(w, px, py); if (wp == NULL) return (KEYC_UNKNOWN); where = server_client_check_mouse_in_pane(wp, px, py, diff --git a/spawn.c b/spawn.c index 69dbb27865..93ffc4b1a0 100644 --- a/spawn.c +++ b/spawn.c @@ -265,14 +265,7 @@ spawn_pane(struct spawn_context *sc, char **cause) new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN); } else if (sc->lc == NULL) { new_wp = window_add_pane(w, NULL, hlimit, sc->flags); - if (sc->flags & SPAWN_FLOATING) { - new_wp->flags |= PANE_FLOATING; - window_pane_resize(new_wp, sc->sx, sc->sy); - new_wp->xoff = sc->xoff; - new_wp->yoff = sc->yoff; - } else { - layout_init(w, new_wp); - } + layout_init(w, new_wp); } else { new_wp = window_add_pane(w, sc->wp0, hlimit, sc->flags); if (sc->flags & SPAWN_ZOOM) @@ -281,6 +274,13 @@ spawn_pane(struct spawn_context *sc, char **cause) layout_assign_pane(sc->lc, new_wp, 0); } + /* + * If window currently zoomed, window_set_active_pane calls + * window_unzoom which it copies back the saved_layout_cell. + */ + if (w->flags & WINDOW_ZOOMED) + new_wp->saved_layout_cell = new_wp->layout_cell; + /* * Now we have a pane with nothing running in it ready for the new * process. Work out the command and arguments and store the working diff --git a/tmux.h b/tmux.h index ff04ba958b..eaeb6eeb12 100644 --- a/tmux.h +++ b/tmux.h @@ -1176,6 +1176,7 @@ struct window_pane { int yoff; int flags; + int saved_flags; #define PANE_REDRAW 0x1 #define PANE_DROP 0x2 #define PANE_FOCUSED 0x4 @@ -1365,6 +1366,7 @@ TAILQ_HEAD(winlink_stack, winlink); enum layout_type { LAYOUT_LEFTRIGHT, LAYOUT_TOPBOTTOM, + LAYOUT_FLOATING, LAYOUT_WINDOWPANE }; @@ -2226,10 +2228,6 @@ struct spawn_context { struct window_pane *wp0; struct layout_cell *lc; - u_int xoff; - u_int yoff; - u_int sx; - u_int sy; const char *name; char **argv; @@ -3350,6 +3348,7 @@ void layout_set_size(struct layout_cell *, u_int, u_int, u_int, u_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 *); void layout_fix_offsets(struct window *); void layout_fix_panes(struct window *, struct window_pane *); void layout_resize_adjust(struct window *, struct layout_cell *, @@ -3370,7 +3369,7 @@ int layout_spread_cell(struct window *, struct layout_cell *); void layout_spread_out(struct window_pane *); /* layout-custom.c */ -char *layout_dump(struct layout_cell *); +char *layout_dump(struct window *, struct layout_cell *); int layout_parse(struct window *, const char *, char **); /* layout-set.c */ diff --git a/tty.c b/tty.c index c484cbdd64..26752a9a63 100644 --- a/tty.c +++ b/tty.c @@ -1382,7 +1382,7 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) if (!ctx->bigger) { if (wp) { - vr = screen_redraw_get_visible_ranges(wp, 0, py, nx); + vr = screen_redraw_get_visible_ranges(wp, 0, ctx->yoff + py, nx); for (r=0; r < vr->used; r++) { if (vr->nx[r] == 0) continue; @@ -2048,7 +2048,7 @@ tty_is_obscured(const struct tty_ctx *ctx) found_self = 1; continue; } - if (found_self && wp->layout_cell == NULL && + if (found_self && wp->flags & PANE_FLOATING && ! (wp->flags & PANE_MINIMISED) && ((wp->yoff >= base_wp->yoff && wp->yoff <= base_wp->yoff + (int)base_wp->sy) || diff --git a/window.c b/window.c index 4660cd187a..aaa22ce13e 100644 --- a/window.c +++ b/window.c @@ -525,6 +525,8 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) if (wp == w->active) return (0); + if (w->flags & WINDOW_ZOOMED) + window_unzoom(w, 1); lastwp = w->active; window_pane_stack_remove(&w->last_panes, wp); @@ -589,9 +591,9 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) /* If you want tiled planes to be able to bury * floating planes then do this regardless of - * wp->layout_cell==NULL or not. A new option? + * wp->flags & PANE_FLOATING or not. A new option? */ - if (wp->layout_cell == NULL) { + if (wp->flags & PANE_FLOATING) { TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); wp->flags |= PANE_REDRAW; @@ -614,7 +616,7 @@ window_get_active_at(struct window *w, u_int x, u_int y) if (!window_pane_visible(wp)) continue; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); - if (wp->layout_cell != NULL) { + if (~wp->flags & PANE_FLOATING) { /* Tiled, select up to including bottom or right border. */ if ((int)x < xoff || x > xoff + sx) @@ -688,12 +690,24 @@ window_zoom(struct window_pane *wp) if (w->flags & WINDOW_ZOOMED) return (-1); - if (window_count_panes(w) == 1) - return (-1); - if (w->active != wp) window_set_active_pane(w, wp, 1); + /* Bring pane above other tiled panes and minimise floating panes. */ + TAILQ_FOREACH(wp1, &w->z_index, zentry) { + if (wp1 == wp) { + wp1->saved_flags |= (wp1->flags & PANE_MINIMISED); + wp1->flags &= ~PANE_MINIMISED; + continue; + } + if (wp1->flags & PANE_FLOATING) { + wp1->saved_flags |= (wp1->flags & PANE_MINIMISED); + wp1->flags |= PANE_MINIMISED; + continue; + } + break; + } + TAILQ_FOREACH(wp1, &w->panes, entry) { wp1->saved_layout_cell = wp1->layout_cell; wp1->layout_cell = NULL; @@ -720,6 +734,14 @@ window_unzoom(struct window *w, int notify) w->layout_root = w->saved_layout_root; w->saved_layout_root = NULL; + TAILQ_FOREACH(wp, &w->z_index, zentry) { + if (wp->flags & PANE_FLOATING) { + wp->flags &= ~PANE_MINIMISED | (wp->saved_flags & PANE_MINIMISED); + continue; + } + break; + } + TAILQ_FOREACH(wp, &w->panes, entry) { wp->layout_cell = wp->saved_layout_cell; wp->saved_layout_cell = NULL; @@ -781,9 +803,10 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, TAILQ_INSERT_AFTER(&w->panes, other, wp, entry); } /* Floating panes are created above tiled planes. */ - if (flags & (SPAWN_FLOATING)) + if (flags & SPAWN_FLOATING) { + wp->flags |= PANE_FLOATING; TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); - else + } else TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); return (wp); } @@ -1311,8 +1334,10 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s, int window_pane_visible(struct window_pane *wp) { - if (~wp->window->flags & WINDOW_ZOOMED) + if (~wp->window->flags & WINDOW_ZOOMED && + ~wp->flags & PANE_MINIMISED) return (1); + return (wp == wp->window->active); } From 2ac78bccb55e34b5fed3f4336ebf9cb07ee3c21c Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 8 Dec 2025 15:01:54 +0000 Subject: [PATCH 049/167] Bugfix status line at top. --- screen-redraw.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 82dcb4c969..9244348f18 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1313,10 +1313,8 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, int yoff = wp->yoff; struct visible_ranges *vr; - if (ctx->statustop) { - sb_y += ctx->statuslines; + if (ctx->statustop) sy += ctx->statuslines; - } /* Set up style for slider. */ gc = sb_style->gc; From 2591df66cc380724572fd1af6bbd63ec90cc80d4 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 9 Dec 2025 00:14:06 +0000 Subject: [PATCH 050/167] Bugfix status line at top and floating panes. --- screen-redraw.c | 18 +++++++++--------- server-client.c | 2 +- tty.c | 3 +++ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 9244348f18..90ee88ded3 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -671,12 +671,12 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) width = size - x; } - if (ctx->statustop) - yoff += ctx->statuslines; - vr = screen_redraw_get_visible_ranges(wp, x, yoff - ctx->oy, width); + if (ctx->statustop) + yoff += ctx->statuslines; + for (r=0; r < vr->used; r++) { if (vr->nx[r] == 0) continue; @@ -1173,7 +1173,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) if (wp->yoff + (int)j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy) continue; - y = top + wp->yoff + j - ctx->oy; + y = wp->yoff + j - ctx->oy; /* Note: i is apparenty not used now that the vr array * returns where in s to read from. @@ -1217,7 +1217,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) * contents of pane shifted. note: i apparently unnec. */ tty_draw_line(tty, s, /* i + */ vr->px[r] - wp->xoff, j, - vr->nx[r], vr->px[r], y, &defaults, palette); + vr->nx[r], vr->px[r], top + y, &defaults, palette); } } @@ -1281,9 +1281,6 @@ screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx, else sb_x = xoff + wp->sx - ox; - if (ctx->statustop) - sb_y += ctx->statuslines; - if (slider_h < 1) slider_h = 1; if (slider_y >= sb_h) @@ -1313,8 +1310,11 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, int yoff = wp->yoff; struct visible_ranges *vr; - if (ctx->statustop) + if (ctx->statustop) { + sb_y += ctx->statuslines; sy += ctx->statuslines; + } + /* Set up style for slider. */ gc = sb_style->gc; diff --git a/server-client.c b/server-client.c index 820e306a01..1d892bdb05 100644 --- a/server-client.c +++ b/server-client.c @@ -1302,7 +1302,7 @@ server_client_check_mouse(struct client *c, struct key_event *event) c->tty.mouse_drag_flag = MOUSE_BUTTONS(b) + 1; /* Only change pane if not already dragging a pane border. */ if (c->tty.mouse_wp == NULL) { - wp = window_get_active_at(w, x, y); + wp = window_get_active_at(w, px, py); c->tty.mouse_wp = wp; } if (c->tty.mouse_scrolling_flag == 0 && diff --git a/tty.c b/tty.c index eda897d86a..fab7158a15 100644 --- a/tty.c +++ b/tty.c @@ -2096,6 +2096,9 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) else rlower = ctx->wsy - 1; + if (tty->client->session->statusat == 0) + rlower += tty->client->session->statuslines; + if (ctx->num == 1 || !tty_term_has(tty->term, TTYC_INDN)) { if (!tty_use_margin(tty)) tty_cursor(tty, 0, rlower); From 67319ad9d95b5c6c53ad9d55ee10bedf12a05700 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 9 Dec 2025 13:34:26 +0000 Subject: [PATCH 051/167] Fix tty-clear-area and make aware of floating panes. --- tty.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/tty.c b/tty.c index fab7158a15..8b94acd639 100644 --- a/tty.c +++ b/tty.c @@ -1217,13 +1217,21 @@ static void tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py, u_int px, u_int nx, u_int bg) { - struct client *c = tty->client; - u_int i, x, rx, ry; + struct client *c = tty->client; + struct window_pane *wp = ctx->arg; + struct visible_ranges *vr = NULL; + u_int r, i, x, rx, ry, oy = 0; log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); - if (tty_clamp_line(tty, ctx, px, py, nx, &i, &x, &rx, &ry)) - tty_clear_line(tty, &ctx->defaults, ry, x, rx, bg); + if (tty_clamp_line(tty, ctx, px, py, nx, &i, &x, &rx, &ry)) { + vr = screen_redraw_get_visible_ranges(wp, x, ry, rx); + for (r=0; r < vr->used; r++) { + if (vr->nx[r] == 0) + continue; + tty_clear_line(tty, &ctx->defaults, ry, vr->px[r], vr->nx[r], bg); + } + } } /* Clamp area position to visible part of pane. */ @@ -1290,12 +1298,16 @@ tty_clamp_area(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, /* Clear an area, adjusting to visible part of pane. */ static void -tty_clear_area(struct tty *tty, const struct grid_cell *defaults, u_int py, +tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, u_int ny, u_int px, u_int nx, u_int bg) { - struct client *c = tty->client; - u_int yy; - char tmp[64]; + struct client *c = tty->client; + const struct grid_cell *defaults = &ctx->defaults; + struct window *w = c->session->curw->window; + struct window_pane *wpl, *wp = ctx->arg; + struct visible_ranges *vr = NULL; + u_int r, yy, overlap = 0, oy = 0; + char tmp[64]; log_debug("%s: %s, %u,%u at %u,%u", __func__, c->name, nx, ny, px, py); @@ -1303,8 +1315,21 @@ tty_clear_area(struct tty *tty, const struct grid_cell *defaults, u_int py, if (nx == 0 || ny == 0) return; + /* Verify there's nothing overlapping in z-index before using BCE. */ + TAILQ_FOREACH(wpl, &w->z_index, zentry) { + if (wpl == wp || ~wpl->flags & PANE_FLOATING) + continue; + if (wpl->xoff - 1 > (int)(px + nx) || wpl->xoff + wpl->sx + 1 < px) + continue; + if (wpl->yoff - 1 > (int)(py + ny) || wpl->yoff + wpl->sy + 1 < py) + continue; + overlap++; + if (overlap > 1) break; + } + /* If genuine BCE is available, can try escape sequences. */ - if (c->overlay_check == NULL && !tty_fake_bce(tty, defaults, bg)) { + if (!overlap && c->overlay_check == NULL && + !tty_fake_bce(tty, defaults, bg)) { /* Use ED if clearing off the bottom of the terminal. */ if (px == 0 && px + nx >= tty->sx && @@ -1355,9 +1380,18 @@ tty_clear_area(struct tty *tty, const struct grid_cell *defaults, u_int py, } } + if (c->session->statusat == 0) + oy = c->session->statuslines; + /* Couldn't use an escape sequence, loop over the lines. */ - for (yy = py; yy < py + ny; yy++) - tty_clear_line(tty, defaults, yy, px, nx, bg); + for (yy = py; yy < py + ny; yy++) { + vr = screen_redraw_get_visible_ranges(wp, px, yy - oy, nx); + for (r=0; r < vr->used; r++) { + if (vr->nx[r] == 0) + continue; + tty_clear_line(tty, defaults, yy, vr->px[r], vr->nx[r], bg); + } + } } /* Clear an area in a pane. */ @@ -1365,10 +1399,10 @@ static void tty_clear_pane_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, u_int ny, u_int px, u_int nx, u_int bg) { - u_int i, j, x, y, rx, ry; + u_int i, j, x, y, rx, ry; if (tty_clamp_area(tty, ctx, px, py, nx, ny, &i, &j, &x, &y, &rx, &ry)) - tty_clear_area(tty, &ctx->defaults, y, ry, x, rx, bg); + tty_clear_area(tty, ctx, y, ry, x, rx, bg); } /* Redraw a line at py of a screen taking into account obscured ranges. From 818797ff08336520dcb37203daac01e10cdb6749 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 9 Dec 2025 17:47:36 +0000 Subject: [PATCH 052/167] Fix scrolling issue that collided with PR #4738 Fix y offset of mouse if status at top. --- tty.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tty.c b/tty.c index 8b94acd639..492bac5d13 100644 --- a/tty.c +++ b/tty.c @@ -2125,13 +2125,8 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); - if (tty->rlower < ctx->wsy) - rlower = tty->rlower; - else - rlower = ctx->wsy - 1; - - if (tty->client->session->statusat == 0) - rlower += tty->client->session->statuslines; + /* rlower is set in tty_region_pane above. */ + rlower = tty->rlower; if (ctx->num == 1 || !tty_term_has(tty->term, TTYC_INDN)) { if (!tty_use_margin(tty)) From 263529e8864eff7af1bb3fd929110545d4c10ba9 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 9 Dec 2025 20:11:44 +0000 Subject: [PATCH 053/167] Cleanup from previous commit. --- tty.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tty.c b/tty.c index 492bac5d13..1c374ee377 100644 --- a/tty.c +++ b/tty.c @@ -2105,7 +2105,7 @@ void tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - u_int i, rlower; + u_int i; if (ctx->bigger || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || @@ -2125,14 +2125,11 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); - /* rlower is set in tty_region_pane above. */ - rlower = tty->rlower; - if (ctx->num == 1 || !tty_term_has(tty->term, TTYC_INDN)) { if (!tty_use_margin(tty)) - tty_cursor(tty, 0, rlower); + tty_cursor(tty, 0, tty->rlower); else - tty_cursor(tty, tty->rright, rlower); + tty_cursor(tty, tty->rright, tty->rlower); for (i = 0; i < ctx->num; i++) tty_putc(tty, '\n'); } else { From 6a4a4a432b9b041db504d0099b871b9af0af44f5 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 18 Dec 2025 18:19:17 +0000 Subject: [PATCH 054/167] Add support to minimise panes, both tiled and floating. New PREFIX _ key binding to minimise a pane. New functions minimise-pane and unminimise-pane. Add double-click on pane in status to minimise pane. Single click on pane in status unminimises pane. --- Makefile.am | 1 + cmd-find.c | 9 +++++-- cmd-kill-pane.c | 5 ++++ cmd.c | 8 ++++-- key-bindings.c | 4 +++ layout.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++-- screen-redraw.c | 9 +++++-- server-client.c | 8 +++--- tmux.h | 4 +++ window.c | 43 +++++++++++++++++++++++++++++ 10 files changed, 152 insertions(+), 11 deletions(-) diff --git a/Makefile.am b/Makefile.am index 79d4458a70..b59ca9e9eb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -106,6 +106,7 @@ dist_tmux_SOURCES = \ cmd-list-windows.c \ cmd-load-buffer.c \ cmd-lock-server.c \ + cmd-minimise-pane.c \ cmd-move-window.c \ cmd-new-pane.c \ cmd-new-session.c \ diff --git a/cmd-find.c b/cmd-find.c index 8a3499a113..c5d9e0f31b 100644 --- a/cmd-find.c +++ b/cmd-find.c @@ -977,15 +977,20 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, } else if (cmd_find_from_client(¤t, cmdq_get_client(item), flags) == 0) { fs->current = ¤t; + /* No active pane, window empty, return the window instead. */ + if (current.wp == NULL) { + type = CMD_FIND_WINDOW; + } log_debug("%s: current is from client", __func__); } else { if (~flags & CMD_FIND_QUIET) cmdq_error(item, "no current target"); goto error; } + /* if (!cmd_find_valid_state(fs->current)) fatalx("invalid current find state"); - + */ /* An empty or NULL target is the current. */ if (target == NULL || *target == '\0') goto current; @@ -1010,7 +1015,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, fs->w = fs->wl->window; fs->wp = fs->w->active; } - break; + goto found; } if (fs->wp == NULL) { if (~flags & CMD_FIND_QUIET) diff --git a/cmd-kill-pane.c b/cmd-kill-pane.c index e1134a1ee2..7c352b8672 100644 --- a/cmd-kill-pane.c +++ b/cmd-kill-pane.c @@ -62,6 +62,11 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } + if (wp == NULL) { + /* No active window pane. */ + cmdq_error(item, "No active pane to kill."); + return (CMD_RETURN_ERROR); + } server_kill_pane(wp); return (CMD_RETURN_NORMAL); } diff --git a/cmd.c b/cmd.c index 233c6dc955..dfb9ca55e6 100644 --- a/cmd.c +++ b/cmd.c @@ -69,8 +69,10 @@ extern const struct cmd_entry cmd_load_buffer_entry; extern const struct cmd_entry cmd_lock_client_entry; extern const struct cmd_entry cmd_lock_server_entry; extern const struct cmd_entry cmd_lock_session_entry; +extern const struct cmd_entry cmd_minimise_pane_entry; extern const struct cmd_entry cmd_move_pane_entry; extern const struct cmd_entry cmd_move_window_entry; +extern const struct cmd_entry cmd_new_pane_entry; extern const struct cmd_entry cmd_new_session_entry; extern const struct cmd_entry cmd_new_window_entry; extern const struct cmd_entry cmd_next_layout_entry; @@ -109,7 +111,6 @@ extern const struct cmd_entry cmd_show_prompt_history_entry; extern const struct cmd_entry cmd_show_window_options_entry; extern const struct cmd_entry cmd_source_file_entry; extern const struct cmd_entry cmd_split_window_entry; -extern const struct cmd_entry cmd_new_pane_entry; extern const struct cmd_entry cmd_start_server_entry; extern const struct cmd_entry cmd_suspend_client_entry; extern const struct cmd_entry cmd_swap_pane_entry; @@ -117,6 +118,7 @@ extern const struct cmd_entry cmd_swap_window_entry; extern const struct cmd_entry cmd_switch_client_entry; extern const struct cmd_entry cmd_unbind_key_entry; extern const struct cmd_entry cmd_unlink_window_entry; +extern const struct cmd_entry cmd_unminimise_pane_entry; extern const struct cmd_entry cmd_wait_for_entry; const struct cmd_entry *cmd_table[] = { @@ -162,8 +164,10 @@ const struct cmd_entry *cmd_table[] = { &cmd_lock_client_entry, &cmd_lock_server_entry, &cmd_lock_session_entry, + &cmd_minimise_pane_entry, &cmd_move_pane_entry, &cmd_move_window_entry, + &cmd_new_pane_entry, &cmd_new_session_entry, &cmd_new_window_entry, &cmd_next_layout_entry, @@ -202,7 +206,6 @@ const struct cmd_entry *cmd_table[] = { &cmd_show_window_options_entry, &cmd_source_file_entry, &cmd_split_window_entry, - &cmd_new_pane_entry, &cmd_start_server_entry, &cmd_suspend_client_entry, &cmd_swap_pane_entry, @@ -210,6 +213,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_switch_client_entry, &cmd_unbind_key_entry, &cmd_unlink_window_entry, + &cmd_unminimise_pane_entry, &cmd_wait_for_entry, NULL }; diff --git a/key-bindings.c b/key-bindings.c index 436bf0327d..1275bf8480 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -347,6 +347,10 @@ key_bindings_init(void) { static const char *const defaults[] = { /* Prefix keys. */ + "bind -N 'Minimise pane' _ { minimise-pane }", + /* Mouse button 1 double click on status line. */ + "bind -n DoubleClick1Status { minimise-pane -t= }", + "bind -N 'Send the prefix key' C-b { send-prefix }", "bind -N 'Rotate through the panes' C-o { rotate-window }", "bind -N 'Suspend the current client' C-z { suspend-client }", diff --git a/layout.c b/layout.c index 885398a7f0..29912810b4 100644 --- a/layout.c +++ b/layout.c @@ -254,6 +254,9 @@ layout_fix_offsets1(struct layout_cell *lc) if (lc->type == LAYOUT_LEFTRIGHT) { xoff = lc->xoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (lcchild->type == LAYOUT_WINDOWPANE && + lcchild->wp->flags & PANE_MINIMISED) + continue; lcchild->xoff = xoff; lcchild->yoff = lc->yoff; if (lcchild->type != LAYOUT_WINDOWPANE) @@ -263,6 +266,8 @@ layout_fix_offsets1(struct layout_cell *lc) } else { yoff = lc->yoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (lcchild->wp->flags & PANE_MINIMISED) + continue; lcchild->xoff = lc->xoff; lcchild->yoff = yoff; if (lcchild->type != LAYOUT_WINDOWPANE) @@ -526,8 +531,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, if (lcparent == NULL) { if (lc->wp != NULL && ~lc->wp->flags & PANE_FLOATING) *lcroot = NULL; - /* xxx if (lc->type == LAYOUT_WINDOWPANE) */ - layout_free_cell(lc); + layout_free_cell(lc); return; } @@ -569,6 +573,70 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, } } +/* Minimise a cell and redistribute the space in tiled cells. */ +void +layout_minimise_cell(struct window *w, struct layout_cell *lc) +{ + struct layout_cell *lcother, *lcparent, *lcchild; + u_int space = 0; + + lcparent = lc->parent; + if (lcparent == NULL || + lcparent->type == LAYOUT_FLOATING) { + return; + } + + /* Merge the space into the previous or next cell. */ + if (lc == TAILQ_FIRST(&lcparent->cells)) + lcother = TAILQ_NEXT(lc, entry); + else + lcother = TAILQ_PREV(lc, layout_cells, entry); + if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) + layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); + else if (lcother != NULL) + layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1); + + /* If the parent cells are all minimised, minimise it too. */ + if (lcparent != NULL) { + TAILQ_FOREACH(lcchild, &lcparent->cells, entry) { + if (lcchild->wp == NULL || + lcchild->wp->flags & PANE_MINIMISED) + continue; + if (lcparent->type == LAYOUT_LEFTRIGHT) { + space += lcchild->sx; + } else if (lcparent->type == LAYOUT_TOPBOTTOM) { + space += lcchild->sy; + } + } + if (space == 0) + layout_minimise_cell(w, lcparent); + } +} + +/* Unminimise a cell and redistribute the space in tiled cells. */ +void +layout_unminimise_cell(struct window *w, struct layout_cell *lc) +{ + struct layout_cell *lcother, *lcparent; + + lcparent = lc->parent; + if (lcparent == NULL) { + return; + } + + /* In tiled layouts, merge the space into the previous or next cell. */ + if (lcparent->type != LAYOUT_FLOATING) { + if (lc == TAILQ_FIRST(&lcparent->cells)) + lcother = TAILQ_NEXT(lc, entry); + else + lcother = TAILQ_PREV(lc, layout_cells, entry); + if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) + layout_resize_adjust(w, lcother, lcparent->type, -(lc->sx + 1)); + else if (lcother != NULL) + layout_resize_adjust(w, lcother, lcparent->type, -(lc->sy + 1)); + } +} + void layout_init(struct window *w, struct window_pane *wp) { diff --git a/screen-redraw.c b/screen-redraw.c index 90ee88ded3..108648bb40 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -439,10 +439,13 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, (int)py <= wp->yoff + (int)wp->sy)) break; } - if (wp == NULL) + if (wp == NULL) { start = wp = server_client_get_pane(c); - else + if (wp == NULL) + return (CELL_OUTSIDE); + } else { start = wp; + } if (px == sx || py == sy) /* window border */ return (screen_redraw_type_of_cell(ctx, wp, px, py)); @@ -538,6 +541,8 @@ screen_redraw_check_is(struct screen_redraw_ctx *ctx, u_int px, u_int py, { enum screen_redraw_border_type border; + if (wp == NULL) + return (0); /* No active pane. */ border = screen_redraw_pane_border(ctx, wp, px, py); if (border != SCREEN_REDRAW_INSIDE && border != SCREEN_REDRAW_OUTSIDE) return (1); diff --git a/server-client.c b/server-client.c index 1d892bdb05..0bdff34ce5 100644 --- a/server-client.c +++ b/server-client.c @@ -2957,7 +2957,7 @@ server_client_reset_state(struct client *c) if (c->overlay_draw != NULL) { if (c->overlay_mode != NULL) s = c->overlay_mode(c, c->overlay_data, &cx, &cy); - } else if (c->prompt_string == NULL) + } else if (wp != NULL && c->prompt_string == NULL) s = wp->screen; else s = c->status.active; @@ -2985,7 +2985,7 @@ server_client_reset_state(struct client *c) cy = tty->sy - 1; } cx = c->prompt_cursor; - } else if (c->overlay_draw == NULL) { + } else if (wp != NULL && c->overlay_draw == NULL) { cursor = 0; tty_window_offset(tty, &ox, &oy, &sx, &sy); if (wp->xoff + s->cx >= ox && wp->xoff + s->cx <= ox + sx && @@ -3004,7 +3004,9 @@ server_client_reset_state(struct client *c) if (!cursor) mode &= ~MODE_CURSOR; - } + } else + mode &= ~MODE_CURSOR; + log_debug("%s: cursor to %u,%u", __func__, cx, cy); tty_cursor(tty, cx, cy); diff --git a/tmux.h b/tmux.h index 48e7af87ec..d67e96effe 100644 --- a/tmux.h +++ b/tmux.h @@ -3270,6 +3270,8 @@ struct window_pane *window_find_string(struct window *, const char *); int window_has_pane(struct window *, struct window_pane *); int window_set_active_pane(struct window *, struct window_pane *, int); +int window_deactivate_pane(struct window *, struct window_pane *, + int); void window_update_focus(struct window *); void window_pane_update_focus(struct window_pane *); void window_redraw_active_switch(struct window *, @@ -3350,6 +3352,8 @@ void layout_free_cell(struct layout_cell *); void layout_print_cell(struct layout_cell *, const char *, u_int); void layout_destroy_cell(struct window *, struct layout_cell *, struct layout_cell **); +void layout_minimise_cell(struct window *, struct layout_cell *); +void layout_unminimise_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); diff --git a/window.c b/window.c index a6cf94577f..916fb7d44d 100644 --- a/window.c +++ b/window.c @@ -536,6 +536,20 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) w->active->active_point = next_active_point++; w->active->flags |= PANE_CHANGED; + if (wp->flags & PANE_MINIMISED) { + wp->flags &= ~PANE_MINIMISED; + if (w->layout_root != NULL) { + wp->layout_cell = wp->saved_layout_cell; + wp->saved_layout_cell = NULL; + layout_unminimise_cell(w, wp->layout_cell); + layout_fix_offsets(w); + layout_fix_panes(w, NULL); + } + } + notify_window("window-layout-changed", w); + server_redraw_window(w); + + if (options_get_number(global_options, "focus-events")) { window_pane_update_focus(lastwp); window_pane_update_focus(w->active); @@ -548,6 +562,33 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) return (1); } +int +window_deactivate_pane(struct window *w, struct window_pane *wp, int notify) +{ + struct window_pane *lastwp; + + log_debug("%s: pane %%%u", __func__, wp->id); + + if (w->flags & WINDOW_ZOOMED) + window_unzoom(w, 1); + lastwp = w->active; + + window_pane_stack_remove(&w->last_panes, wp); + window_pane_stack_push(&w->last_panes, lastwp); + + w->active = NULL; + + if (options_get_number(global_options, "focus-events")) { + window_pane_update_focus(lastwp); + } + + tty_update_window_offset(w); + + if (notify) + notify_window("window-pane-changed", w); + return (1); +} + static int window_pane_get_palette(struct window_pane *wp, int c) { @@ -600,6 +641,8 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) } wp = w->active; + if (wp == NULL) + break; } } From bd442a27eecec7fd3c58f49e8866ba240d55a1d8 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 24 Dec 2025 10:45:17 +0000 Subject: [PATCH 055/167] Fix some redraw issues. Fix scrollbar left/right display issues. --- screen-redraw.c | 183 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 125 insertions(+), 58 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 2910ac7a6b..4bc806df0d 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -147,18 +147,26 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, /* Floating pane borders */ if (wp->flags & PANE_FLOATING) { - if ((int)px == wp->xoff - 1 && - (int)py >= wp->yoff - 1 && (int)py <= wp->yoff + (int)wp->sy) - return (SCREEN_REDRAW_BORDER_LEFT); - if ((int)px == wp->xoff + (int)wp->sx + sb_w && - (int)py >= wp->yoff && (int)py <= wp->yoff + (int)wp->sy) - return (SCREEN_REDRAW_BORDER_RIGHT); - if ((int)py == wp->yoff - 1 && - (int)px >= wp->xoff && (int)px <= wp->xoff + (int)wp->sx) - return (SCREEN_REDRAW_BORDER_TOP); - if ((int)py == wp->yoff + (int)wp->sy && - (int)px >= wp->xoff && (int)px <= wp->xoff + (int)wp->sx) - return (SCREEN_REDRAW_BORDER_BOTTOM); + if ((int)py >= wp->yoff - 1 && + (int)py <= wp->yoff + (int)wp->sy) { + if (sb_pos == PANE_SCROLLBARS_LEFT) { + if ((int)px == wp->xoff - 1 - sb_w) + return (SCREEN_REDRAW_BORDER_LEFT); + if ((int)px == wp->xoff + (int)wp->sx) + return (SCREEN_REDRAW_BORDER_RIGHT); + } else { /* PANE_SCROLLBARS_RIGHT or none. */ + if ((int)px == wp->xoff - 1) + return (SCREEN_REDRAW_BORDER_LEFT); + if ((int)px == wp->xoff + (int)wp->sx + sb_w) + return (SCREEN_REDRAW_BORDER_RIGHT); + } + } + if ((int)px >= wp->xoff && (int)px <= wp->xoff + (int)wp->sx) { + if ((int)py == wp->yoff - 1) + return (SCREEN_REDRAW_BORDER_TOP); + if ((int)py == wp->yoff + (int)wp->sy) + return (SCREEN_REDRAW_BORDER_BOTTOM); + } } /* Get pane indicator. */ @@ -188,7 +196,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if ((int)px == wp->xoff + (int)wp->sx + sb_w - 1) return (SCREEN_REDRAW_BORDER_RIGHT); } - } else { /* sb_pos == PANE_SCROLLBARS_RIGHT or disabled*/ + } else { /* sb_pos == PANE_SCROLLBARS_RIGHT or none. */ if (wp->xoff == 0 && px == wp->sx + sb_w) if (!hsplit || (hsplit && py <= wp->sy / 2)) return (SCREEN_REDRAW_BORDER_RIGHT); @@ -224,11 +232,15 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if ((wp->xoff == 0 || (int)px >= wp->xoff) && ((int)px <= ex || (sb_w != 0 && (int)px < ex + sb_w))) { - if (pane_status != PANE_STATUS_BOTTOM && - wp->yoff != 0 && (int)py == wp->yoff - 1) - return (SCREEN_REDRAW_BORDER_TOP); - if (pane_status != PANE_STATUS_TOP && (int)py == ey) + if (pane_status == PANE_STATUS_TOP) { + if ((int)py == wp->yoff - 1) + return (SCREEN_REDRAW_BORDER_TOP); + } else { /* PANE_STATUS_BOTTOM or OFF. */ + if (wp->yoff != 0 && (int)py == wp->yoff - 1) + return (SCREEN_REDRAW_BORDER_TOP); + if ((int)py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); + } } } } @@ -246,10 +258,15 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, struct window *w = c->session->curw->window; struct window_pane *wp2; u_int sy = w->sy; - int sb_w, floating = 0; + int sb_w, sb_pos, floating = 0; floating = (wp->flags & PANE_FLOATING); + if (ctx->pane_scrollbars != 0) + sb_pos = ctx->pane_scrollbars_pos; + else + sb_pos = 0; + sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; @@ -281,11 +298,19 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, (!floating && (wp2->flags & PANE_FLOATING)) || (floating && wp2 != wp)) continue; - if (((int)px < wp2->xoff - 1 || - (int)px > wp2->xoff + (int)wp2->sx + sb_w) && - ((int)py < wp2->yoff - 1 || - (int)py > wp2->yoff + (int)wp2->sy)) - continue; + if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (((int)px < wp2->xoff - 1 - sb_w || + (int)px > wp2->xoff + (int)wp2->sx) && + ((int)py < wp2->yoff - 1 || + (int)py > wp2->yoff + (int)wp2->sy)) + continue; + } else { /* PANE_SCROLLBARS_RIGHT or off. */ + if (((int)px < wp2->xoff - 1 || + (int)px > wp2->xoff + (int)wp2->sx + sb_w) && + ((int)py < wp2->yoff - 1 || + (int)py > wp2->yoff + (int)wp2->sy)) + continue; + } switch (screen_redraw_pane_border(ctx, wp2, px, py)) { case SCREEN_REDRAW_INSIDE: return (0); @@ -433,12 +458,21 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, TAILQ_FOREACH(wp, &w->z_index, zentry) { sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - if (~wp->flags & PANE_MINIMISED && - ((int)px >= wp->xoff - 1 && - (int)px <= wp->xoff + (int)wp->sx + sb_w) && - ((int)py >= wp->yoff - 1 && - (int)py <= wp->yoff + (int)wp->sy)) - break; + if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (~wp->flags & PANE_MINIMISED && + ((int)px >= wp->xoff - 1 - sb_w && + (int)px <= wp->xoff + (int)wp->sx) && + ((int)py >= wp->yoff - 1 && + (int)py <= wp->yoff + (int)wp->sy)) + break; + } else { /* PANE_SCROLLBARS_RIGHT or none. */ + if (~wp->flags & PANE_MINIMISED && + ((int)px >= wp->xoff - 1 && + (int)px <= wp->xoff + (int)wp->sx + sb_w) && + ((int)py >= wp->yoff - 1 && + (int)py <= wp->yoff + (int)wp->sy)) + break; + } } if (wp == NULL) { start = wp = server_client_get_pane(c); @@ -469,11 +503,19 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - if (((int)px < wp->xoff - 1 || - (int)px > wp->xoff + (int)wp->sx + sb_w) && - ((int)py < wp->yoff - 1 || - (int)py > wp->yoff + (int)wp->sy)) - goto next; + if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (((int)px < wp->xoff - 1 - sb_w || + (int)px > wp->xoff + (int)wp->sx) && + ((int)py < wp->yoff - 1 || + (int)py > wp->yoff + (int)wp->sy)) + goto next; + } else { /* PANE_SCROLLBARS_RIGHT or none. */ + if (((int)px < wp->xoff - 1 || + (int)px > wp->xoff + (int)wp->sx + sb_w) && + ((int)py < wp->yoff - 1 || + (int)py > wp->yoff + (int)wp->sy)) + goto next; + } if (pane_status != PANE_STATUS_OFF) { /* Pane border status inside top/bottom border is @@ -506,11 +548,11 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, (int)py < wp->yoff + (int)wp->sy)) { /* Check if px lies within a scrollbar. */ if ((sb_pos == PANE_SCROLLBARS_RIGHT && - ((int)px >= wp->xoff + (int)wp->sx && - (int)px < wp->xoff + (int)wp->sx + sb_w)) || + ((int)px >= wp->xoff + (int)wp->sx && + (int)px < wp->xoff + (int)wp->sx + sb_w)) || (sb_pos == PANE_SCROLLBARS_LEFT && - ((int)px >= wp->xoff - sb_w && - (int)px < wp->xoff))) + ((int)px >= wp->xoff - sb_w && + (int)px < wp->xoff))) return (CELL_SCROLLBAR); } } @@ -1044,7 +1086,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, int found_self, sb_w; u_int lb, rb, tb, bb; u_int r, s; - int pane_scrollbars; + int pane_scrollbars, pane_scrollbars_pos; /* For efficiency vr is static and space reused. */ if (vr.size == 0) { @@ -1063,6 +1105,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, w = base_wp->window; pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); + pane_scrollbars_pos = options_get_number(w->options, "pane-scrollbars-position"); found_self = 0; TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { @@ -1072,12 +1115,12 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, } tb = wp->yoff - 1; - bb = wp->yoff + wp->sy + 1; + bb = wp->yoff + wp->sy; if (!found_self || !window_pane_visible(wp) || py < tb || ((wp->flags & PANE_FLOATING) && py > bb) || - (~(wp->flags & PANE_FLOATING) && py >= bb)) + (~(wp->flags & PANE_FLOATING) && py > bb)) continue; /* Are scrollbars enabled? */ @@ -1086,11 +1129,21 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, wp->scrollbar_style.pad; for (r=0; r < vr.used; r++) { - if (wp->xoff > 0) - lb = wp->xoff - 1; - else - lb = 0; - rb = wp->xoff + wp->sx + sb_w; + if (pane_scrollbars_pos == PANE_SCROLLBARS_LEFT) { + if (wp->xoff > sb_w) + lb = wp->xoff - 1 - sb_w; + else + lb = 0; + } else { /* PANE_SCROLLBARS_RIGHT or none. */ + if (wp->xoff > 0) + lb = wp->xoff - 1; + else + lb = 0; + } + if (pane_scrollbars_pos == PANE_SCROLLBARS_LEFT) + rb = wp->xoff + wp->sx; + else /* PANE_SCROLLBARS_RIGHT or none. */ + rb = wp->xoff + wp->sx + sb_w; if (rb > w->sx) rb = w->sx - 1; /* If the left edge of floating wp @@ -1314,14 +1367,22 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, struct style *sb_style = &wp->scrollbar_style; u_int i, j, imin = 0, jmin = 0, imax, jmax; u_int sb_w = sb_style->width, sb_pad = sb_style->pad; - int px, py, ox = ctx->ox, oy = ctx->oy; - int sx = ctx->sx, sy = ctx->sy, xoff = wp->xoff; + int px, py, wx, wy, ox, oy, sx, sy; + int xoff = wp->xoff; int yoff = wp->yoff; struct visible_ranges *vr; + /* + * Size and offset of window relative to tty. + * Status at top offsets window downward. + */ + sx = ctx->sx; + sy = ctx->sy; + ox = ctx->ox; + oy = ctx->oy; if (ctx->statustop) { - sb_y += ctx->statuslines; - sy += ctx->statuslines; + sy += ctx->statuslines; /* Height of tty. */ + oy += ctx->statuslines; /* Top of w in tty. */ } @@ -1335,6 +1396,7 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, /* Whole sb off screen. */ return; if (sb_x < 0) + /* Part of sb on screen. */ imin = - sb_x; imax = sb_w + sb_pad; if ((int)imax + sb_x > sx) { @@ -1344,25 +1406,30 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, imax = sx - sb_x; } if (sb_y > oy + sy) + /* Whole sb off screen. */ return; if (sb_y < 0) + /* Part of sb on screen. */ jmin = -sb_y; if ((int)sb_h < oy) return; jmax = sb_h; if ((int)jmax + sb_y > sy) + /* Clip to height of tty. */ jmax = sy - sb_y; for (j = jmin; j < jmax; j++) { - py = sb_y + j; - vr = screen_redraw_get_visible_ranges(wp, sb_x, py, imax); + py = sb_y + oy + j; /* tty y coordinate. */ + wy = sb_y + j; /* window y coordinate. */ + vr = screen_redraw_get_visible_ranges(wp, sb_x, wy, imax); for (i = imin; i < imax; i++) { - px = sb_x + i; - if (px < xoff - ox - (int)sb_w - (int)sb_pad || - px >= sx || px < 0 || - py < yoff - oy - 1 || - py >= sy || py < 0 || - ! screen_redraw_is_visible(vr, px)) + px = sb_x + ox + i; /* tty x coordinate. */ + wx = sb_x + i; /* window x coordinate. */ + if (wx < xoff - (int)sb_w - (int)sb_pad || + wx >= sx || wx < 0 || + wy < yoff - 1 || + wy >= sy || wy < 0 || + ! screen_redraw_is_visible(vr, wx)) continue; tty_cursor(tty, px, py); if ((sb_pos == PANE_SCROLLBARS_LEFT && From f2f6a05e2c50068116536233894dbf8efc21e4e5 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 6 Jan 2026 16:44:38 +0000 Subject: [PATCH 056/167] Add cmd-minimise-pane.c --- cmd-minimise-pane.c | 192 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 cmd-minimise-pane.c diff --git a/cmd-minimise-pane.c b/cmd-minimise-pane.c new file mode 100644 index 0000000000..310019f2c3 --- /dev/null +++ b/cmd-minimise-pane.c @@ -0,0 +1,192 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include + +#include "tmux.h" + +/* + * Increase or decrease pane size. + */ + +static enum cmd_retval cmd_minimise_pane_minimise_exec(struct cmd *, struct cmdq_item *); +static enum cmd_retval cmd_minimise_pane_unminimise_exec(struct cmd *, struct cmdq_item *); + +static enum cmd_retval cmd_minimise_pane_minimise(struct window *, struct window_pane *); +static enum cmd_retval cmd_minimise_pane_unminimise(struct window *, struct window_pane *); + +const struct cmd_entry cmd_minimise_pane_entry = { + .name = "minimise-pane", + .alias = "minimize-pane", + + .args = { "at:", 0, 1, NULL }, + .usage = "[-a] " CMD_TARGET_PANE_USAGE, + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_minimise_pane_minimise_exec +}; + +const struct cmd_entry cmd_unminimise_pane_entry = { + .name = "unminimise-pane", + .alias = "unminimize-pane", + + .args = { "at:", 0, 1, NULL }, + .usage = "[-a] " CMD_TARGET_PANE_USAGE, + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_minimise_pane_unminimise_exec +}; + + +static enum cmd_retval +cmd_minimise_pane_minimise_exec(struct cmd *self, struct cmdq_item *item) +{ + __attribute((unused)) struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct winlink *wl = target->wl; + struct window *w = wl->window; + struct window_pane *wp; + u_int id; + char *cause = NULL; + enum cmd_retval rv; + + if (args_has(args, 'a')) { + TAILQ_FOREACH(wp, &w->z_index, zentry) { + if (!window_pane_visible(wp)) + continue; + rv = cmd_minimise_pane_minimise(w, wp); + if (rv != CMD_RETURN_NORMAL) + return(rv); + } + return (CMD_RETURN_NORMAL); + } else { + wp = target->wp; + if (wp == NULL) { + id = args_strtonum_and_expand(args, 't', 0, INT_MAX, item, &cause); + if (cause != NULL) { + cmdq_error(item, "%s target pane", cause); + return (CMD_RETURN_ERROR); + } + wp = window_pane_find_by_id(id); + } + if (wp == NULL) { + cmdq_error(item, "No target pane to miminise."); + return (CMD_RETURN_ERROR); + } + return(cmd_minimise_pane_minimise(w, wp)); + } +} + +static enum cmd_retval +cmd_minimise_pane_unminimise_exec(struct cmd *self, struct cmdq_item *item) +{ + __attribute((unused)) struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct winlink *wl = target->wl; + struct window *w = wl->window; + struct window_pane *wp; + u_int id; + char *cause = NULL; + enum cmd_retval rv; + + if (args_has(args, 'a')) { + TAILQ_FOREACH(wp, &w->z_index, zentry) { + if (!window_pane_visible(wp)) + continue; + rv = cmd_minimise_pane_unminimise(w, wp); + if (rv != CMD_RETURN_NORMAL) + return(rv); + } + return (CMD_RETURN_NORMAL); + } else { + wp = target->wp; + if (wp == NULL) { + id = args_strtonum_and_expand(args, 't', 0, INT_MAX, item, &cause); + if (cause != NULL) { + cmdq_error(item, "%s target pane", cause); + return (CMD_RETURN_ERROR); + } + wp = window_pane_find_by_id(id); + } + if (wp == NULL) { + cmdq_error(item, "No target pane to unmiminise."); + return (CMD_RETURN_ERROR); + } + return(cmd_minimise_pane_unminimise(w, wp)); + } +} + +static enum cmd_retval +cmd_minimise_pane_minimise(struct window *w, struct window_pane *wp) +{ + struct window_pane *wp2; + + wp->flags |= PANE_MINIMISED; + window_deactivate_pane(w, wp, 1); + + /* Fix pane offsets and sizes. */ + if (w->layout_root != NULL) { + wp->saved_layout_cell = wp->layout_cell; + layout_minimise_cell(w, wp->layout_cell); + layout_fix_offsets(w); + layout_fix_panes(w, NULL); + } + + /* Find next visible window in z-index. */ + TAILQ_FOREACH(wp2, &w->z_index, zentry) { + if (!window_pane_visible(wp2)) + continue; + break; + } + if (wp2 != NULL) + window_set_active_pane(w, wp2, 1); + + notify_window("window-layout-changed", w); + server_redraw_window(w); + + return (CMD_RETURN_NORMAL); +} + +static enum cmd_retval +cmd_minimise_pane_unminimise(struct window *w, struct window_pane *wp) +{ + wp->flags &= ~PANE_MINIMISED; + + /* Fix pane offsets and sizes. */ + if (w->layout_root != NULL && wp->saved_layout_cell != NULL) { + wp->layout_cell = wp->saved_layout_cell; + wp->saved_layout_cell = NULL; + layout_unminimise_cell(w, wp->layout_cell); + layout_fix_offsets(w); + layout_fix_panes(w, NULL); + } + + window_set_active_pane(w, wp, 1); + + notify_window("window-layout-changed", w); + server_redraw_window(w); + + return (CMD_RETURN_NORMAL); +} From 95f85efc499c71c7dbfb261d31bbde5b6650f841 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 7 Jan 2026 22:28:23 +0000 Subject: [PATCH 057/167] Fix some of the offset issues when windows size is greater than tty size. --- screen-redraw.c | 112 ++++++++++++++++++++++++++++-------------------- tty.c | 7 ++- 2 files changed, 70 insertions(+), 49 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 4bc806df0d..96f7b915f2 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -232,15 +232,13 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if ((wp->xoff == 0 || (int)px >= wp->xoff) && ((int)px <= ex || (sb_w != 0 && (int)px < ex + sb_w))) { - if (pane_status == PANE_STATUS_TOP) { - if ((int)py == wp->yoff - 1) - return (SCREEN_REDRAW_BORDER_TOP); - } else { /* PANE_STATUS_BOTTOM or OFF. */ - if (wp->yoff != 0 && (int)py == wp->yoff - 1) - return (SCREEN_REDRAW_BORDER_TOP); - if ((int)py == ey) + if (pane_status != PANE_STATUS_BOTTOM && + wp->yoff != 0 && + (int)py == wp->yoff - 1) + return (SCREEN_REDRAW_BORDER_TOP); + if (pane_status != PANE_STATUS_TOP && + (int)py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); - } } } } @@ -456,6 +454,9 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, /* Find pane higest in z-index at this point. */ TAILQ_FOREACH(wp, &w->z_index, zentry) { + if (wp->flags & PANE_FLOATING && (px >= sx || py >= sy)) + /* Clip floating panes to window. */ + continue; sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; if (sb_pos == PANE_SCROLLBARS_LEFT) { @@ -525,11 +526,11 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, if (pane_status == PANE_STATUS_TOP) pane_status_line = wp->yoff - 1; else - pane_status_line = wp->yoff + sy; + pane_status_line = wp->yoff + wp->sy; left = wp->xoff + 2; right = wp->xoff + 2 + wp->status_size - 1; - if (py == pane_status_line && + if (py == pane_status_line + ctx->oy && /* XXX unsure about adding oy here, needs more testing. */ (int)px >= left && (int)px <= right) return (CELL_INSIDE); } @@ -719,8 +720,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) width = size - x; } - vr = screen_redraw_get_visible_ranges(wp, x, yoff - ctx->oy, - width); + vr = screen_redraw_get_visible_ranges(wp, x, yoff, width); if (ctx->statustop) yoff += ctx->statuslines; @@ -1083,10 +1083,9 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, struct window_pane *wp; struct window *w; static struct visible_ranges vr = {NULL, NULL, 0, 0}; - int found_self, sb_w; + int found_self, sb_w, sb_pos; u_int lb, rb, tb, bb; u_int r, s; - int pane_scrollbars, pane_scrollbars_pos; /* For efficiency vr is static and space reused. */ if (vr.size == 0) { @@ -1095,6 +1094,11 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, vr.size = 1; } + /* debugging: + * display *vr.px@vr.used + * display *vr.nx@vr.used + */ + /* Start with the entire width of the range. */ vr.px[0] = px; vr.nx[0] = width; @@ -1104,8 +1108,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, return (&vr); w = base_wp->window; - pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); - pane_scrollbars_pos = options_get_number(w->options, "pane-scrollbars-position"); + sb_pos = options_get_number(w->options, "pane-scrollbars-position"); found_self = 0; TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { @@ -1118,18 +1121,23 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, bb = wp->yoff + wp->sy; if (!found_self || !window_pane_visible(wp) || - py < tb || - ((wp->flags & PANE_FLOATING) && py > bb) || - (~(wp->flags & PANE_FLOATING) && py > bb)) + py < tb || py > bb) + continue; + if (~wp->flags & PANE_FLOATING && py == bb) continue; /* Are scrollbars enabled? */ - if (window_pane_show_scrollbar(wp, pane_scrollbars)) + if (window_pane_show_scrollbar(wp, + options_get_number(w->options, "pane-scrollbars"))) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; + else { + sb_w = 0; + sb_pos = 0; + } for (r=0; r < vr.used; r++) { - if (pane_scrollbars_pos == PANE_SCROLLBARS_LEFT) { + if (sb_pos == PANE_SCROLLBARS_LEFT) { if (wp->xoff > sb_w) lb = wp->xoff - 1 - sb_w; else @@ -1140,7 +1148,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, else lb = 0; } - if (pane_scrollbars_pos == PANE_SCROLLBARS_LEFT) + if (sb_pos == PANE_SCROLLBARS_LEFT) rb = wp->xoff + wp->sx; else /* PANE_SCROLLBARS_RIGHT or none. */ rb = wp->xoff + wp->sx + sb_w; @@ -1216,7 +1224,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct colour_palette *palette = &wp->palette; struct grid_cell defaults; struct visible_ranges *vr; - u_int i, j, top, x, y, width, r; + u_int i, j, woy, wx, wy, px, py, width, r; if (wp->base.mode & MODE_SYNC) screen_write_stop_sync(wp); @@ -1226,16 +1234,22 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) if (wp->xoff + (int)wp->sx <= ctx->ox || wp->xoff >= ctx->ox + (int)ctx->sx) return; + + /* woy is window y offset in tty. */ if (ctx->statustop) - top = ctx->statuslines; + woy = ctx->statuslines; else - top = 0; + woy = 0; for (j = 0; j < wp->sy; j++) { if (wp->yoff + (int)j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy) continue; - y = wp->yoff + j - ctx->oy; + wy = wp->yoff + j; /* y line within window w. */ + py = woy + wy - ctx->oy; /* y line within tty. */ + if (py > tty->sy) + /* Continue if this line is off of tty. */ + continue; /* Note: i is apparenty not used now that the vr array * returns where in s to read from. @@ -1244,42 +1258,44 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) wp->xoff + wp->sx <= ctx->ox + ctx->sx) { /* All visible. */ i = 0; - x = wp->xoff - ctx->ox; + wx = wp->xoff - ctx->ox; width = wp->sx; } else if (wp->xoff < ctx->ox && wp->xoff + wp->sx > ctx->ox + ctx->sx) { /* Both left and right not visible. */ i = ctx->ox; - x = 0; + wx = 0; width = ctx->sx; } else if (wp->xoff < ctx->ox) { /* Left not visible. */ i = ctx->ox - wp->xoff; - x = 0; + wx = 0; width = wp->sx - i; } else { /* Right not visible. */ i = 0; - x = wp->xoff - ctx->ox; - width = ctx->sx - x; + wx = wp->xoff - ctx->ox; + width = ctx->sx - wx; } log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", - __func__, c->name, wp->id, i, j, x, y, width); + __func__, c->name, wp->id, i, j, wx, wy, width); /* Get visible ranges of line before we draw it. */ - vr = screen_redraw_get_visible_ranges(wp, x, y, width); + vr = screen_redraw_get_visible_ranges(wp, wx, wy, width); tty_default_colours(&defaults, wp); for (r=0; r < vr->used; r++) { if (vr->nx[r] == 0) continue; + /* Convert window coordinates to tty coordinates. */ + px = vr->px[r]; /* i is px of cell, add px of region, sub the * pane offset. If you don't sub offset, * contents of pane shifted. note: i apparently unnec. */ tty_draw_line(tty, s, /* i + */ vr->px[r] - wp->xoff, j, - vr->nx[r], vr->px[r], top + y, &defaults, palette); + vr->nx[r], px, py, &defaults, palette); } } @@ -1305,7 +1321,7 @@ screen_redraw_draw_pane_scrollbars(struct screen_redraw_ctx *ctx) } } -/* Draw pane scrollbar. */ +/* Calculate position and size of pane scrollbar. */ void screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx, struct window_pane *wp) @@ -1316,8 +1332,8 @@ screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx, u_int sb_pos = ctx->pane_scrollbars_pos, slider_h, slider_y; int sb_w = wp->scrollbar_style.width; int sb_pad = wp->scrollbar_style.pad; - int cm_y, cm_size, xoff = wp->xoff, ox = ctx->ox; - int sb_x, sb_y = (int)(wp->yoff - ctx->oy); /* sb top */ + int cm_y, cm_size, xoff = wp->xoff; + int sb_x, sb_y = (int)(wp->yoff); /* sb top */ if (window_pane_mode(wp) == WINDOW_PANE_NO_MODE) { if (sb == PANE_SCROLLBARS_MODAL) @@ -1335,13 +1351,13 @@ screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx, total_height = cm_size + sb_h; percent_view = (double)sb_h / (cm_size + sb_h); slider_h = (double)sb_h * percent_view; - slider_y = (sb_h + 1) * ((double)cm_y / total_height); + slider_y = (sb_h + 1) * ((double)cm_y / (total_height)); } if (sb_pos == PANE_SCROLLBARS_LEFT) - sb_x = xoff - sb_w - sb_pad - ox; + sb_x = xoff - sb_w - sb_pad; else - sb_x = xoff + wp->sx - ox; + sb_x = xoff + wp->sx; if (slider_h < 1) slider_h = 1; @@ -1356,6 +1372,7 @@ screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx, wp->sb_slider_h = slider_h; /* height of slider */ } +/* Draw pane scrollbar. */ static void screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, struct window_pane *wp, int sb_pos, int sb_x, int sb_y, u_int sb_h, @@ -1377,12 +1394,13 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, * Status at top offsets window downward. */ sx = ctx->sx; - sy = ctx->sy; + sy = tty->sy - ctx->statuslines; ox = ctx->ox; oy = ctx->oy; if (ctx->statustop) { - sy += ctx->statuslines; /* Height of tty. */ - oy += ctx->statuslines; /* Top of w in tty. */ + sb_y += ctx->statuslines; + sy += ctx->statuslines; /* Height of window in tty. */ + oy += ctx->statuslines; /* Top of window in tty. */ } @@ -1419,16 +1437,16 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, jmax = sy - sb_y; for (j = jmin; j < jmax; j++) { - py = sb_y + oy + j; /* tty y coordinate. */ - wy = sb_y + j; /* window y coordinate. */ + py = sb_y + j; /* tty y coordinate. */ + wy = sb_y + j + oy; /* window y coordinate. */ vr = screen_redraw_get_visible_ranges(wp, sb_x, wy, imax); for (i = imin; i < imax; i++) { px = sb_x + ox + i; /* tty x coordinate. */ wx = sb_x + i; /* window x coordinate. */ if (wx < xoff - (int)sb_w - (int)sb_pad || - wx >= sx || wx < 0 || + px >= sx || px < 0 || wy < yoff - 1 || - wy >= sy || wy < 0 || + py >= sy || py < 0 || ! screen_redraw_is_visible(vr, wx)) continue; tty_cursor(tty, px, py); diff --git a/tty.c b/tty.c index 1c374ee377..5c4a8a9ac8 100644 --- a/tty.c +++ b/tty.c @@ -1007,7 +1007,10 @@ tty_window_offset1(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy) else if (cy > w->sy - *sy) *oy = w->sy - *sy; else - *oy = cy - *sy / 2; + /* cy-sy/2 was causing panned panes to scroll + * when the cursor was half way down the pane. + */ + *oy = cy - *sy + 1; /* cy - *sy / 2; */ } c->pan_window = NULL; @@ -1220,7 +1223,7 @@ tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py, struct client *c = tty->client; struct window_pane *wp = ctx->arg; struct visible_ranges *vr = NULL; - u_int r, i, x, rx, ry, oy = 0; + u_int r, i, x, rx, ry; log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); From d1df8dccccedb8406bcd052b0ab1a44eebcfc929 Mon Sep 17 00:00:00 2001 From: Thomas Adam Date: Fri, 9 Jan 2026 19:00:17 +0000 Subject: [PATCH 058/167] focus-follows-mouse: honour floating panes Make sure the z-ordering of floating panes is honoured when focus-follows-mouse is in use. --- server-client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/server-client.c b/server-client.c index 872515fc5b..ccd35c3673 100644 --- a/server-client.c +++ b/server-client.c @@ -1097,6 +1097,7 @@ server_client_check_mouse(struct client *c, struct key_event *event) if (wp != NULL && wp != w->active && options_get_number(s->options, "focus-follows-mouse")) { + window_redraw_active_switch(w, wp); window_set_active_pane(w, wp, 1); server_redraw_window_borders(w); server_status_window(w); From 25f72cf2401cde45e69c9d9bc876b76f540f1cac Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 20 Jan 2026 21:08:49 +0000 Subject: [PATCH 059/167] Rewrite tty_draw_line to be simpler and not to check overlay ranges. --- Makefile.am | 1 + grid.c | 2 +- tmux.h | 6 ++ tty-draw.c | 302 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tty.c | 196 +--------------------------------- 5 files changed, 313 insertions(+), 194 deletions(-) create mode 100644 tty-draw.c diff --git a/Makefile.am b/Makefile.am index 81c9c2fc3b..12bfddf557 100644 --- a/Makefile.am +++ b/Makefile.am @@ -195,6 +195,7 @@ dist_tmux_SOURCES = \ tmux.h \ tmux-protocol.h \ tty-acs.c \ + tty-draw.c \ tty-features.c \ tty-keys.c \ tty-term.c \ diff --git a/grid.c b/grid.c index 7ad6770b6e..edfbe494a9 100644 --- a/grid.c +++ b/grid.c @@ -235,7 +235,7 @@ grid_check_y(struct grid *gd, const char *from, u_int py) int grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2) { - int flags1 = gc1->flags, flags2 = gc2->flags;; + int flags1 = gc1->flags, flags2 = gc2->flags; if (gc1->fg != gc2->fg || gc1->bg != gc2->bg) return (0); diff --git a/tmux.h b/tmux.h index 1917ee19bd..33da541ab7 100644 --- a/tmux.h +++ b/tmux.h @@ -2522,6 +2522,8 @@ void tty_reset(struct tty *); void tty_region_off(struct tty *); void tty_margin_off(struct tty *); void tty_cursor(struct tty *, u_int, u_int); +int tty_fake_bce(const struct tty *, const struct grid_cell *, u_int); +void tty_repeat_space(struct tty *, u_int); void tty_clipboard_query(struct tty *); void tty_putcode(struct tty *, enum tty_code_code); void tty_putcode_i(struct tty *, enum tty_code_code, int); @@ -2546,7 +2548,11 @@ void tty_repeat_requests(struct tty *, int); void tty_stop_tty(struct tty *); void tty_set_title(struct tty *, const char *); void tty_set_path(struct tty *, const char *); +void tty_default_attributes(struct tty *, const struct grid_cell *, + struct colour_palette *, u_int, struct hyperlinks *); void tty_update_mode(struct tty *, int, struct screen *); + +/* tty-draw.c */ void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, u_int, u_int, const struct grid_cell *, struct colour_palette *); diff --git a/tty-draw.c b/tty-draw.c new file mode 100644 index 0000000000..d79325c35f --- /dev/null +++ b/tty-draw.c @@ -0,0 +1,302 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2026 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include + +#include "tmux.h" + +enum tty_draw_line_state { + TTY_DRAW_LINE_FIRST, + TTY_DRAW_LINE_FLUSH, + TTY_DRAW_LINE_NEW1, + TTY_DRAW_LINE_NEW2, + TTY_DRAW_LINE_EMPTY, + TTY_DRAW_LINE_SAME, + TTY_DRAW_LINE_PAD, + TTY_DRAW_LINE_DONE +}; +static const char* tty_draw_line_states[] = { + "FIRST", + "FLUSH", + "NEW1", + "NEW2", + "EMPTY", + "SAME", + "PAD", + "DONE" +}; + +/* Clear part of the line. */ +static void +tty_draw_line_clear(struct tty *tty, u_int px, u_int py, u_int nx, + const struct grid_cell *defaults, u_int bg, int wrapped) +{ + /* Nothing to clear. */ + if (nx == 0) + return; + + /* If genuine BCE is available, can try escape sequences. */ + if (!wrapped && nx >= 10 && !tty_fake_bce(tty, defaults, bg)) { + /* Off the end of the line, use EL if available. */ + if (px + nx >= tty->sx && tty_term_has(tty->term, TTYC_EL)) { + tty_cursor(tty, px, py); + tty_putcode(tty, TTYC_EL); + return; + } + + /* At the start of the line. Use EL1. */ + if (px == 0 && tty_term_has(tty->term, TTYC_EL1)) { + tty_cursor(tty, px + nx - 1, py); + tty_putcode(tty, TTYC_EL1); + return; + } + + /* Section of line. Use ECH if possible. */ + if (tty_term_has(tty->term, TTYC_ECH)) { + tty_cursor(tty, px, py); + tty_putcode_i(tty, TTYC_ECH, nx); + return; + } + } + + /* Couldn't use an escape sequence, use spaces. */ + if (px != 0 || !wrapped) + tty_cursor(tty, px, py); + if (nx == 1) + tty_putc(tty, ' '); + else if (nx == 2) + tty_putn(tty, " ", 2, 2); + else + tty_repeat_space(tty, nx); +} + +/* Is this cell empty? */ +static u_int +tty_draw_line_get_empty(struct grid_cell *gc, u_int nx) +{ + u_int empty = 0; + + if (gc->data.width != 1 && gc->data.width > nx) + empty = nx; + else if (gc->attr == 0 && gc->link == 0) { + if (gc->flags & GRID_FLAG_CLEARED) + empty = 1; + else if (gc->flags & GRID_FLAG_TAB) + empty = gc->data.width; + else if (gc->data.size == 1 && *gc->data.data == ' ') + empty = 1; + } + return (empty); +} + +/* Draw a line from screen to tty. */ +void +tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, + u_int atx, u_int aty, const struct grid_cell *defaults, + struct colour_palette *palette) +{ + struct grid *gd = s->grid; + struct grid_cell gc, last; + struct grid_line *gl; + u_int i, j, last_i, cx, ex, width; + u_int cellsize, bg; + int flags, empty, wrapped = 0; + char buf[1000]; + size_t len; + enum tty_draw_line_state current_state, next_state; + + /* + * py is the line in the screen to draw. px is the start x and nx is + * the width to draw. atx,aty is the line on the terminal to draw it. + */ + log_debug("%s: px=%u py=%u nx=%u atx=%u aty=%u", __func__, px, py, nx, + atx, aty); + + /* + * Clamp the width to cellsize - note this is not cellused, because + * there may be empty background cells after it (from BCE). + */ + cellsize = grid_get_line(gd, gd->hsize + py)->cellsize; + if (screen_size_x(s) > cellsize) + ex = cellsize; + else { + ex = screen_size_x(s); + if (px > ex) + return; + if (px + nx > ex) + nx = ex - px; + } + if (ex < nx) + ex = nx; + log_debug("%s: drawing %u-%u,%u (end %u) at %u,%u; defaults: fg=%d, " + "bg=%d", __func__, px, px + nx, py, ex, atx, aty, defaults->fg, + defaults->bg); + + /* + * If there is padding at the start, we must have truncated a wide + * character. Clear it. + */ + cx = 0; + for (i = px; i < px + nx; i++) { + grid_view_get_cell(gd, i, py, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + cx++; + } + if (cx != 0) { + /* Find the previous cell for the background colour. */ + for (i = px + 1; i > 0; i--) { + grid_view_get_cell(gd, i - 1, py, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + } + if (i == 0) + bg = gc.bg; + else + bg = defaults->bg; + tty_attributes(tty, &last, defaults, palette, s->hyperlinks); + log_debug("%s: clearing %u padding cells", __func__, cx); + tty_draw_line_clear(tty, atx, aty, cx, defaults, bg, 0); + if (cx == ex) + return; + atx += cx; + px += cx; + nx -= cx; + ex -= cx; + } + + /* Did the previous line wrap on to this one? */ + if (py != 0 && atx == 0 && tty->cx >= tty->sx && nx == tty->sx) { + gl = grid_get_line(gd, gd->hsize + py - 1); + if (gl->flags & GRID_LINE_WRAPPED) + wrapped = 1; + } + + /* Turn off cursor while redrawing and reset region and margins. */ + flags = (tty->flags & TTY_NOCURSOR); + tty->flags |= TTY_NOCURSOR; + tty_update_mode(tty, tty->mode, s); + tty_region_off(tty); + tty_margin_off(tty); + + /* Start with the default cell as the last cell. */ + memcpy(&last, &grid_default_cell, sizeof last); + last.bg = defaults->bg; + tty_default_attributes(tty, defaults, palette, 8, s->hyperlinks); + + /* Loop over each character in the range. */ + last_i = i = 0; + len = 0; + width = 0; + current_state = TTY_DRAW_LINE_FIRST; + for (;;) { + /* Work out the next state. */ + if (i == nx) { + /* + * If this is the last cell, we are done. But we need to + * go through the loop again to flush anything in + * the buffer. + */ + empty = 0; + next_state = TTY_DRAW_LINE_DONE; + } else { + /* Get the current cell. */ + grid_view_get_cell(gd, px + i, py, &gc); + + /* Work out the the empty width. */ + if (i >= ex) + empty = 1; + else + empty = tty_draw_line_get_empty(&gc, nx - i); + + /* Work out the next state. */ + if (empty != 0) + next_state = TTY_DRAW_LINE_EMPTY; + else if (current_state == TTY_DRAW_LINE_FIRST) + next_state = TTY_DRAW_LINE_SAME; + else if (gc.flags & GRID_FLAG_PADDING) + next_state = TTY_DRAW_LINE_PAD; + else if (grid_cells_look_equal(&gc, &last)) { + if (gc.data.size > (sizeof buf) - len) + next_state = TTY_DRAW_LINE_FLUSH; + else + next_state = TTY_DRAW_LINE_SAME; + } else if (current_state == TTY_DRAW_LINE_NEW1) + next_state = TTY_DRAW_LINE_NEW2; + else + next_state = TTY_DRAW_LINE_NEW1; + } + log_debug("%s: cell %u empty %u, bg %u; state: current %s, " + "next %s", __func__, px + i, empty, gc.bg, + tty_draw_line_states[current_state], + tty_draw_line_states[next_state]); + + /* If the state has changed, flush any collected data. */ + if (next_state != current_state) { + if (current_state == TTY_DRAW_LINE_EMPTY) { + tty_attributes(tty, &last, defaults, palette, + s->hyperlinks); + tty_draw_line_clear(tty, atx + last_i, aty, + i - last_i, defaults, last.bg, wrapped); + wrapped = 0; + } else if (next_state != TTY_DRAW_LINE_SAME && + len != 0) { + tty_attributes(tty, &last, defaults, palette, + s->hyperlinks); + if (atx + i - width != 0 || !wrapped) + tty_cursor(tty, atx + i - width, aty); + if (~last.attr & GRID_ATTR_CHARSET) + tty_putn(tty, buf, len, width); + else { + for (j = 0; j < len; j++) + tty_putc(tty, buf[j]); + } + len = 0; + width = 0; + wrapped = 0; + } + last_i = i; + } + + /* Append the cell if it is not empty and not padding. */ + if (next_state != TTY_DRAW_LINE_EMPTY && + next_state != TTY_DRAW_LINE_PAD) { + memcpy(buf + len, gc.data.data, gc.data.size); + len += gc.data.size; + width += gc.data.width; + } + + /* If this is the last cell, we are done. */ + if (next_state == TTY_DRAW_LINE_DONE) + break; + + /* Otherwise move to the next. */ + current_state = next_state; + memcpy(&last, &gc, sizeof last); + if (empty != 0) + i += empty; + else + i++; + } + + tty->flags = (tty->flags & ~TTY_NOCURSOR)|flags; + tty_update_mode(tty, tty->mode, s); +} + diff --git a/tty.c b/tty.c index 72debdcc49..bc1df8364d 100644 --- a/tty.c +++ b/tty.c @@ -61,15 +61,10 @@ static void tty_region(struct tty *, u_int, u_int); static void tty_margin_pane(struct tty *, const struct tty_ctx *); static void tty_margin(struct tty *, u_int, u_int); static int tty_large_region(struct tty *, const struct tty_ctx *); -static int tty_fake_bce(const struct tty *, const struct grid_cell *, - u_int); static void tty_redraw_region(struct tty *, const struct tty_ctx *); static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); -static void tty_repeat_space(struct tty *, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); -static void tty_default_attributes(struct tty *, const struct grid_cell *, - struct colour_palette *, u_int, struct hyperlinks *); static int tty_check_overlay(struct tty *, u_int, u_int); static void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, struct overlay_ranges *); @@ -912,7 +907,7 @@ tty_emulate_repeat(struct tty *tty, enum tty_code_code code, } } -static void +void tty_repeat_space(struct tty *tty, u_int n) { static char s[500]; @@ -1071,7 +1066,7 @@ tty_large_region(__unused struct tty *tty, const struct tty_ctx *ctx) * Return if BCE is needed but the terminal doesn't have it - it'll need to be * emulated. */ -static int +int tty_fake_bce(const struct tty *tty, const struct grid_cell *gc, u_int bg) { if (tty_term_flag(tty->term, TTYC_BCE)) @@ -1463,191 +1458,6 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, c->overlay_check(c, c->overlay_data, px, py, nx, r); } -void -tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, - u_int atx, u_int aty, const struct grid_cell *defaults, - struct colour_palette *palette) -{ - struct grid *gd = s->grid; - struct grid_cell gc, last; - const struct grid_cell *gcp; - struct grid_line *gl; - struct client *c = tty->client; - struct overlay_ranges r; - u_int i, j, ux, sx, width, hidden, eux, nxx; - u_int cellsize; - int flags, cleared = 0, wrapped = 0; - char buf[512]; - size_t len; - - log_debug("%s: px=%u py=%u nx=%u atx=%u aty=%u", __func__, - px, py, nx, atx, aty); - log_debug("%s: defaults: fg=%d, bg=%d", __func__, defaults->fg, - defaults->bg); - - /* - * py is the line in the screen to draw. - * px is the start x and nx is the width to draw. - * atx,aty is the line on the terminal to draw it. - */ - - flags = (tty->flags & TTY_NOCURSOR); - tty->flags |= TTY_NOCURSOR; - tty_update_mode(tty, tty->mode, s); - - tty_region_off(tty); - tty_margin_off(tty); - - /* - * Clamp the width to cellsize - note this is not cellused, because - * there may be empty background cells after it (from BCE). - */ - sx = screen_size_x(s); - if (nx > sx) - nx = sx; - cellsize = grid_get_line(gd, gd->hsize + py)->cellsize; - if (sx > cellsize) - sx = cellsize; - if (sx > tty->sx) - sx = tty->sx; - if (sx > nx) - sx = nx; - ux = 0; - - if (py == 0) - gl = NULL; - else - gl = grid_get_line(gd, gd->hsize + py - 1); - if (gl == NULL || - (~gl->flags & GRID_LINE_WRAPPED) || - atx != 0 || - tty->cx < tty->sx || - nx < tty->sx) { - if (nx < tty->sx && - atx == 0 && - px + sx != nx && - tty_term_has(tty->term, TTYC_EL1) && - !tty_fake_bce(tty, defaults, 8) && - c->overlay_check == NULL) { - tty_default_attributes(tty, defaults, palette, 8, - s->hyperlinks); - tty_cursor(tty, nx - 1, aty); - tty_putcode(tty, TTYC_EL1); - cleared = 1; - } - } else { - log_debug("%s: wrapped line %u", __func__, aty); - wrapped = 1; - } - - memcpy(&last, &grid_default_cell, sizeof last); - len = 0; - width = 0; - - for (i = 0; i < sx; i++) { - grid_view_get_cell(gd, px + i, py, &gc); - gcp = tty_check_codeset(tty, &gc); - if (len != 0 && - (!tty_check_overlay(tty, atx + ux + width, aty) || - (gcp->attr & GRID_ATTR_CHARSET) || - gcp->flags != last.flags || - gcp->attr != last.attr || - gcp->fg != last.fg || - gcp->bg != last.bg || - gcp->us != last.us || - gcp->link != last.link || - ux + width + gcp->data.width > nx || - (sizeof buf) - len < gcp->data.size)) { - tty_attributes(tty, &last, defaults, palette, - s->hyperlinks); - if (last.flags & GRID_FLAG_CLEARED) { - log_debug("%s: %zu cleared", __func__, len); - tty_clear_line(tty, defaults, aty, atx + ux, - width, last.bg); - } else { - if (!wrapped || atx != 0 || ux != 0) - tty_cursor(tty, atx + ux, aty); - tty_putn(tty, buf, len, width); - } - ux += width; - - len = 0; - width = 0; - wrapped = 0; - } - - if (gcp->flags & GRID_FLAG_SELECTED) - screen_select_cell(s, &last, gcp); - else - memcpy(&last, gcp, sizeof last); - - tty_check_overlay_range(tty, atx + ux, aty, gcp->data.width, - &r); - hidden = 0; - for (j = 0; j < OVERLAY_MAX_RANGES; j++) - hidden += r.nx[j]; - hidden = gcp->data.width - hidden; - if (hidden != 0 && hidden == gcp->data.width) { - if (~gcp->flags & GRID_FLAG_PADDING) - ux += gcp->data.width; - } else if (hidden != 0 || ux + gcp->data.width > nx) { - if (~gcp->flags & GRID_FLAG_PADDING) { - tty_attributes(tty, &last, defaults, palette, - s->hyperlinks); - for (j = 0; j < OVERLAY_MAX_RANGES; j++) { - if (r.nx[j] == 0) - continue; - /* Effective width drawn so far. */ - eux = r.px[j] - atx; - if (eux < nx) { - tty_cursor(tty, r.px[j], aty); - nxx = nx - eux; - if (r.nx[j] > nxx) - r.nx[j] = nxx; - tty_repeat_space(tty, r.nx[j]); - ux = eux + r.nx[j]; - } - } - } - } else if (gcp->attr & GRID_ATTR_CHARSET) { - tty_attributes(tty, &last, defaults, palette, - s->hyperlinks); - tty_cursor(tty, atx + ux, aty); - for (j = 0; j < gcp->data.size; j++) - tty_putc(tty, gcp->data.data[j]); - ux += gcp->data.width; - } else if (~gcp->flags & GRID_FLAG_PADDING) { - memcpy(buf + len, gcp->data.data, gcp->data.size); - len += gcp->data.size; - width += gcp->data.width; - } - } - if (len != 0 && ((~last.flags & GRID_FLAG_CLEARED) || last.bg != 8)) { - tty_attributes(tty, &last, defaults, palette, s->hyperlinks); - if (last.flags & GRID_FLAG_CLEARED) { - log_debug("%s: %zu cleared (end)", __func__, len); - tty_clear_line(tty, defaults, aty, atx + ux, width, - last.bg); - } else { - if (!wrapped || atx != 0 || ux != 0) - tty_cursor(tty, atx + ux, aty); - tty_putn(tty, buf, len, width); - } - ux += width; - } - - if (!cleared && ux < nx) { - log_debug("%s: %u to end of line (%zu cleared)", __func__, - nx - ux, len); - tty_default_attributes(tty, defaults, palette, 8, - s->hyperlinks); - tty_clear_line(tty, defaults, aty, atx + ux, nx - ux, 8); - } - - tty->flags = (tty->flags & ~TTY_NOCURSOR) | flags; - tty_update_mode(tty, tty->mode, s); -} - #ifdef ENABLE_SIXEL /* Update context for client. */ static int @@ -3203,7 +3013,7 @@ tty_default_colours(struct grid_cell *gc, struct window_pane *wp) } } -static void +void tty_default_attributes(struct tty *tty, const struct grid_cell *defaults, struct colour_palette *palette, u_int bg, struct hyperlinks *hl) { From b108653f02060ba75edce64c768a7526545ec569 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 20 Jan 2026 21:09:30 +0000 Subject: [PATCH 060/167] Change overlay_ranges to visible_ranges. --- menu.c | 10 +++--- popup.c | 39 ++++++++++------------- screen-redraw.c | 26 ++++++++++++--- screen.c | 69 +++++++++++++++++++++++++++++++++++++++ server-client.c | 47 +++++++++++++++------------ tmux.h | 23 +++++++++---- tty.c | 85 ++++++++++++++++++++++++++++++------------------- 7 files changed, 208 insertions(+), 91 deletions(-) diff --git a/menu.c b/menu.c index 7449d88db4..cd361e0003 100644 --- a/menu.c +++ b/menu.c @@ -181,15 +181,17 @@ menu_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) } /* Return parts of the input range which are not obstructed by the menu. */ -void +struct visible_ranges * menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py, - u_int nx, struct overlay_ranges *r) + u_int nx) { struct menu_data *md = data; struct menu *menu = md->menu; + struct visible_ranges *r; - server_client_overlay_range(md->px, md->py, menu->width + 4, - menu->count + 2, px, py, nx, r); + r = server_client_overlay_range(md->px, md->py, menu->width + 4, + menu->count + 2, px, py, nx); + return (r); } void diff --git a/popup.c b/popup.c index 2146693a8e..6d0e1ffd38 100644 --- a/popup.c +++ b/popup.c @@ -164,48 +164,41 @@ popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) } /* Return parts of the input range which are not obstructed by the popup. */ -static void -popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx, - struct overlay_ranges *r) +static struct visible_ranges * +popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx) { struct popup_data *pd = data; - struct overlay_ranges or[2]; + struct visible_ranges *or[2], *r; u_int i, j, k = 0; if (pd->md != NULL) { /* Check each returned range for the menu against the popup. */ - menu_check_cb(c, pd->md, px, py, nx, r); - for (i = 0; i < 2; i++) { - server_client_overlay_range(pd->px, pd->py, pd->sx, - pd->sy, r->px[i], py, r->nx[i], &or[i]); + r = menu_check_cb(c, pd->md, px, py, nx); + for (i = 0; i < r->used; i++) { + or[i] = server_client_overlay_range(pd->px, pd->py, pd->sx, + pd->sy, r->px[i], py, r->nx[i]); } /* * or has up to OVERLAY_MAX_RANGES non-overlapping ranges, * ordered from left to right. Collect them in the output. */ - for (i = 0; i < 2; i++) { - /* Each or[i] only has 2 ranges. */ - for (j = 0; j < 2; j++) { - if (or[i].nx[j] > 0) { - r->px[k] = or[i].px[j]; - r->nx[k] = or[i].nx[j]; + for (i = 0; i < r->used; i++) { + /* Each or[i] only has up to 2 ranges. */ + for (j = 0; j < or[i]->used; j++) { + if (or[i]->nx[j] > 0) { + r->px[k] = or[i]->px[j]; + r->nx[k] = or[i]->nx[j]; k++; } } } - /* Zero remaining ranges if any. */ - for (i = k; i < OVERLAY_MAX_RANGES; i++) { - r->px[i] = 0; - r->nx[i] = 0; - } - - return; + return (r); } - server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx, - r); + return (server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, + nx)); } static void diff --git a/screen-redraw.c b/screen-redraw.c index cce0601154..da3f40b573 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -817,13 +817,13 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) struct window_pane *wp, *active = server_client_get_pane(c); struct grid_cell gc; const struct grid_cell *tmp; - struct overlay_ranges r; + struct visible_ranges *r; u_int cell_type, x = ctx->ox + i, y = ctx->oy + j; int isolates; if (c->overlay_check != NULL) { - c->overlay_check(c, c->overlay_data, x, y, 1, &r); - if (r.nx[0] + r.nx[1] == 0) + r = c->overlay_check(c, c->overlay_data, x, y, 1); + if (r->nx[0] + r->nx[1] == 0) return; } @@ -943,7 +943,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct screen *s = wp->screen; struct colour_palette *palette = &wp->palette; struct grid_cell defaults; - u_int i, j, top, x, y, width; + struct visible_ranges *vr; + u_int i, j, top, x, y, px, width, r; if (wp->base.mode & MODE_SYNC) screen_write_stop_sync(wp); @@ -987,8 +988,23 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", __func__, c->name, wp->id, i, j, x, y, width); + /* xxxx Breaking up the tty_draw_line like this isn't fully working. */ + vr = tty_check_overlay_range(tty, x, y, width); + tty_default_colours(&defaults, wp); - tty_draw_line(tty, s, i, j, width, x, y, &defaults, palette); + + for (r=0; r < vr->used; r++) { + if (vr->nx[r] == 0) + continue; + /* Convert window coordinates to tty coordinates. */ + px = vr->px[r]; + /* i is px of cell, add px of region, sub the + * pane offset. If you don't sub offset, + * contents of pane shifted. note: i apparently unnec. + */ + tty_draw_line(tty, s, /* i + */ vr->px[r] - wp->xoff, j, + vr->nx[r], px, y, &defaults, palette); + } } #ifdef ENABLE_SIXEL diff --git a/screen.c b/screen.c index 7f4f938386..247d596d45 100644 --- a/screen.c +++ b/screen.c @@ -770,3 +770,72 @@ screen_mode_to_string(int mode) tmp[strlen(tmp) - 1] = '\0'; return (tmp); } + +#ifdef DEBUG +/* Debug function. Usage in gdb: + * printf "%S",screen_print(s) + */ +__unused char * +screen_print(struct screen *s) { + static char buf[16384]; + const char *acs; + u_int x, y; + size_t last = 0, n; + struct utf8_data ud; + struct grid_line *gl; + struct grid_cell_entry *gce; + + for (y = 0; y < screen_hsize(s)+s->grid->sy; y++) { + if (last + 6 > sizeof buf) goto out; + n = snprintf(buf + last, sizeof buf - last, "%.4d ", y); + last += n; + buf[last++] = '"'; + gl = &s->grid->linedata[y]; + + for (x = 0; x < gl->cellused; x++) { + gce = &gl->celldata[x]; + + if (gce->flags & GRID_FLAG_PADDING) + continue; + + if (~gce->flags & GRID_FLAG_EXTENDED) { + /* single-byte cell stored inline */ + if (last + 2 > sizeof buf) goto out; + buf[last++] = gce->data.data; + } else if (gce->flags & GRID_FLAG_TAB) { + if (last + 2 > sizeof buf) goto out; + buf[last++] = '\t'; + } else if ((gce->data.data & 0xff) && + (gce->flags & GRID_ATTR_CHARSET)) { + /* single-byte ACS inline: try to map */ + acs = tty_acs_get(NULL, gce->data.data); + if (acs != NULL) { + n = strlen(acs); + if (last + n + 1 > sizeof buf) goto out; + memcpy(buf + last, acs, n); + last += n; + continue; + } + buf[last++] = gce->data.data; + } else { + /* extended cell: convert utf8_char -> bytes */ + utf8_to_data(gl->extddata[gce->offset].data, + &ud); + if (ud.size > 0) { + if (last + ud.size + 1 > sizeof buf) + goto out; + memcpy(buf + last, ud.data, ud.size); + last += ud.size; + } + } + } + + if (last + 3 > sizeof buf) goto out; + buf[last++] = '"'; + buf[last++] = '\n'; + } + +out: buf[last] = '\0'; + return (buf); +} +#endif diff --git a/server-client.c b/server-client.c index 1ea2fe3e3c..a222ac6fa6 100644 --- a/server-client.c +++ b/server-client.c @@ -165,34 +165,37 @@ server_client_clear_overlay(struct client *c) * Given overlay position and dimensions, return parts of the input range which * are visible. */ -void +struct visible_ranges * server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px, - u_int py, u_int nx, struct overlay_ranges *r) + u_int py, u_int nx) { - u_int ox, onx; + u_int ox, onx; + static struct visible_ranges r = {NULL, NULL, 0, 0}; - /* Return up to 2 ranges. */ - r->px[2] = 0; - r->nx[2] = 0; + /* For efficiency vr is static and space reused. */ + if (r.size == 0) { + r.px = xcalloc(2, sizeof(u_int)); + r.nx = xcalloc(2, sizeof(u_int)); + r.size = 2; + } /* Trivial case of no overlap in the y direction. */ if (py < y || py > y + sy - 1) { - r->px[0] = px; - r->nx[0] = nx; - r->px[1] = 0; - r->nx[1] = 0; - return; + r.px[0] = px; + r.nx[0] = nx; + r.used = 1; + return (&r); } /* Visible bit to the left of the popup. */ if (px < x) { - r->px[0] = px; - r->nx[0] = x - px; - if (r->nx[0] > nx) - r->nx[0] = nx; + r.px[0] = px; + r.nx[0] = x - px; + if (r.nx[0] > nx) + r.nx[0] = nx; } else { - r->px[0] = 0; - r->nx[0] = 0; + r.px[0] = 0; + r.nx[0] = 0; } /* Visible bit to the right of the popup. */ @@ -201,12 +204,14 @@ server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px, ox = px; onx = px + nx; if (onx > ox) { - r->px[1] = ox; - r->nx[1] = onx - ox; + r.px[1] = ox; + r.nx[1] = onx - ox; } else { - r->px[1] = 0; - r->nx[1] = 0; + r.px[1] = 0; + r.nx[1] = 0; } + r.used = 2; + return (&r); } /* Check if this client is inside this server. */ diff --git a/tmux.h b/tmux.h index 33da541ab7..ec1bd543bb 100644 --- a/tmux.h +++ b/tmux.h @@ -1922,11 +1922,19 @@ struct overlay_ranges { u_int nx[OVERLAY_MAX_RANGES]; }; +/* Visible range array element. */ +struct visible_ranges { + u_int *px; /* Start */ + u_int *nx; /* Length */ + size_t used; + size_t size; +}; + /* Client connection. */ typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); typedef void (*prompt_free_cb)(void *); -typedef void (*overlay_check_cb)(struct client*, void *, u_int, u_int, u_int, - struct overlay_ranges *); +typedef struct visible_ranges *(*overlay_check_cb)(struct client*, void *, + u_int, u_int, u_int); typedef struct screen *(*overlay_mode_cb)(struct client *, void *, u_int *, u_int *); typedef void (*overlay_draw_cb)(struct client *, void *, @@ -2551,6 +2559,8 @@ void tty_set_path(struct tty *, const char *); void tty_default_attributes(struct tty *, const struct grid_cell *, struct colour_palette *, u_int, struct hyperlinks *); void tty_update_mode(struct tty *, int, struct screen *); +struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int, + u_int); /* tty-draw.c */ void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, @@ -2567,6 +2577,7 @@ void tty_close(struct tty *); void tty_free(struct tty *); void tty_update_features(struct tty *); void tty_set_selection(struct tty *, const char *, const char *, size_t); +u_int tty_cell_width(const struct grid_cell *, u_int); void tty_write(void (*)(struct tty *, const struct tty_ctx *), struct tty_ctx *); void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *); @@ -2909,8 +2920,8 @@ void server_client_set_overlay(struct client *, u_int, overlay_check_cb, overlay_mode_cb, overlay_draw_cb, overlay_key_cb, overlay_free_cb, overlay_resize_cb, void *); void server_client_clear_overlay(struct client *); -void server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int, - u_int, struct overlay_ranges *); +struct visible_ranges *server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int, + u_int); void server_client_set_key_table(struct client *, const char *); const char *server_client_get_key_table(struct client *); int server_client_check_nested(struct client *); @@ -3231,6 +3242,7 @@ void screen_select_cell(struct screen *, struct grid_cell *, void screen_alternate_on(struct screen *, struct grid_cell *, int); void screen_alternate_off(struct screen *, struct grid_cell *, int); const char *screen_mode_to_string(int); +__unused char * screen_print(struct screen *s); /* window.c */ extern struct windows windows; @@ -3611,8 +3623,7 @@ int menu_display(struct menu *, int, int, struct cmdq_item *, const char *, const char *, struct cmd_find_state *, menu_choice_cb, void *); struct screen *menu_mode_cb(struct client *, void *, u_int *, u_int *); -void menu_check_cb(struct client *, void *, u_int, u_int, u_int, - struct overlay_ranges *); +struct visible_ranges *menu_check_cb(struct client *, void *, u_int, u_int, u_int); void menu_draw_cb(struct client *, void *, struct screen_redraw_ctx *); void menu_free_cb(struct client *, void *); diff --git a/tty.c b/tty.c index bc1df8364d..5ff225d6fc 100644 --- a/tty.c +++ b/tty.c @@ -66,8 +66,7 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); static int tty_check_overlay(struct tty *, u_int, u_int); -static void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, - struct overlay_ranges *); +struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int, u_int); #ifdef ENABLE_SIXEL static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), @@ -1161,7 +1160,7 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, u_int px, u_int nx, u_int bg) { struct client *c = tty->client; - struct overlay_ranges r; + struct visible_ranges *r; u_int i; log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); @@ -1198,13 +1197,17 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, * Couldn't use an escape sequence, use spaces. Clear only the visible * bit if there is an overlay. */ - tty_check_overlay_range(tty, px, py, nx, &r); - for (i = 0; i < OVERLAY_MAX_RANGES; i++) { - if (r.nx[i] == 0) + tty_cursor(tty, px, py); + tty_repeat_space(tty, nx); + /* + r = tty_check_overlay_range(tty, px, py, nx); + for (i = 0; i < r->used; i++) { + if (r->nx[i] == 0) continue; - tty_cursor(tty, r.px[i], py); - tty_repeat_space(tty, r.nx[i]); + tty_cursor(tty, r->px[i], py); + tty_repeat_space(tty, r->nx[i]); } + */ } /* Clear a line, adjusting to visible part of pane. */ @@ -1418,6 +1421,20 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc) return (&new); } +/* + * Compute the effective display width (in terminal columns) of a grid cell + * when it will be drawn at terminal column atcol. + */ +u_int +tty_cell_width(const struct grid_cell *gcp, u_int atcol) +{ + /* Tabs expand to the next tab stop (tab width = 8). */ + if (gcp->flags & GRID_FLAG_TAB) + return (8 - (atcol % 8)); + /* Normal characters: width stored in cell (1 or 2 usually). */ + return (gcp->data.width); +} + /* * Check if a single character is obstructed by the overlay and return a * boolean. @@ -1425,37 +1442,41 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc) static int tty_check_overlay(struct tty *tty, u_int px, u_int py) { - struct overlay_ranges r; + struct visible_ranges *r; /* * A unit width range will always return nx[2] == 0 from a check, even * with multiple overlays, so it's sufficient to check just the first * two entries. */ - tty_check_overlay_range(tty, px, py, 1, &r); - if (r.nx[0] + r.nx[1] == 0) + r = tty_check_overlay_range(tty, px, py, 1); + if (r->nx[0] + r->nx[1] == 0) return (0); return (1); } /* Return parts of the input range which are visible. */ -static void -tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, - struct overlay_ranges *r) +struct visible_ranges * +tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx) { + static struct visible_ranges r = {NULL, NULL, 0, 0}; struct client *c = tty->client; + /* For efficiency vr is static and space reused. */ + if (r.size == 0) { + r.px = xcalloc(1, sizeof(u_int)); + r.nx = xcalloc(1, sizeof(u_int)); + r.size = 1; + } + if (c->overlay_check == NULL) { - r->px[0] = px; - r->nx[0] = nx; - r->px[1] = 0; - r->nx[1] = 0; - r->px[2] = 0; - r->nx[2] = 0; - return; + r.px[0] = px; + r.nx[0] = nx; + r.used = 1; + return (&r); } - c->overlay_check(c, c->overlay_data, px, py, nx, r); + return (c->overlay_check(c, c->overlay_data, px, py, nx)); } #ifdef ENABLE_SIXEL @@ -1983,7 +2004,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) { const struct grid_cell *gcp = ctx->cell; struct screen *s = ctx->s; - struct overlay_ranges r; + struct visible_ranges *r; u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2000,9 +2021,9 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) /* Handle partially obstructed wide characters. */ if (gcp->data.width > 1) { - tty_check_overlay_range(tty, px, py, gcp->data.width, &r); - for (i = 0; i < OVERLAY_MAX_RANGES; i++) - vis += r.nx[i]; + r = tty_check_overlay_range(tty, px, py, gcp->data.width); + for (i = 0; i < r->used; i++) + vis += r->nx[i]; if (vis < gcp->data.width) { tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, px, py, &ctx->defaults, ctx->palette); @@ -2028,7 +2049,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) { - struct overlay_ranges r; + struct visible_ranges *r; u_int i, px, py, cx; char *cp = ctx->ptr; @@ -2059,14 +2080,14 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) px = ctx->xoff + ctx->ocx - ctx->wox; py = ctx->yoff + ctx->ocy - ctx->woy; - tty_check_overlay_range(tty, px, py, ctx->num, &r); - for (i = 0; i < OVERLAY_MAX_RANGES; i++) { - if (r.nx[i] == 0) + r = tty_check_overlay_range(tty, px, py, ctx->num); + for (i = 0; i < r->used; i++) { + if (r->nx[i] == 0) continue; /* Convert back to pane position for printing. */ - cx = r.px[i] - ctx->xoff + ctx->wox; + cx = r->px[i] - ctx->xoff + ctx->wox; tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy); - tty_putn(tty, cp + r.px[i] - px, r.nx[i], r.nx[i]); + tty_putn(tty, cp + r->px[i] - px, r->nx[i], r->nx[i]); } } From 35485f2b5ed19b5fb5d49f5c8b1a400c94dca6eb Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 21 Jan 2026 12:23:46 +0000 Subject: [PATCH 061/167] Fix selection with tty_draw_line. --- screen.c | 5 +++-- tmux.h | 4 +++- tty-draw.c | 25 +++++++++++++++++-------- tty.c | 2 +- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/screen.c b/screen.c index 247d596d45..693b50dc30 100644 --- a/screen.c +++ b/screen.c @@ -581,12 +581,12 @@ screen_check_selection(struct screen *s, u_int px, u_int py) } /* Get selected grid cell. */ -void +int screen_select_cell(struct screen *s, struct grid_cell *dst, const struct grid_cell *src) { if (s->sel == NULL || s->sel->hidden) - return; + return (0); memcpy(dst, &s->sel->cell, sizeof *dst); if (COLOUR_DEFAULT(dst->fg)) @@ -600,6 +600,7 @@ screen_select_cell(struct screen *s, struct grid_cell *dst, dst->attr |= (src->attr & GRID_ATTR_CHARSET); else dst->attr |= src->attr; + return (1); } /* Reflow wrapped lines. */ diff --git a/tmux.h b/tmux.h index ec1bd543bb..424b6093a1 100644 --- a/tmux.h +++ b/tmux.h @@ -2559,6 +2559,8 @@ void tty_set_path(struct tty *, const char *); void tty_default_attributes(struct tty *, const struct grid_cell *, struct colour_palette *, u_int, struct hyperlinks *); void tty_update_mode(struct tty *, int, struct screen *); +const struct grid_cell *tty_check_codeset(struct tty *, + const struct grid_cell *); struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int, u_int); @@ -3237,7 +3239,7 @@ void screen_set_selection(struct screen *, u_int, u_int, u_int, u_int, void screen_clear_selection(struct screen *); void screen_hide_selection(struct screen *); int screen_check_selection(struct screen *, u_int, u_int); -void screen_select_cell(struct screen *, struct grid_cell *, +int screen_select_cell(struct screen *, struct grid_cell *, const struct grid_cell *); void screen_alternate_on(struct screen *, struct grid_cell *, int); void screen_alternate_off(struct screen *, struct grid_cell *, int); diff --git a/tty-draw.c b/tty-draw.c index d79325c35f..3d77d4a173 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -113,7 +113,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, struct colour_palette *palette) { struct grid *gd = s->grid; - struct grid_cell gc, last; + struct grid_cell gc, *gcp, ngc, last; struct grid_line *gl; u_int i, j, last_i, cx, ex, width; u_int cellsize, bg; @@ -169,6 +169,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, } if (i == 0) bg = gc.bg; + else if (screen_select_cell(s, &ngc, &gc)) + bg = ngc.bg; else bg = defaults->bg; tty_attributes(tty, &last, defaults, palette, s->hyperlinks); @@ -220,6 +222,13 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, /* Get the current cell. */ grid_view_get_cell(gd, px + i, py, &gc); + /* Update for codeset if needed. */ + gcp = tty_check_codeset(tty, &gc); + + /* And for selection. */ + if (screen_select_cell(s, &ngc, gcp)) + gcp = &ngc; + /* Work out the the empty width. */ if (i >= ex) empty = 1; @@ -231,10 +240,10 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, next_state = TTY_DRAW_LINE_EMPTY; else if (current_state == TTY_DRAW_LINE_FIRST) next_state = TTY_DRAW_LINE_SAME; - else if (gc.flags & GRID_FLAG_PADDING) + else if (gcp->flags & GRID_FLAG_PADDING) next_state = TTY_DRAW_LINE_PAD; else if (grid_cells_look_equal(&gc, &last)) { - if (gc.data.size > (sizeof buf) - len) + if (gcp->data.size > (sizeof buf) - len) next_state = TTY_DRAW_LINE_FLUSH; else next_state = TTY_DRAW_LINE_SAME; @@ -244,7 +253,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, next_state = TTY_DRAW_LINE_NEW1; } log_debug("%s: cell %u empty %u, bg %u; state: current %s, " - "next %s", __func__, px + i, empty, gc.bg, + "next %s", __func__, px + i, empty, gcp->bg, tty_draw_line_states[current_state], tty_draw_line_states[next_state]); @@ -278,9 +287,9 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, /* Append the cell if it is not empty and not padding. */ if (next_state != TTY_DRAW_LINE_EMPTY && next_state != TTY_DRAW_LINE_PAD) { - memcpy(buf + len, gc.data.data, gc.data.size); - len += gc.data.size; - width += gc.data.width; + memcpy(buf + len, gcp->data.data, gcp->data.size); + len += gcp->data.size; + width += gcp->data.width; } /* If this is the last cell, we are done. */ @@ -289,7 +298,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, /* Otherwise move to the next. */ current_state = next_state; - memcpy(&last, &gc, sizeof last); + memcpy(&last, gcp, sizeof last); if (empty != 0) i += empty; else diff --git a/tty.c b/tty.c index 5ff225d6fc..2b5e91c53f 100644 --- a/tty.c +++ b/tty.c @@ -1388,7 +1388,7 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) } } -static const struct grid_cell * +const struct grid_cell * tty_check_codeset(struct tty *tty, const struct grid_cell *gc) { static struct grid_cell new; From 58e498c9d3de650d2b802712bf24f8e572ccd1c9 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 21 Jan 2026 15:35:33 +0000 Subject: [PATCH 062/167] Use right cell for empty check. --- tty-draw.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tty-draw.c b/tty-draw.c index 3d77d4a173..a37121617f 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -89,7 +89,7 @@ tty_draw_line_clear(struct tty *tty, u_int px, u_int py, u_int nx, /* Is this cell empty? */ static u_int -tty_draw_line_get_empty(struct grid_cell *gc, u_int nx) +tty_draw_line_get_empty(const struct grid_cell *gc, u_int nx) { u_int empty = 0; @@ -113,7 +113,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, struct colour_palette *palette) { struct grid *gd = s->grid; - struct grid_cell gc, *gcp, ngc, last; + struct grid_cell gc, ngc, last; + const struct grid_cell *gcp; struct grid_line *gl; u_int i, j, last_i, cx, ex, width; u_int cellsize, bg; @@ -233,7 +234,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, if (i >= ex) empty = 1; else - empty = tty_draw_line_get_empty(&gc, nx - i); + empty = tty_draw_line_get_empty(gcp, nx - i); /* Work out the next state. */ if (empty != 0) @@ -242,7 +243,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, next_state = TTY_DRAW_LINE_SAME; else if (gcp->flags & GRID_FLAG_PADDING) next_state = TTY_DRAW_LINE_PAD; - else if (grid_cells_look_equal(&gc, &last)) { + else if (grid_cells_look_equal(gcp, &last)) { if (gcp->data.size > (sizeof buf) - len) next_state = TTY_DRAW_LINE_FLUSH; else From 7730d38339f43d48246bcc7990a40bf9cb26b250 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 21 Jan 2026 15:58:11 +0000 Subject: [PATCH 063/167] Skip correct width when moving to next position. --- tty-draw.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tty-draw.c b/tty-draw.c index a37121617f..ed83167705 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -169,11 +169,15 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, break; } if (i == 0) - bg = gc.bg; - else if (screen_select_cell(s, &ngc, &gc)) - bg = ngc.bg; - else bg = defaults->bg; + else { + bg = gc.bg; + if (gc.flags & GRID_FLAG_SELECTED) { + memcpy(&ngc, &gc, sizeof ngc); + if (screen_select_cell(s, &ngc, &gc)) + bg = ngc.bg; + } + } tty_attributes(tty, &last, defaults, palette, s->hyperlinks); log_debug("%s: clearing %u padding cells", __func__, cx); tty_draw_line_clear(tty, atx, aty, cx, defaults, bg, 0); @@ -227,8 +231,11 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, gcp = tty_check_codeset(tty, &gc); /* And for selection. */ - if (screen_select_cell(s, &ngc, gcp)) - gcp = &ngc; + if (gcp->flags & GRID_FLAG_SELECTED) { + memcpy(&ngc, gcp, sizeof ngc); + if (screen_select_cell(s, &ngc, gcp)) + gcp = &ngc; + } /* Work out the the empty width. */ if (i >= ex) @@ -303,7 +310,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, if (empty != 0) i += empty; else - i++; + i += gcp->data.width; } tty->flags = (tty->flags & ~TTY_NOCURSOR)|flags; From a25c14d472b2b365ba9cb22c7f49594af5766096 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 21 Jan 2026 19:34:19 +0000 Subject: [PATCH 064/167] Change overlay_ranges into visible_ranges. --- menu.c | 10 +++---- popup.c | 41 ++++++++++++++++++----------- screen-redraw.c | 57 ++++++++++++++++++++-------------------- server-client.c | 46 ++++++++++++++++---------------- tmux.h | 33 +++++++++++------------ tty-draw.c | 3 ++- tty.c | 70 ++++++++++++++++++++++--------------------------- 7 files changed, 130 insertions(+), 130 deletions(-) diff --git a/menu.c b/menu.c index cd361e0003..901e01978f 100644 --- a/menu.c +++ b/menu.c @@ -181,17 +181,15 @@ menu_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) } /* Return parts of the input range which are not obstructed by the menu. */ -struct visible_ranges * +void menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py, - u_int nx) + u_int nx, struct visible_ranges *r) { struct menu_data *md = data; struct menu *menu = md->menu; - struct visible_ranges *r; - r = server_client_overlay_range(md->px, md->py, menu->width + 4, - menu->count + 2, px, py, nx); - return (r); + server_client_overlay_range(md->px, md->py, menu->width + 4, + menu->count + 2, px, py, nx, r); } void diff --git a/popup.c b/popup.c index 6d0e1ffd38..5422809ccb 100644 --- a/popup.c +++ b/popup.c @@ -164,19 +164,27 @@ popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) } /* Return parts of the input range which are not obstructed by the popup. */ -static struct visible_ranges * -popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx) +static void +popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx, + struct visible_ranges *r) { - struct popup_data *pd = data; - struct visible_ranges *or[2], *r; - u_int i, j, k = 0; + struct popup_data *pd = data; + static struct visible_ranges or[2] = { { NULL, 0, 0 }, { NULL, 0, 0 } }; + u_int i, j, k = 0; if (pd->md != NULL) { /* Check each returned range for the menu against the popup. */ - r = menu_check_cb(c, pd->md, px, py, nx); + menu_check_cb(c, pd->md, px, py, nx, r); for (i = 0; i < r->used; i++) { - or[i] = server_client_overlay_range(pd->px, pd->py, pd->sx, - pd->sy, r->px[i], py, r->nx[i]); + server_client_overlay_range(pd->px, pd->py, pd->sx, + pd->sy, r->ranges[i].px, py, r->ranges[i].nx, &or[i]); + } + + /* Caller must free when no longer used. */ + if (r->size == 0) { + r->ranges = xcalloc(2, sizeof(struct visible_range)); + r->size = 2; + r->used = 0; } /* @@ -185,20 +193,21 @@ popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx) */ for (i = 0; i < r->used; i++) { /* Each or[i] only has up to 2 ranges. */ - for (j = 0; j < or[i]->used; j++) { - if (or[i]->nx[j] > 0) { - r->px[k] = or[i]->px[j]; - r->nx[k] = or[i]->nx[j]; + for (j = 0; j < or[i].used; j++) { + if (or[i].ranges[j].nx > 0) { + r->ranges[k].px = or[i].ranges[j].px; + r->ranges[k].nx = or[i].ranges[j].nx; k++; } } } - - return (r); + return; } - return (server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, - nx)); + server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, + px, py, nx, r); + + return; } static void diff --git a/screen-redraw.c b/screen-redraw.c index da3f40b573..15ca76b7be 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -808,22 +808,23 @@ screen_redraw_draw_border_arrows(struct screen_redraw_ctx *ctx, u_int i, static void screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) { - struct client *c = ctx->c; - struct session *s = c->session; - struct window *w = s->curw->window; - struct options *oo = w->options; - struct tty *tty = &c->tty; - struct format_tree *ft; - struct window_pane *wp, *active = server_client_get_pane(c); - struct grid_cell gc; - const struct grid_cell *tmp; - struct visible_ranges *r; - u_int cell_type, x = ctx->ox + i, y = ctx->oy + j; - int isolates; + struct client *c = ctx->c; + struct session *s = c->session; + struct window *w = s->curw->window; + struct options *oo = w->options; + struct tty *tty = &c->tty; + struct format_tree *ft; + struct window_pane *wp, *active = server_client_get_pane(c); + struct grid_cell gc; + const struct grid_cell *tmp; + static struct visible_ranges r = { NULL, 0, 0 }; + u_int cell_type; + u_int x = ctx->ox + i, y = ctx->oy + j; + int isolates; if (c->overlay_check != NULL) { - r = c->overlay_check(c, c->overlay_data, x, y, 1); - if (r->nx[0] + r->nx[1] == 0) + c->overlay_check(c, c->overlay_data, x, y, 1, &r); + if (r.ranges[0].nx + r.ranges[1].nx == 0) return; } @@ -937,14 +938,14 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) static void screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) { - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - struct tty *tty = &c->tty; - struct screen *s = wp->screen; - struct colour_palette *palette = &wp->palette; - struct grid_cell defaults; - struct visible_ranges *vr; - u_int i, j, top, x, y, px, width, r; + struct client *c = ctx->c; + struct window *w = c->session->curw->window; + struct tty *tty = &c->tty; + struct screen *s = wp->screen; + struct colour_palette *palette = &wp->palette; + struct grid_cell defaults; + static struct visible_ranges vr; + u_int i, j, top, x, y, px, width, r; if (wp->base.mode & MODE_SYNC) screen_write_stop_sync(wp); @@ -989,21 +990,21 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) __func__, c->name, wp->id, i, j, x, y, width); /* xxxx Breaking up the tty_draw_line like this isn't fully working. */ - vr = tty_check_overlay_range(tty, x, y, width); + tty_check_overlay_range(tty, x, y, width, &vr); tty_default_colours(&defaults, wp); - for (r=0; r < vr->used; r++) { - if (vr->nx[r] == 0) + for (r=0; r < vr.used; r++) { + if (vr.ranges[r].nx == 0) continue; /* Convert window coordinates to tty coordinates. */ - px = vr->px[r]; + px = vr.ranges[r].px; /* i is px of cell, add px of region, sub the * pane offset. If you don't sub offset, * contents of pane shifted. note: i apparently unnec. */ - tty_draw_line(tty, s, /* i + */ vr->px[r] - wp->xoff, j, - vr->nx[r], px, y, &defaults, palette); + tty_draw_line(tty, s, /* i + */ vr.ranges[r].px - wp->xoff, j, + vr.ranges[r].nx, px, y, &defaults, palette); } } diff --git a/server-client.c b/server-client.c index a222ac6fa6..c4952a929c 100644 --- a/server-client.c +++ b/server-client.c @@ -165,37 +165,36 @@ server_client_clear_overlay(struct client *c) * Given overlay position and dimensions, return parts of the input range which * are visible. */ -struct visible_ranges * +void server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px, - u_int py, u_int nx) + u_int py, u_int nx, struct visible_ranges *r) { u_int ox, onx; - static struct visible_ranges r = {NULL, NULL, 0, 0}; - /* For efficiency vr is static and space reused. */ - if (r.size == 0) { - r.px = xcalloc(2, sizeof(u_int)); - r.nx = xcalloc(2, sizeof(u_int)); - r.size = 2; + /* Caller must free when no longer used. */ + if (r->size == 0) { + r->ranges = xcalloc(2, sizeof(struct visible_range)); + r->size = 2; + r->used = 0; } /* Trivial case of no overlap in the y direction. */ if (py < y || py > y + sy - 1) { - r.px[0] = px; - r.nx[0] = nx; - r.used = 1; - return (&r); + r->ranges[0].px = px; + r->ranges[0].nx = nx; + r->used = 1; + return; } /* Visible bit to the left of the popup. */ if (px < x) { - r.px[0] = px; - r.nx[0] = x - px; - if (r.nx[0] > nx) - r.nx[0] = nx; + r->ranges[0].px = px; + r->ranges[0].nx = x - px; + if (r->ranges[0].nx > nx) + r->ranges[0].nx = nx; } else { - r.px[0] = 0; - r.nx[0] = 0; + r->ranges[0].px = 0; + r->ranges[0].nx = 0; } /* Visible bit to the right of the popup. */ @@ -204,14 +203,13 @@ server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px, ox = px; onx = px + nx; if (onx > ox) { - r.px[1] = ox; - r.nx[1] = onx - ox; + r->ranges[1].px = ox; + r->ranges[1].nx = onx - ox; } else { - r.px[1] = 0; - r.nx[1] = 0; + r->ranges[1].px = 0; + r->ranges[1].nx = 0; } - r.used = 2; - return (&r); + r->used = 2; } /* Check if this client is inside this server. */ diff --git a/tmux.h b/tmux.h index 424b6093a1..4544411c9a 100644 --- a/tmux.h +++ b/tmux.h @@ -1915,26 +1915,24 @@ struct client_window { }; RB_HEAD(client_windows, client_window); -/* Visible areas not obstructed by overlays. */ -#define OVERLAY_MAX_RANGES 3 -struct overlay_ranges { - u_int px[OVERLAY_MAX_RANGES]; - u_int nx[OVERLAY_MAX_RANGES]; +/* Visible range array element. */ +struct visible_range { + u_int px; /* Start */ + u_int nx; /* Length */ }; -/* Visible range array element. */ +/* Visible areas not obstructed. */ struct visible_ranges { - u_int *px; /* Start */ - u_int *nx; /* Length */ - size_t used; - size_t size; + struct visible_range *ranges; /* dynamically allocated array */ + size_t used; /* number of entries in ranges */ + size_t size; /* allocated capacity of ranges */ }; /* Client connection. */ typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); typedef void (*prompt_free_cb)(void *); -typedef struct visible_ranges *(*overlay_check_cb)(struct client*, void *, - u_int, u_int, u_int); +typedef void (*overlay_check_cb)(struct client*, void *, u_int, u_int, u_int, + struct visible_ranges *); typedef struct screen *(*overlay_mode_cb)(struct client *, void *, u_int *, u_int *); typedef void (*overlay_draw_cb)(struct client *, void *, @@ -2561,8 +2559,8 @@ void tty_default_attributes(struct tty *, const struct grid_cell *, void tty_update_mode(struct tty *, int, struct screen *); const struct grid_cell *tty_check_codeset(struct tty *, const struct grid_cell *); -struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int, - u_int); +void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, + struct visible_ranges *); /* tty-draw.c */ void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, @@ -2922,8 +2920,8 @@ void server_client_set_overlay(struct client *, u_int, overlay_check_cb, overlay_mode_cb, overlay_draw_cb, overlay_key_cb, overlay_free_cb, overlay_resize_cb, void *); void server_client_clear_overlay(struct client *); -struct visible_ranges *server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int, - u_int); +void server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int, + u_int, struct visible_ranges *); void server_client_set_key_table(struct client *, const char *); const char *server_client_get_key_table(struct client *); int server_client_check_nested(struct client *); @@ -3625,7 +3623,8 @@ int menu_display(struct menu *, int, int, struct cmdq_item *, const char *, const char *, struct cmd_find_state *, menu_choice_cb, void *); struct screen *menu_mode_cb(struct client *, void *, u_int *, u_int *); -struct visible_ranges *menu_check_cb(struct client *, void *, u_int, u_int, u_int); +void menu_check_cb(struct client *, void *, u_int, u_int, u_int, + struct visible_ranges *); void menu_draw_cb(struct client *, void *, struct screen_redraw_ctx *); void menu_free_cb(struct client *, void *); diff --git a/tty-draw.c b/tty-draw.c index 3d77d4a173..d23bf39ebc 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -113,7 +113,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, struct colour_palette *palette) { struct grid *gd = s->grid; - struct grid_cell gc, *gcp, ngc, last; + const struct grid_cell *gcp; + struct grid_cell gc, ngc, last; struct grid_line *gl; u_int i, j, last_i, cx, ex, width; u_int cellsize, bg; diff --git a/tty.c b/tty.c index 2b5e91c53f..081b56dee0 100644 --- a/tty.c +++ b/tty.c @@ -66,7 +66,8 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); static int tty_check_overlay(struct tty *, u_int, u_int); -struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int, u_int); +void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, + struct visible_ranges *); #ifdef ENABLE_SIXEL static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), @@ -1160,8 +1161,6 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, u_int px, u_int nx, u_int bg) { struct client *c = tty->client; - struct visible_ranges *r; - u_int i; log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); @@ -1199,15 +1198,6 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, */ tty_cursor(tty, px, py); tty_repeat_space(tty, nx); - /* - r = tty_check_overlay_range(tty, px, py, nx); - for (i = 0; i < r->used; i++) { - if (r->nx[i] == 0) - continue; - tty_cursor(tty, r->px[i], py); - tty_repeat_space(tty, r->nx[i]); - } - */ } /* Clear a line, adjusting to visible part of pane. */ @@ -1442,41 +1432,45 @@ tty_cell_width(const struct grid_cell *gcp, u_int atcol) static int tty_check_overlay(struct tty *tty, u_int px, u_int py) { - struct visible_ranges *r; + static struct visible_ranges r = { NULL, 0, 0 }; /* * A unit width range will always return nx[2] == 0 from a check, even * with multiple overlays, so it's sufficient to check just the first * two entries. */ - r = tty_check_overlay_range(tty, px, py, 1); - if (r->nx[0] + r->nx[1] == 0) + if (r.size == 0) { + r.ranges = xcalloc(2, sizeof(struct visible_range)); + r.size = 2; + } + + tty_check_overlay_range(tty, px, py, 1, &r); + if (r.ranges[0].nx + r.ranges[1].nx == 0) return (0); return (1); } /* Return parts of the input range which are visible. */ -struct visible_ranges * -tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx) +void +tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, + struct visible_ranges *r) { - static struct visible_ranges r = {NULL, NULL, 0, 0}; struct client *c = tty->client; - /* For efficiency vr is static and space reused. */ - if (r.size == 0) { - r.px = xcalloc(1, sizeof(u_int)); - r.nx = xcalloc(1, sizeof(u_int)); - r.size = 1; + if (r->size == 0) { + r->ranges = xcalloc(2, sizeof(struct visible_range)); + r->size = 2; } if (c->overlay_check == NULL) { - r.px[0] = px; - r.nx[0] = nx; - r.used = 1; - return (&r); + r->ranges[0].px = px; + r->ranges[0].nx = nx; + r->used = 1; + return; } - return (c->overlay_check(c, c->overlay_data, px, py, nx)); + c->overlay_check(c, c->overlay_data, px, py, nx, r); + return; } #ifdef ENABLE_SIXEL @@ -2004,7 +1998,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) { const struct grid_cell *gcp = ctx->cell; struct screen *s = ctx->s; - struct visible_ranges *r; + static struct visible_ranges r = { NULL, 0, 0 }; u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2021,9 +2015,9 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) /* Handle partially obstructed wide characters. */ if (gcp->data.width > 1) { - r = tty_check_overlay_range(tty, px, py, gcp->data.width); - for (i = 0; i < r->used; i++) - vis += r->nx[i]; + tty_check_overlay_range(tty, px, py, gcp->data.width, &r); + for (i = 0; i < r.used; i++) + vis += r.ranges[i].nx; if (vis < gcp->data.width) { tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, px, py, &ctx->defaults, ctx->palette); @@ -2049,7 +2043,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) { - struct visible_ranges *r; + struct visible_ranges r = { NULL, 0, 0 }; u_int i, px, py, cx; char *cp = ctx->ptr; @@ -2080,14 +2074,14 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) px = ctx->xoff + ctx->ocx - ctx->wox; py = ctx->yoff + ctx->ocy - ctx->woy; - r = tty_check_overlay_range(tty, px, py, ctx->num); - for (i = 0; i < r->used; i++) { - if (r->nx[i] == 0) + tty_check_overlay_range(tty, px, py, ctx->num, &r); + for (i = 0; i < r.used; i++) { + if (r.ranges[i].nx == 0) continue; /* Convert back to pane position for printing. */ - cx = r->px[i] - ctx->xoff + ctx->wox; + cx = r.ranges[i].px - ctx->xoff + ctx->wox; tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy); - tty_putn(tty, cp + r->px[i] - px, r->nx[i], r->nx[i]); + tty_putn(tty, cp + r.ranges[i].px - px, r.ranges[i].nx, r.ranges[i].nx); } } From d1a6ce8e7fbfee502dd6deed72fe7bdd0e4e033d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 21 Jan 2026 21:26:32 +0000 Subject: [PATCH 065/167] Associate each visible_ranges with some other object (tty, popup_data, etc) so it is easier to keep track of its lifecycle, but still avoid allocating for each use. --- menu.c | 10 ++++-- popup.c | 79 ++++++++++++++++++++++++----------------- screen-redraw.c | 68 ++++++++++++++++------------------- server-client.c | 34 +++++++++++++----- tmux.h | 48 +++++++++++++------------ tty.c | 94 +++++++++++++++++-------------------------------- 6 files changed, 167 insertions(+), 166 deletions(-) diff --git a/menu.c b/menu.c index 901e01978f..7b18772ac0 100644 --- a/menu.c +++ b/menu.c @@ -34,6 +34,7 @@ struct menu_data { struct cmd_find_state fs; struct screen s; + struct visible_ranges r; u_int px; u_int py; @@ -181,15 +182,16 @@ menu_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) } /* Return parts of the input range which are not obstructed by the menu. */ -void +struct visible_ranges * menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py, - u_int nx, struct visible_ranges *r) + u_int nx) { struct menu_data *md = data; struct menu *menu = md->menu; server_client_overlay_range(md->px, md->py, menu->width + 4, - menu->count + 2, px, py, nx, r); + menu->count + 2, px, py, nx, &md->r); + return (&md->r); } void @@ -232,7 +234,9 @@ menu_free_cb(__unused struct client *c, void *data) if (md->cb != NULL) md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data); + free(md->r.ranges); screen_free(&md->s); + menu_free(md->menu); free(md); } diff --git a/popup.c b/popup.c index 5422809ccb..adebb9128a 100644 --- a/popup.c +++ b/popup.c @@ -39,6 +39,9 @@ struct popup_data { struct grid_cell defaults; struct colour_palette palette; + struct visible_ranges r; + struct visible_ranges or[2]; + struct job *job; struct input_ctx *ictx; int status; @@ -164,50 +167,57 @@ popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) } /* Return parts of the input range which are not obstructed by the popup. */ -static void -popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx, - struct visible_ranges *r) +static struct visible_ranges * +popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx) { - struct popup_data *pd = data; - static struct visible_ranges or[2] = { { NULL, 0, 0 }, { NULL, 0, 0 } }; - u_int i, j, k = 0; + struct popup_data *pd = data; + struct visible_ranges *r = &pd->r; + struct visible_ranges *mr; + u_int i, j, k = 0; if (pd->md != NULL) { - /* Check each returned range for the menu against the popup. */ - menu_check_cb(c, pd->md, px, py, nx, r); - for (i = 0; i < r->used; i++) { + /* + * Work out the visible ranges for the menu (that is, the + * ranges not covered by the menu). A menu should have at most + * two ranges and we rely on this being the case. + */ + mr = menu_check_cb(c, pd->md, px, py, nx); + if (mr->used > 2) + fatalx("too many menu ranges"); + + /* + * Walk the ranges still visible under the menu and check if + * each is visible under the popup as well. At most there can be + * three total ranges if popup and menu do not intersect. + */ + for (i = 0; i < mr->used; i++) { server_client_overlay_range(pd->px, pd->py, pd->sx, - pd->sy, r->ranges[i].px, py, r->ranges[i].nx, &or[i]); - } - - /* Caller must free when no longer used. */ - if (r->size == 0) { - r->ranges = xcalloc(2, sizeof(struct visible_range)); - r->size = 2; - r->used = 0; + pd->sy, r->ranges[i].px, py, r->ranges[i].nx, + &pd->or[i]); } /* - * or has up to OVERLAY_MAX_RANGES non-overlapping ranges, - * ordered from left to right. Collect them in the output. + * We now have nonoverlapping ranges from left to right. + * Combine them together into the output. */ - for (i = 0; i < r->used; i++) { - /* Each or[i] only has up to 2 ranges. */ - for (j = 0; j < or[i].used; j++) { - if (or[i].ranges[j].nx > 0) { - r->ranges[k].px = or[i].ranges[j].px; - r->ranges[k].nx = or[i].ranges[j].nx; - k++; - } + server_client_ensure_ranges(r, 3); + for (i = 0; i < mr->used; i++) { + for (j = 0; j < pd->or[i].used; j++) { + if (pd->or[i].ranges[j].nx == 0) + continue; + if (k >= 3) + fatalx("too many popup & menu ranges"); + r->ranges[k].px = pd->or[i].ranges[j].px; + r->ranges[k].nx = pd->or[i].ranges[j].nx; + k++; } } - return; + return (r); } - server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, - px, py, nx, r); - - return; + server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx, + r); + return (r); } static void @@ -288,6 +298,9 @@ popup_free_cb(struct client *c, void *data) job_free(pd->job); input_free(pd->ictx); + free(pd->or[0].ranges); + free(pd->or[1].ranges); + free(pd->r.ranges); screen_free(&pd->s); colour_palette_free(&pd->palette); @@ -551,7 +564,7 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) (event->key == '\033' || event->key == ('c'|KEYC_CTRL))) return (1); if (pd->job == NULL && (pd->flags & POPUP_CLOSEANYKEY) && - !KEYC_IS_MOUSE(event->key) && !KEYC_IS_PASTE(event->key)) + !KEYC_IS_MOUSE(event->key) && !KEYC_IS_PASTE(event->key)) return (1); if (pd->job != NULL) { if (KEYC_IS_MOUSE(event->key)) { diff --git a/screen-redraw.c b/screen-redraw.c index 15ca76b7be..8449f02fb5 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -808,23 +808,23 @@ screen_redraw_draw_border_arrows(struct screen_redraw_ctx *ctx, u_int i, static void screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) { - struct client *c = ctx->c; - struct session *s = c->session; - struct window *w = s->curw->window; - struct options *oo = w->options; - struct tty *tty = &c->tty; - struct format_tree *ft; - struct window_pane *wp, *active = server_client_get_pane(c); - struct grid_cell gc; - const struct grid_cell *tmp; - static struct visible_ranges r = { NULL, 0, 0 }; - u_int cell_type; - u_int x = ctx->ox + i, y = ctx->oy + j; - int isolates; + struct client *c = ctx->c; + struct session *s = c->session; + struct window *w = s->curw->window; + struct options *oo = w->options; + struct tty *tty = &c->tty; + struct format_tree *ft; + struct window_pane *wp, *active = server_client_get_pane(c); + struct grid_cell gc; + const struct grid_cell *tmp; + u_int cell_type; + u_int x = ctx->ox + i, y = ctx->oy + j; + int isolates; + struct visible_ranges *r; if (c->overlay_check != NULL) { - c->overlay_check(c, c->overlay_data, x, y, 1, &r); - if (r.ranges[0].nx + r.ranges[1].nx == 0) + r = c->overlay_check(c, c->overlay_data, x, y, 1); + if (server_client_ranges_is_empty(r)) return; } @@ -938,14 +938,15 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) static void screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) { - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - struct tty *tty = &c->tty; - struct screen *s = wp->screen; - struct colour_palette *palette = &wp->palette; - struct grid_cell defaults; - static struct visible_ranges vr; - u_int i, j, top, x, y, px, width, r; + struct client *c = ctx->c; + struct window *w = c->session->curw->window; + struct tty *tty = &c->tty; + struct screen *s = wp->screen; + struct colour_palette *palette = &wp->palette; + struct grid_cell defaults; + struct visible_ranges *r; + struct visible_range *rr; + u_int i, j, k, top, x, y, width; if (wp->base.mode & MODE_SYNC) screen_write_stop_sync(wp); @@ -989,22 +990,15 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", __func__, c->name, wp->id, i, j, x, y, width); - /* xxxx Breaking up the tty_draw_line like this isn't fully working. */ - tty_check_overlay_range(tty, x, y, width, &vr); - tty_default_colours(&defaults, wp); - for (r=0; r < vr.used; r++) { - if (vr.ranges[r].nx == 0) - continue; - /* Convert window coordinates to tty coordinates. */ - px = vr.ranges[r].px; - /* i is px of cell, add px of region, sub the - * pane offset. If you don't sub offset, - * contents of pane shifted. note: i apparently unnec. - */ - tty_draw_line(tty, s, /* i + */ vr.ranges[r].px - wp->xoff, j, - vr.ranges[r].nx, px, y, &defaults, palette); + r = tty_check_overlay_range(tty, x, y, width); + for (k = 0; k < r->used; k++) { + rr = &r->ranges[k]; + if (rr->nx != 0) { + tty_draw_line(tty, s, rr->px - wp->xoff, j, + rr->nx, rr->px, y, &defaults, palette); + } } } diff --git a/server-client.c b/server-client.c index c4952a929c..f9c938cca3 100644 --- a/server-client.c +++ b/server-client.c @@ -161,6 +161,29 @@ server_client_clear_overlay(struct client *c) server_redraw_client(c); } +/* Are these ranges empty? That is, nothing is visible. */ +int +server_client_ranges_is_empty(struct visible_ranges *r) +{ + u_int i; + + for (i = 0; i < r->used; i++) { + if (r->ranges[i].nx != 0) + return (0); + } + return (1); +} + +/* Ensure we have space for at least n ranges. */ +void +server_client_ensure_ranges(struct visible_ranges *r, u_int n) +{ + if (r->size >= n) + return; + r->ranges = xrecallocarray(r->ranges, r->size, n, sizeof *r->ranges); + r->size = n; +} + /* * Given overlay position and dimensions, return parts of the input range which * are visible. @@ -169,22 +192,17 @@ void server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px, u_int py, u_int nx, struct visible_ranges *r) { - u_int ox, onx; - - /* Caller must free when no longer used. */ - if (r->size == 0) { - r->ranges = xcalloc(2, sizeof(struct visible_range)); - r->size = 2; - r->used = 0; - } + u_int ox, onx; /* Trivial case of no overlap in the y direction. */ if (py < y || py > y + sy - 1) { + server_client_ensure_ranges(r, 1); r->ranges[0].px = px; r->ranges[0].nx = nx; r->used = 1; return; } + server_client_ensure_ranges(r, 2); /* Visible bit to the left of the popup. */ if (px < x) { diff --git a/tmux.h b/tmux.h index 4544411c9a..247eced02f 100644 --- a/tmux.h +++ b/tmux.h @@ -956,7 +956,7 @@ struct screen_sel; struct screen_titles; struct screen { char *title; - char *path; + char *path; struct screen_titles *titles; struct grid *grid; /* grid data */ @@ -1532,6 +1532,19 @@ struct key_event { size_t len; }; +/* Visible range array element. */ +struct visible_range { + u_int px; /* start */ + u_int nx; /* length */ +}; + +/* Visible areas not obstructed. */ +struct visible_ranges { + struct visible_range *ranges; /* dynamically allocated array */ + u_int used; /* number of entries in ranges */ + u_int size; /* allocated capacity of ranges */ +}; + /* Terminal definition. */ struct tty_term { char *name; @@ -1599,6 +1612,7 @@ struct tty { size_t discarded; struct termios tio; + struct visible_ranges r; struct grid_cell cell; struct grid_cell last_cell; @@ -1915,28 +1929,15 @@ struct client_window { }; RB_HEAD(client_windows, client_window); -/* Visible range array element. */ -struct visible_range { - u_int px; /* Start */ - u_int nx; /* Length */ -}; - -/* Visible areas not obstructed. */ -struct visible_ranges { - struct visible_range *ranges; /* dynamically allocated array */ - size_t used; /* number of entries in ranges */ - size_t size; /* allocated capacity of ranges */ -}; - /* Client connection. */ typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); typedef void (*prompt_free_cb)(void *); -typedef void (*overlay_check_cb)(struct client*, void *, u_int, u_int, u_int, - struct visible_ranges *); +typedef struct visible_ranges *(*overlay_check_cb)(struct client*, void *, + u_int, u_int, u_int); typedef struct screen *(*overlay_mode_cb)(struct client *, void *, u_int *, - u_int *); + u_int *); typedef void (*overlay_draw_cb)(struct client *, void *, - struct screen_redraw_ctx *); + struct screen_redraw_ctx *); typedef int (*overlay_key_cb)(struct client *, void *, struct key_event *); typedef void (*overlay_free_cb)(struct client *, void *); typedef void (*overlay_resize_cb)(struct client *, void *); @@ -2559,8 +2560,8 @@ void tty_default_attributes(struct tty *, const struct grid_cell *, void tty_update_mode(struct tty *, int, struct screen *); const struct grid_cell *tty_check_codeset(struct tty *, const struct grid_cell *); -void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, - struct visible_ranges *); +struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int, + u_int); /* tty-draw.c */ void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, @@ -2577,7 +2578,6 @@ void tty_close(struct tty *); void tty_free(struct tty *); void tty_update_features(struct tty *); void tty_set_selection(struct tty *, const char *, const char *, size_t); -u_int tty_cell_width(const struct grid_cell *, u_int); void tty_write(void (*)(struct tty *, const struct tty_ctx *), struct tty_ctx *); void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *); @@ -2920,6 +2920,8 @@ void server_client_set_overlay(struct client *, u_int, overlay_check_cb, overlay_mode_cb, overlay_draw_cb, overlay_key_cb, overlay_free_cb, overlay_resize_cb, void *); void server_client_clear_overlay(struct client *); +void server_client_ensure_ranges(struct visible_ranges *, u_int); +int server_client_ranges_is_empty(struct visible_ranges *); void server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int, u_int, struct visible_ranges *); void server_client_set_key_table(struct client *, const char *); @@ -3623,8 +3625,8 @@ int menu_display(struct menu *, int, int, struct cmdq_item *, const char *, const char *, struct cmd_find_state *, menu_choice_cb, void *); struct screen *menu_mode_cb(struct client *, void *, u_int *, u_int *); -void menu_check_cb(struct client *, void *, u_int, u_int, u_int, - struct visible_ranges *); +struct visible_ranges *menu_check_cb(struct client *, void *, u_int, u_int, + u_int); void menu_draw_cb(struct client *, void *, struct screen_redraw_ctx *); void menu_free_cb(struct client *, void *); diff --git a/tty.c b/tty.c index 081b56dee0..d1c98d039c 100644 --- a/tty.c +++ b/tty.c @@ -66,8 +66,6 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); static int tty_check_overlay(struct tty *, u_int, u_int); -void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, - struct visible_ranges *); #ifdef ENABLE_SIXEL static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), @@ -523,6 +521,8 @@ void tty_free(struct tty *tty) { tty_close(tty); + + free(tty->r.ranges); } void @@ -1411,66 +1411,34 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc) return (&new); } -/* - * Compute the effective display width (in terminal columns) of a grid cell - * when it will be drawn at terminal column atcol. - */ -u_int -tty_cell_width(const struct grid_cell *gcp, u_int atcol) -{ - /* Tabs expand to the next tab stop (tab width = 8). */ - if (gcp->flags & GRID_FLAG_TAB) - return (8 - (atcol % 8)); - /* Normal characters: width stored in cell (1 or 2 usually). */ - return (gcp->data.width); -} - -/* - * Check if a single character is obstructed by the overlay and return a - * boolean. - */ +/* Check if a single character is covered by the overlay. */ static int tty_check_overlay(struct tty *tty, u_int px, u_int py) { - static struct visible_ranges r = { NULL, 0, 0 }; + struct visible_ranges *r; /* - * A unit width range will always return nx[2] == 0 from a check, even - * with multiple overlays, so it's sufficient to check just the first - * two entries. + * With a single character, if there is anything visible (that is, the + * range is not empty), it must be that character. */ - if (r.size == 0) { - r.ranges = xcalloc(2, sizeof(struct visible_range)); - r.size = 2; - } - - tty_check_overlay_range(tty, px, py, 1, &r); - if (r.ranges[0].nx + r.ranges[1].nx == 0) - return (0); - return (1); + r = tty_check_overlay_range(tty, px, py, 1); + return (!server_client_ranges_is_empty(r)); } /* Return parts of the input range which are visible. */ -void -tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, - struct visible_ranges *r) +struct visible_ranges * +tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx) { struct client *c = tty->client; - if (r->size == 0) { - r->ranges = xcalloc(2, sizeof(struct visible_range)); - r->size = 2; - } - if (c->overlay_check == NULL) { - r->ranges[0].px = px; - r->ranges[0].nx = nx; - r->used = 1; - return; + server_client_ensure_ranges(&tty->r, 1); + tty->r.ranges[0].px = px; + tty->r.ranges[0].nx = nx; + tty->r.used = 1; + return (&tty->r); } - - c->overlay_check(c, c->overlay_data, px, py, nx, r); - return; + return (c->overlay_check(c, c->overlay_data, px, py, nx)); } #ifdef ENABLE_SIXEL @@ -1998,7 +1966,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) { const struct grid_cell *gcp = ctx->cell; struct screen *s = ctx->s; - static struct visible_ranges r = { NULL, 0, 0 }; + struct visible_ranges *r; u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2015,9 +1983,9 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) /* Handle partially obstructed wide characters. */ if (gcp->data.width > 1) { - tty_check_overlay_range(tty, px, py, gcp->data.width, &r); - for (i = 0; i < r.used; i++) - vis += r.ranges[i].nx; + r = tty_check_overlay_range(tty, px, py, gcp->data.width); + for (i = 0; i < r->used; i++) + vis += r->ranges[i].nx; if (vis < gcp->data.width) { tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, px, py, &ctx->defaults, ctx->palette); @@ -2043,7 +2011,8 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) { - struct visible_ranges r = { NULL, 0, 0 }; + struct visible_ranges *r; + struct visible_range *rr; u_int i, px, py, cx; char *cp = ctx->ptr; @@ -2068,20 +2037,21 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette, ctx->s->hyperlinks); + tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette, + ctx->s->hyperlinks); /* Get tty position from pane position for overlay check. */ px = ctx->xoff + ctx->ocx - ctx->wox; py = ctx->yoff + ctx->ocy - ctx->woy; - tty_check_overlay_range(tty, px, py, ctx->num, &r); - for (i = 0; i < r.used; i++) { - if (r.ranges[i].nx == 0) - continue; - /* Convert back to pane position for printing. */ - cx = r.ranges[i].px - ctx->xoff + ctx->wox; - tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy); - tty_putn(tty, cp + r.ranges[i].px - px, r.ranges[i].nx, r.ranges[i].nx); + r = tty_check_overlay_range(tty, px, py, ctx->num); + for (i = 0; i < r->used; i++) { + rr = &r->ranges[i]; + if (rr->nx != 0) { + cx = rr->px - ctx->xoff + ctx->wox; + tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy); + tty_putn(tty, cp + rr->px - px, rr->nx, rr->nx); + } } } From f67cf7d05303ec7784822df2023a0c387769a595 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 29 Jan 2026 11:46:21 +0000 Subject: [PATCH 066/167] Return static range as last resort. --- screen-redraw.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index decdb272a6..25857be84c 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1137,10 +1137,23 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, struct window_pane *wp; struct window *w; struct visible_range *ri; + static struct visible_range sr0 = { 0, 0 }; + static struct visible_ranges sr = { &sr0, 1, 1 }; int found_self, sb_w, sb_pos; u_int lb, rb, tb, bb; u_int i, s; + if (base_wp == NULL) { + if (r != NULL) { + return (r); + } else { + /* Return static range as last resort. */ + sr.ranges[0].px = px; + sr.ranges[0].nx = width; + return (&sr); + } + } + if (r == NULL) { server_client_ensure_ranges(&base_wp->r, 1); r = &base_wp->r; @@ -1151,9 +1164,6 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, r->used = 1; } - if (base_wp == NULL) - return (r); - w = base_wp->window; sb_pos = options_get_number(w->options, "pane-scrollbars-position"); From b46a96d45458f3374d51c7376bae631912563cbb Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 29 Jan 2026 14:34:18 +0000 Subject: [PATCH 067/167] Second try with static visual_ranges using calloc. --- screen-redraw.c | 8 ++++++-- screen-write.c | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 25857be84c..5350e64aa8 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1137,8 +1137,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, struct window_pane *wp; struct window *w; struct visible_range *ri; - static struct visible_range sr0 = { 0, 0 }; - static struct visible_ranges sr = { &sr0, 1, 1 }; + static struct visible_ranges sr = { NULL, 0, 0 }; int found_self, sb_w, sb_pos; u_int lb, rb, tb, bb; u_int i, s; @@ -1148,8 +1147,13 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, return (r); } else { /* Return static range as last resort. */ + if (sr.ranges == NULL) + sr.ranges = xcalloc(1, + sizeof (struct visible_range)); sr.ranges[0].px = px; sr.ranges[0].nx = width; + sr.size = 1; + sr.used = 1; return (&sr); } } diff --git a/screen-write.c b/screen-write.c index d7362da01d..df17608371 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1849,13 +1849,13 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ctx->scrolled = s->rlower - s->rupper + 1; screen_write_initctx(ctx, &ttyctx, 1); - if (wp->yoff + wp->sy > wp->window->sy) + if (wp != NULL && wp->yoff + wp->sy > wp->window->sy) ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy); ttyctx.num = ctx->scrolled; ttyctx.bg = ctx->bg; tty_write(tty_cmd_scrollup, &ttyctx); - if (ctx->wp != NULL) + if (wp != NULL) ctx->wp->flags |= PANE_REDRAWSCROLLBAR; } ctx->scrolled = 0; From 476c6e89adf3c1242fb7b3e1e3caba755bf45562 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 11 Feb 2026 09:30:48 +0000 Subject: [PATCH 068/167] Check the overlay visible ranges when we have a tty and check the panes visible ranges when we have a wp. Overlays checked at tty level (for most part) and pane overlays checked at screen level where possible to keep separation of abstraction relatively clean. --- screen-redraw.c | 8 ++++---- tty-draw.c | 2 +- tty.c | 9 +-------- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 5350e64aa8..d130cc834a 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1239,10 +1239,11 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, then split range into 2 ranges. */ else if (lb > ri->px && rb < ri->px + ri->nx) { - server_client_ensure_ranges(r, r->size + 1); + server_client_ensure_ranges(r, r->used + 1); for (s=r->used; s>i; s--) - memcpy(&r->ranges[s-1], &r->ranges[s], + memcpy(&r->ranges[s], &r->ranges[s-1], sizeof (struct visible_range)); + ri = &r->ranges[i]; r->ranges[i+1].px = rb + 1; r->ranges[i+1].nx = ri->px + ri->nx - (rb + 1); /* ri->px was copied, unchanged. */ @@ -1338,8 +1339,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) __func__, c->name, wp->id, i, j, wx, wy, width); /* Get visible ranges of line before we draw it. */ - r = tty_check_overlay_range(tty, px, py, width); - r = screen_redraw_get_visible_ranges(wp, wx, wy, width, r); + r = screen_redraw_get_visible_ranges(wp, wx, wy, width, NULL); tty_default_colours(&defaults, wp); diff --git a/tty-draw.c b/tty-draw.c index eac92a8fcb..e10e2f190d 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -122,7 +122,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, char buf[1000]; size_t len; enum tty_draw_line_state current_state, next_state; - +/* xxx maybe check overlay here? */ /* * py is the line in the screen to draw. px is the start x and nx is * the width to draw. atx,aty is the line on the terminal to draw it. diff --git a/tty.c b/tty.c index a8044a566b..878575c450 100644 --- a/tty.c +++ b/tty.c @@ -1209,7 +1209,6 @@ tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py, u_int px, u_int nx, u_int bg) { struct client *c = tty->client; - struct window_pane *wp = ctx->arg; struct visible_ranges *r; struct visible_range *ri; u_int i, l, x, rx, ry; @@ -1218,7 +1217,6 @@ tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py, if (tty_clamp_line(tty, ctx, px, py, nx, &l, &x, &rx, &ry)) { r = tty_check_overlay_range(tty, x, ry, rx); - r = screen_redraw_get_visible_ranges(wp, x, ry, rx, r); for (i=0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) @@ -1382,7 +1380,6 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, /* Couldn't use an escape sequence, loop over the lines. */ for (yy = py; yy < py + ny; yy++) { r = tty_check_overlay_range(tty, px, yy - oy, nx); - r = screen_redraw_get_visible_ranges(wp, px, yy - oy, nx, r); for (i=0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; @@ -1419,8 +1416,6 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) if (!ctx->bigger) { if (wp) { r = tty_check_overlay_range(tty, 0, ctx->yoff + py, nx); - r = screen_redraw_get_visible_ranges(wp, 0, ctx->yoff + - py, nx, r); for (i=0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; @@ -1437,7 +1432,6 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) if (tty_clamp_line(tty, ctx, 0, py, nx, &px, &x, &rx, &ry)) { if (wp) { r = tty_check_overlay_range(tty, i, py, rx); - r = screen_redraw_get_visible_ranges(wp, i, py, rx, r); for (i=0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) @@ -1916,8 +1910,7 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) !tty_term_has(tty->term, TTYC_CSR) || ctx->sx == 1 || ctx->sy == 1 || - c->overlay_check != NULL || - tty_is_obscured(ctx)) { + c->overlay_check != NULL) { tty_redraw_region(tty, ctx); return; } From e2cf40ef99bdb6e15b8229244a0f29a6859191b0 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 23 Feb 2026 08:08:31 +0000 Subject: [PATCH 069/167] Fix crash cause by not having a wp pointer in tty.c. --- screen-write.c | 34 ++++++++++++++++++++++++++++++++++ tmux.h | 3 +++ tty.c | 42 +++++------------------------------------- 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/screen-write.c b/screen-write.c index df17608371..ecbd9e111b 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1814,6 +1814,39 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) TAILQ_INSERT_TAIL(&ctx->s->write_list[s->rlower].items, ci, entry); } +/* Return 1 if there is a floating window pane overlapping this pane. */ +static int +screen_write_pane_obscured(struct window_pane *base_wp) +{ + struct window_pane *wp; + struct window *w; + int found_self = 0; + + if (base_wp == NULL) + return(0); + w = base_wp->window; + + /* Check if there is a floating pane. xxxx borders? scrollbars? */ + TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { + if (wp == base_wp) { + found_self = 1; + continue; + } + if (found_self && wp->flags & PANE_FLOATING && + ! (wp->flags & PANE_MINIMISED) && + ((wp->yoff >= base_wp->yoff && + wp->yoff <= base_wp->yoff + (int)base_wp->sy) || + (wp->yoff + (int)wp->sy >= base_wp->yoff && + wp->yoff + wp->sy <= base_wp->yoff + base_wp->sy)) && + ((wp->xoff >= base_wp->xoff && + wp->xoff <= base_wp->xoff + (int)base_wp->sx) || + (wp->xoff + (int)wp->sx >= base_wp->xoff && + wp->xoff + wp->sx <= base_wp->xoff + base_wp->sx))) + return (1); + } + return (0); +} + /* Flush collected lines. */ static void screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, @@ -1853,6 +1886,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy); ttyctx.num = ctx->scrolled; ttyctx.bg = ctx->bg; + ttyctx.obscured = screen_write_pane_obscured(wp); tty_write(tty_cmd_scrollup, &ttyctx); if (wp != NULL) diff --git a/tmux.h b/tmux.h index 2c7a4376ca..36510028ca 100644 --- a/tmux.h +++ b/tmux.h @@ -1721,6 +1721,9 @@ struct tty_ctx { u_int woy; u_int wsx; u_int wsy; + + /* tty partly obscured, it will need to be surgically scrolled. */ + u_int obscured; }; /* Saved message entry. */ diff --git a/tty.c b/tty.c index f969f46ddd..75cdbe2d3a 100644 --- a/tty.c +++ b/tty.c @@ -1527,7 +1527,7 @@ tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c) ttyctx->bigger = tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx, &ttyctx->wsy); - ttyctx->yoff = ttyctx->ryoff = wp->yoff; + ttyctx->yoff = ttyctx->ryoff = wp->yoff; /* xxxx find another way to do this */ if (status_at_line(c) == 0) ttyctx->yoff += status_line_size(c); @@ -1555,7 +1555,7 @@ tty_draw_images(struct client *c, struct window_pane *wp, struct screen *s) ttyctx.sy = wp->sy; ttyctx.ptr = im; - ttyctx.arg = wp; + ttyctx.arg = wp; /* xxx remove this */ ttyctx.set_client_cb = tty_set_client_cb; ttyctx.allow_invisible_panes = 1; tty_write_one(tty_cmd_sixelimage, c, &ttyctx); @@ -1866,39 +1866,6 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) tty_putc(tty, '\n'); } -/* Return 1 if there is a floating window pane overlapping this pane. */ -static int -tty_is_obscured(const struct tty_ctx *ctx) -{ - struct window_pane *base_wp = ctx->arg, *wp; - struct window *w; - int found_self = 0; - - if (base_wp == NULL) - return(0); - w = base_wp->window; - - /* Check if there is a floating pane. xxxx borders? scrollbars? */ - TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { - if (wp == base_wp) { - found_self = 1; - continue; - } - if (found_self && wp->flags & PANE_FLOATING && - ! (wp->flags & PANE_MINIMISED) && - ((wp->yoff >= base_wp->yoff && - wp->yoff <= base_wp->yoff + (int)base_wp->sy) || - (wp->yoff + (int)wp->sy >= base_wp->yoff && - wp->yoff + wp->sy <= base_wp->yoff + base_wp->sy)) && - ((wp->xoff >= base_wp->xoff && - wp->xoff <= base_wp->xoff + (int)base_wp->sx) || - (wp->xoff + (int)wp->sx >= base_wp->xoff && - wp->xoff + wp->sx <= base_wp->xoff + base_wp->sx))) - return (1); - } - return (0); -} - void tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) { @@ -1911,7 +1878,8 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) !tty_term_has(tty->term, TTYC_CSR) || ctx->sx == 1 || ctx->sy == 1 || - c->overlay_check != NULL) { + c->overlay_check != NULL || + ctx->obscured) { tty_redraw_region(tty, ctx); return; } @@ -2069,7 +2037,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) { const struct grid_cell *gcp = ctx->cell; struct screen *s = ctx->s; - struct visible_ranges *r; + struct visible_ranges *r = NULL; u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; From 6898e26a7a464d456c58cdc2dee8230120ae1150 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 24 Feb 2026 01:19:24 +0000 Subject: [PATCH 070/167] Fix bug with utf-8 extended characters not respecting visible range. --- screen-write.c | 10 +++++----- tty-draw.c | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/screen-write.c b/screen-write.c index ecbd9e111b..813606da18 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2254,6 +2254,9 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) if (selected) skip = 0; + /* xxx cache r in wp ? */ + r = screen_redraw_get_visible_ranges(wp, s->cx, s->cy, width, NULL); + /* * Move the cursor. If not wrapping, stick at the last character and * replace it. @@ -2279,10 +2282,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) memcpy(&tmp_gc, gc, sizeof tmp_gc); ttyctx.cell = &tmp_gc; ttyctx.num = redraw ? 2 : 0; - /* xxx to be cached in wp */ - r = screen_redraw_get_visible_ranges(wp, s->cx, s->cy, width, - NULL); - for (i=0; i < r->used; i++) vis += r->ranges[i].nx; + for (i=0, vis=0; i < r->used; i++) vis += r->ranges[i].nx; if (vis < width) { /* Wide character or tab partly obscured. Write * spaces one by one in unobscured region(s). @@ -2411,7 +2411,7 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) * could be an empty range in the visible ranges so we add them all up. */ r = screen_redraw_get_visible_ranges(wp, cx - n, cy, n, NULL); - for (i=0; i < r->used; i++) vis += r->ranges[i].nx; + for (i=0, vis=0; i < r->used; i++) vis += r->ranges[i].nx; if (vis < n) { /* * Part of this character is obscured. Return 1 diff --git a/tty-draw.c b/tty-draw.c index 080ea42bfc..a27b2d0dd2 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -223,6 +223,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, */ empty = 0; next_state = TTY_DRAW_LINE_DONE; + gcp = &grid_default_cell; } else { /* Get the current cell. */ grid_view_get_cell(gd, px + i, py, &gc); From 0faed7aeb552a0afcf85677eae72904cdec4b5ca Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Thu, 26 Feb 2026 16:20:57 -0800 Subject: [PATCH 071/167] added format flag for floating panes. --- format.c | 17 +++++++++++++++++ tmux.1 | 1 + 2 files changed, 18 insertions(+) diff --git a/format.c b/format.c index 223f624b5a..4a6766f897 100644 --- a/format.c +++ b/format.c @@ -1004,6 +1004,20 @@ format_cb_pane_fg(struct format_tree *ft) return (xstrdup(colour_tostring(gc.fg))); } +/* Callback for pane_floating_flag. */ +static void * +format_cb_pane_floating_flag(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp != NULL) { + if (wp->flags & PANE_FLOATING) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + /* Callback for pane_bg. */ static void * format_cb_pane_bg(struct format_tree *ft) @@ -3284,6 +3298,9 @@ static const struct format_table_entry format_table[] = { { "pane_fg", FORMAT_TABLE_STRING, format_cb_pane_fg }, + { "pane_floating_flag", FORMAT_TABLE_STRING, + format_cb_pane_floating_flag + }, { "pane_format", FORMAT_TABLE_STRING, format_cb_pane_format }, diff --git a/tmux.1 b/tmux.1 index d70f884a8d..99425e84f9 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6270,6 +6270,7 @@ The following variables are available, where appropriate: .It Li "pane_dead_status" Ta "" Ta "Exit status of process in dead pane" .It Li "pane_dead_time" Ta "" Ta "Exit time of process in dead pane" .It Li "pane_fg" Ta "" Ta "Pane foreground colour" +.It Li "pane_floating_flag" Ta "" Ta "1 if pane is floating" .It Li "pane_format" Ta "" Ta "1 if format is for a pane" .It Li "pane_height" Ta "" Ta "Height of pane" .It Li "pane_id" Ta "#D" Ta "Unique pane ID" From 14a934d4123b598e4e639abbbadb76d39367f65f Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 8 Mar 2026 17:11:55 +0000 Subject: [PATCH 072/167] Fix screen clearing issue to surgically clear around floating panes. --- screen-write.c | 172 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 159 insertions(+), 13 deletions(-) diff --git a/screen-write.c b/screen-write.c index d98839e16d..72f5c7bc5e 100644 --- a/screen-write.c +++ b/screen-write.c @@ -36,6 +36,7 @@ static int screen_write_overwrite(struct screen_write_ctx *, struct grid_cell *, u_int); static int screen_write_combine(struct screen_write_ctx *, const struct grid_cell *); +static int screen_write_pane_obscured(struct window_pane *); struct screen_write_citem { u_int x; @@ -1396,6 +1397,23 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_insert(ctx, ci); } +/* Clear part of a line from px for nx columns. */ +static void +screen_write_clearpartofline(struct screen_write_ctx *ctx, u_int px, u_int nx, + u_int bg) +{ + struct screen_write_citem *ci = ctx->item; + + if (nx == 0) + return; + + ci->x = px; + ci->used = nx; + ci->type = CLEAR; + ci->bg = bg; + screen_write_collect_insert(ctx, ci); +} + /* Move cursor to px,py. */ void screen_write_cursormove(struct screen_write_ctx *ctx, int px, int py, @@ -1578,10 +1596,13 @@ screen_write_carriagereturn(struct screen_write_ctx *ctx) void screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct grid *gd = s->grid; - struct tty_ctx ttyctx; - u_int sx = screen_size_x(s), sy = screen_size_y(s); + struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct tty_ctx ttyctx; + u_int sx = screen_size_x(s), sy = screen_size_y(s); + u_int y, i, xoff, yoff, cx_save, cy_save; + struct visible_ranges *r; + struct visible_range *ri; #ifdef ENABLE_SIXEL if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) @@ -1606,16 +1627,64 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, s->cy + 1, sy - (s->cy + 1)); screen_write_collect_flush(ctx, 0, __func__); - tty_write(tty_cmd_clearendofscreen, &ttyctx); + + if (! screen_write_pane_obscured(ctx->wp)) { + tty_write(tty_cmd_clearendofscreen, &ttyctx); + return; + } + + /* Can't just clear screen, must avoid floating windows. */ + cx_save = s->cx; + cy_save = s->cy; + if (ctx->wp != NULL) { + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + } else { + xoff = 0; + yoff = 0; + } + + /* First line: visible ranges from the current cursor to end. */ + if (s->cx <= sx - 1) { + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff + s->cx, yoff + s->cy, sx - s->cx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + screen_write_clearpartofline(ctx, + ri->px - xoff, ri->nx, bg); + } + } + + /* Remaining lines: visible ranges across the full width. */ + for (y = s->cy + 1; y < sy; y++) { + screen_write_set_cursor(ctx, 0, y); + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff, yoff + y, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + screen_write_clearpartofline(ctx, + ri->px - xoff, ri->nx, bg); + } + } + + screen_write_collect_flush(ctx, 0, __func__); + screen_write_set_cursor(ctx, cx_save, cy_save); } /* Clear to start of screen. */ void screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct tty_ctx ttyctx; - u_int sx = screen_size_x(s); + struct screen *s = ctx->s; + struct tty_ctx ttyctx; + u_int sx = screen_size_x(s); + u_int y, i, xoff, yoff, cx_save, cy_save; + struct visible_ranges *r; + struct visible_range *ri; #ifdef ENABLE_SIXEL if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL) @@ -1634,16 +1703,62 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, 0, s->cy); screen_write_collect_flush(ctx, 0, __func__); - tty_write(tty_cmd_clearstartofscreen, &ttyctx); + + if (! screen_write_pane_obscured(ctx->wp)) { + tty_write(tty_cmd_clearstartofscreen, &ttyctx); + return; + } + + /* Can't just clear screen, must avoid floating windows. */ + cx_save = s->cx; + cy_save = s->cy; + if (ctx->wp != NULL) { + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + } else { + xoff = 0; + yoff = 0; + } + + /* Lines 0 to cy-1: visible ranges across the full width. */ + for (y = 0; y < s->cy; y++) { + screen_write_set_cursor(ctx, 0, y); + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff, yoff + y, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + screen_write_clearpartofline(ctx, + ri->px - xoff, ri->nx, bg); + } + } + + /* Last line: visible ranges from 0 to cursor (inclusive). */ + screen_write_set_cursor(ctx, 0, s->cy); + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff, yoff + cy_save, s->cx + 1, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + screen_write_clearpartofline(ctx, ri->px - xoff, ri->nx, bg); + } + + screen_write_collect_flush(ctx, 0, __func__); + screen_write_set_cursor(ctx, cx_save, cy_save); } /* Clear entire screen. */ void screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct tty_ctx ttyctx; - u_int sx = screen_size_x(s), sy = screen_size_y(s); + struct screen *s = ctx->s; + struct tty_ctx ttyctx; + u_int sx = screen_size_x(s), sy = screen_size_y(s); + u_int y, i, xoff, yoff, cx_save, cy_save; + struct visible_ranges *r; + struct visible_range *ri; #ifdef ENABLE_SIXEL if (image_free_all(s) && ctx->wp != NULL) @@ -1662,7 +1777,38 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) grid_view_clear(s->grid, 0, 0, sx, sy, bg); screen_write_collect_clear(ctx, 0, sy); - tty_write(tty_cmd_clearscreen, &ttyctx); + + if (! screen_write_pane_obscured(ctx->wp)) { + tty_write(tty_cmd_clearscreen, &ttyctx); + return; + } + + /* Can't just clear screen, must avoid floating windows. */ + cx_save = s->cx; + cy_save = s->cy; + if (ctx->wp != NULL) { + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + } else { + xoff = 0; + yoff = 0; + } + + for (y = 0; y < sy; y++) { + screen_write_set_cursor(ctx, 0, y); + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff, yoff + y, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + screen_write_clearpartofline(ctx, + ri->px - xoff, ri->nx, bg); + } + } + + screen_write_collect_flush(ctx, 0, __func__); + screen_write_set_cursor(ctx, cx_save, cy_save); } /* Clear entire history. */ From 28c04b21f61250bf960d013dd037e9dba5e9d470 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 9 Mar 2026 08:35:24 +0000 Subject: [PATCH 073/167] Minor fixup. --- tty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tty.c b/tty.c index b3ae9adac4..58b294b55e 100644 --- a/tty.c +++ b/tty.c @@ -1319,7 +1319,7 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, if (wpl->yoff - 1 > (int)(py + ny) || wpl->yoff + wpl->sy + 1 < py) continue; overlap++; - if (overlap > 1) break; + if (overlap > 0) break; } /* If genuine BCE is available, can try escape sequences. */ From e928e80a42320a1edd115d1ad5a2e55992b8f407 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 9 Mar 2026 16:28:06 +0000 Subject: [PATCH 074/167] Fix scrollbar issue not respecting oy when window size taller than tty. --- screen-redraw.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index d130cc834a..7e54607b54 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1438,7 +1438,7 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, struct style *sb_style = &wp->scrollbar_style; u_int i, j, imin = 0, jmin = 0, imax, jmax; u_int sb_w = sb_style->width, sb_pad = sb_style->pad; - int px, py, wx, wy, ox, oy, sx, sy; + int px, py, wx, wy, ox, oy, sx, sy, sb_tty_y; int xoff = wp->xoff; int yoff = wp->yoff; struct visible_ranges *r; @@ -1477,22 +1477,29 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, return; imax = sx - sb_x; } - if (sb_y > oy + sy) + /* + * sb_y is a window coordinate; convert to tty coordinate by + * subtracting the pan offset oy. + */ + sb_tty_y = sb_y - oy; /* scrollbar top in tty coordinates */ + + if (sb_tty_y > (int)sy) /* Whole sb off screen. */ return; - if (sb_y < 0) - /* Part of sb on screen. */ - jmin = -sb_y; - if ((int)sb_h < oy) + if (sb_tty_y < 0) + /* Scrollbar starts above visible area; skip those rows. */ + jmin = -sb_tty_y; + if (sb_tty_y + (int)sb_h <= 0) + /* Whole sb above visible area. */ return; jmax = sb_h; - if ((int)jmax + sb_y > sy) + if (sb_tty_y + (int)jmax > (int)sy) /* Clip to height of tty. */ - jmax = sy - sb_y; + jmax = sy - sb_tty_y; for (j = jmin; j < jmax; j++) { - py = sb_y + j; /* tty y coordinate. */ - wy = sb_y + j + oy; /* window y coordinate. */ + wy = sb_y + j; /* window y coordinate. */ + py = sb_tty_y + j; /* tty y coordinate. */ r = tty_check_overlay_range(tty, sb_x, wy, imax); r = screen_redraw_get_visible_ranges(wp, sb_x, wy, imax, r); for (i = imin; i < imax; i++) { From 8a90fce47655ed24a4656b8ebebb7b0c2fbc34dc Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 10 Mar 2026 13:21:01 +0000 Subject: [PATCH 075/167] Fix for window size taller than tty. --- cmd-copy-mode.c | 4 +++- screen-write.c | 24 ++++++++++++++++-------- server-client.c | 6 ++++-- tmux.h | 2 +- window-copy.c | 29 ++++++++++++++++++++--------- 5 files changed, 44 insertions(+), 21 deletions(-) diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c index fff0d9dd7b..5ac0192fb3 100644 --- a/cmd-copy-mode.c +++ b/cmd-copy-mode.c @@ -63,6 +63,7 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item) struct client *c = cmdq_get_client(item); struct session *s; struct window_pane *wp = target->wp, *swp; + u_int tty_ox, tty_oy, tty_sx, tty_sy; if (args_has(args, 'q')) { window_pane_reset_mode_all(wp); @@ -94,8 +95,9 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'd')) window_copy_pagedown(wp, 0, args_has(args, 'e')); if (args_has(args, 'S')) { + tty_window_offset(&c->tty, &tty_ox, &tty_oy, &tty_sx, &tty_sy); window_copy_scroll(wp, c->tty.mouse_slider_mpos, event->m.y, - args_has(args, 'e')); + tty_oy, args_has(args, 'e')); return (CMD_RETURN_NORMAL); } diff --git a/screen-write.c b/screen-write.c index 72f5c7bc5e..92759adcd4 100644 --- a/screen-write.c +++ b/screen-write.c @@ -575,6 +575,7 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, struct grid *gd = src->grid; struct grid_cell gc; u_int xx, yy, cx = s->cx, cy = s->cy; + int yoff = 0; struct visible_ranges *r; if (nx == 0 || ny == 0) @@ -584,9 +585,12 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, if (yy >= gd->hsize + gd->sy) break; s->cx = cx; - if (wp != NULL) - screen_write_initctx(ctx, &ttyctx, 0); - r = screen_redraw_get_visible_ranges(wp, px, py, nx, NULL); + screen_write_initctx(ctx, &ttyctx, 0); + if (wp != NULL) { + yoff = wp->yoff; + } + r = screen_redraw_get_visible_ranges(wp, px, s->cy + yoff, nx, + NULL); for (xx = px; xx < px + nx; xx++) { if (xx >= grid_get_line(gd, yy)->cellsize && s->cx >= grid_get_line(ctx->s->grid, @@ -2300,7 +2304,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) struct tty_ctx ttyctx; u_int sx = screen_size_x(s), sy = screen_size_y(s); u_int width = ud->width, xx, not_wrap, i, n, vis; - int selected, skip = 1, redraw = 0; + int selected, skip = 1, redraw = 0, yoff = 0; struct visible_ranges *r; struct visible_range *ri; @@ -2400,8 +2404,10 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) if (selected) skip = 0; - /* xxx cache r in wp ? */ - r = screen_redraw_get_visible_ranges(wp, s->cx, s->cy, width, NULL); + if (wp != NULL) + yoff = wp->yoff; + r = screen_redraw_get_visible_ranges(wp, s->cx, s->cy + yoff, width, + NULL); /* * Move the cursor. If not wrapping, stick at the last character and @@ -2459,7 +2465,7 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) struct window_pane *wp = ctx->wp; struct grid *gd = s->grid; const struct utf8_data *ud = &gc->data; - u_int i, n, cx = s->cx, cy = s->cy, vis; + u_int i, n, cx = s->cx, cy = s->cy, vis, yoff = 0; struct grid_cell last; struct tty_ctx ttyctx; int force_wide = 0, zero_width = 0; @@ -2556,7 +2562,9 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) * be obscured in the middle, only on left or right, but there * could be an empty range in the visible ranges so we add them all up. */ - r = screen_redraw_get_visible_ranges(wp, cx - n, cy, n, NULL); + if (wp != NULL) + yoff = wp->yoff; + r = screen_redraw_get_visible_ranges(wp, cx - n, cy + yoff, n, NULL); for (i=0, vis=0; i < r->used; i++) vis += r->ranges[i].nx; if (vis < n) { /* diff --git a/server-client.c b/server-client.c index e8433a5192..60e0f41062 100644 --- a/server-client.c +++ b/server-client.c @@ -875,9 +875,11 @@ server_client_check_mouse(struct client *c, struct key_event *event) * scrollbar. */ if (where == NOWHERE) { - if (c->tty.mouse_scrolling_flag) + if (c->tty.mouse_scrolling_flag) { where = SCROLLBAR_SLIDER; - else { + m->wp = c->tty.mouse_wp->id; + m->w = c->tty.mouse_wp->window->id; + } else { px = x; if (m->statusat == 0 && y >= m->statuslines) py = y - m->statuslines; diff --git a/tmux.h b/tmux.h index 8106aafb55..de841a27f7 100644 --- a/tmux.h +++ b/tmux.h @@ -3537,7 +3537,7 @@ void printflike(3, 4) window_copy_add(struct window_pane *, int, const char *, ...); void printflike(3, 0) window_copy_vadd(struct window_pane *, int, const char *, va_list); -void window_copy_scroll(struct window_pane *, int, u_int, int); +void window_copy_scroll(struct window_pane *, int, u_int, u_int, int); void window_copy_pageup(struct window_pane *, int); void window_copy_pagedown(struct window_pane *, int, int); void window_copy_start_drag(struct client *, struct mouse_event *); diff --git a/window-copy.c b/window-copy.c index 900b9ccc51..9835706d42 100644 --- a/window-copy.c +++ b/window-copy.c @@ -42,7 +42,7 @@ static void window_copy_formats(struct window_mode_entry *, struct format_tree *); static struct screen *window_copy_get_screen(struct window_mode_entry *); static void window_copy_scroll1(struct window_mode_entry *, - struct window_pane *wp, int, u_int, int); + struct window_pane *wp, int, u_int, u_int, int); static void window_copy_pageup1(struct window_mode_entry *, int); static int window_copy_pagedown1(struct window_mode_entry *, int, int); static void window_copy_next_paragraph(struct window_mode_entry *); @@ -596,19 +596,19 @@ window_copy_vadd(struct window_pane *wp, int parse, const char *fmt, va_list ap) void window_copy_scroll(struct window_pane *wp, int sl_mpos, u_int my, - int scroll_exit) + u_int tty_oy, int scroll_exit) { struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); if (wme != NULL) { window_set_active_pane(wp->window, wp, 0); - window_copy_scroll1(wme, wp, sl_mpos, my, scroll_exit); + window_copy_scroll1(wme, wp, sl_mpos, my, tty_oy, scroll_exit); } } static void window_copy_scroll1(struct window_mode_entry *wme, struct window_pane *wp, - int sl_mpos, u_int my, int scroll_exit) + int sl_mpos, u_int my, u_int tty_oy, int scroll_exit) { struct window_copy_mode_data *data = wme->data; u_int ox, oy, px, py, n, offset, size; @@ -616,21 +616,29 @@ window_copy_scroll1(struct window_mode_entry *wme, struct window_pane *wp, u_int slider_height = wp->sb_slider_h; u_int sb_height = wp->sy, sb_top = wp->yoff; u_int sy = screen_size_y(data->backing); + u_int my_w; int new_slider_y, delta; /* * sl_mpos is where in the slider the user is dragging, mouse is * dragging this y point relative to top of slider. + * + * my is a raw tty y coordinate; sb_top (= wp->yoff) is a window + * coordinate. Convert my to window coordinates by adding tty_oy + * (the window pan offset). sl_mpos already has the statuslines + * adjustment baked in (see server_client_check_mouse), so no further + * statuslines correction is needed here. */ - if (my <= sb_top + sl_mpos) { + my_w = my + tty_oy; + if (my_w <= sb_top + (u_int)sl_mpos) { /* Slider banged into top. */ new_slider_y = sb_top - wp->yoff; - } else if (my - sl_mpos > sb_top + sb_height - slider_height) { + } else if (my_w - sl_mpos > sb_top + sb_height - slider_height) { /* Slider banged into bottom. */ new_slider_y = sb_top - wp->yoff + (sb_height - slider_height); } else { /* Slider is somewhere in the middle. */ - new_slider_y = my - wp->yoff - sl_mpos; + new_slider_y = my_w - wp->yoff - sl_mpos; } if (TAILQ_FIRST(&wp->modes) == NULL || @@ -1503,8 +1511,11 @@ window_copy_cmd_scroll_to_mouse(struct window_copy_cmd_state *cs) struct client *c = cs->c; struct mouse_event *m = cs->m; int scroll_exit = args_has(cs->wargs, 'e'); + u_int tty_ox, tty_oy, tty_sx, tty_sy; - window_copy_scroll(wp, c->tty.mouse_slider_mpos, m->y, scroll_exit); + tty_window_offset(&c->tty, &tty_ox, &tty_oy, &tty_sx, &tty_sy); + window_copy_scroll(wp, c->tty.mouse_slider_mpos, m->y, tty_oy, + scroll_exit); return (WINDOW_COPY_CMD_NOTHING); } @@ -4705,7 +4716,7 @@ window_copy_write_lines(struct window_mode_entry *wme, u_int yy; for (yy = py; yy < py + ny; yy++) - window_copy_write_line(wme, ctx, py); + window_copy_write_line(wme, ctx, yy); } static void From 6c9e2f7e6c59e839d38617dd83b9a5af3490bd61 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 16 Mar 2026 12:07:41 +0000 Subject: [PATCH 076/167] Fix no cursor in popup. --- server-client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server-client.c b/server-client.c index 60e0f41062..f04c77138f 100644 --- a/server-client.c +++ b/server-client.c @@ -3067,7 +3067,7 @@ server_client_reset_state(struct client *c) if (!cursor) mode &= ~MODE_CURSOR; - } else + } else if (c->overlay_mode == NULL || s == NULL) mode &= ~MODE_CURSOR; log_debug("%s: cursor to %u,%u", __func__, cx, cy); From c42a939e98abdcbac47f940bbebade7ed6c76166 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 17 Mar 2026 17:05:57 +0000 Subject: [PATCH 077/167] Fix some int/u_int bugs and added some logging. --- screen-redraw.c | 10 +++++----- screen-write.c | 12 +++++++++++- tty.c | 9 +++++++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 7e54607b54..2fcfe43bcb 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -461,16 +461,16 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, wp->scrollbar_style.pad; if (sb_pos == PANE_SCROLLBARS_LEFT) { if (~wp->flags & PANE_MINIMISED && - ((int)px >= wp->xoff - 1 - sb_w && + ((int)px >= (int)wp->xoff - 1 - sb_w && (int)px <= wp->xoff + (int)wp->sx) && - ((int)py >= wp->yoff - 1 && + ((int)py >= (int)wp->yoff - 1 && (int)py <= wp->yoff + (int)wp->sy)) break; } else { /* PANE_SCROLLBARS_RIGHT or none. */ if (~wp->flags & PANE_MINIMISED && - ((int)px >= wp->xoff - 1 && + ((int)px >= (int)wp->xoff - 1 && (int)px <= wp->xoff + (int)wp->sx + sb_w) && - ((int)py >= wp->yoff - 1 && + ((int)py >= (int)wp->yoff - 1 && (int)py <= wp->yoff + (int)wp->sy)) break; } @@ -1178,7 +1178,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, continue; } - tb = wp->yoff - 1; + tb = (wp->yoff > 0) ? wp->yoff - 1 : 0; bb = wp->yoff + wp->sy; if (!found_self || !window_pane_visible(wp) || diff --git a/screen-write.c b/screen-write.c index 92759adcd4..dbfc8b3cca 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1991,9 +1991,14 @@ screen_write_pane_obscured(struct window_pane *base_wp) ((wp->xoff >= base_wp->xoff && wp->xoff <= base_wp->xoff + (int)base_wp->sx) || (wp->xoff + (int)wp->sx >= base_wp->xoff && - wp->xoff + wp->sx <= base_wp->xoff + base_wp->sx))) + wp->xoff + wp->sx <= base_wp->xoff + base_wp->sx))) { + log_debug("%s: base %%%u obscured by %%%u " + "(xoff=%u sx=%u vs base xoff=%u sx=%u)", __func__, + base_wp->id, wp->id, + wp->xoff, wp->sx, base_wp->xoff, base_wp->sx); return (1); } + } return (0); } @@ -2037,7 +2042,12 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ttyctx.num = ctx->scrolled; ttyctx.bg = ctx->bg; ttyctx.obscured = screen_write_pane_obscured(wp); + log_debug("%s: obscured=%d for pane %%%u", __func__, + ttyctx.obscured, wp != NULL ? wp->id : 0); tty_write(tty_cmd_scrollup, &ttyctx); + if (wp != NULL) + log_debug("%s: after scrollup, PANE_REDRAW=%d for %%%u", + __func__, !!(wp->flags & PANE_REDRAW), wp->id); if (wp != NULL) ctx->wp->flags |= PANE_REDRAWSCROLLBAR; diff --git a/tty.c b/tty.c index 58b294b55e..ab3d822f8d 100644 --- a/tty.c +++ b/tty.c @@ -1095,12 +1095,17 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) * If region is large, schedule a redraw. In most cases this is likely * to be followed by some more scrolling. */ + log_debug("%s: %s orlower=%u orupper=%u sy=%u large=%d", __func__, + c->name, ctx->orlower, ctx->orupper, ctx->sy, + tty_large_region(tty, ctx)); if (tty_large_region(tty, ctx)) { log_debug("%s: %s large redraw", __func__, c->name); ctx->redraw_cb(ctx); return; } + log_debug("%s: %s small redraw, drawing rows %u-%u", __func__, + c->name, ctx->orupper, ctx->orlower); for (i = ctx->orupper; i <= ctx->orlower; i++) tty_draw_pane(tty, ctx, i); } @@ -1314,9 +1319,9 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, TAILQ_FOREACH(wpl, &w->z_index, zentry) { if (wpl == wp || ~wpl->flags & PANE_FLOATING) continue; - if (wpl->xoff - 1 > (int)(px + nx) || wpl->xoff + wpl->sx + 1 < px) + if ((int)wpl->xoff - 1 > (int)(px + nx) || wpl->xoff + wpl->sx + 1 < px) continue; - if (wpl->yoff - 1 > (int)(py + ny) || wpl->yoff + wpl->sy + 1 < py) + if ((int)wpl->yoff - 1 > (int)(py + ny) || wpl->yoff + wpl->sy + 1 < py) continue; overlap++; if (overlap > 0) break; From ab0081294c26bf88652b6867b62ee7f07948c553 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 17 Mar 2026 17:08:02 +0000 Subject: [PATCH 078/167] Fix scrolling issue when a floating pane overlaps a tiled pane. --- server-client.c | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/server-client.c b/server-client.c index f04c77138f..4f85710e1e 100644 --- a/server-client.c +++ b/server-client.c @@ -2747,8 +2747,9 @@ server_client_loop(void) { struct client *c; struct window *w; - struct window_pane *wp; + struct window_pane *wp, *twp; struct window_mode_entry *wme; + u_int bit; /* Check for window resize. This is done before redrawing. */ RB_FOREACH(w, windows, &windows) @@ -2786,6 +2787,42 @@ server_client_loop(void) server_client_check_pane_resize(wp); server_client_check_pane_buffer(wp); } + /* + * If PANE_REDRAW was set during buffer processing + * above, check_redraw has already run for this + * iteration and will not see it. Defer the redraw + * to the next iteration via CLIENT_REDRAWPANES so + * screen_redraw_pane fires once the grid is complete + * (e.g. after the shell prompt has been written). + */ + if (wp->flags & PANE_REDRAW) { + bit = 0; + TAILQ_FOREACH(twp, &w->panes, entry) { + if (twp == wp) { + TAILQ_FOREACH(c, &clients, + entry) { + if (c->session == NULL || + c->session->curw == + NULL || + c->session->curw->window + != w) + continue; + if (bit < 64) { + c->redraw_panes + |= (1ULL + << bit); + c->flags |= + CLIENT_REDRAWPANES; + } else + c->flags |= + CLIENT_REDRAWWINDOW; + } + break; + } + if (++bit == 64) + break; + } + } wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR); } check_window_name(w); From 08fd890a890e6c827a8cdd7b5872cecdf57d140b Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Tue, 17 Mar 2026 22:50:04 -0700 Subject: [PATCH 079/167] Added status format functionality to panes along with flags --- format.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ options-table.c | 19 +++++++++++++++++-- tmux.1 | 17 +++++++++++++++++ tmux.h | 2 ++ window.c | 24 ++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 2 deletions(-) diff --git a/format.c b/format.c index 398161e717..37316046d3 100644 --- a/format.c +++ b/format.c @@ -1004,6 +1004,15 @@ format_cb_pane_fg(struct format_tree *ft) return (xstrdup(colour_tostring(gc.fg))); } +/* Callback for pane_flags. */ +static void * +format_cb_pane_flags(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (xstrdup(window_pane_printable_flags(ft->wp, 1))); + return (NULL); +} + /* Callback for pane_floating_flag. */ static void * format_cb_pane_floating_flag(struct format_tree *ft) @@ -2205,6 +2214,21 @@ format_cb_pane_marked_set(struct format_tree *ft) return (NULL); } +/* Callback for pane_minimised_flag. */ +static void * +format_cb_pane_minimised_flag(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp != NULL) { + if (wp->flags & PANE_MINIMISED) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + + /* Callback for pane_mode. */ static void * format_cb_pane_mode(struct format_tree *ft) @@ -2333,6 +2357,20 @@ format_cb_pane_width(struct format_tree *ft) return (NULL); } +/* Callback for pane_zoomed_flag. */ +static void * +format_cb_pane_zoomed_flag(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp != NULL) { + if (wp->flags & PANE_ZOOMED) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + /* Callback for scroll_region_lower. */ static void * format_cb_scroll_region_lower(struct format_tree *ft) @@ -3309,6 +3347,9 @@ static const struct format_table_entry format_table[] = { { "pane_fg", FORMAT_TABLE_STRING, format_cb_pane_fg }, + { "pane_flags", FORMAT_TABLE_STRING, + format_cb_pane_flags + }, { "pane_floating_flag", FORMAT_TABLE_STRING, format_cb_pane_floating_flag }, @@ -3345,6 +3386,9 @@ static const struct format_table_entry format_table[] = { { "pane_marked_set", FORMAT_TABLE_STRING, format_cb_pane_marked_set }, + { "pane_minimised_flag", FORMAT_TABLE_STRING, + format_cb_pane_minimised_flag + }, { "pane_mode", FORMAT_TABLE_STRING, format_cb_pane_mode }, @@ -3393,6 +3437,9 @@ static const struct format_table_entry format_table[] = { { "pane_width", FORMAT_TABLE_STRING, format_cb_pane_width }, + { "pane_zoomed_flag", FORMAT_TABLE_STRING, + format_cb_pane_zoomed_flag + }, { "pid", FORMAT_TABLE_STRING, format_cb_pid }, diff --git a/options-table.c b/options-table.c index dfe0c9e504..39e5237baf 100644 --- a/options-table.c +++ b/options-table.c @@ -185,7 +185,7 @@ static const char *options_table_allow_passthrough_list[] = { "#{E:pane-status-style}" \ "]" \ "#[push-default]" \ - "#P[#{pane_width}x#{pane_height}]" \ + "#{T:window-pane-status-format}" \ "#[pop-default]" \ "#[norange list=on default] " \ "," \ @@ -196,7 +196,7 @@ static const char *options_table_allow_passthrough_list[] = { "}" \ "]" \ "#[push-default]" \ - "#P[#{pane_width}x#{pane_height}]*" \ + "#{T:window-pane-current-status-format}" \ "#[pop-default]" \ "#[norange list=on default] " \ "}" @@ -1458,6 +1458,21 @@ const struct options_table_entry options_table[] = { .text = "Default style of the active pane." }, + { .name = "window-pane-current-status-format", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "#P:[#T]#{?pane_flags,#{pane_flags}, }", + .text = "Format of the current window pane in the status line." + }, + + { .name = "window-pane-status-format", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "#P:[#T]#{?pane_flags,#{pane_flags}, }", + .text = "Format of window panes in the status line, except the " + "current pane." + }, + { .name = "window-size", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, diff --git a/tmux.1 b/tmux.1 index 2cdac4e43f..5afda8f173 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5613,6 +5613,20 @@ For how to specify see the .Sx STYLES section. +.Pp +.It Ic window-pane-status-format Ar string +Set the format in which the window pane is displayed in the status line window +pane list. +See the +.Sx FORMATS +and +.Sx STYLES +sections. +.Pp +.It Ic window-pane-current-status-format Ar string +Like +.Ar window-status-format , +but is the format used when the window is the current window. .El .Sh HOOKS .Nm @@ -6364,6 +6378,7 @@ The following variables are available, where appropriate: .It Li "pane_dead_status" Ta "" Ta "Exit status of process in dead pane" .It Li "pane_dead_time" Ta "" Ta "Exit time of process in dead pane" .It Li "pane_fg" Ta "" Ta "Pane foreground colour" +.It Li "pane_flags" Ta "" Ta "Pane flags" .It Li "pane_floating_flag" Ta "" Ta "1 if pane is floating" .It Li "pane_format" Ta "" Ta "1 if format is for a pane" .It Li "pane_height" Ta "" Ta "Height of pane" @@ -6376,6 +6391,7 @@ The following variables are available, where appropriate: .It Li "pane_left" Ta "" Ta "Left of pane" .It Li "pane_marked" Ta "" Ta "1 if this is the marked pane" .It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set" +.It Li "pane_minimised_flag" Ta "" Ta "1 if pane is minimised" .It Li "pane_mode" Ta "" Ta "Name of pane mode, if any" .It Li "pane_path" Ta "" Ta "Path of pane (can be set by application)" .It Li "pane_pid" Ta "" Ta "PID of first process in pane" @@ -6392,6 +6408,7 @@ The following variables are available, where appropriate: .It Li "pane_tty" Ta "" Ta "Pseudo terminal of pane" .It Li "pane_unseen_changes" Ta "" Ta "1 if there were changes in pane while in mode" .It Li "pane_width" Ta "" Ta "Width of pane" +.It Li "pane_zoomed_flag" Ta "" Ta "1 if pane is zoomed" .It Li "pid" Ta "" Ta "Server PID" .It Li "prev_window_active" Ta "" Ta "1 if previous window in W: loop is active" .It Li "prev_window_index" Ta "" Ta "Index of previous window in W: loop" diff --git a/tmux.h b/tmux.h index c9d3c04c2c..29934922b6 100644 --- a/tmux.h +++ b/tmux.h @@ -1230,6 +1230,7 @@ struct window_pane { #define PANE_REDRAWSCROLLBAR 0x8000 #define PANE_FLOATING 0x10000 #define PANE_MINIMISED 0x20000 +#define PANE_ZOOMED 0x40000 u_int sb_slider_y; u_int sb_slider_h; @@ -3396,6 +3397,7 @@ int window_pane_exited(struct window_pane *); u_int window_pane_search(struct window_pane *, const char *, int, int); const char *window_printable_flags(struct winlink *, int); +const char *window_pane_printable_flags(struct window_pane *, int); struct window_pane *window_pane_find_up(struct window_pane *); struct window_pane *window_pane_find_down(struct window_pane *); struct window_pane *window_pane_find_left(struct window_pane *); diff --git a/window.c b/window.c index d1a2219e9d..29d03c173b 100644 --- a/window.c +++ b/window.c @@ -738,6 +738,7 @@ window_zoom(struct window_pane *wp) if (w->active != wp) window_set_active_pane(w, wp, 1); + wp->flags |= PANE_ZOOMED; /* Bring pane above other tiled panes and minimise floating panes. */ TAILQ_FOREACH(wp1, &w->z_index, zentry) { @@ -791,6 +792,7 @@ window_unzoom(struct window *w, int notify) TAILQ_FOREACH(wp, &w->panes, entry) { wp->layout_cell = wp->saved_layout_cell; wp->saved_layout_cell = NULL; + wp->flags &= ~PANE_ZOOMED; } layout_fix_panes(w, NULL); @@ -1006,6 +1008,28 @@ window_printable_flags(struct winlink *wl, int escape) return (flags); } +const char * +window_pane_printable_flags(struct window_pane *wp, __unused int escape) +{ + static char flags[32]; + struct window *w = wp->window; + int pos = 0; + + if (wp == w->active) + flags[pos++] = '*'; + if (wp == TAILQ_FIRST(&w->last_panes)) + flags[pos++] = '-'; + if (wp->flags & PANE_ZOOMED) + flags[pos++] = 'Z'; + if (wp->flags & PANE_FLOATING) + flags[pos++] = 'F'; + if (wp->flags & PANE_MINIMISED) + flags[pos++] = 'm'; + + flags[pos] = '\0'; + return (flags); +} + struct window_pane * window_pane_find_by_id_str(const char *s) { From 0d195698f8aff607d00dae3e046796aaca2456c4 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 18 Mar 2026 09:38:17 +0000 Subject: [PATCH 080/167] Fix dispay bugs where the cursor was on a line that had 2 visible ranges because it was partially obscured by a floating pane. --- screen-write.c | 36 ++++++++++++++++++++++++++---- tty.c | 59 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/screen-write.c b/screen-write.c index dbfc8b3cca..6618b5cc52 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1128,6 +1128,7 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; + ttyctx.obscured = screen_write_pane_obscured(ctx->wp); grid_view_insert_cells(s->grid, s->cx, s->cy, nx, bg); @@ -1161,6 +1162,7 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; + ttyctx.obscured = screen_write_pane_obscured(ctx->wp); grid_view_delete_cells(s->grid, s->cx, s->cy, nx, bg); @@ -1230,6 +1232,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; + ttyctx.obscured = screen_write_pane_obscured(ctx->wp); grid_view_insert_lines(gd, s->cy, ny, bg); @@ -1246,6 +1249,9 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; + ttyctx.obscured = screen_write_pane_obscured(ctx->wp); + log_debug("%s: obscured=%d for pane %%%u", __func__, + ttyctx.obscured, ctx->wp != NULL ? ctx->wp->id : 0); if (s->cy < s->rupper || s->cy > s->rlower) grid_view_insert_lines(gd, s->cy, ny, bg); @@ -1283,6 +1289,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; + ttyctx.obscured = screen_write_pane_obscured(ctx->wp); grid_view_delete_lines(gd, s->cy, ny, bg); @@ -1299,6 +1306,9 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; + ttyctx.obscured = screen_write_pane_obscured(ctx->wp); + log_debug("%s: obscured=%d for pane %%%u", __func__, + ttyctx.obscured, ctx->wp != NULL ? ctx->wp->id : 0); if (s->cy < s->rupper || s->cy > s->rlower) grid_view_delete_lines(gd, s->cy, ny, bg); @@ -1570,6 +1580,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; + ttyctx.obscured = screen_write_pane_obscured(ctx->wp); if (lines == 0) lines = 1; @@ -1928,8 +1939,11 @@ screen_write_collect_clear(struct screen_write_ctx *ctx, u_int y, u_int n) struct screen_write_cline *cl; u_int i; + log_debug("%s: clearing rows %u..%u", __func__, y, y + n - 1); for (i = y; i < y + n; i++) { cl = &ctx->s->write_list[i]; + if (!TAILQ_EMPTY(&cl->items)) + log_debug("%s: row %u had items!", __func__, i); TAILQ_CONCAT(&screen_write_citem_freelist, &cl->items, entry); } } @@ -1944,8 +1958,8 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) char *saved; struct screen_write_citem *ci; - log_debug("%s: at %u,%u (region %u-%u)", __func__, s->cx, s->cy, - s->rupper, s->rlower); + log_debug("%s: at %u,%u (region %u-%u) cy=%u", __func__, s->cx, s->cy, + s->rupper, s->rlower, s->cy); screen_write_collect_clear(ctx, s->rupper, 1); saved = ctx->s->write_list[s->rupper].data; @@ -2012,6 +2026,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct screen_write_cline *cl; u_int y, cx, cy, last, items = 0, i; u_int wr_start, wr_end, wr_length, wsx, wsy; + int written; int r_start, r_end, ci_start, ci_end; int xoff, yoff; struct tty_ctx ttyctx; @@ -2083,6 +2098,16 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, NULL); last = UINT_MAX; + if (y == (u_int)s->cy || !TAILQ_EMPTY(&cl->items)) { + u_int dbg_cnt = 0; + struct screen_write_citem *dbg_ci; + TAILQ_FOREACH(dbg_ci, &cl->items, entry) dbg_cnt++; + log_debug("%s: row y=%u has %u items (wp_xoff=%d yoff=%d wsx=%u)", + __func__, y, dbg_cnt, + wp ? (int)wp->xoff : -1, + wp ? (int)wp->yoff : -1, + wsx); + } TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { log_debug("collect list: x=%u (last %u), y=%u, used=%u", ci->x, last, y, ci->used); @@ -2091,6 +2116,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ci->x, last); } wr_length = 0; + written = 0; for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; @@ -2133,10 +2159,12 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, tty_write(tty_cmd_cells, &ttyctx); } items++; - + written = 1; + } + if (written) { + last = ci->x; TAILQ_REMOVE(&cl->items, ci, entry); screen_write_free_citem(ci); - last = ci->x; } } } diff --git a/tty.c b/tty.c index ab3d822f8d..5c79061603 100644 --- a/tty.c +++ b/tty.c @@ -1098,8 +1098,8 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) log_debug("%s: %s orlower=%u orupper=%u sy=%u large=%d", __func__, c->name, ctx->orlower, ctx->orupper, ctx->sy, tty_large_region(tty, ctx)); - if (tty_large_region(tty, ctx)) { - log_debug("%s: %s large redraw", __func__, c->name); + if (tty_large_region(tty, ctx) && !ctx->obscured) { + log_debug("%s: %s large region redraw", __func__, c->name); ctx->redraw_cb(ctx); return; } @@ -1421,13 +1421,35 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) if (!ctx->bigger) { if (wp) { - r = tty_check_overlay_range(tty, 0, ctx->yoff + py, nx); - for (i=0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) continue; - tty_draw_line(tty, s, ri->px, py, ri->nx, - ctx->xoff + ri->px, ctx->yoff + py, - &ctx->defaults, ctx->palette); + if (ctx->obscured) { + /* + * Floating pane is present: use physical + * coordinates and clip to visible ranges to + * avoid drawing over the floating pane. + */ + r = tty_check_overlay_range(tty, ctx->xoff, + ctx->yoff + py, nx); + r = screen_redraw_get_visible_ranges(wp, + ctx->xoff, ctx->yoff + py, nx, r); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) continue; + tty_draw_line(tty, s, + ri->px - ctx->xoff, py, ri->nx, + ri->px, ctx->yoff + py, + &ctx->defaults, ctx->palette); + } + } else { + r = tty_check_overlay_range(tty, 0, + ctx->yoff + py, nx); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) continue; + tty_draw_line(tty, s, ri->px, py, + ri->nx, ctx->xoff + ri->px, + ctx->yoff + py, + &ctx->defaults, ctx->palette); + } } } else { tty_draw_line(tty, s, 0, py, nx, ctx->xoff, @@ -1664,7 +1686,8 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) tty_fake_bce(tty, &ctx->defaults, ctx->bg) || (!tty_term_has(tty->term, TTYC_ICH) && !tty_term_has(tty->term, TTYC_ICH1)) || - c->overlay_check != NULL) { + c->overlay_check != NULL || + ctx->obscured) { tty_draw_pane(tty, ctx, ctx->ocy); return; } @@ -1687,7 +1710,8 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) tty_fake_bce(tty, &ctx->defaults, ctx->bg) || (!tty_term_has(tty->term, TTYC_DCH) && !tty_term_has(tty->term, TTYC_DCH1)) || - c->overlay_check != NULL) { + c->overlay_check != NULL || + ctx->obscured) { tty_draw_pane(tty, ctx, ctx->ocy); return; } @@ -1721,7 +1745,8 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) !tty_term_has(tty->term, TTYC_IL1) || ctx->sx == 1 || ctx->sy == 1 || - c->overlay_check != NULL) { + c->overlay_check != NULL || + ctx->obscured) { tty_redraw_region(tty, ctx); return; } @@ -1749,7 +1774,8 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) !tty_term_has(tty->term, TTYC_DL1) || ctx->sx == 1 || ctx->sy == 1 || - c->overlay_check != NULL) { + c->overlay_check != NULL || + ctx->obscured) { tty_redraw_region(tty, ctx); return; } @@ -1925,7 +1951,8 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) !tty_term_has(tty->term, TTYC_RIN)) || ctx->sx == 1 || ctx->sy == 1 || - c->overlay_check != NULL) { + c->overlay_check != NULL || + ctx->obscured) { tty_redraw_region(tty, ctx); return; } @@ -2257,6 +2284,10 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, if (gc->flags & GRID_FLAG_PADDING) return; + /* Check if character is covered by overlay/floating pane. */ + if (!tty_check_overlay(tty, tty->cx, tty->cy)) + return; /* Character obscured by floating pane. */ + /* Check the output codeset and apply attributes. */ gcp = tty_check_codeset(tty, gc); tty_attributes(tty, gcp, defaults, palette, hl); From 7e6bbc63aba8e39e783050c767ae9be8487ef312 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 18 Mar 2026 13:04:21 +0000 Subject: [PATCH 081/167] Fix a slew of possible int vs u_int bugs which would likely have caused an overflow crash. --- cmd-new-pane.c | 21 ++++++++------- cmd-resize-pane.c | 45 ++++++++++++++++--------------- format.c | 14 +++++----- layout-custom.c | 9 ++++--- layout.c | 24 ++++++++++------- screen-redraw.c | 32 +++++++++++----------- server-client.c | 62 +++++++++++++++++++++++------------------- tmux.h | 7 +++-- tty.c | 6 +++-- window.c | 68 ++++++++++++++++++++++++++--------------------- 10 files changed, 155 insertions(+), 133 deletions(-) diff --git a/cmd-new-pane.c b/cmd-new-pane.c index a014aec6b5..a99180e7c6 100644 --- a/cmd-new-pane.c +++ b/cmd-new-pane.c @@ -66,8 +66,9 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) char *cause = NULL, *cp; struct args_value *av; u_int count = args_count(args); - u_int x, y, sx, sy, pct; - static u_int last_x = 0, last_y = 0; + int x, y; + u_int sx, sy, pct; + static int last_x = 0, last_y = 0; if (args_has(args, 'f')) { sx = w->sx; @@ -96,7 +97,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) } } if (args_has(args, 'w')) { - sx = args_strtonum_and_expand(args, 'w', 0, w->sx, item, + sx = args_strtonum_and_expand(args, 'w', 1, USHRT_MAX, item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); @@ -105,7 +106,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) } } if (args_has(args, 'h')) { - sy = args_strtonum_and_expand(args, 'h', 0, w->sy, item, + sy = args_strtonum_and_expand(args, 'h', 1, USHRT_MAX, item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); @@ -114,8 +115,8 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) } } if (args_has(args, 'x')) { - x = args_strtonum_and_expand(args, 'x', 0, w->sx, item, - &cause); + x = args_strtonum_and_expand(args, 'x', SHRT_MIN, SHRT_MAX, + item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); @@ -126,13 +127,13 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) x = 5; } else { x = (last_x += 5); - if (last_x > w->sx) + if (last_x > (int)w->sx) x = 5; } } if (args_has(args, 'y')) { - y = args_strtonum_and_expand(args, 'y', 0, w->sx, item, - &cause); + y = args_strtonum_and_expand(args, 'y', SHRT_MIN, SHRT_MAX, + item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); @@ -143,7 +144,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) y = 5; } else { y = (last_y += 5); - if (last_y > w->sy) + if (last_y > (int)w->sy) y = 5; } } diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 5abbaa3cae..e1d6438d6e 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -164,7 +164,8 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) struct window *w; struct window_pane *wp; struct layout_cell *lc; - u_int y, ly, x, lx, new_sx, new_sy; + u_int y, ly, x, lx; + int new_sx, new_sy; int new_xoff, new_yoff, resizes = 0; wl = cmd_mouse_window(m, NULL); @@ -188,83 +189,83 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) wp = c->tty.mouse_wp; lc = wp->layout_cell; - log_debug("%s: %%%u resize_pane xoff=%u sx=%u xy=%ux%u lxy=%ux%u", + log_debug("%s: %%%u resize_pane xoff=%d sx=%u xy=%ux%u lxy=%ux%u", __func__, wp->id, wp->xoff, wp->sx, x, y, lx, ly); if ((((int)lx == wp->xoff - 1) || ((int)lx == wp->xoff)) && ((int)ly == wp->yoff - 1)) { /* Top left corner */ - new_sx = lc->sx + (lx - x); + new_sx = (int)lc->sx + ((int)lx - (int)x); if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = lc->sy + (ly - y); + new_sy = (int)lc->sy + ((int)ly - (int)y); if (new_sy < PANE_MINIMUM) new_sy = PANE_MINIMUM; new_xoff = x + 1; /* Because mouse is on border at xoff - 1 */ new_yoff = y + 1; - layout_set_size(lc, new_sx, new_sy, new_xoff, new_yoff); + layout_set_size(lc, (u_int)new_sx, (u_int)new_sy, new_xoff, new_yoff); resizes++; } else if ((((int)lx == wp->xoff + (int)wp->sx + 1) || ((int)lx == wp->xoff + (int)wp->sx)) && ((int)ly == wp->yoff - 1)) { /* Top right corner */ - new_sx = x - lc->xoff; + new_sx = (int)x - lc->xoff; if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = lc->sy + (ly - y); + new_sy = (int)lc->sy + ((int)ly - (int)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, (u_int)new_sx, (u_int)new_sy, lc->xoff, new_yoff); resizes++; } else if ((((int)lx == wp->xoff - 1) || ((int)lx == wp->xoff)) && ((int)ly == wp->yoff + (int)wp->sy)) { /* Bottom left corner */ - new_sx = lc->sx + (lx - x); + new_sx = (int)lc->sx + ((int)lx - (int)x); if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = y - lc->yoff; + new_sy = (int)y - lc->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, (u_int)new_sx, (u_int)new_sy, new_xoff, lc->yoff); resizes++; } else if ((((int)lx == wp->xoff + (int)wp->sx + 1) || ((int)lx == wp->xoff + (int)wp->sx)) && ((int)ly == wp->yoff + (int)wp->sy)) { /* Bottom right corner */ - new_sx = x - lc->xoff; + new_sx = (int)x - lc->xoff; if (new_sx < PANE_MINIMUM) new_sx = PANE_MINIMUM; - new_sy = y - lc->yoff; + new_sy = (int)y - lc->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, (u_int)new_sx, (u_int)new_sy, lc->xoff, lc->yoff); resizes++; } else if ((int)lx == wp->xoff + (int)wp->sx + 1) { /* Right border */ - new_sx = x - lc->xoff; + new_sx = (int)x - lc->xoff; if (new_sx < PANE_MINIMUM) return; - layout_set_size(lc, new_sx, lc->sy, lc->xoff, lc->yoff); + layout_set_size(lc, (u_int)new_sx, lc->sy, lc->xoff, lc->yoff); resizes++; } else if ((int)lx == wp->xoff - 1) { /* Left border */ - new_sx = lc->sx + (lx - x); + new_sx = (int)lc->sx + ((int)lx - (int)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, (u_int)new_sx, lc->sy, new_xoff, lc->yoff); resizes++; } else if ((int)ly == wp->yoff + (int)wp->sy) { /* Bottom border */ - new_sy = y - lc->yoff; + new_sy = (int)y - lc->yoff; if (new_sy < PANE_MINIMUM) return; - layout_set_size(lc, lc->sx, new_sy, lc->xoff, lc->yoff); + layout_set_size(lc, lc->sx, (u_int)new_sy, lc->xoff, lc->yoff); resizes++; } else if ((int)ly == wp->yoff - 1) { /* Top border (move instead of resize) */ - new_xoff = lc->xoff + (x - lx); + new_xoff = lc->xoff + ((int)x - (int)lx); new_yoff = y + 1; layout_set_size(lc, lc->sx, lc->sy, new_xoff, new_yoff); /* To resize instead of move: @@ -276,7 +277,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) */ resizes++; } else { - log_debug("%s: %%%u resize_pane xoff=%u sx=%u xy=%ux%u lxy=%ux%u ", + log_debug("%s: %%%u resize_pane xoff=%d sx=%u xy=%ux%u lxy=%ux%u ", __func__, wp->id, wp->xoff, wp->sx, x, y, lx, ly); } if (resizes != 0) { diff --git a/format.c b/format.c index 398161e717..18fc9a1a17 100644 --- a/format.c +++ b/format.c @@ -1160,9 +1160,9 @@ format_cb_pane_at_bottom(struct format_tree *ft) status = options_get_number(w->options, "pane-border-status"); if (status == PANE_STATUS_BOTTOM) - flag = (wp->yoff + wp->sy == w->sy - 1); + flag = (wp->yoff + (int)wp->sy == (int)w->sy - 1); else - flag = (wp->yoff + wp->sy == w->sy); + flag = (wp->yoff + (int)wp->sy == (int)w->sy); xasprintf(&value, "%d", flag); return (value); } @@ -2006,7 +2006,7 @@ static void * format_cb_pane_at_right(struct format_tree *ft) { if (ft->wp != NULL) { - if (ft->wp->xoff + ft->wp->sx == ft->wp->window->sx) + if (ft->wp->xoff + (int)ft->wp->sx == (int)ft->wp->window->sx) return (xstrdup("1")); return (xstrdup("0")); } @@ -2018,7 +2018,7 @@ static void * format_cb_pane_bottom(struct format_tree *ft) { if (ft->wp != NULL) - return (format_printf("%u", ft->wp->yoff + ft->wp->sy - 1)); + return (format_printf("%d", ft->wp->yoff + (int)ft->wp->sy - 1)); return (NULL); } @@ -2177,7 +2177,7 @@ static void * format_cb_pane_left(struct format_tree *ft) { if (ft->wp != NULL) - return (format_printf("%u", ft->wp->xoff)); + return (format_printf("%d", ft->wp->xoff)); return (NULL); } @@ -2269,7 +2269,7 @@ static void * format_cb_pane_right(struct format_tree *ft) { if (ft->wp != NULL) - return (format_printf("%u", ft->wp->xoff + ft->wp->sx - 1)); + return (format_printf("%d", ft->wp->xoff + (int)ft->wp->sx - 1)); return (NULL); } @@ -2311,7 +2311,7 @@ static void * format_cb_pane_top(struct format_tree *ft) { if (ft->wp != NULL) - return (format_printf("%u", ft->wp->yoff)); + return (format_printf("%d", ft->wp->yoff)); return (NULL); } diff --git a/layout-custom.c b/layout-custom.c index 30f8909b01..fc310fb67e 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -104,10 +104,10 @@ layout_append(struct layout_cell *lc, char *buf, size_t len) if (lc == NULL) return (0); if (lc->wp != NULL) { - tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%u,%u,%u", + tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%d,%d,%u", lc->sx, lc->sy, lc->xoff, lc->yoff, lc->wp->id); } else { - tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%u,%u", + tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%d,%d", lc->sx, lc->sy, lc->xoff, lc->yoff); } if (tmplen > (sizeof tmp) - 1) @@ -349,12 +349,13 @@ static struct layout_cell * layout_construct_cell(struct layout_cell *lcparent, const char **layout) { struct layout_cell *lc; - u_int sx, sy, xoff, yoff; + u_int sx, sy; + int xoff, yoff; const char *saved; if (!isdigit((u_char) **layout)) return (NULL); - if (sscanf(*layout, "%ux%u,%u,%u", &sx, &sy, &xoff, &yoff) != 4) + if (sscanf(*layout, "%ux%u,%d,%d", &sx, &sy, &xoff, &yoff) != 4) return (NULL); while (isdigit((u_char) **layout)) diff --git a/layout.c b/layout.c index 29912810b4..ca85a7af0a 100644 --- a/layout.c +++ b/layout.c @@ -61,8 +61,8 @@ layout_create_cell(struct layout_cell *lcparent) lc->sx = UINT_MAX; lc->sy = UINT_MAX; - lc->xoff = UINT_MAX; - lc->yoff = UINT_MAX; + lc->xoff = INT_MAX; + lc->yoff = INT_MAX; lc->wp = NULL; @@ -133,7 +133,7 @@ layout_print_cell(struct layout_cell *lc, const char *hdr, u_int n) type = "UNKNOWN"; break; } - log_debug("%s:%*s%p type %s [parent %p] wp=%p [%u,%u %ux%u]", hdr, n, + 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); switch (lc->type) { @@ -154,8 +154,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 (x >= lcchild->xoff && x < lcchild->xoff + lcchild->sx && - y >= lcchild->yoff && y < lcchild->yoff + lcchild->sy) { + if ((int)x >= lcchild->xoff && + (int)x < lcchild->xoff + (int)lcchild->sx && + (int)y >= lcchild->yoff && + (int)y < lcchild->yoff + (int)lcchild->sy) { /* Inside the cell - recurse. */ return (layout_search_by_border(lcchild, x, y)); } @@ -167,11 +169,13 @@ layout_search_by_border(struct layout_cell *lc, u_int x, u_int y) switch (lc->type) { case LAYOUT_LEFTRIGHT: - if (x < lcchild->xoff && x >= last->xoff + last->sx) + if ((int)x < lcchild->xoff && + (int)x >= last->xoff + (int)last->sx) return (last); break; case LAYOUT_TOPBOTTOM: - if (y < lcchild->yoff && y >= last->yoff + last->sy) + if ((int)y < lcchild->yoff && + (int)y >= last->yoff + (int)last->sy) return (last); break; case LAYOUT_WINDOWPANE: @@ -186,8 +190,8 @@ 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, u_int xoff, - u_int yoff) +layout_set_size(struct layout_cell *lc, u_int sx, u_int sy, int xoff, + int yoff) { lc->sx = sx; lc->sy = sy; @@ -249,7 +253,7 @@ static void layout_fix_offsets1(struct layout_cell *lc) { struct layout_cell *lcchild; - u_int xoff, yoff; + int xoff, yoff; if (lc->type == LAYOUT_LEFTRIGHT) { xoff = lc->xoff; diff --git a/screen-redraw.c b/screen-redraw.c index 2fcfe43bcb..259fc173dd 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -443,7 +443,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, int pane_status = ctx->pane_status; u_int sx = w->sx, sy = w->sy; int border, pane_scrollbars = ctx->pane_scrollbars; - u_int pane_status_line; + int pane_status_line; int sb_pos = ctx->pane_scrollbars_pos; int sb_w, left, right, tiled_only=0; @@ -526,11 +526,11 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, if (pane_status == PANE_STATUS_TOP) pane_status_line = wp->yoff - 1; else - pane_status_line = wp->yoff + wp->sy; + pane_status_line = wp->yoff + (int)wp->sy; left = wp->xoff + 2; - right = wp->xoff + 2 + wp->status_size - 1; + right = wp->xoff + 2 + (int)wp->status_size - 1; - if (py == pane_status_line + ctx->oy && /* XXX unsure about adding oy here, needs more testing. */ + if ((int)py == pane_status_line + (int)ctx->oy && (int)px >= left && (int)px <= right) return (CELL_INSIDE); } @@ -1289,8 +1289,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); - if (wp->xoff + (int)wp->sx <= ctx->ox || - wp->xoff >= ctx->ox + (int)ctx->sx) + if (wp->xoff + (int)wp->sx <= (int)ctx->ox || + wp->xoff >= (int)ctx->ox + (int)ctx->sx) return; /* woy is window y offset in tty. */ @@ -1300,8 +1300,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) woy = 0; for (j = 0; j < wp->sy; j++) { - if (wp->yoff + (int)j < ctx->oy || - wp->yoff + j >= ctx->oy + ctx->sy) + if (wp->yoff + (int)j < (int)ctx->oy || + wp->yoff + (int)j >= (int)ctx->oy + (int)ctx->sy) continue; wy = wp->yoff + j; /* y line within window w. */ py = woy + wy - ctx->oy; /* y line within tty. */ @@ -1312,27 +1312,27 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) /* Note: i is apparenty not used now that the vr array * returns where in s to read from. */ - if (wp->xoff >= ctx->ox && - wp->xoff + wp->sx <= ctx->ox + ctx->sx) { + if (wp->xoff >= (int)ctx->ox && + wp->xoff + (int)wp->sx <= (int)ctx->ox + (int)ctx->sx) { /* All visible. */ i = 0; - wx = wp->xoff - ctx->ox; + wx = (u_int)(wp->xoff - (int)ctx->ox); width = wp->sx; - } else if (wp->xoff < ctx->ox && - wp->xoff + wp->sx > ctx->ox + ctx->sx) { + } else if (wp->xoff < (int)ctx->ox && + wp->xoff + (int)wp->sx > (int)ctx->ox + (int)ctx->sx) { /* Both left and right not visible. */ i = ctx->ox; wx = 0; width = ctx->sx; - } else if (wp->xoff < ctx->ox) { + } else if (wp->xoff < (int)ctx->ox) { /* Left not visible. */ - i = ctx->ox - wp->xoff; + i = (u_int)((int)ctx->ox - wp->xoff); wx = 0; width = wp->sx - i; } else { /* Right not visible. */ i = 0; - wx = wp->xoff - ctx->ox; + wx = (u_int)(wp->xoff - (int)ctx->ox); width = ctx->sx - wx; } log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", diff --git a/server-client.c b/server-client.c index 4f85710e1e..ab67d49a63 100644 --- a/server-client.c +++ b/server-client.c @@ -617,8 +617,8 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, struct options *wo = w->options; struct window_pane *fwp; int pane_status, sb, sb_pos, sb_w, sb_pad; - u_int pane_status_line, sl_top, sl_bottom; - u_int bdr_bottom, bdr_top, bdr_left, bdr_right; + int pane_status_line, sl_top, sl_bottom; + int bdr_bottom, bdr_top, bdr_left, bdr_right; sb = options_get_number(wo, "pane-scrollbars"); sb_pos = options_get_number(wo, "pane-scrollbars-position"); @@ -641,29 +641,30 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, /* Check if point is within the pane or scrollbar. */ if (((pane_status != PANE_STATUS_OFF && - py != pane_status_line && py != wp->yoff + wp->sy) || + (int)py != pane_status_line && (int)py != wp->yoff + (int)wp->sy) || (wp->yoff == 0 && py < wp->sy) || - ((int)py >= wp->yoff && py < wp->yoff + wp->sy)) && + ((int)py >= wp->yoff && (int)py < wp->yoff + (int)wp->sy)) && ((sb_pos == PANE_SCROLLBARS_RIGHT && - px < wp->xoff + wp->sx + sb_pad + sb_w) || + (int)px < wp->xoff + (int)wp->sx + sb_pad + sb_w) || (sb_pos == PANE_SCROLLBARS_LEFT && - px < wp->xoff + wp->sx - sb_pad - sb_w))) { + (int)px < wp->xoff + (int)wp->sx - sb_pad - sb_w))) { /* Check if in the scrollbar. */ if ((sb_pos == PANE_SCROLLBARS_RIGHT && - (px >= wp->xoff + wp->sx + sb_pad && - px < wp->xoff + wp->sx + sb_pad + sb_w)) || + ((int)px >= wp->xoff + (int)wp->sx + sb_pad && + (int)px < wp->xoff + (int)wp->sx + sb_pad + sb_w)) || (sb_pos == PANE_SCROLLBARS_LEFT && ((int)px >= wp->xoff - sb_pad - sb_w && (int)px < wp->xoff - sb_pad))) { /* Check where inside the scrollbar. */ - sl_top = wp->yoff + wp->sb_slider_y; - sl_bottom = (wp->yoff + wp->sb_slider_y + - wp->sb_slider_h - 1); - if (py < sl_top) + sl_top = wp->yoff + (int)wp->sb_slider_y; + sl_bottom = wp->yoff + (int)wp->sb_slider_y + + (int)wp->sb_slider_h - 1; + if ((int)py < sl_top) return (SCROLLBAR_UP); - else if (py >= sl_top && - py <= sl_bottom) { - *sl_mpos = (py - wp->sb_slider_y - wp->yoff); + else if ((int)py >= sl_top && + (int)py <= sl_bottom) { + *sl_mpos = ((int)py - (int)wp->sb_slider_y - + wp->yoff); return (SCROLLBAR_SLIDER); } else /* py > sl_bottom */ return (SCROLLBAR_DOWN); @@ -681,28 +682,31 @@ server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, /* Try the pane borders if not zoomed. */ TAILQ_FOREACH(fwp, &w->panes, entry) { if (sb_pos == PANE_SCROLLBARS_LEFT) - bdr_right = fwp->xoff + fwp->sx; + bdr_right = fwp->xoff + (int)fwp->sx; else /* PANE_SCROLLBARS_RIGHT or none. */ - bdr_right = fwp->xoff + fwp->sx + sb_pad + sb_w; - if ((int)py >= fwp->yoff - 1 && py <= fwp->yoff + fwp->sy) { - if (px == bdr_right) + bdr_right = fwp->xoff + (int)fwp->sx + + sb_pad + sb_w; + if ((int)py >= fwp->yoff - 1 && + (int)py <= fwp->yoff + (int)fwp->sy) { + if ((int)px == bdr_right) break; if (wp->flags & PANE_FLOATING) { /* Floating pane, check if left border. */ bdr_left = fwp->xoff - 1; - if (px == bdr_left) + if ((int)px == bdr_left) break; } } - if ((int)px >= fwp->xoff - 1 && px <= fwp->xoff + fwp->sx) { - bdr_bottom = fwp->yoff + fwp->sy; - if (py == bdr_bottom) + if ((int)px >= fwp->xoff - 1 && + (int)px <= fwp->xoff + (int)fwp->sx) { + bdr_bottom = fwp->yoff + (int)fwp->sy; + if ((int)py == bdr_bottom) break; if (wp->flags & PANE_FLOATING) { /* Floating pane, check if top border. */ bdr_top = fwp->yoff - 1; - if (py == bdr_top) + if ((int)py == bdr_top) break; } } @@ -3088,12 +3092,14 @@ server_client_reset_state(struct client *c) } else if (wp != NULL && c->overlay_draw == NULL) { cursor = 0; tty_window_offset(tty, &ox, &oy, &sx, &sy); - if (wp->xoff + s->cx >= ox && wp->xoff + s->cx <= ox + sx && - wp->yoff + s->cy >= oy && wp->yoff + s->cy <= oy + sy) { + if (wp->xoff + (int)s->cx >= (int)ox && + wp->xoff + (int)s->cx <= (int)ox + (int)sx && + wp->yoff + (int)s->cy >= (int)oy && + wp->yoff + (int)s->cy <= (int)oy + (int)sy) { cursor = 1; - cx = wp->xoff + s->cx - ox; - cy = wp->yoff + s->cy - oy; + cx = (u_int)(wp->xoff + (int)s->cx - (int)ox); + cy = (u_int)(wp->yoff + (int)s->cy - (int)oy); if (status_at_line(c) == 0) cy += status_line_size(c); diff --git a/tmux.h b/tmux.h index c9d3c04c2c..bfc5f8bb0a 100644 --- a/tmux.h +++ b/tmux.h @@ -1423,8 +1423,8 @@ struct layout_cell { u_int sx; u_int sy; - u_int xoff; - u_int yoff; + int xoff; + int yoff; struct window_pane *wp; struct layout_cells cells; @@ -3439,8 +3439,7 @@ void layout_unminimise_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, u_int, - u_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 *); diff --git a/tty.c b/tty.c index 5c79061603..f533b9df1e 100644 --- a/tty.c +++ b/tty.c @@ -1319,9 +1319,11 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, TAILQ_FOREACH(wpl, &w->z_index, zentry) { if (wpl == wp || ~wpl->flags & PANE_FLOATING) continue; - if ((int)wpl->xoff - 1 > (int)(px + nx) || wpl->xoff + wpl->sx + 1 < px) + if ((int)wpl->xoff - 1 > (int)(px + nx) || + wpl->xoff + (int)wpl->sx + 1 < (int)px) continue; - if ((int)wpl->yoff - 1 > (int)(py + ny) || wpl->yoff + wpl->sy + 1 < py) + if ((int)wpl->yoff - 1 > (int)(py + ny) || + wpl->yoff + (int)wpl->sy + 1 < (int)py) continue; overlap++; if (overlap > 0) break; diff --git a/window.c b/window.c index d1a2219e9d..7dd8992884 100644 --- a/window.c +++ b/window.c @@ -71,7 +71,7 @@ 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_full_size_offset(struct window_pane *wp, - u_int *xoff, u_int *yoff, u_int *sx, u_int *sy); + int *xoff, int *yoff, u_int *sx, u_int *sy); RB_GENERATE(windows, window, entry, window_cmp); RB_GENERATE(winlinks, winlink, entry, winlink_cmp); @@ -1469,7 +1469,7 @@ window_pane_choose_best(struct window_pane **list, u_int size) * scrollbars if they were visible but not including the border(s). */ static void -window_pane_full_size_offset(struct window_pane *wp, u_int *xoff, u_int *yoff, +window_pane_full_size_offset(struct window_pane *wp, int *xoff, int *yoff, u_int *sx, u_int *sy) { struct window *w = wp->window; @@ -1503,9 +1503,11 @@ window_pane_find_up(struct window_pane *wp) { struct window *w; struct window_pane *next, *best, **list; - u_int edge, left, right, end, size; + int edge, left, right, end; + u_int size; int status, found; - u_int xoff, yoff, sx, sy; + int xoff, yoff; + u_int sx, sy; if (wp == NULL) return (NULL); @@ -1520,25 +1522,25 @@ window_pane_find_up(struct window_pane *wp) edge = yoff; if (status == PANE_STATUS_TOP) { if (edge == 1) - edge = w->sy + 1; + edge = (int)w->sy + 1; } else if (status == PANE_STATUS_BOTTOM) { if (edge == 0) - edge = w->sy; + edge = (int)w->sy; } else { if (edge == 0) - edge = w->sy + 1; + edge = (int)w->sy + 1; } left = xoff; - right = xoff + sx; + right = xoff + (int)sx; TAILQ_FOREACH(next, &w->panes, entry) { window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); if (next == wp) continue; - if (yoff + sy + 1 != edge) + if (yoff + (int)sy + 1 != edge) continue; - end = xoff + sx - 1; + end = xoff + (int)sx - 1; found = 0; if (xoff < left && end > right) @@ -1564,9 +1566,11 @@ window_pane_find_down(struct window_pane *wp) { struct window *w; struct window_pane *next, *best, **list; - u_int edge, left, right, end, size; + int edge, left, right, end; + u_int size; int status, found; - u_int xoff, yoff, sx, sy; + int xoff, yoff; + u_int sx, sy; if (wp == NULL) return (NULL); @@ -1578,20 +1582,20 @@ window_pane_find_down(struct window_pane *wp) window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); - edge = yoff + sy + 1; + edge = yoff + (int)sy + 1; if (status == PANE_STATUS_TOP) { - if (edge >= w->sy) + if (edge >= (int)w->sy) edge = 1; } else if (status == PANE_STATUS_BOTTOM) { - if (edge >= w->sy - 1) + if (edge >= (int)w->sy - 1) edge = 0; } else { - if (edge >= w->sy) + if (edge >= (int)w->sy) edge = 0; } left = wp->xoff; - right = wp->xoff + wp->sx; + right = wp->xoff + (int)wp->sx; TAILQ_FOREACH(next, &w->panes, entry) { window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); @@ -1599,7 +1603,7 @@ window_pane_find_down(struct window_pane *wp) continue; if (yoff != edge) continue; - end = xoff + sx - 1; + end = xoff + (int)sx - 1; found = 0; if (xoff < left && end > right) @@ -1625,9 +1629,11 @@ window_pane_find_left(struct window_pane *wp) { struct window *w; struct window_pane *next, *best, **list; - u_int edge, top, bottom, end, size; + int edge, top, bottom, end; + u_int size; int found; - u_int xoff, yoff, sx, sy; + int xoff, yoff; + u_int sx, sy; if (wp == NULL) return (NULL); @@ -1640,18 +1646,18 @@ window_pane_find_left(struct window_pane *wp) edge = xoff; if (edge == 0) - edge = w->sx + 1; + edge = (int)w->sx + 1; top = yoff; - bottom = yoff + sy; + bottom = yoff + (int)sy; TAILQ_FOREACH(next, &w->panes, entry) { window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); if (next == wp) continue; - if (xoff + sx + 1 != edge) + if (xoff + (int)sx + 1 != edge) continue; - end = yoff + sy - 1; + end = yoff + (int)sy - 1; found = 0; if (yoff < top && end > bottom) @@ -1677,9 +1683,11 @@ window_pane_find_right(struct window_pane *wp) { struct window *w; struct window_pane *next, *best, **list; - u_int edge, top, bottom, end, size; + int edge, top, bottom, end; + u_int size; int found; - u_int xoff, yoff, sx, sy; + int xoff, yoff; + u_int sx, sy; if (wp == NULL) return (NULL); @@ -1690,12 +1698,12 @@ window_pane_find_right(struct window_pane *wp) window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); - edge = xoff + sx + 1; - if (edge >= w->sx) + edge = xoff + (int)sx + 1; + if (edge >= (int)w->sx) edge = 0; top = wp->yoff; - bottom = wp->yoff + wp->sy; + bottom = wp->yoff + (int)wp->sy; TAILQ_FOREACH(next, &w->panes, entry) { window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); @@ -1703,7 +1711,7 @@ window_pane_find_right(struct window_pane *wp) continue; if (xoff != edge) continue; - end = yoff + sy - 1; + end = yoff + (int)sy - 1; found = 0; if (yoff < top && end > bottom) From 943490cfa17027fbee2b3d1d8cde7c219cc31e66 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 20 Mar 2026 07:49:49 +0000 Subject: [PATCH 082/167] Fix issue when a wp disappears in the middle of a drag event. --- server-client.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server-client.c b/server-client.c index ab67d49a63..4c5b398c26 100644 --- a/server-client.c +++ b/server-client.c @@ -4044,6 +4044,11 @@ server_client_remove_pane(struct window_pane *wp) RB_REMOVE(client_windows, &c->windows, cw); free(cw); } + if (c->tty.mouse_wp == wp) { + c->tty.mouse_wp = NULL; + c->tty.mouse_drag_update = NULL; + c->tty.mouse_scrolling_flag = 0; + } } } From 10a9ce1ed282e29bbc65065956689ebf5bad0919 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 20 Mar 2026 07:59:25 +0000 Subject: [PATCH 083/167] Prohibit swapping 2 floating panes, that doesn't make sense. --- cmd-swap-pane.c | 12 ++++++++++++ layout.c | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 7271e50e2b..f0238e247b 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -79,6 +79,11 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) if (src_wp == dst_wp) goto out; + if ((src_wp->flags & PANE_FLOATING) && (dst_wp->flags & PANE_FLOATING)) { + cmdq_error(item, "cannot swap two floating panes"); + return (CMD_RETURN_ERROR); + } + server_client_remove_pane(src_wp); server_client_remove_pane(dst_wp); @@ -99,6 +104,13 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) dst_lc->wp = src_wp; src_wp->layout_cell = dst_lc; + /* Swap PANE_FLOATING flag to keep each pane consistent with its new + * layout cell (floating cells have parent == NULL). */ + if ((src_wp->flags ^ dst_wp->flags) & PANE_FLOATING) { + src_wp->flags ^= PANE_FLOATING; + dst_wp->flags ^= PANE_FLOATING; + } + src_wp->window = dst_w; options_set_parent(src_wp->options, dst_w->options); src_wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); diff --git a/layout.c b/layout.c index ca85a7af0a..c2811f1eeb 100644 --- a/layout.c +++ b/layout.c @@ -301,6 +301,8 @@ layout_cell_is_top(struct window *w, struct layout_cell *lc) while (lc != w->layout_root) { next = lc->parent; + if (next == NULL) + return (0); if (next->type == LAYOUT_TOPBOTTOM && lc != TAILQ_FIRST(&next->cells)) return (0); @@ -317,6 +319,8 @@ layout_cell_is_bottom(struct window *w, struct layout_cell *lc) while (lc != w->layout_root) { next = lc->parent; + if (next == NULL) + return (0); if (next->type == LAYOUT_TOPBOTTOM && lc != TAILQ_LAST(&next->cells, layout_cells)) return (0); From 0328fe44e46a8e30d54ea40d072c360dac098795 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 22 Mar 2026 09:28:15 +0000 Subject: [PATCH 084/167] Fix sixel image writing so that it is aware of floating panes. --- tty.c | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 156 insertions(+), 17 deletions(-) diff --git a/tty.c b/tty.c index f533b9df1e..1c0c8ad246 100644 --- a/tty.c +++ b/tty.c @@ -2199,20 +2199,169 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx) } #ifdef ENABLE_SIXEL +/* + * Render one sub-rectangle of a sixel image at tty position (dst_x, dst_y). + * Caller must have set tty->flags |= TTY_NOBLOCK and called tty_region_off + * and tty_margin_off. After the call, tty->cx and tty->cy are UINT_MAX + * because the cursor position after a sixel DCS sequence is terminal-defined. + */ +static void +tty_sixel_subrect(struct tty *tty, struct sixel_image *si, + u_int src_i, u_int src_j, u_int src_rx, u_int src_ry, + u_int dst_x, u_int dst_y) +{ + struct sixel_image *sub; + char *data; + size_t size; + + if (src_rx == 0 || src_ry == 0) + return; + sub = sixel_scale(si, tty->xpixel, tty->ypixel, + src_i, src_j, src_rx, src_ry, 0); + if (sub == NULL) + return; + data = sixel_print(sub, si, &size); + sixel_free(sub); + if (data == NULL) + return; + tty_cursor(tty, dst_x, dst_y); + tty_add(tty, data, size); + tty->cx = tty->cy = UINT_MAX; + free(data); +} + +/* + * Render the clamped region of sixel image si, split into sub-rectangles + * that avoid cells obscured by floating panes above this one. + * + * Row breakpoints are computed from each floating pane's top/bottom border + * row. Between breakpoints the visible column ranges are constant, so each + * strip is rendered with one set of sub-rectangles. When no floating panes + * overlap the sixel the loop produces a single strip and one sub-rect equal + * to the full clamped rectangle. + * + * i, j : source cell offset (top-left of visible portion after viewport clip) + * rx, ry: rendered size in cells + * x, y : tty destination position + */ +static void +tty_sixelimage_draw(struct tty *tty, const struct tty_ctx *ctx, + struct sixel_image *si, u_int i, u_int j, u_int rx, u_int ry, + u_int x, u_int y) +{ + struct window_pane *wp, *fp; + struct window *w; + struct visible_ranges *vr; + struct visible_range *ri; + u_int sixel_wx0, sixel_wx1, sixel_y0; + u_int breaks[64], nb, b, s, tmp; + u_int strip_start, strip_end, strip_h; + u_int seg_wx0, seg_wx1, seg_w; + u_int src_i2, src_j2, dst_x2; + int fp_tb, fp_bb1, r_top, r_bot; + + tty->flags |= TTY_NOBLOCK; + tty_region_off(tty); + tty_margin_off(tty); + + wp = ctx->arg; + if (wp == NULL) { + /* Overlay/popup: no pane context, render the full clamped rect. */ + tty_sixel_subrect(tty, si, i, j, rx, ry, x, y); + tty_invalidate(tty); + return; + } + + /* + * sixel_y0 is the window y-coordinate of the first rendered row. + * Derived from tty_clamp_area: y = ctx->yoff + ocy + j - ctx->woy, + * so y + ctx->woy = ctx->yoff + ocy + j. + */ + w = wp->window; + sixel_wx0 = x + ctx->wox; + sixel_wx1 = sixel_wx0 + rx; + sixel_y0 = y + ctx->woy; + + nb = 0; + breaks[nb++] = 0; + breaks[nb++] = ry; + + TAILQ_FOREACH(fp, &w->z_index, zentry) { + if (~fp->flags & PANE_FLOATING) + continue; + fp_tb = (int)((fp->yoff > 0) ? fp->yoff - 1 : 0); + fp_bb1 = (int)fp->yoff + (int)fp->sy + 1; + r_top = fp_tb - (int)sixel_y0; + r_bot = fp_bb1 - (int)sixel_y0; + if (r_top > 0 && (u_int)r_top < ry && nb < 62) + breaks[nb++] = (u_int)r_top; + if (r_bot > 0 && (u_int)r_bot < ry && nb < 62) + breaks[nb++] = (u_int)r_bot; + } + + /* Sort breakpoints (insertion sort; small array). */ + for (b = 1; b < nb; b++) { + tmp = breaks[b]; + s = b; + while (s > 0 && breaks[s - 1] > tmp) { + breaks[s] = breaks[s - 1]; + s--; + } + breaks[s] = tmp; + } + + if (nb > 2) + log_debug("%s: sixel %%%u clipped around floating panes", + __func__, wp->id); + + for (b = 0; b + 1 < nb; b++) { + strip_start = breaks[b]; + strip_end = breaks[b + 1]; + if (strip_end == strip_start) + continue; + strip_h = strip_end - strip_start; + + vr = screen_redraw_get_visible_ranges(wp, ctx->xoff, + sixel_y0 + strip_start, wp->sx, NULL); + + for (s = 0; s < vr->used; s++) { + ri = &vr->ranges[s]; + if (ri->nx == 0) + continue; + seg_wx0 = ri->px; + seg_wx1 = ri->px + ri->nx; + if (seg_wx0 < sixel_wx0) + seg_wx0 = sixel_wx0; + if (seg_wx1 > sixel_wx1) + seg_wx1 = sixel_wx1; + if (seg_wx1 <= seg_wx0) + continue; + seg_w = seg_wx1 - seg_wx0; + src_i2 = i + (seg_wx0 - sixel_wx0); + src_j2 = j + strip_start; + dst_x2 = seg_wx0 - ctx->wox; + tty_sixel_subrect(tty, si, src_i2, src_j2, + seg_w, strip_h, dst_x2, y + strip_start); + } + } + + tty_invalidate(tty); +} + void tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx) { struct image *im = ctx->ptr; struct sixel_image *si = im->data; - struct sixel_image *new; char *data; size_t size; u_int cx = ctx->ocx, cy = ctx->ocy, sx, sy; u_int i, j, x, y, rx, ry; - int fallback = 0; + int fallback; + fallback = 0; if ((~tty->term->flags & TERM_SIXEL) && - !tty_term_has(tty->term, TTYC_SXL)) + !tty_term_has(tty->term, TTYC_SXL)) fallback = 1; if (tty->xpixel == 0 || tty->ypixel == 0) fallback = 1; @@ -2223,30 +2372,20 @@ tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx) return; log_debug("%s: clamping to %u,%u-%u,%u", __func__, i, j, rx, ry); - if (fallback == 1) { + if (fallback) { data = xstrdup(im->fallback); size = strlen(data); - } else { - new = sixel_scale(si, tty->xpixel, tty->ypixel, i, j, rx, ry, 0); - if (new == NULL) - return; - - data = sixel_print(new, si, &size); - } - if (data != NULL) { - log_debug("%s: %zu bytes: %s", __func__, size, data); + tty->flags |= TTY_NOBLOCK; tty_region_off(tty); tty_margin_off(tty); tty_cursor(tty, x, y); - - tty->flags |= TTY_NOBLOCK; tty_add(tty, data, size); tty_invalidate(tty); free(data); + return; } - if (fallback == 0) - sixel_free(new); + tty_sixelimage_draw(tty, ctx, si, i, j, rx, ry, x, y); } #endif From f201d246fd47f9b47530eee39adc97b8c9bc3edb Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 24 Mar 2026 12:41:04 +0000 Subject: [PATCH 085/167] Fix next-layout to ignore floating panes. --- cmd-break-pane.c | 2 +- cmd-join-pane.c | 2 +- cmd-select-pane.c | 2 +- format.c | 2 +- layout-custom.c | 2 +- layout-set.c | 79 ++++++++++++++++++++++++++++++++++------------- layout.c | 3 +- server-fn.c | 2 +- tmux.h | 2 +- window-tree.c | 4 +-- window.c | 7 +++-- 11 files changed, 74 insertions(+), 33 deletions(-) diff --git a/cmd-break-pane.c b/cmd-break-pane.c index add3743b1c..790b8df268 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -73,7 +73,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) } server_unzoom_window(w); - if (window_count_panes(w) == 1) { + if (window_count_panes(w, 1) == 1) { if (server_link_window(src_s, wl, dst_s, idx, 0, !args_has(args, 'd'), &cause) != 0) { cmdq_error(item, "%s", cause); diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 6b6421fa30..c68d358864 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -170,7 +170,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) } else server_status_session(dst_s); - if (window_count_panes(src_w) == 0) + if (window_count_panes(src_w, 1) == 0) server_kill_window(src_w, 1); else notify_window("window-layout-changed", src_w); diff --git a/cmd-select-pane.c b/cmd-select-pane.c index 99b0841c12..febf80adf4 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -103,7 +103,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) * spawned without being visited (for example split-window -d). */ lastwp = TAILQ_FIRST(&w->last_panes); - if (lastwp == NULL && window_count_panes(w) == 2) { + if (lastwp == NULL && window_count_panes(w, 1) == 2) { lastwp = TAILQ_PREV(w->active, window_panes, entry); if (lastwp == NULL) lastwp = TAILQ_NEXT(w->active, entry); diff --git a/format.c b/format.c index 09dfde7716..3cb4e34a7e 100644 --- a/format.c +++ b/format.c @@ -2886,7 +2886,7 @@ static void * format_cb_window_panes(struct format_tree *ft) { if (ft->w != NULL) - return (format_printf("%u", window_count_panes(ft->w))); + return (format_printf("%u", window_count_panes(ft->w, 1))); return (NULL); } diff --git a/layout-custom.c b/layout-custom.c index fc310fb67e..7b91f9ac61 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -213,7 +213,7 @@ layout_parse(struct window *w, const char *layout, char **cause) /* Check this window will fit into the layout. */ for (;;) { - npanes = window_count_panes(w); + npanes = window_count_panes(w, 1); ncells = layout_count_cells(tiled_lc); ncells += layout_count_cells(floating_lc); if (npanes > ncells) { diff --git a/layout-set.c b/layout-set.c index bd68f66387..319016fd3c 100644 --- a/layout-set.c +++ b/layout-set.c @@ -123,6 +123,18 @@ layout_set_previous(struct window *w) return (layout); } +static struct window_pane * +layout_first_tiled(struct window *w) +{ + struct window_pane *wp; + + TAILQ_FOREACH(wp, &w->panes, entry) { + if (~wp->flags & PANE_FLOATING) + return (wp); + } + return (NULL); +} + static void layout_set_even(struct window *w, enum layout_type type) { @@ -133,7 +145,7 @@ layout_set_even(struct window *w, enum layout_type type) layout_print_cell(w->layout_root, __func__, 1); /* Get number of panes. */ - n = window_count_panes(w); + n = window_count_panes(w, 0); if (n <= 1) return; @@ -156,6 +168,8 @@ layout_set_even(struct window *w, enum layout_type type) /* Build new leaf cells. */ TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->flags & PANE_FLOATING) + continue; lcnew = layout_create_cell(lc); layout_make_leaf(lcnew, wp); lcnew->sx = w->sx; @@ -201,7 +215,7 @@ layout_set_main_h(struct window *w) layout_print_cell(w->layout_root, __func__, 1); /* Get number of panes. */ - n = window_count_panes(w); + n = window_count_panes(w, 0); if (n <= 1) return; n--; /* take off main pane */ @@ -250,14 +264,16 @@ layout_set_main_h(struct window *w) /* Create the main pane. */ lcmain = layout_create_cell(lc); layout_set_size(lcmain, sx, mainh, 0, 0); - layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes)); + layout_make_leaf(lcmain, layout_first_tiled(w)); TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); /* Create the other pane. */ lcother = layout_create_cell(lc); layout_set_size(lcother, sx, otherh, 0, 0); if (n == 1) { - wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); + wp = TAILQ_NEXT(layout_first_tiled(w), entry); + while (wp != NULL && (wp->flags & PANE_FLOATING)) + wp = TAILQ_NEXT(wp, entry); layout_make_leaf(lcother, wp); TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); } else { @@ -266,7 +282,9 @@ layout_set_main_h(struct window *w) /* Add the remaining panes as children. */ TAILQ_FOREACH(wp, &w->panes, entry) { - if (wp == TAILQ_FIRST(&w->panes)) + if (wp->flags & PANE_FLOATING) + continue; + if (wp == layout_first_tiled(w)) continue; lcchild = layout_create_cell(lcother); layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0); @@ -299,7 +317,7 @@ layout_set_main_h_mirrored(struct window *w) layout_print_cell(w->layout_root, __func__, 1); /* Get number of panes. */ - n = window_count_panes(w); + n = window_count_panes(w, 0); if (n <= 1) return; n--; /* take off main pane */ @@ -349,7 +367,9 @@ layout_set_main_h_mirrored(struct window *w) lcother = layout_create_cell(lc); layout_set_size(lcother, sx, otherh, 0, 0); if (n == 1) { - wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); + wp = TAILQ_NEXT(layout_first_tiled(w), entry); + while (wp != NULL && (wp->flags & PANE_FLOATING)) + wp = TAILQ_NEXT(wp, entry); layout_make_leaf(lcother, wp); TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); } else { @@ -358,7 +378,9 @@ layout_set_main_h_mirrored(struct window *w) /* Add the remaining panes as children. */ TAILQ_FOREACH(wp, &w->panes, entry) { - if (wp == TAILQ_FIRST(&w->panes)) + if (wp->flags & PANE_FLOATING) + continue; + if (wp == layout_first_tiled(w)) continue; lcchild = layout_create_cell(lcother); layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0); @@ -371,7 +393,7 @@ layout_set_main_h_mirrored(struct window *w) /* Create the main pane. */ lcmain = layout_create_cell(lc); layout_set_size(lcmain, sx, mainh, 0, 0); - layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes)); + layout_make_leaf(lcmain, layout_first_tiled(w)); TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); /* Fix cell offsets. */ @@ -397,7 +419,7 @@ layout_set_main_v(struct window *w) layout_print_cell(w->layout_root, __func__, 1); /* Get number of panes. */ - n = window_count_panes(w); + n = window_count_panes(w, 0); if (n <= 1) return; n--; /* take off main pane */ @@ -446,14 +468,16 @@ layout_set_main_v(struct window *w) /* Create the main pane. */ lcmain = layout_create_cell(lc); layout_set_size(lcmain, mainw, sy, 0, 0); - layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes)); + layout_make_leaf(lcmain, layout_first_tiled(w)); TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); /* Create the other pane. */ lcother = layout_create_cell(lc); layout_set_size(lcother, otherw, sy, 0, 0); if (n == 1) { - wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); + wp = TAILQ_NEXT(layout_first_tiled(w), entry); + while (wp != NULL && (wp->flags & PANE_FLOATING)) + wp = TAILQ_NEXT(wp, entry); layout_make_leaf(lcother, wp); TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); } else { @@ -462,7 +486,9 @@ layout_set_main_v(struct window *w) /* Add the remaining panes as children. */ TAILQ_FOREACH(wp, &w->panes, entry) { - if (wp == TAILQ_FIRST(&w->panes)) + if (wp->flags & PANE_FLOATING) + continue; + if (wp == layout_first_tiled(w)) continue; lcchild = layout_create_cell(lcother); layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0); @@ -495,7 +521,7 @@ layout_set_main_v_mirrored(struct window *w) layout_print_cell(w->layout_root, __func__, 1); /* Get number of panes. */ - n = window_count_panes(w); + n = window_count_panes(w, 0); if (n <= 1) return; n--; /* take off main pane */ @@ -545,7 +571,9 @@ layout_set_main_v_mirrored(struct window *w) lcother = layout_create_cell(lc); layout_set_size(lcother, otherw, sy, 0, 0); if (n == 1) { - wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); + wp = TAILQ_NEXT(layout_first_tiled(w), entry); + while (wp != NULL && (wp->flags & PANE_FLOATING)) + wp = TAILQ_NEXT(wp, entry); layout_make_leaf(lcother, wp); TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); } else { @@ -554,7 +582,9 @@ layout_set_main_v_mirrored(struct window *w) /* Add the remaining panes as children. */ TAILQ_FOREACH(wp, &w->panes, entry) { - if (wp == TAILQ_FIRST(&w->panes)) + if (wp->flags & PANE_FLOATING) + continue; + if (wp == layout_first_tiled(w)) continue; lcchild = layout_create_cell(lcother); layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0); @@ -567,7 +597,7 @@ layout_set_main_v_mirrored(struct window *w) /* Create the main pane. */ lcmain = layout_create_cell(lc); layout_set_size(lcmain, mainw, sy, 0, 0); - layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes)); + layout_make_leaf(lcmain, layout_first_tiled(w)); TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); /* Fix cell offsets. */ @@ -593,7 +623,7 @@ layout_set_tiled(struct window *w) layout_print_cell(w->layout_root, __func__, 1); /* Get number of panes. */ - n = window_count_panes(w); + n = window_count_panes(w, 0); if (n <= 1) return; @@ -629,8 +659,10 @@ layout_set_tiled(struct window *w) layout_set_size(lc, sx, sy, 0, 0); layout_make_node(lc, LAYOUT_TOPBOTTOM); - /* Create a grid of the cells. */ + /* Create a grid of the cells, skipping any floating panes. */ wp = TAILQ_FIRST(&w->panes); + while (wp != NULL && (wp->flags & PANE_FLOATING)) + wp = TAILQ_NEXT(wp, entry); for (j = 0; j < rows; j++) { /* If this is the last cell, all done. */ if (wp == NULL) @@ -645,6 +677,8 @@ layout_set_tiled(struct window *w) if (n - (j * columns) == 1 || columns == 1) { layout_make_leaf(lcrow, wp); wp = TAILQ_NEXT(wp, entry); + while (wp != NULL && (wp->flags & PANE_FLOATING)) + wp = TAILQ_NEXT(wp, entry); continue; } @@ -657,8 +691,11 @@ layout_set_tiled(struct window *w) layout_make_leaf(lcchild, wp); TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry); - /* Move to the next cell. */ - if ((wp = TAILQ_NEXT(wp, entry)) == NULL) + /* Move to the next non-floating cell. */ + wp = TAILQ_NEXT(wp, entry); + while (wp != NULL && (wp->flags & PANE_FLOATING)) + wp = TAILQ_NEXT(wp, entry); + if (wp == NULL) break; } diff --git a/layout.c b/layout.c index c2811f1eeb..3bc9609d19 100644 --- a/layout.c +++ b/layout.c @@ -270,7 +270,8 @@ layout_fix_offsets1(struct layout_cell *lc) } else { yoff = lc->yoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (lcchild->wp->flags & PANE_MINIMISED) + if (lcchild->type == LAYOUT_WINDOWPANE && + lcchild->wp->flags & PANE_MINIMISED) continue; lcchild->xoff = lc->xoff; lcchild->yoff = yoff; diff --git a/server-fn.c b/server-fn.c index 25b7e75c7a..986b8335b6 100644 --- a/server-fn.c +++ b/server-fn.c @@ -184,7 +184,7 @@ server_kill_pane(struct window_pane *wp) { struct window *w = wp->window; - if (window_count_panes(w) == 1) { + if (window_count_panes(w, 1) == 1) { server_kill_window(w, 1); recalculate_sizes(); } else { diff --git a/tmux.h b/tmux.h index 28c7810660..bb2961e12c 100644 --- a/tmux.h +++ b/tmux.h @@ -3375,7 +3375,7 @@ struct window_pane *window_pane_next_by_number(struct window *, struct window_pane *window_pane_previous_by_number(struct window *, struct window_pane *, u_int); int window_pane_index(struct window_pane *, u_int *); -u_int window_count_panes(struct window *); +u_int window_count_panes(struct window *, int); void window_destroy_panes(struct window *); struct window_pane *window_pane_find_by_id_str(const char *); struct window_pane *window_pane_find_by_id(u_int); diff --git a/window-tree.c b/window-tree.c index 2087780942..9d2f857525 100644 --- a/window-tree.c +++ b/window-tree.c @@ -387,7 +387,7 @@ window_tree_build(void *modedata, struct sort_criteria *sort_crit, *tag = (uint64_t)data->fs.wl; break; case WINDOW_TREE_PANE: - if (window_count_panes(data->fs.wl->window) == 1) + if (window_count_panes(data->fs.wl->window, 1) == 1) *tag = (uint64_t)data->fs.wl; else *tag = (uint64_t)data->fs.wp; @@ -566,7 +566,7 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, int colour, active_colour, left, right; char *label; - total = window_count_panes(w); + total = window_count_panes(w, 1); memcpy(&gc, &grid_default_cell, sizeof gc); colour = options_get_number(oo, "display-panes-colour"); diff --git a/window.c b/window.c index ef3d35999e..2f61eb1455 100644 --- a/window.c +++ b/window.c @@ -950,14 +950,17 @@ window_pane_index(struct window_pane *wp, u_int *i) } u_int -window_count_panes(struct window *w) +window_count_panes(struct window *w, int inc_floating) { struct window_pane *wp; u_int n; n = 0; - TAILQ_FOREACH(wp, &w->panes, entry) + TAILQ_FOREACH(wp, &w->panes, entry) { + if ((inc_floating == 0) && (wp->flags & PANE_FLOATING)) + continue; n++; + } return (n); } From 6a62d497206c5c0746ba052a330414e682392a6c Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 24 Mar 2026 14:23:58 +0000 Subject: [PATCH 086/167] Add options to new-pane: -k -m message to wait for key before closing a floating pane. --- cmd-new-pane.c | 12 +++++++++--- options-table.c | 4 ++-- server-client.c | 8 ++++++++ server-fn.c | 5 ++++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/cmd-new-pane.c b/cmd-new-pane.c index a99180e7c6..02c2cdc4da 100644 --- a/cmd-new-pane.c +++ b/cmd-new-pane.c @@ -35,9 +35,9 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:fF:h:Il:p:Pt:w:x:y:Z", 0, -1, NULL }, - .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " - "[-F format] [-l size] " CMD_TARGET_PANE_USAGE + .args = { "bc:de:fF:h:Iklm:p:Pt:w:x:y:Z", 0, -1, NULL }, + .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " + "[-F format] [-l size] [-m message] " CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -204,6 +204,12 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) environ_free(sc.environ); return (CMD_RETURN_ERROR); } + if (args_has(args, 'k') || args_has(args, 'm')) { + options_set_number(new_wp->options, "remain-on-exit", 3); + if (args_has(args, 'm')) + options_set_string(new_wp->options, "remain-on-exit-format", + 0, "%s", args_get(args, 'm')); + } if (input) { switch (window_pane_start_input(new_wp, item, &cause)) { case -1: diff --git a/options-table.c b/options-table.c index 39e5237baf..6c9615f517 100644 --- a/options-table.c +++ b/options-table.c @@ -91,7 +91,7 @@ static const char *options_table_window_size_list[] = { "largest", "smallest", "manual", "latest", NULL }; static const char *options_table_remain_on_exit_list[] = { - "off", "on", "failed", NULL + "off", "on", "failed", "keypress", NULL }; static const char *options_table_destroy_unattached_list[] = { "off", "on", "keep-last", "keep-group", NULL @@ -1414,7 +1414,7 @@ const struct options_table_entry options_table[] = { { .name = "remain-on-exit-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "Pane is dead (" + .default_str = "Command exited (" "#{?#{!=:#{pane_dead_status},}," "status #{pane_dead_status},}" "#{?#{!=:#{pane_dead_signal},}," diff --git a/server-client.c b/server-client.c index 4c5b398c26..4d11c2cca6 100644 --- a/server-client.c +++ b/server-client.c @@ -2665,6 +2665,14 @@ server_client_key_callback(struct cmdq_item *item, void *data) forward_key: if (c->flags & CLIENT_READONLY) goto out; + if (wp != NULL && + options_get_number(wp->options, "remain-on-exit") == 3 && + (wp->flags & PANE_EXITED) && + !KEYC_IS_MOUSE(key) && !KEYC_IS_PASTE(key)) { + options_set_number(wp->options, "remain-on-exit", 0); + server_destroy_pane(wp, 0); + goto out; + } if (wp != NULL) window_pane_key(wp, c, s, wl, key, m); goto out; diff --git a/server-fn.c b/server-fn.c index 986b8335b6..b08ebbf24f 100644 --- a/server-fn.c +++ b/server-fn.c @@ -339,8 +339,11 @@ server_destroy_pane(struct window_pane *wp, int notify) switch (remain_on_exit) { case 0: break; + case 3: /* keypress — fall through to draw remain-on-exit-format message */ + /* FALLTHROUGH */ case 2: - if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0) + if (remain_on_exit == 2 && + WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0) break; /* FALLTHROUGH */ case 1: From 866cb2d16fb1bdbb6787085a2fd579528ddbc6d9 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 24 Mar 2026 14:29:26 +0000 Subject: [PATCH 087/167] Update tmux.1 man page to add new-pane floating pane section. --- tmux.1 | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/tmux.1 b/tmux.1 index d249b257b7..68e4fd8840 100644 --- a/tmux.1 +++ b/tmux.1 @@ -298,6 +298,8 @@ Prompt for a window index to select. Switch the attached client to the previous session. .It \&) Switch the attached client to the next session. +.It * +Create a new floating pane. .It , Rename the current window. .It - @@ -3265,6 +3267,76 @@ By default, it uses the format .Ql #{session_name}:#{window_index} but a different format may be specified with .Fl F . +.Tg newp +.It Xo Ic new-pane +.Op Fl bdefIPZ +.Op Fl c Ar start-directory +.Op Fl e Ar environment +.Op Fl F Ar format +.Op Fl h Ar height +.Op Fl k +.Op Fl l Ar size +.Op Fl m Ar message +.Op Fl p Ar percentage +.Op Fl t Ar target-pane +.Op Fl w Ar width +.Op Fl x Ar x-position +.Op Fl y Ar y-position +.Op Ar shell-command Op Ar argument ... +.Xc +.D1 Pq alias: Ic newp +Create a new floating pane. +The +.Fl w , +.Fl h , +.Fl x , +and +.Fl y +options set the width, height, and position of the pane; if not given, +the pane is sized to half the window dimensions and offset from the +previous floating pane. +The +.Fl l +and +.Fl p +options set the size in lines or as a percentage. +The +.Fl f +option uses the full window dimensions. +.Pp +If +.Fl d +is given, the session does not make the new pane the current pane. +.Fl Z +zooms if the window is not zoomed. +.Pp +.Fl k +keeps the pane open after the shell command exits and waits for a +keypress (any non-mouse key) before closing it. +The message shown is controlled by the +.Ic remain-on-exit-format +option. +.Fl m Ar message +is equivalent to +.Fl k +but also sets the +.Ic remain-on-exit-format +option for this pane to +.Ar message . +.Pp +An empty +.Ar shell-command +(\[aq]\[aq]) will create a pane with no command running in it. +The +.Fl I +flag (if +.Ar shell-command +is not specified or empty) +will create an empty pane and forward any output from stdin to it. +.Pp +All other options have the same meaning as for the +.Ic new-window +command. .Tg nextl .It Ic next-layout Op Fl t Ar target-window .D1 Pq alias: Ic nextl @@ -5571,13 +5643,17 @@ uses when the colour with that index is requested. The index may be from zero to 255. .Pp .It Xo Ic remain-on-exit -.Op Ic on | off | failed +.Op Ic on | off | failed | keypress .Xc A pane with this flag set is not destroyed when the program running in it exits. If set to .Ic failed , then only when the program exit status is not zero. +If set to +.Ic keypress , +the pane stays open and closes when the user presses any key (other than +a mouse button or paste). The pane may be reactivated with the .Ic respawn-pane command. From ef01e9daf8e039d31c7e422de8f7f67980fe8ca3 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 24 Mar 2026 14:35:59 +0000 Subject: [PATCH 088/167] Reviewed and removed the xxx fix-me comments. --- screen-write.c | 1 - tty-draw.c | 2 +- tty.c | 5 ++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/screen-write.c b/screen-write.c index 6618b5cc52..b428581431 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1990,7 +1990,6 @@ screen_write_pane_obscured(struct window_pane *base_wp) return(0); w = base_wp->window; - /* Check if there is a floating pane. xxxx borders? scrollbars? */ TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { if (wp == base_wp) { found_self = 1; diff --git a/tty-draw.c b/tty-draw.c index a27b2d0dd2..fa9cbcd575 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -122,7 +122,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, char buf[1000]; size_t len; enum tty_draw_line_state current_state, next_state; -/* xxx maybe check overlay here? */ + /* * py is the line in the screen to draw. px is the start x and nx is * the width to draw. atx,aty is the line on the terminal to draw it. diff --git a/tty.c b/tty.c index 1c0c8ad246..cdf9f49f09 100644 --- a/tty.c +++ b/tty.c @@ -1556,7 +1556,7 @@ tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c) ttyctx->bigger = tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx, &ttyctx->wsy); - ttyctx->yoff = ttyctx->ryoff = wp->yoff; /* xxxx find another way to do this */ + ttyctx->yoff = ttyctx->ryoff = wp->yoff; if (status_at_line(c) == 0) ttyctx->yoff += status_line_size(c); @@ -1584,7 +1584,7 @@ tty_draw_images(struct client *c, struct window_pane *wp, struct screen *s) ttyctx.sy = wp->sy; ttyctx.ptr = im; - ttyctx.arg = wp; /* xxx remove this */ + ttyctx.arg = wp; ttyctx.set_client_cb = tty_set_client_cb; ttyctx.allow_invisible_panes = 1; tty_write_one(tty_cmd_sixelimage, c, &ttyctx); @@ -2081,7 +2081,6 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) return; if (ctx->num == 2) { - /* xxxx need to check visible range */ tty_draw_line(tty, s, 0, s->cy, screen_size_x(s), ctx->xoff - ctx->wox, py, &ctx->defaults, ctx->palette); return; From 75ed7b27c6ad347c7300edc6d25e61938aeec182 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 24 Mar 2026 17:49:55 +0000 Subject: [PATCH 089/167] Add -S and -s options to style floating panes. --- cmd-new-pane.c | 29 +++++++++++++++++++++++++---- options-table.c | 26 ++++++++++++++++++++++++-- screen-redraw.c | 41 ++++++++++++++++++++++++++++++----------- tmux.1 | 44 ++++++++++++++++++++++++++++++++++++++++++++ tty.c | 27 ++++++++++++++++++++++++--- 5 files changed, 147 insertions(+), 20 deletions(-) diff --git a/cmd-new-pane.c b/cmd-new-pane.c index 02c2cdc4da..1a7911e877 100644 --- a/cmd-new-pane.c +++ b/cmd-new-pane.c @@ -35,10 +35,10 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:fF:h:Iklm:p:Pt:w:x:y:Z", 0, -1, NULL }, + .args = { "bc:de:fF:h:Iklm:p:Ps:S:t:w:x:y:Z", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " - "[-F format] [-l size] [-m message] " CMD_TARGET_PANE_USAGE - " [shell-command [argument ...]]", + "[-F format] [-l size] [-m message] [-s style] [-S border-style] " + CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -62,7 +62,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) struct layout_cell *lc; struct cmd_find_state fs; int flags, input; - const char *template; + const char *template, *style; char *cause = NULL, *cp; struct args_value *av; u_int count = args_count(args); @@ -204,6 +204,27 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) environ_free(sc.environ); return (CMD_RETURN_ERROR); } + style = args_get(args, 's'); + if (style != NULL) { + if (options_set_string(new_wp->options, "window-style", 0, + "%s", style) == NULL) { + cmdq_error(item, "bad style: %s", style); + return (CMD_RETURN_ERROR); + } + options_set_string(new_wp->options, "window-active-style", 0, + "%s", style); + new_wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|PANE_THEMECHANGED); + } + style = args_get(args, 'S'); + if (style != NULL) { + if (options_set_string(new_wp->options, "pane-border-style", 0, + "%s", style) == NULL) { + cmdq_error(item, "bad border style: %s", style); + return (CMD_RETURN_ERROR); + } + options_set_string(new_wp->options, "pane-active-border-style", + 0, "%s", style); + } if (args_has(args, 'k') || args_has(args, 'm')) { options_set_number(new_wp->options, "remain-on-exit", 3); if (args_has(args, 'm')) diff --git a/options-table.c b/options-table.c index 6c9615f517..4125275c94 100644 --- a/options-table.c +++ b/options-table.c @@ -1207,6 +1207,28 @@ const struct options_table_entry options_table[] = { .text = "Character used to fill unused parts of window." }, + { .name = "floating-pane-border-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Default border style for floating panes. " + "Overrides pane-border-style for floating panes unless " + "a per-pane style is set." + }, + + { .name = "floating-pane-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Default content style for floating panes. " + "Overrides window-style for floating panes unless " + "a per-pane style is set." + }, + { .name = "main-pane-height", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, @@ -1283,7 +1305,7 @@ const struct options_table_entry options_table[] = { { .name = "pane-active-border-style", .type = OPTIONS_TABLE_STRING, - .scope = OPTIONS_TABLE_WINDOW, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "#{?pane_in_mode,fg=yellow,#{?synchronize-panes,fg=red,fg=green}}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", @@ -1335,7 +1357,7 @@ const struct options_table_entry options_table[] = { { .name = "pane-border-style", .type = OPTIONS_TABLE_STRING, - .scope = OPTIONS_TABLE_WINDOW, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", diff --git a/screen-redraw.c b/screen-redraw.c index 259fc173dd..539fa1592c 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -600,7 +600,7 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, { struct window *w = wp->window; struct grid_cell gc; - const char *fmt; + const char *fmt, *border_opt; struct format_tree *ft; char *expanded; int pane_status = rctx->pane_status, sb_w = 0; @@ -616,10 +616,19 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS); format_defaults(ft, c, c->session, c->session->curw, wp); - if (wp == server_client_get_pane(c)) - style_apply(&gc, w->options, "pane-active-border-style", ft); - else - style_apply(&gc, w->options, "pane-border-style", ft); + border_opt = (wp == server_client_get_pane(c)) ? + "pane-active-border-style" : "pane-border-style"; + + /* Window-level baseline. */ + style_apply(&gc, w->options, border_opt, ft); + + /* Floating pane window default overrides window baseline. */ + if (wp->flags & PANE_FLOATING) + style_add(&gc, w->options, "floating-pane-border-style", ft); + + /* Per-pane override (set via new-pane -S or set-option -p). */ + if (options_get_only(wp->options, border_opt) != NULL) + style_add(&gc, wp->options, border_opt, ft); fmt = options_get_string(wp->options, "pane-border-format"); expanded = format_expand_time(ft, fmt); @@ -885,7 +894,8 @@ screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x, struct session *s = c->session; struct window *w = s->curw->window; struct window_pane *active = server_client_get_pane(c); - struct options *oo = w->options; + struct options *wo = w->options; + const char *border_opt; struct format_tree *ft; if (wp->border_gc_set) @@ -894,12 +904,21 @@ screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x, ft = format_create_defaults(NULL, c, s, s->curw, wp); - if (screen_redraw_check_is(ctx, x, y, active)) - style_apply(&wp->border_gc, oo, "pane-active-border-style", ft); - else - style_apply(&wp->border_gc, oo, "pane-border-style", ft); - format_free(ft); + border_opt = screen_redraw_check_is(ctx, x, y, active) ? + "pane-active-border-style" : "pane-border-style"; + + /* Window-level baseline. */ + style_apply(&wp->border_gc, wo, border_opt, ft); + + /* Floating pane window default overrides window baseline. */ + if (wp->flags & PANE_FLOATING) + style_add(&wp->border_gc, wo, "floating-pane-border-style", ft); + /* Per-pane override (set via new-pane -S or set-option -p). */ + if (options_get_only(wp->options, border_opt) != NULL) + style_add(&wp->border_gc, wp->options, border_opt, ft); + + format_free(ft); return (&wp->border_gc); } diff --git a/tmux.1 b/tmux.1 index 68e4fd8840..0b52fd937b 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3278,6 +3278,8 @@ but a different format may be specified with .Op Fl l Ar size .Op Fl m Ar message .Op Fl p Ar percentage +.Op Fl s Ar style +.Op Fl S Ar border-style .Op Fl t Ar target-pane .Op Fl w Ar width .Op Fl x Ar x-position @@ -3334,6 +3336,12 @@ flag (if is not specified or empty) will create an empty pane and forward any output from stdin to it. .Pp +.Fl s +sets the style for the pane and +.Fl S +sets the style for the pane border (see +.Sx STYLES ) . +.Pp All other options have the same meaning as for the .Ic new-window command. @@ -5178,6 +5186,42 @@ Set clock hour format. .It Ic fill-character Ar character Set the character used to fill areas of the terminal unused by a window. .Pp +.It Ic floating-pane-border-style Ar style +Set the default border style for all floating panes in the window. +This overrides +.Ic pane-border-style +for floating panes. +A per-pane style set with +.Fl S +on the +.Ic new-pane +command or with +.Ic set-option Fl p +takes priority over this option. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp +.It Ic floating-pane-style Ar style +Set the default content style for all floating panes in the window. +This overrides +.Ic window-style +for floating panes. +A per-pane style set with +.Fl s +on the +.Ic new-pane +command or with +.Ic select-pane Fl P +takes priority over this option. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp .It Ic main-pane-height Ar height .It Ic main-pane-width Ar width Set the width or height of the main (left or top) pane in the diff --git a/tty.c b/tty.c index cdf9f49f09..0804a7434d 100644 --- a/tty.c +++ b/tty.c @@ -3221,7 +3221,8 @@ tty_window_default_style(struct grid_cell *gc, struct window_pane *wp) void tty_default_colours(struct grid_cell *gc, struct window_pane *wp) { - struct options *oo = wp->options; + struct window *w = wp->window; + struct options *wo = w->options; struct format_tree *ft; memcpy(gc, &grid_default_cell, sizeof *gc); @@ -3233,10 +3234,30 @@ tty_default_colours(struct grid_cell *gc, struct window_pane *wp) ft = format_create(NULL, NULL, FORMAT_PANE|wp->id, FORMAT_NOJOBS); format_defaults(ft, NULL, NULL, NULL, wp); + + /* Window-level baseline. */ tty_window_default_style(&wp->cached_active_gc, wp); - style_add(&wp->cached_active_gc, oo, "window-active-style", ft); + style_add(&wp->cached_active_gc, wo, "window-active-style", ft); + /* Floating pane window default overrides window baseline. */ + if (wp->flags & PANE_FLOATING) + style_add(&wp->cached_active_gc, wo, + "floating-pane-style", ft); + /* Per-pane override (set via new-pane -s or select-pane -P). */ + if (options_get_only(wp->options, "window-active-style") != NULL) + style_add(&wp->cached_active_gc, wp->options, + "window-active-style", ft); + + /* Window-level baseline. */ tty_window_default_style(&wp->cached_gc, wp); - style_add(&wp->cached_gc, oo, "window-style", ft); + style_add(&wp->cached_gc, wo, "window-style", ft); + /* Floating pane window default overrides window baseline. */ + if (wp->flags & PANE_FLOATING) + style_add(&wp->cached_gc, wo, "floating-pane-style", ft); + /* Per-pane override (set via new-pane -s or select-pane -P). */ + if (options_get_only(wp->options, "window-style") != NULL) + style_add(&wp->cached_gc, wp->options, + "window-style", ft); + format_free(ft); } From c10a597bf0197cc1012be19f6b79c987b840f4e6 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 24 Mar 2026 18:15:24 +0000 Subject: [PATCH 090/167] add -R to be able to set floating pane inactive border style. --- cmd-new-pane.c | 17 ++++++++++++----- tmux.1 | 9 ++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/cmd-new-pane.c b/cmd-new-pane.c index 1a7911e877..82a600e2f4 100644 --- a/cmd-new-pane.c +++ b/cmd-new-pane.c @@ -35,9 +35,10 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:fF:h:Iklm:p:Ps:S:t:w:x:y:Z", 0, -1, NULL }, + .args = { "bc:de:fF:h:Iklm:p:PR:s:S:t:w:x:y:Z", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " - "[-F format] [-l size] [-m message] [-s style] [-S border-style] " + "[-F format] [-l size] [-m message] " + "[-R inactive-border-style] [-s style] [-S active-border-style] " CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -216,14 +217,20 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) new_wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|PANE_THEMECHANGED); } style = args_get(args, 'S'); + if (style != NULL) { + if (options_set_string(new_wp->options, + "pane-active-border-style", 0, "%s", style) == NULL) { + cmdq_error(item, "bad active border style: %s", style); + return (CMD_RETURN_ERROR); + } + } + style = args_get(args, 'R'); if (style != NULL) { if (options_set_string(new_wp->options, "pane-border-style", 0, "%s", style) == NULL) { - cmdq_error(item, "bad border style: %s", style); + cmdq_error(item, "bad inactive border style: %s", style); return (CMD_RETURN_ERROR); } - options_set_string(new_wp->options, "pane-active-border-style", - 0, "%s", style); } if (args_has(args, 'k') || args_has(args, 'm')) { options_set_number(new_wp->options, "remain-on-exit", 3); diff --git a/tmux.1 b/tmux.1 index 0b52fd937b..10daa5b6a8 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3278,8 +3278,9 @@ but a different format may be specified with .Op Fl l Ar size .Op Fl m Ar message .Op Fl p Ar percentage +.Op Fl R Ar inactive-border-style .Op Fl s Ar style -.Op Fl S Ar border-style +.Op Fl S Ar active-border-style .Op Fl t Ar target-pane .Op Fl w Ar width .Op Fl x Ar x-position @@ -3337,9 +3338,11 @@ is not specified or empty) will create an empty pane and forward any output from stdin to it. .Pp .Fl s -sets the style for the pane and +sets the style for the pane content. .Fl S -sets the style for the pane border (see +sets the border style when the pane is active and +.Fl R +sets the border style when the pane is inactive (see .Sx STYLES ) . .Pp All other options have the same meaning as for the From 999c7246c3c5b75290a8cceddfdd7b623a8a51aa Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 26 Mar 2026 08:46:28 +0000 Subject: [PATCH 091/167] Formatting. --- cmd-minimise-pane.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-minimise-pane.c b/cmd-minimise-pane.c index 310019f2c3..14712726fe 100644 --- a/cmd-minimise-pane.c +++ b/cmd-minimise-pane.c @@ -165,7 +165,7 @@ cmd_minimise_pane_minimise(struct window *w, struct window_pane *wp) notify_window("window-layout-changed", w); server_redraw_window(w); - + return (CMD_RETURN_NORMAL); } From 44229e8af617c80b22a7973d3aa0f454e864a9e4 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sat, 28 Mar 2026 08:35:35 +0000 Subject: [PATCH 092/167] Fix a crash when clicking a floating pane in the status line which was minimised after it was made active by window-tree. Correct the way window-tree displays floating panes so they can be easily unminimmised. --- layout.c | 2 ++ window-tree.c | 18 +++++++++++++++--- window.c | 7 +++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/layout.c b/layout.c index 3bc9609d19..788497db63 100644 --- a/layout.c +++ b/layout.c @@ -628,6 +628,8 @@ layout_unminimise_cell(struct window *w, struct layout_cell *lc) { struct layout_cell *lcother, *lcparent; + if (lc == NULL) + return; lcparent = lc->parent; if (lcparent == NULL) { return; diff --git a/window-tree.c b/window-tree.c index 9d2f857525..ed3c9d69ff 100644 --- a/window-tree.c +++ b/window-tree.c @@ -249,7 +249,7 @@ window_tree_build_window(struct session *s, struct winlink *wl, struct window_tree_itemdata *item; struct mode_tree_item *mti; char *name, *text; - struct window_pane *wp, **l; + struct window_pane *wp, *fwp, **l; u_int n, i; int expanded; struct format_tree *ft; @@ -267,9 +267,21 @@ window_tree_build_window(struct session *s, struct winlink *wl, format_free(ft); if (data->type == WINDOW_TREE_SESSION || - data->type == WINDOW_TREE_WINDOW) + data->type == WINDOW_TREE_WINDOW) { expanded = 0; - else + /* Without this, the only way to reach a minimised + * floating pane would be to first expand the window + * manually (with the right-arrow key) and then press + * its number — which is non-obvious and breaks the + * expected workflow. + */ + TAILQ_FOREACH(fwp, &wl->window->panes, entry) { + if (fwp->flags & PANE_FLOATING) { + expanded = 1; + break; + } + } + } else expanded = 1; mti = mode_tree_add(data->data, parent, item, (uint64_t)wl, name, text, expanded); diff --git a/window.c b/window.c index 2f61eb1455..0ec69372b8 100644 --- a/window.c +++ b/window.c @@ -541,7 +541,7 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) if (wp->flags & PANE_MINIMISED) { wp->flags &= ~PANE_MINIMISED; - if (w->layout_root != NULL) { + if (w->layout_root != NULL && wp->saved_layout_cell != NULL) { wp->layout_cell = wp->saved_layout_cell; wp->saved_layout_cell = NULL; layout_unminimise_cell(w, wp->layout_cell); @@ -791,7 +791,10 @@ window_unzoom(struct window *w, int notify) TAILQ_FOREACH(wp, &w->panes, entry) { wp->layout_cell = wp->saved_layout_cell; - wp->saved_layout_cell = NULL; + if (wp->flags & PANE_MINIMISED) + wp->saved_layout_cell = wp->layout_cell; + else + wp->saved_layout_cell = NULL; wp->flags &= ~PANE_ZOOMED; } layout_fix_panes(w, NULL); From 14088615117dc0fafcb2543d848265309cdba40b Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sat, 28 Mar 2026 09:25:19 +0000 Subject: [PATCH 093/167] Fix minimise window gesture to be like MacOS and Windows where if you click an active window in the status line it minimises it and if its already minimised it unminimises it. The ux feel now is natural. --- key-bindings.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/key-bindings.c b/key-bindings.c index 6a0e110ae5..f3bdabded8 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -351,8 +351,6 @@ key_bindings_init(void) static const char *const defaults[] = { /* Prefix keys. */ "bind -N 'Minimise pane' _ { minimise-pane }", - /* Mouse button 1 double click on status line. */ - "bind -n DoubleClick1Status { minimise-pane -t= }", "bind -N 'Send the prefix key' C-b { send-prefix }", "bind -N 'Rotate through the panes' C-o { rotate-window }", @@ -471,7 +469,7 @@ key_bindings_init(void) "bind -n MouseDrag1Border { resize-pane -M }", /* Mouse button 1 down on status line. */ - "bind -n MouseDown1Status { switch-client -t= }", + "bind -n MouseDown1Status { if -F '#{&&:#{pane_active},#{!#{pane_minimised_flag}}}' { minimise-pane -t= } { switch-client -t= } }", "bind -n C-MouseDown1Status { swap-window -t@ }", /* Mouse wheel down on status line. */ From 8a3620e438c51b10f4c25cbd54fc45a1f1b9e73a Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 30 Mar 2026 17:47:28 -0400 Subject: [PATCH 094/167] Initial commit. --- cmd-tile-float-pane.c | 341 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 cmd-tile-float-pane.c diff --git a/cmd-tile-float-pane.c b/cmd-tile-float-pane.c new file mode 100644 index 0000000000..58129289e6 --- /dev/null +++ b/cmd-tile-float-pane.c @@ -0,0 +1,341 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2026 Michael Grant + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include + +#include "tmux.h" + +/* + * float-pane: lift a tiled pane out of the layout tree into a floating pane. + * tile-pane: insert a floating pane back into the tiled layout. + * + * saved_layout_cell is reused to remember the pane's tiled slot while it is + * floating, using the same mechanism as minimise-pane. The cell's wp pointer + * is cleared while the pane is floating so that layout helpers treat the slot + * as empty. + */ + +static enum cmd_retval cmd_float_pane_exec(struct cmd *, struct cmdq_item *); +static enum cmd_retval cmd_tile_pane_exec(struct cmd *, struct cmdq_item *); + +static enum cmd_retval do_float_pane(struct window *, struct window_pane *, + int, int, u_int, u_int); +static enum cmd_retval do_tile_pane(struct window *, struct window_pane *, + struct cmdq_item *); + +const struct cmd_entry cmd_float_pane_entry = { + .name = "float-pane", + .alias = NULL, + + .args = { "t:x:y:w:h:", 0, 0, NULL }, + .usage = "[-h height] [-w width] [-x x] [-y y] " + CMD_TARGET_PANE_USAGE, + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_float_pane_exec +}; + +const struct cmd_entry cmd_tile_pane_entry = { + .name = "tile-pane", + .alias = NULL, + + .args = { "t:", 0, 0, NULL }, + .usage = CMD_TARGET_PANE_USAGE, + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_tile_pane_exec +}; + +/* + * Parse geometry arguments for float-pane. + * Returns 0 on success, -1 on error (error message already set on item). + * x/y/sx/sy are set to parsed values or cascade defaults. + * last_x/last_y are the static cascade counters; pass the address of the + * caller's statics. + */ +static int +parse_float_geometry(struct args *args, struct cmdq_item *item, + struct window *w, int *out_x, int *out_y, u_int *out_sx, u_int *out_sy, + int *last_x, int *last_y) +{ + char *cause = NULL; + int x, y; + u_int sx, sy; + + /* Default size: half the window. */ + sx = w->sx / 2; + sy = w->sy / 2; + + if (args_has(args, 'w')) { + sx = args_strtonum_and_expand(args, 'w', 1, USHRT_MAX, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "width %s", cause); + free(cause); + return (-1); + } + } + if (args_has(args, 'h')) { + sy = args_strtonum_and_expand(args, 'h', 1, USHRT_MAX, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "height %s", cause); + free(cause); + return (-1); + } + } + + /* Default position: cascade from (5,5), step +5, wrap at window edge. */ + if (args_has(args, 'x')) { + x = args_strtonum_and_expand(args, 'x', SHRT_MIN, SHRT_MAX, + item, &cause); + if (cause != NULL) { + cmdq_error(item, "x %s", cause); + free(cause); + return (-1); + } + } else { + if (*last_x == 0) { + x = 5; + } else { + x = (*last_x += 5); + if (*last_x > (int)w->sx) + x = *last_x = 5; + } + } + if (args_has(args, 'y')) { + y = args_strtonum_and_expand(args, 'y', SHRT_MIN, SHRT_MAX, + item, &cause); + if (cause != NULL) { + cmdq_error(item, "y %s", cause); + free(cause); + return (-1); + } + } else { + if (*last_y == 0) { + y = 5; + } else { + y = (*last_y += 5); + if (*last_y > (int)w->sy) + y = *last_y = 5; + } + } + + *last_x = x; + *last_y = y; + *out_x = x; + *out_y = y; + *out_sx = sx; + *out_sy = sy; + return (0); +} + +static enum cmd_retval +cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct window *w = target->wl->window; + struct window_pane *wp = target->wp; + static int last_x = 0, last_y = 0; + int x, y; + u_int sx, sy; + + if (wp->flags & PANE_FLOATING) { + cmdq_error(item, "pane is already floating"); + return (CMD_RETURN_ERROR); + } + if (wp->flags & PANE_MINIMISED) { + cmdq_error(item, "can't float a minimised pane"); + return (CMD_RETURN_ERROR); + } + if (w->flags & WINDOW_ZOOMED) { + cmdq_error(item, "can't float a pane while window is zoomed"); + return (CMD_RETURN_ERROR); + } + + /* + * If no geometry was given explicitly and we have a saved floating + * position from a previous tile-pane, restore it. + */ + if ((wp->flags & PANE_SAVED_FLOAT) && + !args_has(args, 'x') && !args_has(args, 'y') && + !args_has(args, 'w') && !args_has(args, 'h')) { + x = wp->saved_float_xoff; + y = wp->saved_float_yoff; + sx = wp->saved_float_sx; + sy = wp->saved_float_sy; + } else { + if (parse_float_geometry(args, item, w, &x, &y, &sx, &sy, + &last_x, &last_y) != 0) + return (CMD_RETURN_ERROR); + } + + return (do_float_pane(w, wp, x, y, sx, sy)); +} + +static enum cmd_retval +cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) +{ + __attribute((unused)) struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct window *w = target->wl->window; + struct window_pane *wp = target->wp; + + if (!(wp->flags & PANE_FLOATING)) { + cmdq_error(item, "pane is not floating"); + return (CMD_RETURN_ERROR); + } + if (w->flags & WINDOW_ZOOMED) { + cmdq_error(item, "can't tile a pane while window is zoomed"); + return (CMD_RETURN_ERROR); + } + + return (do_tile_pane(w, wp, item)); +} + +static enum cmd_retval +do_float_pane(struct window *w, struct window_pane *wp, int x, int y, + u_int sx, u_int sy) +{ + struct layout_cell *lc; + + /* + * Remove the pane from the tiled layout tree so neighbours reclaim + * the space. layout_close_pane calls layout_destroy_cell which frees + * the tiled layout_cell and sets wp->layout_cell = NULL via + * layout_free_cell. It also calls layout_fix_offsets/fix_panes and + * notify_window, which is fine to do here before we set up the + * floating cell. + */ + layout_close_pane(wp); /* wp->layout_cell is NULL afterwards */ + + /* Create a detached floating cell with the requested geometry. */ + lc = layout_create_cell(NULL); + lc->xoff = x; + lc->yoff = y; + lc->sx = sx; + lc->sy = sy; + layout_make_leaf(lc, wp); /* sets wp->layout_cell = lc, lc->wp = wp */ + + wp->flags |= PANE_FLOATING; + TAILQ_REMOVE(&w->z_index, wp, zentry); + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); + + if (w->layout_root != NULL) + layout_fix_offsets(w); + layout_fix_panes(w, NULL); + notify_window("window-layout-changed", w); + server_redraw_window(w); + + return (CMD_RETURN_NORMAL); +} + +static enum cmd_retval +do_tile_pane(struct window *w, struct window_pane *wp, struct cmdq_item *item) +{ + struct window_pane *target_wp; + struct layout_cell *float_lc, *lc; + + /* + * Save the floating geometry so we can restore it next time this pane + * is floated without an explicit position/size. + */ + float_lc = wp->layout_cell; + wp->saved_float_xoff = float_lc->xoff; + wp->saved_float_yoff = float_lc->yoff; + wp->saved_float_sx = float_lc->sx; + wp->saved_float_sy = float_lc->sy; + wp->flags |= PANE_SAVED_FLOAT; + + /* + * Free the detached floating cell. Clear its wp pointer first so + * layout_free_cell's WINDOWPANE case does not corrupt wp->layout_cell. + */ + float_lc->wp = NULL; + layout_free_cell(float_lc); /* wp->layout_cell already NULL */ + wp->layout_cell = NULL; + + /* + * Find the best tiled pane to split after: prefer the active pane + * (if tiled), then the most-recently-visited tiled pane, then any + * visible tiled pane. + */ + target_wp = NULL; + if (w->active != NULL && !(w->active->flags & PANE_FLOATING)) + target_wp = w->active; + if (target_wp == NULL) { + TAILQ_FOREACH(target_wp, &w->last_panes, sentry) { + if (!(target_wp->flags & PANE_FLOATING) && + window_pane_visible(target_wp)) + break; + } + } + if (target_wp == NULL) { + TAILQ_FOREACH(target_wp, &w->panes, entry) { + if (!(target_wp->flags & PANE_FLOATING) && + window_pane_visible(target_wp)) + break; + } + } + + if (target_wp != NULL) { + lc = layout_split_pane(target_wp, LAYOUT_TOPBOTTOM, -1, 0); + if (lc == NULL) + lc = layout_split_pane(target_wp, LAYOUT_LEFTRIGHT, + -1, 0); + if (lc == NULL) { + cmdq_error(item, "not enough space to tile pane"); + return (CMD_RETURN_ERROR); + } + layout_assign_pane(lc, wp, 0); + } else { + /* + * No tiled panes at all: make this pane the sole tiled pane + * (new layout root). + */ + lc = layout_create_cell(NULL); + lc->sx = w->sx; + lc->sy = w->sy; + lc->xoff = 0; + lc->yoff = 0; + w->layout_root = lc; + layout_make_leaf(lc, wp); + } + + wp->flags &= ~PANE_FLOATING; + TAILQ_REMOVE(&w->z_index, wp, zentry); + TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); + + window_set_active_pane(w, wp, 1); + + if (w->layout_root != NULL) + layout_fix_offsets(w); + layout_fix_panes(w, NULL); + notify_window("window-layout-changed", w); + server_redraw_window(w); + + return (CMD_RETURN_NORMAL); +} From 4232bf9e2f95994a41eb2a7981562faffaaa1901 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 30 Mar 2026 18:01:55 -0400 Subject: [PATCH 095/167] Add new tile-pane and float-pane commands. --- Makefile.am | 1 + cmd.c | 4 ++++ key-bindings.c | 1 + screen-redraw.c | 3 +-- tmux.1 | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ tmux.h | 7 +++++++ 6 files changed, 65 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index c2cd0dd257..500672de9a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -147,6 +147,7 @@ dist_tmux_SOURCES = \ cmd-source-file.c \ cmd-split-window.c \ cmd-swap-pane.c \ + cmd-tile-float-pane.c \ cmd-swap-window.c \ cmd-switch-client.c \ cmd-unbind-key.c \ diff --git a/cmd.c b/cmd.c index 74f4a18d81..7e2e4fa100 100644 --- a/cmd.c +++ b/cmd.c @@ -69,6 +69,7 @@ extern const struct cmd_entry cmd_load_buffer_entry; extern const struct cmd_entry cmd_lock_client_entry; extern const struct cmd_entry cmd_lock_server_entry; extern const struct cmd_entry cmd_lock_session_entry; +extern const struct cmd_entry cmd_float_pane_entry; extern const struct cmd_entry cmd_minimise_pane_entry; extern const struct cmd_entry cmd_move_pane_entry; extern const struct cmd_entry cmd_move_window_entry; @@ -118,6 +119,7 @@ extern const struct cmd_entry cmd_swap_window_entry; extern const struct cmd_entry cmd_switch_client_entry; extern const struct cmd_entry cmd_unbind_key_entry; extern const struct cmd_entry cmd_unlink_window_entry; +extern const struct cmd_entry cmd_tile_pane_entry; extern const struct cmd_entry cmd_unminimise_pane_entry; extern const struct cmd_entry cmd_wait_for_entry; @@ -164,6 +166,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_lock_client_entry, &cmd_lock_server_entry, &cmd_lock_session_entry, + &cmd_float_pane_entry, &cmd_minimise_pane_entry, &cmd_move_pane_entry, &cmd_move_window_entry, @@ -213,6 +216,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_switch_client_entry, &cmd_unbind_key_entry, &cmd_unlink_window_entry, + &cmd_tile_pane_entry, &cmd_unminimise_pane_entry, &cmd_wait_for_entry, NULL diff --git a/key-bindings.c b/key-bindings.c index f3bdabded8..3ccb7475b0 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -364,6 +364,7 @@ key_bindings_init(void) "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 'New floating pane' * { new-pane }", + "bind -N 'Toggle pane between floating and tiled' @ { if -F '#{pane_floating_flag}' { tile-pane } { float-pane } }", "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 -- '%%' } }", diff --git a/screen-redraw.c b/screen-redraw.c index 4c421533ab..07171b7dd7 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -730,8 +730,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) width = size - x; } - r = tty_check_overlay_range(&c->tty, x, yoff, width); - r = screen_redraw_get_visible_ranges(wp, x, yoff, width, r); + r = screen_redraw_get_visible_ranges(wp, x, yoff, width, NULL); if (ctx->statustop) yoff += ctx->statuslines; diff --git a/tmux.1 b/tmux.1 index c9ca134764..8b36494433 100644 --- a/tmux.1 +++ b/tmux.1 @@ -292,6 +292,8 @@ Rename the current session. Split the current pane into two, left and right. .It & Kill the current window. +.It @ +Toggle the current pane between floating and tiled. .It \[aq] Prompt for a window index to select. .It \&( @@ -2971,6 +2973,41 @@ The default is zooms the pane. .Pp This command works only if at least one client is attached. +.Tg floatp +.It Xo Ic float\-pane +.Op Fl h Ar height +.Op Fl w Ar width +.Op Fl x Ar x +.Op Fl y Ar y +.Op Fl t Ar target\-pane +.Xc +Lift +.Ar target\-pane +out of the tiled layout and make it a floating pane. +The +.Fl w +and +.Fl h +options set the width and height of the floating pane in columns and lines +respectively; the default is half the window width and height. +The +.Fl x +and +.Fl y +options set the position of the upper-left corner of the pane; +if omitted, new floating panes are cascaded from the top-left of the window. +If +.Fl x , +.Fl y , +.Fl w , +and +.Fl h +are all omitted and the pane was previously returned to the tiled layout +with +.Ic tile\-pane , +its last floating position and size are restored. +The pane must not already be floating or minimised, and the window must not +be zoomed. .Tg joinp .It Xo Ic join\-pane .Op Fl bdfhv @@ -3782,6 +3819,20 @@ is omitted and a marked pane is present (see .Ic select\-pane .Fl m ) , the window containing the marked pane is used rather than the current window. +.Tg tilep +.It Xo Ic tile\-pane +.Op Fl t Ar target\-pane +.Xc +Insert a floating +.Ar target\-pane +back into the tiled layout. +The pane is placed by splitting the active tiled pane (or the most recently +active tiled pane, or any visible tiled pane if none is active). +The current floating position and size are saved so they can be restored by +a subsequent +.Ic float\-pane +command with no geometry options. +The pane must be floating and the window must not be zoomed. .Tg unlinkw .It Xo Ic unlink\-window .Op Fl k diff --git a/tmux.h b/tmux.h index bb2961e12c..1a2c644919 100644 --- a/tmux.h +++ b/tmux.h @@ -1231,6 +1231,13 @@ struct window_pane { #define PANE_FLOATING 0x10000 #define PANE_MINIMISED 0x20000 #define PANE_ZOOMED 0x40000 +#define PANE_SAVED_FLOAT 0x80000 /* saved_float_* fields are valid */ + + /* Last floating position/size, saved when the pane is tiled. */ + int saved_float_xoff; + int saved_float_yoff; + u_int saved_float_sx; + u_int saved_float_sy; u_int sb_slider_y; u_int sb_slider_h; From 811604a66377fc71c978a18cdbebc3b280d6be5d Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 5 Apr 2026 19:04:10 -0400 Subject: [PATCH 096/167] Fix bugs with tiling floating panes. --- cmd-minimise-pane.c | 4 ++ cmd-tile-float-pane.c | 124 ++++++++++++++++++++--------------- layout.c | 147 +++++++++++++++++++++++++++++++++++------- tmux.1 | 28 ++++++++ tmux.h | 2 + 5 files changed, 230 insertions(+), 75 deletions(-) diff --git a/cmd-minimise-pane.c b/cmd-minimise-pane.c index 14712726fe..89e47c4e7f 100644 --- a/cmd-minimise-pane.c +++ b/cmd-minimise-pane.c @@ -143,6 +143,10 @@ cmd_minimise_pane_minimise(struct window *w, struct window_pane *wp) { struct window_pane *wp2; + /* Ignore if already minimised to prevent double-redistribution. */ + if (wp->flags & PANE_MINIMISED) + return (CMD_RETURN_NORMAL); + wp->flags |= PANE_MINIMISED; window_deactivate_pane(w, wp, 1); diff --git a/cmd-tile-float-pane.c b/cmd-tile-float-pane.c index 58129289e6..6f3dd8ca4c 100644 --- a/cmd-tile-float-pane.c +++ b/cmd-tile-float-pane.c @@ -36,11 +36,6 @@ static enum cmd_retval cmd_float_pane_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_tile_pane_exec(struct cmd *, struct cmdq_item *); -static enum cmd_retval do_float_pane(struct window *, struct window_pane *, - int, int, u_int, u_int); -static enum cmd_retval do_tile_pane(struct window *, struct window_pane *, - struct cmdq_item *); - const struct cmd_entry cmd_float_pane_entry = { .name = "float-pane", .alias = NULL, @@ -76,7 +71,7 @@ const struct cmd_entry cmd_tile_pane_entry = { * caller's statics. */ static int -parse_float_geometry(struct args *args, struct cmdq_item *item, +cmd_float_pane_parse_geometry(struct args *args, struct cmdq_item *item, struct window *w, int *out_x, int *out_y, u_int *out_sx, u_int *out_sy, int *last_x, int *last_y) { @@ -162,6 +157,7 @@ cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item) static int last_x = 0, last_y = 0; int x, y; u_int sx, sy; + struct layout_cell *lc; if (wp->flags & PANE_FLOATING) { cmdq_error(item, "pane is already floating"); @@ -188,40 +184,11 @@ cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item) sx = wp->saved_float_sx; sy = wp->saved_float_sy; } else { - if (parse_float_geometry(args, item, w, &x, &y, &sx, &sy, - &last_x, &last_y) != 0) + if (cmd_float_pane_parse_geometry(args, item, w, &x, &y, &sx, + &sy, &last_x, &last_y) != 0) return (CMD_RETURN_ERROR); } - return (do_float_pane(w, wp, x, y, sx, sy)); -} - -static enum cmd_retval -cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) -{ - __attribute((unused)) struct args *args = cmd_get_args(self); - struct cmd_find_state *target = cmdq_get_target(item); - struct window *w = target->wl->window; - struct window_pane *wp = target->wp; - - if (!(wp->flags & PANE_FLOATING)) { - cmdq_error(item, "pane is not floating"); - return (CMD_RETURN_ERROR); - } - if (w->flags & WINDOW_ZOOMED) { - cmdq_error(item, "can't tile a pane while window is zoomed"); - return (CMD_RETURN_ERROR); - } - - return (do_tile_pane(w, wp, item)); -} - -static enum cmd_retval -do_float_pane(struct window *w, struct window_pane *wp, int x, int y, - u_int sx, u_int sy) -{ - struct layout_cell *lc; - /* * Remove the pane from the tiled layout tree so neighbours reclaim * the space. layout_close_pane calls layout_destroy_cell which frees @@ -254,10 +221,26 @@ do_float_pane(struct window *w, struct window_pane *wp, int x, int y, } static enum cmd_retval -do_tile_pane(struct window *w, struct window_pane *wp, struct cmdq_item *item) +cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) { - struct window_pane *target_wp; + __attribute((unused)) struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct window *w = target->wl->window; + struct window_pane *wp = target->wp; + struct window_pane *target_wp, *wpiter; struct layout_cell *float_lc, *lc; + int was_minimised; + + if (!(wp->flags & PANE_FLOATING)) { + cmdq_error(item, "pane is not floating"); + return (CMD_RETURN_ERROR); + } + if (w->flags & WINDOW_ZOOMED) { + cmdq_error(item, "can't tile a pane while window is zoomed"); + return (CMD_RETURN_ERROR); + } + + was_minimised = (wp->flags & PANE_MINIMISED) != 0; /* * Save the floating geometry so we can restore it next time this pane @@ -270,37 +253,58 @@ do_tile_pane(struct window *w, struct window_pane *wp, struct cmdq_item *item) wp->saved_float_sy = float_lc->sy; wp->flags |= PANE_SAVED_FLOAT; + /* + * If the pane is also minimised, clear saved_layout_cell before + * freeing the floating cell — otherwise the pointer would dangle. + */ + if (was_minimised) + wp->saved_layout_cell = NULL; + /* * Free the detached floating cell. Clear its wp pointer first so * layout_free_cell's WINDOWPANE case does not corrupt wp->layout_cell. */ float_lc->wp = NULL; - layout_free_cell(float_lc); /* wp->layout_cell already NULL */ + layout_free_cell(float_lc); wp->layout_cell = NULL; /* - * Find the best tiled pane to split after: prefer the active pane - * (if tiled), then the most-recently-visited tiled pane, then any - * visible tiled pane. + * Find the best tiled pane to split after, prefer a visible (non- + * minimised) tiled pane. If all tiled panes are minimised, fall back + * to any tiled pane so the new pane enters the existing tree rather + * than becoming a disconnected root. */ target_wp = NULL; - if (w->active != NULL && !(w->active->flags & PANE_FLOATING)) + if (w->active != NULL && !(w->active->flags & PANE_FLOATING) && + !(w->active->flags & PANE_MINIMISED)) target_wp = w->active; if (target_wp == NULL) { - TAILQ_FOREACH(target_wp, &w->last_panes, sentry) { - if (!(target_wp->flags & PANE_FLOATING) && - window_pane_visible(target_wp)) + TAILQ_FOREACH(wpiter, &w->last_panes, sentry) { + if (!(wpiter->flags & (PANE_FLOATING|PANE_MINIMISED)) && + window_pane_visible(wpiter)) { + target_wp = wpiter; break; + } } } if (target_wp == NULL) { - TAILQ_FOREACH(target_wp, &w->panes, entry) { - if (!(target_wp->flags & PANE_FLOATING) && - window_pane_visible(target_wp)) + TAILQ_FOREACH(wpiter, &w->panes, entry) { + if (!(wpiter->flags & (PANE_FLOATING|PANE_MINIMISED)) && + window_pane_visible(wpiter)) { + target_wp = wpiter; break; + } + } + } + /* Fall back to any tiled pane (even minimised) to stay in the tree. */ + if (target_wp == NULL) { + TAILQ_FOREACH(wpiter, &w->panes, entry) { + if (!(wpiter->flags & PANE_FLOATING)) { + target_wp = wpiter; + break; + } } } - if (target_wp != NULL) { lc = layout_split_pane(target_wp, LAYOUT_TOPBOTTOM, -1, 0); if (lc == NULL) @@ -311,6 +315,14 @@ do_tile_pane(struct window *w, struct window_pane *wp, struct cmdq_item *item) return (CMD_RETURN_ERROR); } layout_assign_pane(lc, wp, 0); + /* + * Redistribute space equally among all visible panes at this + * level, so the new pane gets an equal share rather than just + * half of the split target. + */ + if (wp->layout_cell != NULL && wp->layout_cell->parent != NULL) + layout_redistribute_cells(w, wp->layout_cell->parent, + wp->layout_cell->parent->type); } else { /* * No tiled panes at all: make this pane the sole tiled pane @@ -325,11 +337,19 @@ do_tile_pane(struct window *w, struct window_pane *wp, struct cmdq_item *item) layout_make_leaf(lc, wp); } + /* + * If the pane was minimised while floating, record its new tiled cell + * as the saved cell so unminimise can restore it correctly. + */ + if (was_minimised) + wp->saved_layout_cell = wp->layout_cell; + wp->flags &= ~PANE_FLOATING; TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); - window_set_active_pane(w, wp, 1); + if (!(wp->flags & PANE_MINIMISED)) + window_set_active_pane(w, wp, 1); if (w->layout_root != NULL) layout_fix_offsets(w); diff --git a/layout.c b/layout.c index 788497db63..b83af8a460 100644 --- a/layout.c +++ b/layout.c @@ -46,6 +46,9 @@ static int layout_set_size_check(struct window *, struct layout_cell *, enum layout_type, int); static void layout_resize_child_cells(struct window *, struct layout_cell *); +static struct layout_cell *layout_active_neighbour(struct layout_cell *, int); +void layout_redistribute_cells(struct window *, struct layout_cell *, + enum layout_type); struct layout_cell * layout_create_cell(struct layout_cell *lcparent) @@ -524,6 +527,92 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, } } +/* + * Return the nearest sibling of lc that is not a minimised WINDOWPANE leaf, + * walking forward (forward=1) or backward (forward=0) in the parent's list. + * Container cells (TOPBOTTOM/LEFTRIGHT) are never skipped. + */ +static struct layout_cell * +layout_active_neighbour(struct layout_cell *lc, int forward) +{ + struct layout_cell *lcother; + + if (forward) + lcother = TAILQ_NEXT(lc, entry); + else + lcother = TAILQ_PREV(lc, layout_cells, entry); + + while (lcother != NULL) { + if (lcother->type != LAYOUT_WINDOWPANE) + return (lcother); /* container — not skipped */ + if (lcother->wp == NULL || + !(lcother->wp->flags & PANE_MINIMISED)) + return (lcother); /* visible leaf */ + /* minimised leaf — keep walking */ + if (forward) + lcother = TAILQ_NEXT(lcother, entry); + else + lcother = TAILQ_PREV(lcother, layout_cells, entry); + } + return (NULL); +} + +/* + * Redistribute space equally among all visible (non-minimised WINDOWPANE) + * children of lcparent in the given direction. Minimised WINDOWPANE leaves + * are skipped; their stored sizes are left untouched. Container children + * have their own children resized proportionally via layout_resize_child_cells. + * + * If all children happen to be minimised (n==0), nothing is done. + */ +void +layout_redistribute_cells(struct window *w, struct layout_cell *lcparent, + enum layout_type type) +{ + struct layout_cell *lc; + u_int n, total, each, rem, i, target; + + /* Count visible cells at this level. */ + n = 0; + TAILQ_FOREACH(lc, &lcparent->cells, entry) { + if (lc->type == LAYOUT_WINDOWPANE && + lc->wp != NULL && + (lc->wp->flags & PANE_MINIMISED)) + continue; + n++; + } + if (n == 0) + return; + + total = (type == LAYOUT_LEFTRIGHT) ? lcparent->sx : lcparent->sy; + if (total + 1 < n) /* can't fit even the minimum borders */ + return; + + /* + * each * n + (n-1) borders = total + * → each = (total - (n-1)) / n, rem = (total - (n-1)) % n + * The first `rem` visible cells get (each+1) to consume the remainder. + */ + each = (total - (n - 1)) / n; + rem = (total - (n - 1)) % n; + + i = 0; + TAILQ_FOREACH(lc, &lcparent->cells, entry) { + if (lc->type == LAYOUT_WINDOWPANE && + lc->wp != NULL && + (lc->wp->flags & PANE_MINIMISED)) + continue; + target = each + (i < rem ? 1 : 0); + if (type == LAYOUT_LEFTRIGHT) + lc->sx = target; + else + lc->sy = target; + if (lc->type != LAYOUT_WINDOWPANE) + layout_resize_child_cells(w, lc); + i++; + } +} + /* Destroy a cell and redistribute the space in tiled cells. */ void layout_destroy_cell(struct window *w, struct layout_cell *lc, @@ -546,10 +635,11 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, /* In tiled layouts, merge the space into the previous or next cell. */ if (lcparent->type != LAYOUT_FLOATING) { - if (lc == TAILQ_FIRST(&lcparent->cells)) - lcother = TAILQ_NEXT(lc, entry); - else - lcother = TAILQ_PREV(lc, layout_cells, entry); + int forward; + forward = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0; + lcother = layout_active_neighbour(lc, forward); + if (lcother == NULL) + lcother = layout_active_neighbour(lc, !forward); if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); else if (lcother != NULL) @@ -574,6 +664,19 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, lc->parent = lcparent->parent; if (lc->parent == NULL) { lc->xoff = 0; lc->yoff = 0; + /* + * If the sole remaining child is a minimised + * WINDOWPANE, its stored size may be stale (it never + * received the space that was given to the removed + * cell). Restore the full window size so that + * unminimise can reclaim the correct amount. + */ + if (lc->type == LAYOUT_WINDOWPANE && + lc->wp != NULL && + (lc->wp->flags & PANE_MINIMISED)) { + lc->sx = lcparent->sx; + lc->sy = lcparent->sy; + } *lcroot = lc; } else TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry); @@ -595,11 +698,14 @@ layout_minimise_cell(struct window *w, struct layout_cell *lc) return; } - /* Merge the space into the previous or next cell. */ - if (lc == TAILQ_FIRST(&lcparent->cells)) - lcother = TAILQ_NEXT(lc, entry); - else - lcother = TAILQ_PREV(lc, layout_cells, entry); + /* Merge the space into the nearest non-minimised sibling. */ + { + int forward; + forward = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0; + lcother = layout_active_neighbour(lc, forward); + if (lcother == NULL) + lcother = layout_active_neighbour(lc, !forward); + } if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); else if (lcother != NULL) @@ -626,26 +732,21 @@ layout_minimise_cell(struct window *w, struct layout_cell *lc) void layout_unminimise_cell(struct window *w, struct layout_cell *lc) { - struct layout_cell *lcother, *lcparent; + struct layout_cell *lcparent; if (lc == NULL) return; lcparent = lc->parent; - if (lcparent == NULL) { + if (lcparent == NULL || lcparent->type == LAYOUT_FLOATING) return; - } - /* In tiled layouts, merge the space into the previous or next cell. */ - if (lcparent->type != LAYOUT_FLOATING) { - if (lc == TAILQ_FIRST(&lcparent->cells)) - lcother = TAILQ_NEXT(lc, entry); - else - lcother = TAILQ_PREV(lc, layout_cells, entry); - if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) - layout_resize_adjust(w, lcother, lcparent->type, -(lc->sx + 1)); - else if (lcother != NULL) - layout_resize_adjust(w, lcother, lcparent->type, -(lc->sy + 1)); - } + /* + * Redistribute the parent's space equally among all visible (non- + * minimised) children, including lc which has just been unminimised. + * This ensures every pane at this level gets an equal share rather + * than one pane losing most of its space to the restored pane. + */ + layout_redistribute_cells(w, lcparent, lcparent->type); } void diff --git a/tmux.1 b/tmux.1 index 8b36494433..8f0fdc2d17 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3192,6 +3192,23 @@ or (time). .Fl r reverses the sort order. +.Tg minp +.It Xo Ic minimise\-pane +.Op Fl a +.Op Fl t Ar target\-pane +.Xc +.D1 Pq alias: Ic minimize\-pane +Hide +.Ar target\-pane +from the tiled layout without closing it. +The pane continues to run but is no longer visible and does not occupy any +screen space. +Minimised panes are shown in the status line and can be restored with +.Ic unminimise\-pane . +With +.Fl a , +all visible panes in the window are minimised. +The pane must not already be minimised. .Tg movep .It Xo Ic move\-pane .Op Fl bdfhv @@ -3833,6 +3850,17 @@ a subsequent .Ic float\-pane command with no geometry options. The pane must be floating and the window must not be zoomed. +.Tg unminp +.It Xo Ic unminimise\-pane +.Op Fl t Ar target\-pane +.Xc +.D1 Pq alias: Ic unminimize\-pane +Restore a minimised +.Ar target\-pane +to the tiled layout. +Space is redistributed equally among all visible panes at the same layout +level after the pane is restored. +The pane must be minimised. .Tg unlinkw .It Xo Ic unlink\-window .Op Fl k diff --git a/tmux.h b/tmux.h index 1a2c644919..3b958237f7 100644 --- a/tmux.h +++ b/tmux.h @@ -3445,6 +3445,8 @@ void layout_destroy_cell(struct window *, struct layout_cell *, struct layout_cell **); void layout_minimise_cell(struct window *, struct layout_cell *); void layout_unminimise_cell(struct window *, struct layout_cell *); +void layout_redistribute_cells(struct window *, struct layout_cell *, + enum layout_type); 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); From 950194fb636e575fc0f3cbbe46a9c26006f61014 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 6 Apr 2026 20:10:15 -0700 Subject: [PATCH 097/167] Migrated cmd-split-window.c into cmd-new-pane.c for code reuse. --- Makefile.am | 1 - cmd-new-pane.c | 282 +++++++++++++++++++++++++++++++++------------ cmd-split-window.c | 204 -------------------------------- 3 files changed, 211 insertions(+), 276 deletions(-) delete mode 100644 cmd-split-window.c diff --git a/Makefile.am b/Makefile.am index a956035e6e..c22a9dab76 100644 --- a/Makefile.am +++ b/Makefile.am @@ -145,7 +145,6 @@ dist_tmux_SOURCES = \ cmd-show-options.c \ cmd-show-prompt-history.c \ cmd-source-file.c \ - cmd-split-window.c \ cmd-swap-pane.c \ cmd-tile-float-pane.c \ cmd-swap-window.c \ diff --git a/cmd-new-pane.c b/cmd-new-pane.c index 82a600e2f4..6db42f2a0a 100644 --- a/cmd-new-pane.c +++ b/cmd-new-pane.c @@ -26,16 +26,19 @@ #include "tmux.h" +/* + * Split a window (add a new pane). + */ + #define NEW_PANE_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}" -static enum cmd_retval cmd_new_pane_exec(struct cmd *, - struct cmdq_item *); +static enum cmd_retval cmd_new_pane_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:fF:h:Iklm:p:PR:s:S:t:w:x:y:Z", 0, -1, NULL }, + .args = { "bc:de:fF:h:Iklm:p:PR:s:S:t:T:w:x:y:Z", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] " "[-R inactive-border-style] [-s style] [-S active-border-style] " @@ -47,26 +50,45 @@ const struct cmd_entry cmd_new_pane_entry = { .exec = cmd_new_pane_exec }; +const struct cmd_entry cmd_split_window_entry = { + .name = "split-window", + .alias = "splitw", -static enum cmd_retval -cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) + .args = { "bc:de:fF:hIl:p:Pt:T:vZ", 0, -1, NULL }, + .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " + "[-F format] [-l size] " CMD_TARGET_PANE_USAGE + " [shell-command [argument ...]]", + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = 0, + .exec = cmd_new_pane_exec +}; + +enum new_pane_type { + FLOATING, + TILED, + NONE, +}; + +static enum new_pane_type +cmd_new_pane_get_type(const char* val) { - struct args *args = cmd_get_args(self); - struct cmd_find_state *current = cmdq_get_current(item); - struct cmd_find_state *target = cmdq_get_target(item); - struct spawn_context sc = { 0 }; - struct client *tc = cmdq_get_target_client(item); - struct session *s = target->s; - struct winlink *wl = target->wl; - struct window *w = wl->window; - struct window_pane *wp = target->wp, *new_wp; - struct layout_cell *lc; - struct cmd_find_state fs; - int flags, input; - const char *template, *style; - char *cause = NULL, *cp; - struct args_value *av; - u_int count = args_count(args); + if (strncmp(val, "floating", (sizeof "floating") - 1) == 0 || + strncmp(val, "f", (sizeof "f") - 1) == 0) + return FLOATING; + if (strncmp(val, "tiled", (sizeof "tiled") - 1) == 0 || + strncmp(val, "t", (sizeof "t") - 1) == 0) + return TILED; + return NONE; +} + +static struct layout_cell * +cmd_new_pane_get_float_layout_cell(struct cmdq_item *item, struct args *args, + struct window *w) +{ + struct layout_cell *lc = NULL; + char *cause = NULL; int x, y; u_int sx, sy, pct; static int last_x = 0, last_y = 0; @@ -94,7 +116,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); - return (CMD_RETURN_ERROR); + return (NULL); } } if (args_has(args, 'w')) { @@ -103,7 +125,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); - return (CMD_RETURN_ERROR); + return (NULL); } } if (args_has(args, 'h')) { @@ -112,7 +134,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); - return (CMD_RETURN_ERROR); + return (NULL); } } if (args_has(args, 'x')) { @@ -121,7 +143,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); - return (CMD_RETURN_ERROR); + return (NULL); } } else { if (last_x == 0) { @@ -138,7 +160,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); - return (CMD_RETURN_ERROR); + return (NULL); } } else { if (last_y == 0) { @@ -150,22 +172,6 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) } } - input = (args_has(args, 'I') && count == 0); - - flags = SPAWN_FLOATING; - if (args_has(args, 'b')) - flags |= SPAWN_BEFORE; - if (args_has(args, 'f')) - flags |= SPAWN_FULLSIZE; - if (input || (count == 1 && *args_string(args, 0) == '\0')) - flags |= SPAWN_EMPTY; - - sc.item = item; - sc.s = s; - sc.wl = wl; - - sc.wp0 = wp; - /* Floating panes sit in layout cells which are not in the layout_root * tree so we call it with parent == NULL. */ @@ -174,43 +180,24 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) lc->yoff = y; lc->sx = sx; lc->sy = sy; - sc.lc = lc; - last_x = x; /* Statically save last xoff & yoff so that new */ last_y = y; /* floating panes offset so they don't overlap. */ - args_to_vector(args, &sc.argc, &sc.argv); - sc.environ = environ_create(); - - av = args_first_value(args, 'e'); - while (av != NULL) { - environ_put(sc.environ, av->string, 0); - av = args_next_value(av); - } - - sc.idx = -1; - sc.cwd = args_get(args, 'c'); + return (lc); +} - sc.flags = flags; - if (args_has(args, 'd')) - sc.flags |= SPAWN_DETACHED; - if (args_has(args, 'Z')) - sc.flags |= SPAWN_ZOOM; +static int +cmd_new_pane_style_floating_pane(struct cmdq_item *item, struct args *args, + struct window_pane *new_wp) +{ + const char *style; - if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { - cmdq_error(item, "create pane failed: %s", cause); - free(cause); - if (sc.argv != NULL) - cmd_free_argv(sc.argc, sc.argv); - environ_free(sc.environ); - return (CMD_RETURN_ERROR); - } style = args_get(args, 's'); if (style != NULL) { if (options_set_string(new_wp->options, "window-style", 0, "%s", style) == NULL) { cmdq_error(item, "bad style: %s", style); - return (CMD_RETURN_ERROR); + return (-1); } options_set_string(new_wp->options, "window-active-style", 0, "%s", style); @@ -221,7 +208,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) if (options_set_string(new_wp->options, "pane-active-border-style", 0, "%s", style) == NULL) { cmdq_error(item, "bad active border style: %s", style); - return (CMD_RETURN_ERROR); + return (-1); } } style = args_get(args, 'R'); @@ -229,7 +216,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) if (options_set_string(new_wp->options, "pane-border-style", 0, "%s", style) == NULL) { cmdq_error(item, "bad inactive border style: %s", style); - return (CMD_RETURN_ERROR); + return (-1); } } if (args_has(args, 'k') || args_has(args, 'm')) { @@ -238,10 +225,163 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) options_set_string(new_wp->options, "remain-on-exit-format", 0, "%s", args_get(args, 'm')); } + return (0); +} + +static struct layout_cell * +cmd_new_pane_get_split_layout_cell(struct cmdq_item *item, struct args *args, + struct window *w, struct window_pane *wp, int flags) +{ + enum layout_type type; + struct layout_cell *lc = NULL; + char *cause = NULL; + int size; + u_int curval = 0; + + if (wp->flags & PANE_FLOATING) { + cmdq_error(item, "can't split a floating pane"); + return (NULL); + } + + type = LAYOUT_TOPBOTTOM; + if (args_has(args, 'h')) + type = LAYOUT_LEFTRIGHT; + + /* If the 'p' flag is dropped then this bit can be moved into 'l'. */ + if (args_has(args, 'l') || args_has(args, 'p')) { + if (args_has(args, 'f')) { + if (type == LAYOUT_TOPBOTTOM) + curval = w->sy; + else + curval = w->sx; + } else { + if (type == LAYOUT_TOPBOTTOM) + curval = wp->sy; + else + curval = wp->sx; + } + } + + size = -1; + if (args_has(args, 'l')) { + size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, + item, &cause); + } else if (args_has(args, 'p')) { + size = args_strtonum_and_expand(args, 'p', 0, 100, item, + &cause); + if (cause == NULL) + size = curval * size / 100; + } + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (NULL); + } + + window_push_zoom(wp->window, 1, args_has(args, 'Z')); + lc = layout_split_pane(wp, type, size, flags); + if (lc == NULL) + cmdq_error(item, "no space for new pane"); + + return (lc); +} + +static enum cmd_retval +cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct spawn_context sc = { 0 }; + struct client *tc = cmdq_get_target_client(item); + struct session *s = target->s; + struct winlink *wl = target->wl; + struct window *w = wl->window; + struct window_pane *wp = target->wp, *new_wp; + struct layout_cell *lc = NULL; + struct cmd_find_state fs; + int flags, input; + const char *template; + char *cause = NULL, *cp; + struct args_value *av; + u_int count = args_count(args); + enum new_pane_type pane_type = NONE; + + if (args_has(args, 'T')) { + pane_type = cmd_new_pane_get_type(args_get(args, 'T')); + } else { + if (cmd_get_entry(self) == &cmd_new_pane_entry) + pane_type = FLOATING; + else + pane_type = TILED; + } + + input = (args_has(args, 'I') && count == 0); + + flags = pane_type == FLOATING ? SPAWN_FLOATING : 0; + if (args_has(args, 'b')) + flags |= SPAWN_BEFORE; + if (args_has(args, 'f')) + flags |= SPAWN_FULLSIZE; + if (input || (count == 1 && *args_string(args, 0) == '\0')) + flags |= SPAWN_EMPTY; + + + if (pane_type == FLOATING) { + // return (cmd_new_pane_exec(self, item)); + lc = cmd_new_pane_get_float_layout_cell(item, args, w); + } else if (pane_type == TILED) + lc = cmd_new_pane_get_split_layout_cell(item, args, w, wp, + flags); + else + cmdq_error(item, "unrecognized pane type '%s'", args_get(args, 'T')); + if (lc == NULL) + return (CMD_RETURN_ERROR); + + sc.item = item; + sc.s = s; + sc.wl = wl; + + sc.wp0 = wp; + sc.lc = lc; + + args_to_vector(args, &sc.argc, &sc.argv); + sc.environ = environ_create(); + + av = args_first_value(args, 'e'); + while (av != NULL) { + environ_put(sc.environ, av->string, 0); + av = args_next_value(av); + } + + sc.idx = -1; + sc.cwd = args_get(args, 'c'); + + sc.flags = flags; + if (args_has(args, 'd')) + sc.flags |= SPAWN_DETACHED; + if (args_has(args, 'Z')) + sc.flags |= SPAWN_ZOOM; + + if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { + cmdq_error(item, "create pane failed: %s", cause); + free(cause); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); + return (CMD_RETURN_ERROR); + } + + if (pane_type == FLOATING) + if (cmd_new_pane_style_floating_pane(item, args, new_wp) != 0) + return (CMD_RETURN_ERROR); + if (input) { switch (window_pane_start_input(new_wp, item, &cause)) { case -1: server_client_remove_pane(new_wp); + if (pane_type == TILED) + layout_close_pane(new_wp); window_remove_pane(wp->window, new_wp); cmdq_error(item, "%s", cause); free(cause); diff --git a/cmd-split-window.c b/cmd-split-window.c deleted file mode 100644 index bd2a4b48f2..0000000000 --- a/cmd-split-window.c +++ /dev/null @@ -1,204 +0,0 @@ -/* $OpenBSD$ */ - -/* - * Copyright (c) 2009 Nicholas Marriott - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include - -#include -#include -#include -#include -#include - -#include "tmux.h" - -/* - * Split a window (add a new pane). - */ - -#define SPLIT_WINDOW_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}" - -static enum cmd_retval cmd_split_window_exec(struct cmd *, - struct cmdq_item *); - -const struct cmd_entry cmd_split_window_entry = { - .name = "split-window", - .alias = "splitw", - - .args = { "bc:de:fF:hIl:p:Pt:vZ", 0, -1, NULL }, - .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " - "[-F format] [-l size] " CMD_TARGET_PANE_USAGE - " [shell-command [argument ...]]", - - .target = { 't', CMD_FIND_PANE, 0 }, - - .flags = 0, - .exec = cmd_split_window_exec -}; - -static enum cmd_retval -cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) -{ - struct args *args = cmd_get_args(self); - struct cmd_find_state *current = cmdq_get_current(item); - struct cmd_find_state *target = cmdq_get_target(item); - struct spawn_context sc = { 0 }; - struct client *tc = cmdq_get_target_client(item); - struct session *s = target->s; - struct winlink *wl = target->wl; - struct window *w = wl->window; - struct window_pane *wp = target->wp, *new_wp; - enum layout_type type; - struct layout_cell *lc; - struct cmd_find_state fs; - int size, flags, input; - const char *template; - char *cause = NULL, *cp; - struct args_value *av; - u_int count = args_count(args), curval = 0; - - if (wp->flags & PANE_FLOATING) { - cmdq_error(item, "can't split a floating pane"); - return (CMD_RETURN_ERROR); - } - - type = LAYOUT_TOPBOTTOM; - if (args_has(args, 'h')) - type = LAYOUT_LEFTRIGHT; - - /* If the 'p' flag is dropped then this bit can be moved into 'l'. */ - if (args_has(args, 'l') || args_has(args, 'p')) { - if (args_has(args, 'f')) { - if (type == LAYOUT_TOPBOTTOM) - curval = w->sy; - else - curval = w->sx; - } else { - if (type == LAYOUT_TOPBOTTOM) - curval = wp->sy; - else - curval = wp->sx; - } - } - - size = -1; - if (args_has(args, 'l')) { - size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, - item, &cause); - } else if (args_has(args, 'p')) { - size = args_strtonum_and_expand(args, 'p', 0, 100, item, - &cause); - if (cause == NULL) - size = curval * size / 100; - } - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - - window_push_zoom(wp->window, 1, args_has(args, 'Z')); - input = (args_has(args, 'I') && count == 0); - - flags = 0; - if (args_has(args, 'b')) - flags |= SPAWN_BEFORE; - if (args_has(args, 'f')) - flags |= SPAWN_FULLSIZE; - if (input || (count == 1 && *args_string(args, 0) == '\0')) - flags |= SPAWN_EMPTY; - - lc = layout_split_pane(wp, type, size, flags); - if (lc == NULL) { - cmdq_error(item, "no space for new pane"); - return (CMD_RETURN_ERROR); - } - - sc.item = item; - sc.s = s; - sc.wl = wl; - - sc.wp0 = wp; - sc.lc = lc; - - args_to_vector(args, &sc.argc, &sc.argv); - sc.environ = environ_create(); - - av = args_first_value(args, 'e'); - while (av != NULL) { - environ_put(sc.environ, av->string, 0); - av = args_next_value(av); - } - - sc.idx = -1; - sc.cwd = args_get(args, 'c'); - - sc.flags = flags; - if (args_has(args, 'd')) - sc.flags |= SPAWN_DETACHED; - if (args_has(args, 'Z')) - sc.flags |= SPAWN_ZOOM; - - if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { - cmdq_error(item, "create pane failed: %s", cause); - free(cause); - if (sc.argv != NULL) - cmd_free_argv(sc.argc, sc.argv); - environ_free(sc.environ); - return (CMD_RETURN_ERROR); - } - if (input) { - switch (window_pane_start_input(new_wp, item, &cause)) { - case -1: - server_client_remove_pane(new_wp); - layout_close_pane(new_wp); - window_remove_pane(wp->window, new_wp); - cmdq_error(item, "%s", cause); - free(cause); - if (sc.argv != NULL) - cmd_free_argv(sc.argc, sc.argv); - environ_free(sc.environ); - return (CMD_RETURN_ERROR); - case 1: - input = 0; - break; - } - } - if (!args_has(args, 'd')) - cmd_find_from_winlink_pane(current, wl, new_wp, 0); - window_pop_zoom(wp->window); - server_redraw_window(wp->window); - server_status_session(s); - - if (args_has(args, 'P')) { - if ((template = args_get(args, 'F')) == NULL) - template = SPLIT_WINDOW_TEMPLATE; - cp = format_single(item, template, tc, s, wl, new_wp); - cmdq_print(item, "%s", cp); - free(cp); - } - - cmd_find_from_winlink_pane(&fs, wl, new_wp, 0); - cmdq_insert_hook(s, item, &fs, "after-split-window"); - - if (sc.argv != NULL) - cmd_free_argv(sc.argc, sc.argv); - environ_free(sc.environ); - if (input) - return (CMD_RETURN_WAIT); - return (CMD_RETURN_NORMAL); -} From 4bb7a8675361c12a7a6164d759fd2a547e89e2e1 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 13 Apr 2026 13:50:13 -0700 Subject: [PATCH 098/167] Tested the new commands and updated documentation. --- cmd-find.c | 2 +- cmd-new-pane.c | 175 ++++++++++++++++++++---------------------------- tmux.1 | 176 ++++++++++++++++++++++++------------------------- 3 files changed, 159 insertions(+), 194 deletions(-) diff --git a/cmd-find.c b/cmd-find.c index 747d204a22..8b747a1c2a 100644 --- a/cmd-find.c +++ b/cmd-find.c @@ -1030,7 +1030,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, fs->w = fs->wl->window; fs->wp = fs->w->active; } - goto found; + break; } if (fs->wp == NULL) { if (~flags & CMD_FIND_QUIET) diff --git a/cmd-new-pane.c b/cmd-new-pane.c index 6db42f2a0a..a2e8269a69 100644 --- a/cmd-new-pane.c +++ b/cmd-new-pane.c @@ -38,11 +38,13 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:fF:h:Iklm:p:PR:s:S:t:T:w:x:y:Z", 0, -1, NULL }, + .args = { "bc:de:fF:hH:Ikl:m:p:PR:s:S:t:T:w:x:y:vZ", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " - "[-F format] [-l size] [-m message] " - "[-R inactive-border-style] [-s style] [-S active-border-style] " - CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]", + "[-F format] [-H height] [-l size] [-m message] " + "[-R inactive-border-style] [-s style] " + "[-S active-border-style] [-w width] [-x x-position] " + "[-y y-position]" CMD_TARGET_PANE_USAGE "[-T type] " + " [shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -54,9 +56,12 @@ const struct cmd_entry cmd_split_window_entry = { .name = "split-window", .alias = "splitw", - .args = { "bc:de:fF:hIl:p:Pt:T:vZ", 0, -1, NULL }, - .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " - "[-F format] [-l size] " CMD_TARGET_PANE_USAGE + .args = { "bc:de:fF:hH:Ikl:m:p:PR:s:S:t:T:w:x:y:vZ", 0, -1, NULL }, + .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " + "[-F format] [-H height] [-l size] [-m message] " + "[-R inactive-border-style] [-s style] " + "[-S active-border-style] [-w width] [-x x-position] " + "[-y y-position]" CMD_TARGET_PANE_USAGE "[-T type] " " [shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -84,53 +89,41 @@ cmd_new_pane_get_type(const char* val) } static struct layout_cell * -cmd_new_pane_get_float_layout_cell(struct cmdq_item *item, struct args *args, +cmd_new_pane_get_floating_layout_cell(struct cmdq_item *item, struct args *args, struct window *w) { struct layout_cell *lc = NULL; char *cause = NULL; int x, y; - u_int sx, sy, pct; + u_int sx = w->sx / 2, sy = w->sy / 2; static int last_x = 0, last_y = 0; - if (args_has(args, 'f')) { - sx = w->sx; - sy = w->sy; + if (last_x == 0) { + x = 4; } else { - if (args_has(args, 'l')) { - sx = args_percentage_and_expand(args, 'l', 0, INT_MAX, w->sx, - item, &cause); - sy = args_percentage_and_expand(args, 'l', 0, INT_MAX, w->sy, - item, &cause); - } else if (args_has(args, 'p')) { - pct = args_strtonum_and_expand(args, 'p', 0, 100, item, - &cause); - if (cause == NULL) { - sx = w->sx * pct / 100; - sy = w->sy * pct / 100; - } - } else if (cause == NULL) { - sx = w->sx / 2; - sy = w->sy / 2; - } - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (NULL); - } + x = (last_x += 4); + if (last_x > (int)w->sx) + x = 4; + } + if (last_y == 0) { + y = 2; + } else { + y = (last_y += 2); + if (last_y > (int)w->sy) + y = 2; } if (args_has(args, 'w')) { - sx = args_strtonum_and_expand(args, 'w', 1, USHRT_MAX, item, - &cause); + sx = args_percentage_and_expand(args, 'w', 0, USHRT_MAX, w->sx, + item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); return (NULL); } } - if (args_has(args, 'h')) { - sy = args_strtonum_and_expand(args, 'h', 1, USHRT_MAX, item, - &cause); + if (args_has(args, 'H')) { + sy = args_percentage_and_expand(args, 'H', 0, USHRT_MAX, w->sy, + item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); @@ -138,38 +131,22 @@ cmd_new_pane_get_float_layout_cell(struct cmdq_item *item, struct args *args, } } if (args_has(args, 'x')) { - x = args_strtonum_and_expand(args, 'x', SHRT_MIN, SHRT_MAX, + x = args_percentage_and_expand(args, 'x', 0, USHRT_MAX, w->sx, item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); return (NULL); } - } else { - if (last_x == 0) { - x = 5; - } else { - x = (last_x += 5); - if (last_x > (int)w->sx) - x = 5; - } } if (args_has(args, 'y')) { - y = args_strtonum_and_expand(args, 'y', SHRT_MIN, SHRT_MAX, + y = args_percentage_and_expand(args, 'y', 0, USHRT_MAX, w->sy, item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); return (NULL); } - } else { - if (last_y == 0) { - y = 5; - } else { - y = (last_y += 5); - if (last_y > (int)w->sy) - y = 5; - } } /* Floating panes sit in layout cells which are not in the layout_root @@ -186,50 +163,8 @@ cmd_new_pane_get_float_layout_cell(struct cmdq_item *item, struct args *args, return (lc); } -static int -cmd_new_pane_style_floating_pane(struct cmdq_item *item, struct args *args, - struct window_pane *new_wp) -{ - const char *style; - - style = args_get(args, 's'); - if (style != NULL) { - if (options_set_string(new_wp->options, "window-style", 0, - "%s", style) == NULL) { - cmdq_error(item, "bad style: %s", style); - return (-1); - } - options_set_string(new_wp->options, "window-active-style", 0, - "%s", style); - new_wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|PANE_THEMECHANGED); - } - style = args_get(args, 'S'); - if (style != NULL) { - if (options_set_string(new_wp->options, - "pane-active-border-style", 0, "%s", style) == NULL) { - cmdq_error(item, "bad active border style: %s", style); - return (-1); - } - } - style = args_get(args, 'R'); - if (style != NULL) { - if (options_set_string(new_wp->options, "pane-border-style", 0, - "%s", style) == NULL) { - cmdq_error(item, "bad inactive border style: %s", style); - return (-1); - } - } - if (args_has(args, 'k') || args_has(args, 'm')) { - options_set_number(new_wp->options, "remain-on-exit", 3); - if (args_has(args, 'm')) - options_set_string(new_wp->options, "remain-on-exit-format", - 0, "%s", args_get(args, 'm')); - } - return (0); -} - static struct layout_cell * -cmd_new_pane_get_split_layout_cell(struct cmdq_item *item, struct args *args, +cmd_new_pane_get_tiled_layout_cell(struct cmdq_item *item, struct args *args, struct window *w, struct window_pane *wp, int flags) { enum layout_type type; @@ -301,7 +236,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) struct layout_cell *lc = NULL; struct cmd_find_state fs; int flags, input; - const char *template; + const char *template, *style; char *cause = NULL, *cp; struct args_value *av; u_int count = args_count(args); @@ -328,10 +263,9 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) if (pane_type == FLOATING) { - // return (cmd_new_pane_exec(self, item)); - lc = cmd_new_pane_get_float_layout_cell(item, args, w); + lc = cmd_new_pane_get_floating_layout_cell(item, args, w); } else if (pane_type == TILED) - lc = cmd_new_pane_get_split_layout_cell(item, args, w, wp, + lc = cmd_new_pane_get_tiled_layout_cell(item, args, w, wp, flags); else cmdq_error(item, "unrecognized pane type '%s'", args_get(args, 'T')); @@ -372,9 +306,40 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } - if (pane_type == FLOATING) - if (cmd_new_pane_style_floating_pane(item, args, new_wp) != 0) + style = args_get(args, 's'); + if (style != NULL) { + if (options_set_string(new_wp->options, "window-style", 0, + "%s", style) == NULL) { + cmdq_error(item, "bad style: %s", style); + return (CMD_RETURN_ERROR); + } + options_set_string(new_wp->options, "window-active-style", 0, + "%s", style); + new_wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|PANE_THEMECHANGED); + } + style = args_get(args, 'S'); + if (style != NULL) { + if (options_set_string(new_wp->options, + "pane-active-border-style", 0, "%s", style) == NULL) { + cmdq_error(item, "bad active border style: %s", style); + return (CMD_RETURN_ERROR); + } + } + style = args_get(args, 'R'); + if (style != NULL) { + if (options_set_string(new_wp->options, "pane-border-style", 0, + "%s", style) == NULL) { + cmdq_error(item, "bad inactive border style: %s", style); return (CMD_RETURN_ERROR); + } + } + if (args_has(args, 'k') || args_has(args, 'm')) { + options_set_number(new_wp->options, "remain-on-exit", 3); + if (args_has(args, 'm')) + options_set_string(new_wp->options, + "remain-on-exit-format", + 0, "%s", args_get(args, 'm')); + } if (input) { switch (window_pane_start_input(new_wp, item, &cause)) { diff --git a/tmux.1 b/tmux.1 index c3f0d9b2b8..cae207483f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3323,12 +3323,11 @@ but a different format may be specified with .Fl F . .Tg newp .It Xo Ic new-pane -.Op Fl bdefIPZ +.Op Fl bdefhIkPvZ .Op Fl c Ar start-directory .Op Fl e Ar environment .Op Fl F Ar format -.Op Fl h Ar height -.Op Fl k +.Op Fl H Ar height .Op Fl l Ar size .Op Fl m Ar message .Op Fl p Ar percentage @@ -3342,35 +3341,36 @@ but a different format may be specified with .Op Ar shell-command Op Ar argument ... .Xc .D1 Pq alias: Ic newp -Create a new floating pane. -The -.Fl w , -.Fl h , -.Fl x , -and -.Fl y -options set the width, height, and position of the pane; if not given, -the pane is sized to half the window dimensions and offset from the -previous floating pane. -The -.Fl l -and -.Fl p -options set the size in lines or as a percentage. -The -.Fl f -option uses the full window dimensions. +Create a new pane. The new pane may be floating by specifying the type with +.Fl Tf +/ +.Fl Tfloating , +or tiled into the layout by splitting an existing pane with +.Fl Tt +/ +.Fl Ttiled . +When creating a tiled pane, a target pane may be specified with +.Fl t . +Note that some options are related to dimensions/layout and so will only affect +one pane type. Those options will be in their own sections. .Pp If .Fl d is given, the session does not make the new pane the current pane. .Fl Z -zooms if the window is not zoomed. -.Pp +zooms if the window is not zoomed, or keeps it zoomed if already zoomed. +.Fl s +sets the style for the pane content. +.Fl S +sets the border style when the pane is active and +.Fl R +sets the border style when the pane is inactive (see +.Sx STYLES ) . .Fl k -keeps the pane open after the shell command exits and waits for a -keypress (any non-mouse key) before closing it. -The message shown is controlled by the +keeps the pane open after the optional +.Ar shell-command +exits and waits for a keypress (any non-mouse key) before closing it. The +message shown is controlled by the .Ic remain-on-exit-format option. .Fl m Ar message @@ -3380,7 +3380,6 @@ but also sets the .Ic remain-on-exit-format option for this pane to .Ar message . -.Pp An empty .Ar shell-command (\[aq]\[aq]) will create a pane with no command running in it. @@ -3389,15 +3388,49 @@ The flag (if .Ar shell-command is not specified or empty) -will create an empty pane and forward any output from stdin to it. +will create an empty pane and forward any output from stdin to it. For example: +.Bd -literal -offset indent +$ make 2>&1|tmux splitw \-dI & +.Ed .Pp -.Fl s -sets the style for the pane content. -.Fl S -sets the border style when the pane is active and -.Fl R -sets the border style when the pane is inactive (see -.Sx STYLES ) . +For floating panes, the following flags are availible: +The +.Fl w , +.Fl h , +.Fl x , +and +.Fl y +options set the width, height, and position of the pane; if not given, +the pane is sized to half the window dimensions and offset from the +previous floating pane. These four options may be followed by '%' to specify +a percentage of the current window dimensions. +.Pp +For tiled panes, the following flags are availible: +.Fl h +does a horizontal split and +.Fl v +a vertical split; if neither is specified, +.Fl v +is assumed. +The +.Fl l +option specifies the size of the new pane in lines (for vertical split) or in +columns (for horizontal split); +.Ar size +may be followed by +.Ql % +to specify a percentage of the available space. +The +.Fl b +option causes the new pane to be created to the left of or above +.Ar target\-pane . +The +.Fl f +option creates a new pane spanning the full window height (with +.Fl h ) +or full window width (with +.Fl v ) , +instead of splitting the active pane. .Pp All other options have the same meaning as for the .Ic new-window @@ -3726,65 +3759,32 @@ the command behaves like .Ic last\-window . .Tg splitw .It Xo Ic split\-window -.Op Fl bdfhIvPZ -.Op Fl c Ar start\-directory +.Op Fl bdefhIkPvZ +.Op Fl c Ar start-directory .Op Fl e Ar environment .Op Fl F Ar format +.Op Fl H Ar height .Op Fl l Ar size -.Op Fl t Ar target\-pane -.Op Ar shell\-command Op Ar argument ... +.Op Fl m Ar message +.Op Fl p Ar percentage +.Op Fl R Ar inactive-border-style +.Op Fl s Ar style +.Op Fl S Ar active-border-style +.Op Fl t Ar target-pane +.Op Fl T Ar type +.Op Fl w Ar width +.Op Fl x Ar x-position +.Op Fl y Ar y-position +.Op Ar shell-command Op Ar argument ... .Xc .D1 Pq alias: Ic splitw -Create a new pane by splitting -.Ar target\-pane : -.Fl h -does a horizontal split and -.Fl v -a vertical split; if neither is specified, -.Fl v -is assumed. -The -.Fl l -option specifies the size of the new pane in lines (for vertical split) or in -columns (for horizontal split); -.Ar size -may be followed by -.Ql % -to specify a percentage of the available space. -The -.Fl b -option causes the new pane to be created to the left of or above -.Ar target\-pane . -The -.Fl f -option creates a new pane spanning the full window height (with -.Fl h ) -or full window width (with -.Fl v ) , -instead of splitting the active pane. -.Fl Z -zooms if the window is not zoomed, or keeps it zoomed if already zoomed. +Creates a new pane. Default behavior is to split the pane in a tiled layout. +Shares behavior with +.Ic new-pane . .Pp -An empty -.Ar shell\-command -(\[aq]\[aq]) will create a pane with no command running in it. -Output can be sent to such a pane with the -.Ic display\-message -command. -The -.Fl I -flag (if -.Ar shell\-command -is not specified or empty) -will create an empty pane and forward any output from stdin to it. -For example: -.Bd -literal -offset indent -$ make 2>&1|tmux splitw \-dI & -.Ed -.Pp -All other options have the same meaning as for the -.Ic new\-window -command. +See +.Ic new-pane +for more details. .Tg swapp .It Xo Ic swap\-pane .Op Fl dDUZ From f992c68fd878c0d0dd7b2c0b3911d46682eeb750 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 13 Apr 2026 22:04:08 -0700 Subject: [PATCH 099/167] slight touchup --- tmux.1 | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tmux.1 b/tmux.1 index cae207483f..1bf1791004 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3335,13 +3335,16 @@ but a different format may be specified with .Op Fl s Ar style .Op Fl S Ar active-border-style .Op Fl t Ar target-pane +.Op Fl T Ar type .Op Fl w Ar width .Op Fl x Ar x-position .Op Fl y Ar y-position .Op Ar shell-command Op Ar argument ... .Xc .D1 Pq alias: Ic newp -Create a new pane. The new pane may be floating by specifying the type with +Create a new pane. The new pane may be floating by specifying the +.Ar type +with .Fl Tf / .Fl Tfloating , @@ -3351,8 +3354,10 @@ or tiled into the layout by splitting an existing pane with .Fl Ttiled . When creating a tiled pane, a target pane may be specified with .Fl t . -Note that some options are related to dimensions/layout and so will only affect -one pane type. Those options will be in their own sections. +Note that some options are related to dimensions/layout. Those options will +only affect one creation +.Ar type +and will be in their own sections. .Pp If .Fl d From 1bc85cb59e90df5ad1d2e4234202315114dbec3b Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Tue, 14 Apr 2026 12:40:59 -0700 Subject: [PATCH 100/167] Adjustment from feedback and slight touchups. --- Makefile.am | 2 +- cmd-find.c | 2 +- cmd-new-pane.c => cmd-split-window.c | 82 +++++++++++++--------------- tmux.1 | 58 +++++++++++--------- 4 files changed, 74 insertions(+), 70 deletions(-) rename cmd-new-pane.c => cmd-split-window.c (82%) diff --git a/Makefile.am b/Makefile.am index c22a9dab76..999e990968 100644 --- a/Makefile.am +++ b/Makefile.am @@ -115,7 +115,6 @@ dist_tmux_SOURCES = \ cmd-lock-server.c \ cmd-minimise-pane.c \ cmd-move-window.c \ - cmd-new-pane.c \ cmd-new-session.c \ cmd-new-window.c \ cmd-parse.y \ @@ -145,6 +144,7 @@ dist_tmux_SOURCES = \ cmd-show-options.c \ cmd-show-prompt-history.c \ cmd-source-file.c \ + cmd-split-window.c \ cmd-swap-pane.c \ cmd-tile-float-pane.c \ cmd-swap-window.c \ diff --git a/cmd-find.c b/cmd-find.c index 8b747a1c2a..747d204a22 100644 --- a/cmd-find.c +++ b/cmd-find.c @@ -1030,7 +1030,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, fs->w = fs->wl->window; fs->wp = fs->w->active; } - break; + goto found; } if (fs->wp == NULL) { if (~flags & CMD_FIND_QUIET) diff --git a/cmd-new-pane.c b/cmd-split-window.c similarity index 82% rename from cmd-new-pane.c rename to cmd-split-window.c index a2e8269a69..0789d0dc58 100644 --- a/cmd-new-pane.c +++ b/cmd-split-window.c @@ -27,70 +27,58 @@ #include "tmux.h" /* - * Split a window (add a new pane). + * Create a new pane. */ #define NEW_PANE_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}" -static enum cmd_retval cmd_new_pane_exec(struct cmd *, struct cmdq_item *); +static enum cmd_retval cmd_split_window_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:fF:hH:Ikl:m:p:PR:s:S:t:T:w:x:y:vZ", 0, -1, NULL }, + .args = { "bc:de:fF:hH:Ikl:m:M:p:PR:s:S:t:w:x:y:vZ", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " "[-F format] [-H height] [-l size] [-m message] " - "[-R inactive-border-style] [-s style] " + "[-M mode] [-R inactive-border-style] [-s style] " "[-S active-border-style] [-w width] [-x x-position] " - "[-y y-position]" CMD_TARGET_PANE_USAGE "[-T type] " - " [shell-command [argument ...]]", + "[-y y-position]" CMD_TARGET_PANE_USAGE + "[shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, .flags = 0, - .exec = cmd_new_pane_exec + .exec = cmd_split_window_exec }; const struct cmd_entry cmd_split_window_entry = { .name = "split-window", .alias = "splitw", - .args = { "bc:de:fF:hH:Ikl:m:p:PR:s:S:t:T:w:x:y:vZ", 0, -1, NULL }, + .args = { "bc:de:fF:hH:Ikl:m:M:p:PR:s:S:t:w:x:y:vZ", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " "[-F format] [-H height] [-l size] [-m message] " - "[-R inactive-border-style] [-s style] " + "[-M mode] [-R inactive-border-style] [-s style] " "[-S active-border-style] [-w width] [-x x-position] " - "[-y y-position]" CMD_TARGET_PANE_USAGE "[-T type] " - " [shell-command [argument ...]]", + "[-y y-position]" CMD_TARGET_PANE_USAGE + "[shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, .flags = 0, - .exec = cmd_new_pane_exec + .exec = cmd_split_window_exec }; -enum new_pane_type { +enum new_pane_mode { FLOATING, TILED, NONE, }; -static enum new_pane_type -cmd_new_pane_get_type(const char* val) -{ - if (strncmp(val, "floating", (sizeof "floating") - 1) == 0 || - strncmp(val, "f", (sizeof "f") - 1) == 0) - return FLOATING; - if (strncmp(val, "tiled", (sizeof "tiled") - 1) == 0 || - strncmp(val, "t", (sizeof "t") - 1) == 0) - return TILED; - return NONE; -} - static struct layout_cell * -cmd_new_pane_get_floating_layout_cell(struct cmdq_item *item, struct args *args, - struct window *w) +cmd_split_window_get_floating_layout_cell(struct cmdq_item *item, + struct args *args, struct window *w) { struct layout_cell *lc = NULL; char *cause = NULL; @@ -164,8 +152,8 @@ cmd_new_pane_get_floating_layout_cell(struct cmdq_item *item, struct args *args, } static struct layout_cell * -cmd_new_pane_get_tiled_layout_cell(struct cmdq_item *item, struct args *args, - struct window *w, struct window_pane *wp, int flags) +cmd_split_window_get_tiled_layout_cell(struct cmdq_item *item, + struct args *args, struct window *w, struct window_pane *wp, int flags) { enum layout_type type; struct layout_cell *lc = NULL; @@ -222,7 +210,7 @@ cmd_new_pane_get_tiled_layout_cell(struct cmdq_item *item, struct args *args, } static enum cmd_retval -cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) +cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); struct cmd_find_state *current = cmdq_get_current(item); @@ -240,20 +228,25 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) char *cause = NULL, *cp; struct args_value *av; u_int count = args_count(args); - enum new_pane_type pane_type = NONE; + enum new_pane_mode pane_mode = NONE; - if (args_has(args, 'T')) { - pane_type = cmd_new_pane_get_type(args_get(args, 'T')); + if (args_has(args, 'M')) { + if (strcasecmp(args_get(args, 'M'), "f") == 0) + pane_mode = FLOATING; + else if (strcasecmp(args_get(args, 'M'), "t") == 0) + pane_mode = TILED; + else + pane_mode = NONE; } else { if (cmd_get_entry(self) == &cmd_new_pane_entry) - pane_type = FLOATING; + pane_mode = FLOATING; else - pane_type = TILED; + pane_mode = TILED; } input = (args_has(args, 'I') && count == 0); - flags = pane_type == FLOATING ? SPAWN_FLOATING : 0; + flags = pane_mode == FLOATING ? SPAWN_FLOATING : 0; if (args_has(args, 'b')) flags |= SPAWN_BEFORE; if (args_has(args, 'f')) @@ -262,13 +255,16 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) flags |= SPAWN_EMPTY; - if (pane_type == FLOATING) { - lc = cmd_new_pane_get_floating_layout_cell(item, args, w); - } else if (pane_type == TILED) - lc = cmd_new_pane_get_tiled_layout_cell(item, args, w, wp, + if (pane_mode == FLOATING) + lc = cmd_split_window_get_floating_layout_cell(item, args, w); + else if (pane_mode == TILED) + lc = cmd_split_window_get_tiled_layout_cell(item, args, w, wp, flags); - else - cmdq_error(item, "unrecognized pane type '%s'", args_get(args, 'T')); + else { + cmdq_error(item, "unrecognized pane mode '%s'", + args_get(args, 'M')); + return (CMD_RETURN_ERROR); + } if (lc == NULL) return (CMD_RETURN_ERROR); @@ -345,7 +341,7 @@ cmd_new_pane_exec(struct cmd *self, struct cmdq_item *item) switch (window_pane_start_input(new_wp, item, &cause)) { case -1: server_client_remove_pane(new_wp); - if (pane_type == TILED) + if (pane_mode == TILED) layout_close_pane(new_wp); window_remove_pane(wp->window, new_wp); cmdq_error(item, "%s", cause); diff --git a/tmux.1 b/tmux.1 index 1bf1791004..dcb1549061 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3330,34 +3330,39 @@ but a different format may be specified with .Op Fl H Ar height .Op Fl l Ar size .Op Fl m Ar message +.Op Fl M Ar mode .Op Fl p Ar percentage .Op Fl R Ar inactive-border-style .Op Fl s Ar style .Op Fl S Ar active-border-style .Op Fl t Ar target-pane -.Op Fl T Ar type .Op Fl w Ar width .Op Fl x Ar x-position .Op Fl y Ar y-position .Op Ar shell-command Op Ar argument ... .Xc .D1 Pq alias: Ic newp -Create a new pane. The new pane may be floating by specifying the -.Ar type -with -.Fl Tf -/ -.Fl Tfloating , -or tiled into the layout by splitting an existing pane with -.Fl Tt -/ -.Fl Ttiled . +Create a new pane. +A +.Ar mode +may be specified with the +.Fl M +option and must be followed by one of the following special values: +.Bl -column "XXXXX" -offset indent +.It Sy "Value" Ta Sy "Meaning" +.It Li "f" Ta "floating pane above the current layout" +.It Li "t" Ta "tiled into the layout by splitting a pane" +.El +.Pp +If no +.Ar mode +is specified, +.Ic f +is assumed. When creating a tiled pane, a target pane may be specified with .Fl t . -Note that some options are related to dimensions/layout. Those options will -only affect one creation -.Ar type -and will be in their own sections. +Note that some options will only affect one +.Ar mode . .Pp If .Fl d @@ -3374,8 +3379,8 @@ sets the border style when the pane is inactive (see .Fl k keeps the pane open after the optional .Ar shell-command -exits and waits for a keypress (any non-mouse key) before closing it. The -message shown is controlled by the +exits and waits for a keypress (any non-mouse key) before closing it. +The message shown is controlled by the .Ic remain-on-exit-format option. .Fl m Ar message @@ -3393,24 +3398,26 @@ The flag (if .Ar shell-command is not specified or empty) -will create an empty pane and forward any output from stdin to it. For example: +will create an empty pane and forward any output from stdin to it. +For example: .Bd -literal -offset indent $ make 2>&1|tmux splitw \-dI & .Ed .Pp -For floating panes, the following flags are availible: +For floating panes, the following options are availible: The .Fl w , .Fl h , .Fl x , and .Fl y -options set the width, height, and position of the pane; if not given, -the pane is sized to half the window dimensions and offset from the -previous floating pane. These four options may be followed by '%' to specify -a percentage of the current window dimensions. +options set the width, height, and position of the pane. +If not given, the pane is sized to half the window dimensions and offset from +the previous floating pane. +These four options may be followed by '%' to specify a percentage of the +current window dimensions. .Pp -For tiled panes, the following flags are availible: +For tiled panes, the following options are availible: .Fl h does a horizontal split and .Fl v @@ -3783,7 +3790,8 @@ the command behaves like .Op Ar shell-command Op Ar argument ... .Xc .D1 Pq alias: Ic splitw -Creates a new pane. Default behavior is to split the pane in a tiled layout. +Creates a new pane. +Default behavior is to split the pane in a tiled layout. Shares behavior with .Ic new-pane . .Pp From 7a4e35e317f4d0d9dc9b0943bc85d75657311b79 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Tue, 14 Apr 2026 13:08:19 -0700 Subject: [PATCH 101/167] doc fix. --- tmux.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tmux.1 b/tmux.1 index dcb1549061..19bfd9781f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3778,12 +3778,12 @@ the command behaves like .Op Fl H Ar height .Op Fl l Ar size .Op Fl m Ar message +.Op Fl M Ar mode .Op Fl p Ar percentage .Op Fl R Ar inactive-border-style .Op Fl s Ar style .Op Fl S Ar active-border-style .Op Fl t Ar target-pane -.Op Fl T Ar type .Op Fl w Ar width .Op Fl x Ar x-position .Op Fl y Ar y-position From 914ffc888785f4046d9a3fab59a4042871842c3d Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Wed, 15 Apr 2026 12:33:08 -0700 Subject: [PATCH 102/167] touchup. --- cmd-split-window.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 0789d0dc58..d2979646d0 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -30,7 +30,7 @@ * Create a new pane. */ -#define NEW_PANE_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}" +#define SPLIT_WINDOW_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}" static enum cmd_retval cmd_split_window_exec(struct cmd *, struct cmdq_item *); @@ -363,7 +363,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) - template = NEW_PANE_TEMPLATE; + template = SPLIT_WINDOW_TEMPLATE; cp = format_single(item, template, tc, s, wl, new_wp); cmdq_print(item, "%s", cp); free(cp); From a7ad0c443408ad27125bcd70bf464be378ffa000 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Thu, 16 Apr 2026 11:38:33 -0700 Subject: [PATCH 103/167] Option flag adjustment for eventual consistency. --- cmd-split-window.c | 36 ++++++++++++++++++------------------ tmux.1 | 14 +++++++------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index d2979646d0..46b365fc8a 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -38,12 +38,12 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:fF:hH:Ikl:m:M:p:PR:s:S:t:w:x:y:vZ", 0, -1, NULL }, + .args = { "bc:de:fF:hIkl:m:M:p:PR:s:S:t:x:X:y:Y:vZ", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " - "[-F format] [-H height] [-l size] [-m message] " - "[-M mode] [-R inactive-border-style] [-s style] " - "[-S active-border-style] [-w width] [-x x-position] " - "[-y y-position]" CMD_TARGET_PANE_USAGE + "[-F format] [-l size] [-m message] [-M mode] " + "[-R inactive-border-style] [-s style] " + "[-S active-border-style] [-x width] [-X x-position]" + "[-y length] [-Y y-position]" CMD_TARGET_PANE_USAGE "[shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -56,12 +56,12 @@ const struct cmd_entry cmd_split_window_entry = { .name = "split-window", .alias = "splitw", - .args = { "bc:de:fF:hH:Ikl:m:M:p:PR:s:S:t:w:x:y:vZ", 0, -1, NULL }, + .args = { "bc:de:fF:hIkl:m:M:p:PR:s:S:t:x:X:y:Y:vZ", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " - "[-F format] [-H height] [-l size] [-m message] " - "[-M mode] [-R inactive-border-style] [-s style] " - "[-S active-border-style] [-w width] [-x x-position] " - "[-y y-position]" CMD_TARGET_PANE_USAGE + "[-F format] [-l size] [-m message] [-M mode] " + "[-R inactive-border-style] [-s style] " + "[-S active-border-style] [-x width] [-X x-position]" + "[-y length] [-Y y-position]" CMD_TARGET_PANE_USAGE "[shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -100,8 +100,8 @@ cmd_split_window_get_floating_layout_cell(struct cmdq_item *item, if (last_y > (int)w->sy) y = 2; } - if (args_has(args, 'w')) { - sx = args_percentage_and_expand(args, 'w', 0, USHRT_MAX, w->sx, + if (args_has(args, 'x')) { + sx = args_percentage_and_expand(args, 'x', 0, USHRT_MAX, w->sx, item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); @@ -109,8 +109,8 @@ cmd_split_window_get_floating_layout_cell(struct cmdq_item *item, return (NULL); } } - if (args_has(args, 'H')) { - sy = args_percentage_and_expand(args, 'H', 0, USHRT_MAX, w->sy, + if (args_has(args, 'y')) { + sy = args_percentage_and_expand(args, 'y', 0, USHRT_MAX, w->sy, item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); @@ -118,8 +118,8 @@ cmd_split_window_get_floating_layout_cell(struct cmdq_item *item, return (NULL); } } - if (args_has(args, 'x')) { - x = args_percentage_and_expand(args, 'x', 0, USHRT_MAX, w->sx, + if (args_has(args, 'X')) { + x = args_percentage_and_expand(args, 'X', 0, USHRT_MAX, w->sx, item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); @@ -127,8 +127,8 @@ cmd_split_window_get_floating_layout_cell(struct cmdq_item *item, return (NULL); } } - if (args_has(args, 'y')) { - y = args_percentage_and_expand(args, 'y', 0, USHRT_MAX, w->sy, + if (args_has(args, 'Y')) { + y = args_percentage_and_expand(args, 'Y', 0, USHRT_MAX, w->sy, item, &cause); if (cause != NULL) { cmdq_error(item, "size %s", cause); diff --git a/tmux.1 b/tmux.1 index 19bfd9781f..7fa136d733 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3327,7 +3327,6 @@ but a different format may be specified with .Op Fl c Ar start-directory .Op Fl e Ar environment .Op Fl F Ar format -.Op Fl H Ar height .Op Fl l Ar size .Op Fl m Ar message .Op Fl M Ar mode @@ -3336,9 +3335,10 @@ but a different format may be specified with .Op Fl s Ar style .Op Fl S Ar active-border-style .Op Fl t Ar target-pane -.Op Fl w Ar width -.Op Fl x Ar x-position -.Op Fl y Ar y-position +.Op Fl x Ar width +.Op Fl X Ar x-position +.Op Fl y Ar length +.Op Fl Y Ar y-position .Op Ar shell-command Op Ar argument ... .Xc .D1 Pq alias: Ic newp @@ -3406,11 +3406,11 @@ $ make 2>&1|tmux splitw \-dI & .Pp For floating panes, the following options are availible: The -.Fl w , -.Fl h , .Fl x , +.Fl y , +.Fl X , and -.Fl y +.Fl Y options set the width, height, and position of the pane. If not given, the pane is sized to half the window dimensions and offset from the previous floating pane. From 0a55e5ca541c924c4f8515e55f8db18560222801 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Fri, 17 Apr 2026 12:44:50 -0700 Subject: [PATCH 104/167] Fixed logical error in handing default cascading. --- cmd-split-window.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 46b365fc8a..046dc66e23 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -83,23 +83,13 @@ cmd_split_window_get_floating_layout_cell(struct cmdq_item *item, struct layout_cell *lc = NULL; char *cause = NULL; int x, y; - u_int sx = w->sx / 2, sy = w->sy / 2; + u_int sx, sy; static int last_x = 0, last_y = 0; - if (last_x == 0) { - x = 4; - } else { - x = (last_x += 4); - if (last_x > (int)w->sx) - x = 4; - } - if (last_y == 0) { - y = 2; - } else { - y = (last_y += 2); - if (last_y > (int)w->sy) - y = 2; - } + /* Default size. */ + sx = w->sx / 2; + sy = w->sy / 2; + if (args_has(args, 'x')) { sx = args_percentage_and_expand(args, 'x', 0, USHRT_MAX, w->sx, item, &cause); @@ -118,6 +108,8 @@ cmd_split_window_get_floating_layout_cell(struct cmdq_item *item, return (NULL); } } + + /* If a position is not defined, it defaults to cascading. */ if (args_has(args, 'X')) { x = args_percentage_and_expand(args, 'X', 0, USHRT_MAX, w->sx, item, &cause); @@ -126,6 +118,12 @@ cmd_split_window_get_floating_layout_cell(struct cmdq_item *item, free(cause); return (NULL); } + } else if (last_x == 0) + x = 4; + else { + x = (last_x += 4); + if (last_x > (int)w->sx) + x = 4; } if (args_has(args, 'Y')) { y = args_percentage_and_expand(args, 'Y', 0, USHRT_MAX, w->sy, @@ -135,6 +133,12 @@ cmd_split_window_get_floating_layout_cell(struct cmdq_item *item, free(cause); return (NULL); } + } else if (last_y == 0) + y = 2; + else { + y = (last_y += 2); + if (last_y > (int)w->sy) + y = 2; } /* Floating panes sit in layout cells which are not in the layout_root From 05fdd042629327a9ced3a219df2f5f67ad04b8c1 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 24 Apr 2026 00:49:22 +0100 Subject: [PATCH 105/167] Fix the z-index of the pane. (nic's fix.) --- cmd-break-pane.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 790b8df268..4be989c3ee 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -96,6 +96,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) } TAILQ_REMOVE(&w->panes, wp, entry); + TAILQ_REMOVE(&w->z_index, wp, zentry); server_client_remove_pane(wp); window_lost_pane(w, wp); layout_close_pane(wp); @@ -104,6 +105,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) options_set_parent(wp->options, w->options); wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); TAILQ_INSERT_HEAD(&w->panes, wp, entry); + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); w->active = wp; w->latest = tc; From eec2c19ad100d261a2b95e0715259fa3260ec564 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Thu, 23 Apr 2026 17:07:53 -0700 Subject: [PATCH 106/167] oversights. --- tmux.1 | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tmux.1 b/tmux.1 index 0ae9241f91..4d4621596b 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3322,7 +3322,7 @@ By default, it uses the format but a different format may be specified with .Fl F . .Tg newp -.It Xo Ic new-pane +.It Xo Ic new\-pane .Op Fl bdefhIkPvZ .Op Fl c Ar start-directory .Op Fl e Ar environment @@ -3775,7 +3775,6 @@ the command behaves like .Op Fl c Ar start-directory .Op Fl e Ar environment .Op Fl F Ar format -.Op Fl H Ar height .Op Fl l Ar size .Op Fl m Ar message .Op Fl M Ar mode @@ -3784,19 +3783,20 @@ the command behaves like .Op Fl s Ar style .Op Fl S Ar active-border-style .Op Fl t Ar target-pane -.Op Fl w Ar width -.Op Fl x Ar x-position -.Op Fl y Ar y-position +.Op Fl x Ar width +.Op Fl X Ar x-position +.Op Fl y Ar height +.Op Fl Y Ar y-position .Op Ar shell-command Op Ar argument ... .Xc .D1 Pq alias: Ic splitw Creates a new pane. Default behavior is to split the pane in a tiled layout. Shares behavior with -.Ic new-pane . +.Ic new\-pane . .Pp See -.Ic new-pane +.Ic new\-pane for more details. .Tg swapp .It Xo Ic swap\-pane @@ -5289,7 +5289,7 @@ for floating panes. A per-pane style set with .Fl S on the -.Ic new-pane +.Ic new\-pane command or with .Ic set-option Fl p takes priority over this option. @@ -5307,7 +5307,7 @@ for floating panes. A per-pane style set with .Fl s on the -.Ic new-pane +.Ic new\-pane command or with .Ic select-pane Fl P takes priority over this option. From a1cd68e3f82b6a017b80d18948b88bd9a176f1d5 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Thu, 23 Apr 2026 20:23:50 -0700 Subject: [PATCH 107/167] Fixed z-index related crash in `join-pane` --- cmd-join-pane.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd-join-pane.c b/cmd-join-pane.c index c68d358864..2dfbda83b3 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -146,14 +146,18 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) server_client_remove_pane(src_wp); window_lost_pane(src_w, src_wp); TAILQ_REMOVE(&src_w->panes, src_wp, entry); + TAILQ_REMOVE(&src_w->z_index, src_wp, zentry); src_wp->window = dst_w; options_set_parent(src_wp->options, dst_w->options); src_wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); - if (flags & SPAWN_BEFORE) + if (flags & SPAWN_BEFORE) { TAILQ_INSERT_BEFORE(dst_wp, src_wp, entry); - else + TAILQ_INSERT_BEFORE(dst_wp, src_wp, zentry); + } else { TAILQ_INSERT_AFTER(&dst_w->panes, dst_wp, src_wp, entry); + TAILQ_INSERT_AFTER(&dst_w->z_index, dst_wp, src_wp, zentry); + } layout_assign_pane(lc, src_wp, 0); colour_palette_from_option(&src_wp->palette, src_wp->options); From a74f1739026386b9276c153e6818fa0d86d34221 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Thu, 23 Apr 2026 21:53:06 -0700 Subject: [PATCH 108/167] more. --- cmd-split-window.c | 4 ++-- tmux.1 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 046dc66e23..0cd6d8daaa 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -43,7 +43,7 @@ const struct cmd_entry cmd_new_pane_entry = { "[-F format] [-l size] [-m message] [-M mode] " "[-R inactive-border-style] [-s style] " "[-S active-border-style] [-x width] [-X x-position]" - "[-y length] [-Y y-position]" CMD_TARGET_PANE_USAGE + "[-y height] [-Y y-position]" CMD_TARGET_PANE_USAGE "[shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -61,7 +61,7 @@ const struct cmd_entry cmd_split_window_entry = { "[-F format] [-l size] [-m message] [-M mode] " "[-R inactive-border-style] [-s style] " "[-S active-border-style] [-x width] [-X x-position]" - "[-y length] [-Y y-position]" CMD_TARGET_PANE_USAGE + "[-y height] [-Y y-position]" CMD_TARGET_PANE_USAGE "[shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, diff --git a/tmux.1 b/tmux.1 index 4d4621596b..764a9f4b8b 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3337,7 +3337,7 @@ but a different format may be specified with .Op Fl t Ar target-pane .Op Fl x Ar width .Op Fl X Ar x-position -.Op Fl y Ar length +.Op Fl y Ar height .Op Fl Y Ar y-position .Op Ar shell-command Op Ar argument ... .Xc From 0719dcf2e75938fc738b6e18deb377cc21fbb492 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Sat, 25 Apr 2026 15:05:58 -0700 Subject: [PATCH 109/167] fixed site of null dereference in layout.c --- layout.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/layout.c b/layout.c index b83af8a460..df7c5177e8 100644 --- a/layout.c +++ b/layout.c @@ -262,6 +262,7 @@ layout_fix_offsets1(struct layout_cell *lc) xoff = lc->xoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (lcchild->type == LAYOUT_WINDOWPANE && + lcchild->wp != NULL && lcchild->wp->flags & PANE_MINIMISED) continue; lcchild->xoff = xoff; @@ -274,6 +275,7 @@ layout_fix_offsets1(struct layout_cell *lc) yoff = lc->yoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (lcchild->type == LAYOUT_WINDOWPANE && + lcchild->wp != NULL && lcchild->wp->flags & PANE_MINIMISED) continue; lcchild->xoff = lc->xoff; From e18c10d340ae92559b5bf8a50ffdf7612b526003 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 27 Apr 2026 19:59:12 -0700 Subject: [PATCH 110/167] Passing in '&type' to avoid null dereference --- screen-redraw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen-redraw.c b/screen-redraw.c index 066bc94feb..8fd9839eae 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -974,7 +974,7 @@ screen_redraw_draw_border_arrows(struct screen_redraw_ctx *ctx, u_int i, } if ((int)j == wp->yoff + 1) { if (border == SCREEN_REDRAW_OUTSIDE) { - if (screen_redraw_two_panes(wp->window, 0)) { + if (screen_redraw_two_panes(wp->window, &type)) { if (active == TAILQ_FIRST(&w->panes)) border = SCREEN_REDRAW_BORDER_RIGHT; else From 1ff3dafe4e9c093f2c3b39843928ee90af47624a Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 13 May 2026 22:28:14 +0100 Subject: [PATCH 111/167] Fix crash when minimising pane. --- cmd-minimise-pane.c | 70 +++++++++++++++++++++++++++++++-------------- control-notify.c | 3 ++ server-client.c | 4 +-- tmux.h | 2 -- window.c | 26 ----------------- 5 files changed, 53 insertions(+), 52 deletions(-) diff --git a/cmd-minimise-pane.c b/cmd-minimise-pane.c index 89e47c4e7f..29807608e2 100644 --- a/cmd-minimise-pane.c +++ b/cmd-minimise-pane.c @@ -30,7 +30,8 @@ static enum cmd_retval cmd_minimise_pane_minimise_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_minimise_pane_unminimise_exec(struct cmd *, struct cmdq_item *); -static enum cmd_retval cmd_minimise_pane_minimise(struct window *, struct window_pane *); +static enum cmd_retval cmd_minimise_pane_minimise(struct window *, struct window_pane *, + struct cmdq_item *); static enum cmd_retval cmd_minimise_pane_unminimise(struct window *, struct window_pane *); const struct cmd_entry cmd_minimise_pane_entry = { @@ -73,12 +74,13 @@ cmd_minimise_pane_minimise_exec(struct cmd *self, struct cmdq_item *item) enum cmd_retval rv; if (args_has(args, 'a')) { + struct window_pane *active_pane = w->active; TAILQ_FOREACH(wp, &w->z_index, zentry) { - if (!window_pane_visible(wp)) + if (!window_pane_visible(wp) || wp == active_pane) continue; - rv = cmd_minimise_pane_minimise(w, wp); + rv = cmd_minimise_pane_minimise(w, wp, item); if (rv != CMD_RETURN_NORMAL) - return(rv); + return (rv); } return (CMD_RETURN_NORMAL); } else { @@ -95,7 +97,7 @@ cmd_minimise_pane_minimise_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "No target pane to miminise."); return (CMD_RETURN_ERROR); } - return(cmd_minimise_pane_minimise(w, wp)); + return (cmd_minimise_pane_minimise(w, wp, item)); } } @@ -117,7 +119,7 @@ cmd_minimise_pane_unminimise_exec(struct cmd *self, struct cmdq_item *item) continue; rv = cmd_minimise_pane_unminimise(w, wp); if (rv != CMD_RETURN_NORMAL) - return(rv); + return (rv); } return (CMD_RETURN_NORMAL); } else { @@ -134,23 +136,42 @@ cmd_minimise_pane_unminimise_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "No target pane to unmiminise."); return (CMD_RETURN_ERROR); } - return(cmd_minimise_pane_unminimise(w, wp)); + return (cmd_minimise_pane_unminimise(w, wp)); } } static enum cmd_retval -cmd_minimise_pane_minimise(struct window *w, struct window_pane *wp) +cmd_minimise_pane_minimise(struct window *w, struct window_pane *wp, + __attribute__((unused)) struct cmdq_item *item) { - struct window_pane *wp2; + struct window_pane *pwp = NULL; - /* Ignore if already minimised to prevent double-redistribution. */ if (wp->flags & PANE_MINIMISED) return (CMD_RETURN_NORMAL); + if (wp == w->active) { + /* + * Unzoom before searching: under zoom, window_pane_visible + * returns false for every non-active pane. + */ + if (w->flags & WINDOW_ZOOMED) + window_unzoom(w, 1); + /* Find previous active pane. */ + TAILQ_FOREACH(pwp, &w->last_panes, sentry) { + if (pwp != wp && window_pane_visible(pwp)) + break; + } + if (pwp == NULL) { + TAILQ_FOREACH(pwp, &w->z_index, zentry) { + if (pwp != wp && + window_pane_visible(pwp)) + break; + } + } + } + wp->flags |= PANE_MINIMISED; - window_deactivate_pane(w, wp, 1); - /* Fix pane offsets and sizes. */ if (w->layout_root != NULL) { wp->saved_layout_cell = wp->layout_cell; layout_minimise_cell(w, wp->layout_cell); @@ -158,17 +179,22 @@ cmd_minimise_pane_minimise(struct window *w, struct window_pane *wp) layout_fix_panes(w, NULL); } - /* Find next visible window in z-index. */ - TAILQ_FOREACH(wp2, &w->z_index, zentry) { - if (!window_pane_visible(wp2)) - continue; - break; + window_pane_stack_remove(&w->last_panes, wp); + if (pwp != NULL) { + window_set_active_pane(w, pwp, 1); + } else if (wp == w->active) { + /* No visible previous active pane; null active pane + * to show dots background. */ + w->active = NULL; + if (options_get_number(global_options, "focus-events")) + window_pane_update_focus(wp); + notify_window("window-pane-changed", w); + notify_window("window-layout-changed", w); + server_redraw_window(w); + } else { + notify_window("window-layout-changed", w); + server_redraw_window(w); } - if (wp2 != NULL) - window_set_active_pane(w, wp2, 1); - - notify_window("window-layout-changed", w); - server_redraw_window(w); return (CMD_RETURN_NORMAL); } diff --git a/control-notify.c b/control-notify.c index 0ced0c87f5..ba93c627d9 100644 --- a/control-notify.c +++ b/control-notify.c @@ -77,6 +77,9 @@ control_notify_window_pane_changed(struct window *w) { struct client *c; + if (w->active == NULL) + return; + TAILQ_FOREACH(c, &clients, entry) { if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) continue; diff --git a/server-client.c b/server-client.c index 9ca4b289f4..b5e5606069 100644 --- a/server-client.c +++ b/server-client.c @@ -2197,7 +2197,7 @@ server_client_set_path(struct client *c) struct session *s = c->session; const char *path; - if (s->curw == NULL) + if (s->curw == NULL || s->curw->window->active == NULL) return; if (s->curw->window->active->base.path == NULL) path = ""; @@ -2217,7 +2217,7 @@ server_client_set_progress_bar(struct client *c) struct session *s = c->session; struct progress_bar *pane_pb; - if (s->curw == NULL) + if (s->curw == NULL || s->curw->window->active == NULL) return; pane_pb = &s->curw->window->active->base.progress_bar; if (pane_pb->state == c->progress_bar.state && diff --git a/tmux.h b/tmux.h index 02e010fd6c..b3b841fce8 100644 --- a/tmux.h +++ b/tmux.h @@ -3412,8 +3412,6 @@ struct window_pane *window_find_string(struct window *, const char *); int window_has_pane(struct window *, struct window_pane *); int window_set_active_pane(struct window *, struct window_pane *, int); -int window_deactivate_pane(struct window *, struct window_pane *, - int); void window_update_focus(struct window *); void window_pane_update_focus(struct window_pane *); void window_redraw_active_switch(struct window *, diff --git a/window.c b/window.c index f61411707f..cf9e8649b8 100644 --- a/window.c +++ b/window.c @@ -570,32 +570,6 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) return (1); } -int -window_deactivate_pane(struct window *w, struct window_pane *wp, int notify) -{ - struct window_pane *lastwp; - - log_debug("%s: pane %%%u", __func__, wp->id); - - if (w->flags & WINDOW_ZOOMED) - window_unzoom(w, 1); - lastwp = w->active; - - window_pane_stack_remove(&w->last_panes, wp); - window_pane_stack_push(&w->last_panes, lastwp); - - w->active = NULL; - - if (options_get_number(global_options, "focus-events")) { - window_pane_update_focus(lastwp); - } - - tty_update_window_offset(w); - - if (notify) - notify_window("window-pane-changed", w); - return (1); -} static int window_pane_get_palette(struct window_pane *wp, int c) From 1b2435fab60040af92f6ea6310319c9ff60250e2 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 13 May 2026 22:46:32 +0100 Subject: [PATCH 112/167] Fix bug where tty output of command outputing was getting written to the window aafter it was minimised. --- screen-write.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/screen-write.c b/screen-write.c index b428581431..2c7aa28c34 100644 --- a/screen-write.c +++ b/screen-write.c @@ -145,6 +145,9 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c) if (wp->layout_cell == NULL) return (0); + if (wp->flags & PANE_MINIMISED) + return (0); + if (wp->flags & (PANE_REDRAW|PANE_DROP)) return (-1); if (c->flags & CLIENT_REDRAWPANES) { From 2e0eabbf0a5cfcd827f2d858da3bd6f1675c06ac Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 15 May 2026 18:20:29 +0100 Subject: [PATCH 113/167] Fix screen_redraw_get_visible_ranges to ignore minimised panes. --- screen-redraw.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/screen-redraw.c b/screen-redraw.c index 7a15b15885..3b2c5467bb 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1164,7 +1164,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, u_int lb, rb, tb, bb; u_int i, s; - if (base_wp == NULL) { + if (base_wp == NULL || base_wp->flags & PANE_MINIMISED) { if (r != NULL) { return (r); } else { @@ -1195,6 +1195,8 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, found_self = 0; TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { + if (wp->flags & PANE_MINIMISED) + continue; if (wp == base_wp) { found_self = 1; continue; From c4ec9234e948af56205dda3709bd76784e63e7fd Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 15 May 2026 18:23:39 +0100 Subject: [PATCH 114/167] Fix to ignore minimised panes when redoing the layout. --- layout.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/layout.c b/layout.c index df7c5177e8..0d2bc7f1f9 100644 --- a/layout.c +++ b/layout.c @@ -531,15 +531,15 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, /* * Return the nearest sibling of lc that is not a minimised WINDOWPANE leaf, - * walking forward (forward=1) or backward (forward=0) in the parent's list. + * walking forward (direction=1) or backward (direction=0) in the parent's list. * Container cells (TOPBOTTOM/LEFTRIGHT) are never skipped. */ static struct layout_cell * -layout_active_neighbour(struct layout_cell *lc, int forward) +layout_active_neighbour(struct layout_cell *lc, int direction) { struct layout_cell *lcother; - if (forward) + if (direction) lcother = TAILQ_NEXT(lc, entry); else lcother = TAILQ_PREV(lc, layout_cells, entry); @@ -548,10 +548,10 @@ layout_active_neighbour(struct layout_cell *lc, int forward) if (lcother->type != LAYOUT_WINDOWPANE) return (lcother); /* container — not skipped */ if (lcother->wp == NULL || - !(lcother->wp->flags & PANE_MINIMISED)) + (~lcother->wp->flags & PANE_MINIMISED)) return (lcother); /* visible leaf */ /* minimised leaf — keep walking */ - if (forward) + if (direction) lcother = TAILQ_NEXT(lcother, entry); else lcother = TAILQ_PREV(lcother, layout_cells, entry); @@ -621,6 +621,8 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, struct layout_cell **lcroot) { struct layout_cell *lcother, *lcparent; + int direction; + int is_minimised; /* * If no parent, this is either a floating pane or the last @@ -637,14 +639,15 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, /* In tiled layouts, merge the space into the previous or next cell. */ if (lcparent->type != LAYOUT_FLOATING) { - int forward; - forward = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0; - lcother = layout_active_neighbour(lc, forward); + is_minimised = (lc->wp != NULL && (lc->wp->flags & PANE_MINIMISED)); + direction = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0; + lcother = layout_active_neighbour(lc, direction); if (lcother == NULL) - lcother = layout_active_neighbour(lc, !forward); - if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) + lcother = layout_active_neighbour(lc, !direction); + if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT && + !is_minimised) layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); - else if (lcother != NULL) + else if (lcother != NULL && !is_minimised) layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1); } @@ -693,6 +696,7 @@ layout_minimise_cell(struct window *w, struct layout_cell *lc) { struct layout_cell *lcother, *lcparent, *lcchild; u_int space = 0; + int direction; lcparent = lc->parent; if (lcparent == NULL || @@ -702,11 +706,10 @@ layout_minimise_cell(struct window *w, struct layout_cell *lc) /* Merge the space into the nearest non-minimised sibling. */ { - int forward; - forward = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0; - lcother = layout_active_neighbour(lc, forward); + direction = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0; + lcother = layout_active_neighbour(lc, direction); if (lcother == NULL) - lcother = layout_active_neighbour(lc, !forward); + lcother = layout_active_neighbour(lc, !direction); } if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); From 8e966688a769b1c3249074567cadb6c4d36170cd Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 17 May 2026 15:49:21 +0100 Subject: [PATCH 115/167] If outside floating pane, do not carry on to check as if tiled pane. --- screen-redraw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/screen-redraw.c b/screen-redraw.c index 3b2c5467bb..7290e7c98d 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -167,6 +167,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if ((int)py == wp->yoff + (int)wp->sy) return (SCREEN_REDRAW_BORDER_BOTTOM); } + return (SCREEN_REDRAW_OUTSIDE); } /* Get pane indicator. */ From 76196b8ffa17659e653840d86229d27448735c84 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Sat, 16 May 2026 17:30:30 -0700 Subject: [PATCH 116/167] Moved new-pane into split-window. --- cmd-split-window.c | 137 +++++++++++++-------------------------------- spawn.c | 2 +- tmux.h | 12 +++- window.c | 96 ++++++++++++++++++++++++++++--- 4 files changed, 137 insertions(+), 110 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 2286d41d1c..5859983ce8 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -38,13 +38,12 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:fF:hIkl:m:M:p:PR:s:S:t:x:X:y:Y:vZ", 0, -1, NULL }, + .args = { "bc:de:fF:hIkl:Lm:p:PR:s:S:t:vx:X:y:Y:Z", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " - "[-F format] [-l size] [-m message] [-M mode] " - "[-R inactive-border-style] [-s style] " - "[-S active-border-style] [-x width] [-X x-position]" - "[-y height] [-Y y-position]" CMD_TARGET_PANE_USAGE - "[shell-command [argument ...]]", + "[-F format] [-l size] [-m message] [-p percentage] [-s style] " + "[-S active-border-style] [-R inactive-border-style] " + "[-x width] [-y height] [-X x-position] [-Y y-position] " + CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -56,13 +55,11 @@ const struct cmd_entry cmd_split_window_entry = { .name = "split-window", .alias = "splitw", - .args = { "bc:de:fF:hIkl:m:M:p:PR:s:S:t:x:X:y:Y:vZ", 0, -1, NULL }, + .args = { "bc:de:fF:hIkl:m:p:PR:s:S:t:vZ", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " - "[-F format] [-l size] [-m message] [-M mode] " - "[-R inactive-border-style] [-s style] " - "[-S active-border-style] [-x width] [-X x-position]" - "[-y height] [-Y y-position]" CMD_TARGET_PANE_USAGE - "[shell-command [argument ...]]", + "[-F format] [-l size] [-m message] [-p percentage] [-s style] " + "[-S active-border-style] [-R inactive-border-style] " + CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -72,67 +69,17 @@ const struct cmd_entry cmd_split_window_entry = { static struct layout_cell * cmd_split_window_get_floating_layout_cell(struct cmdq_item *item, - struct args *args, struct window *w) + struct args *args, struct window *w, struct window_pane *wp) { struct layout_cell *lc = NULL; char *cause = NULL; - int x, y; - u_int sx, sy; - static int last_x = 0, last_y = 0; - - /* Default size. */ - sx = w->sx / 2; - sy = w->sy / 2; - - if (args_has(args, 'x')) { - sx = args_percentage_and_expand(args, 'x', 0, USHRT_MAX, w->sx, - item, &cause); - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (NULL); - } - } - if (args_has(args, 'y')) { - sy = args_percentage_and_expand(args, 'y', 0, USHRT_MAX, w->sy, - item, &cause); - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (NULL); - } - } + u_int x, y, sx, sy; - /* If a position is not defined, it defaults to cascading. */ - if (args_has(args, 'X')) { - x = args_percentage_and_expand(args, 'X', 0, USHRT_MAX, w->sx, - item, &cause); - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (NULL); - } - } else if (last_x == 0) - x = 4; - else { - x = (last_x += 4); - if (last_x > (int)w->sx) - x = 4; - } - if (args_has(args, 'Y')) { - y = args_percentage_and_expand(args, 'Y', 0, USHRT_MAX, w->sy, - item, &cause); - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (NULL); - } - } else if (last_y == 0) - y = 2; - else { - y = (last_y += 2); - if (last_y > (int)w->sy) - y = 2; + if (window_pane_float_geometry(w, wp, &x, &y, &sx, &sy, item, args, &cause) + != 0) { + cmdq_error(item, "invalid float geometry %s", cause); + free(cause); + return (NULL); } /* Floating panes sit in layout cells which are not in the layout_root @@ -143,8 +90,6 @@ cmd_split_window_get_floating_layout_cell(struct cmdq_item *item, lc->yoff = y; lc->sx = sx; lc->sy = sy; - last_x = x; /* Statically save last xoff & yoff so that new */ - last_y = y; /* floating panes offset so they don't overlap. */ return (lc); } @@ -163,6 +108,11 @@ cmd_split_window_get_tiled_layout_cell(struct cmdq_item *item, return (NULL); } + if (wp->flags & PANE_MINIMISED) { + cmdq_error(item, "can't split a minimised pane"); + return (NULL); + } + if (window_pane_tile_geometry(w, wp, &size, &flags, &type, item, args, &cause) != 0) { cmdq_error(item, "invalid tiled geometry %s", cause); @@ -191,30 +141,20 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) struct window_pane *wp = target->wp, *new_wp; struct layout_cell *lc = NULL; struct cmd_find_state fs; - int flags, input; + int input, is_floating, flags = 0; const char *template, *style; char *cause = NULL, *cp; struct args_value *av; u_int count = args_count(args); - enum { FLOATING, TILED, NONE } pane_mode; - - if (args_has(args, 'M')) { - if (strcasecmp(args_get(args, 'M'), "f") == 0) - pane_mode = FLOATING; - else if (strcasecmp(args_get(args, 'M'), "t") == 0) - pane_mode = TILED; - else - pane_mode = NONE; - } else { - if (cmd_get_entry(self) == &cmd_new_pane_entry) - pane_mode = FLOATING; - else - pane_mode = TILED; - } + + if (cmd_get_entry(self) == &cmd_new_pane_entry) + is_floating = !args_has(args, 'L'); + else + is_floating = 0; input = (args_has(args, 'I') && count == 0); - flags = (pane_mode == FLOATING) ? SPAWN_FLOATING : 0; + flags = is_floating ? SPAWN_FLOATING : 0; if (args_has(args, 'b')) flags |= SPAWN_BEFORE; if (args_has(args, 'f')) @@ -222,16 +162,12 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) if (input || (count == 1 && *args_string(args, 0) == '\0')) flags |= SPAWN_EMPTY; - if (pane_mode == FLOATING) - lc = cmd_split_window_get_floating_layout_cell(item, args, w); - else if (pane_mode == TILED) { + if (is_floating) + lc = cmd_split_window_get_floating_layout_cell(item, args, w, + wp); + else lc = cmd_split_window_get_tiled_layout_cell(item, args, w, wp, flags); - } else { - cmdq_error(item, "unrecognized pane mode '%s'", - args_get(args, 'M')); - return (CMD_RETURN_ERROR); - } if (lc == NULL) return (CMD_RETURN_ERROR); @@ -310,7 +246,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) switch (window_pane_start_input(new_wp, item, &cause)) { case -1: server_client_remove_pane(new_wp); - if (pane_mode == TILED) + if (!is_floating) layout_close_pane(new_wp); window_remove_pane(wp->window, new_wp); cmdq_error(item, "%s", cause); @@ -326,8 +262,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) } if (!args_has(args, 'd')) cmd_find_from_winlink_pane(current, wl, new_wp, 0); - window_pop_zoom(wp->window); - server_redraw_window(wp->window); + + if (!is_floating) { + window_pop_zoom(wp->window); + server_redraw_window(wp->window); + } server_status_session(s); if (args_has(args, 'P')) { diff --git a/spawn.c b/spawn.c index 24617bdd2c..7219a67a98 100644 --- a/spawn.c +++ b/spawn.c @@ -379,7 +379,7 @@ spawn_pane(struct spawn_context *sc, char **cause) goto complete; } - /* Store current working directory and change to new one. */ + /* Store current working directory and change to new one. */ if (getcwd(path, sizeof path) != NULL) { if (chdir(new_wp->cwd) == 0) actual_cwd = new_wp->cwd; diff --git a/tmux.h b/tmux.h index 0150e277f9..a18defe094 100644 --- a/tmux.h +++ b/tmux.h @@ -1386,6 +1386,9 @@ struct window { u_int xpixel; u_int ypixel; + u_int last_new_pane_x; + u_int last_new_pane_y; + u_int new_sx; u_int new_sy; u_int new_xpixel; @@ -3491,9 +3494,12 @@ enum client_theme window_pane_get_theme(struct window_pane *); void window_pane_send_theme_update(struct window_pane *); struct style_range *window_pane_border_status_get_range(struct window_pane *, u_int, u_int); -int window_pane_tile_geometry(struct window *, - struct window_pane *, int *, int *, enum layout_type *, - struct cmdq_item *, struct args *, char **); +int window_pane_tile_geometry(struct window *, struct window_pane *, + int *, int *, enum layout_type *, struct cmdq_item *, + struct args *, char **); +int window_pane_float_geometry(struct window *, struct window_pane *, + u_int *, u_int *, u_int *, u_int *, struct cmdq_item *, + struct args *, char **); /* layout.c */ u_int layout_count_cells(struct layout_cell *); diff --git a/window.c b/window.c index c183fd3726..850793dd75 100644 --- a/window.c +++ b/window.c @@ -2128,11 +2128,11 @@ window_pane_border_status_get_range(struct window_pane *wp, u_int x, u_int y) } int -window_pane_tile_geometry(struct window *w, struct window_pane *wp, - int *out_size, int *out_flags, enum layout_type *out_type, - struct cmdq_item *item, struct args *args, char **cause) +window_pane_tile_geometry(struct window *w, struct window_pane *wp, int *out_size, + int *out_flags, enum layout_type *out_type, struct cmdq_item *item, + struct args *args, char **cause) { - int size = -1, flags = *out_flags; + int size, flags = *out_flags; enum layout_type type; u_int curval = 0; @@ -2140,6 +2140,7 @@ window_pane_tile_geometry(struct window *w, struct window_pane *wp, if (args_has(args, 'h')) type = LAYOUT_LEFTRIGHT; + /* If the 'p' flag is dropped then this bit can be moved into 'l'. */ if (args_has(args, 'l') || args_has(args, 'p')) { if (args_has(args, 'f')) { if (type == LAYOUT_TOPBOTTOM) @@ -2154,6 +2155,7 @@ window_pane_tile_geometry(struct window *w, struct window_pane *wp, } } + size = -1; if (args_has(args, 'l')) { size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, item, cause); @@ -2161,11 +2163,11 @@ window_pane_tile_geometry(struct window *w, struct window_pane *wp, size = args_strtonum_and_expand(args, 'p', 0, 100, item, cause); if (cause == NULL) - size = curval * size / 100; + size = curval * (size) / 100; } - if (*cause != NULL) + if (*cause != NULL) { return (-1); - + } if (args_has(args, 'b')) flags |= SPAWN_BEFORE; if (args_has(args, 'f')) @@ -2174,5 +2176,85 @@ window_pane_tile_geometry(struct window *w, struct window_pane *wp, *out_size = size; *out_flags = flags; *out_type = type; + + return (0); +} + +int +window_pane_float_geometry(struct window *w, struct window_pane *wp, + u_int *out_x, u_int *out_y, u_int *out_sx, u_int *out_sy, + struct cmdq_item *item, struct args *args, char **cause) +{ + u_int x, y, sx, sy; + const u_int cx = 4, cy = 2; + + if ((wp->flags & PANE_SAVED_FLOAT) && + !args_has(args, 'x') && !args_has(args, 'y') && + !args_has(args, 'X') && !args_has(args, 'Y')) { + x = wp->saved_float_xoff; + y = wp->saved_float_yoff; + sx = wp->saved_float_sx; + sy = wp->saved_float_sy; + goto out; + } + + /* Default size */ + sx = w->sx / 2; + sy = w->sy / 2; + + if (args_has(args, 'x')) { + sx = args_percentage_and_expand(args, 'x', 0, USHRT_MAX, w->sx, + item, cause); + if (*cause != NULL) { + return (-1); + } + } + if (args_has(args, 'y')) { + sy = args_percentage_and_expand(args, 'y', 0, USHRT_MAX, w->sy, + item, cause); + if (*cause != NULL) { + return (-1); + } + } + + /* Position defaults to cascading when not defined */ + if (args_has(args, 'X')) { + x = args_percentage_and_expand(args, 'X', 0, USHRT_MAX, w->sx, + item, cause); + if (*cause != NULL) { + return (-1); + } + } else { + if (w->last_new_pane_x == 0) + x = cx; + else { + x = w->last_new_pane_x + cx; + if (w->last_new_pane_x > w->sx) + x = cx; + } + w->last_new_pane_x = x; + } + if (args_has(args, 'Y')) { + y = args_percentage_and_expand(args, 'Y', 0, USHRT_MAX, w->sy, + item, cause); + if (*cause != NULL) { + return (-1); + } + } else { + if (w->last_new_pane_y == 0) + y = cy; + else { + y = w->last_new_pane_y + cy; + if (w->last_new_pane_y > w->sy) + y = cy; + } + w->last_new_pane_y = y; + } + +out: + *out_x = x; + *out_y = y; + *out_sx = sx; + *out_sy = sy; return (0); } From c3fc0229e5d4dae317fb1af1572fb9434971bee3 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 18 May 2026 15:41:28 -0700 Subject: [PATCH 117/167] style fixes --- window.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/window.c b/window.c index 850793dd75..88269c3537 100644 --- a/window.c +++ b/window.c @@ -2165,9 +2165,9 @@ window_pane_tile_geometry(struct window *w, struct window_pane *wp, int *out_siz if (cause == NULL) size = curval * (size) / 100; } - if (*cause != NULL) { + if (*cause != NULL) return (-1); - } + if (args_has(args, 'b')) flags |= SPAWN_BEFORE; if (args_has(args, 'f')) @@ -2205,25 +2205,22 @@ window_pane_float_geometry(struct window *w, struct window_pane *wp, if (args_has(args, 'x')) { sx = args_percentage_and_expand(args, 'x', 0, USHRT_MAX, w->sx, item, cause); - if (*cause != NULL) { + if (*cause != NULL) return (-1); - } } if (args_has(args, 'y')) { sy = args_percentage_and_expand(args, 'y', 0, USHRT_MAX, w->sy, item, cause); - if (*cause != NULL) { + if (*cause != NULL) return (-1); - } } /* Position defaults to cascading when not defined */ if (args_has(args, 'X')) { x = args_percentage_and_expand(args, 'X', 0, USHRT_MAX, w->sx, item, cause); - if (*cause != NULL) { + if (*cause != NULL) return (-1); - } } else { if (w->last_new_pane_x == 0) x = cx; @@ -2237,9 +2234,8 @@ window_pane_float_geometry(struct window *w, struct window_pane *wp, if (args_has(args, 'Y')) { y = args_percentage_and_expand(args, 'Y', 0, USHRT_MAX, w->sy, item, cause); - if (*cause != NULL) { + if (*cause != NULL) return (-1); - } } else { if (w->last_new_pane_y == 0) y = cy; From b7cc218a036b6cbf0db28f08c4f84615fc7ac680 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 18 May 2026 17:14:19 -0700 Subject: [PATCH 118/167] Fixed null dereference. --- window.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/window.c b/window.c index 88269c3537..051daed81f 100644 --- a/window.c +++ b/window.c @@ -2188,7 +2188,7 @@ window_pane_float_geometry(struct window *w, struct window_pane *wp, u_int x, y, sx, sy; const u_int cx = 4, cy = 2; - if ((wp->flags & PANE_SAVED_FLOAT) && + if (wp != NULL && (wp->flags & PANE_SAVED_FLOAT) && !args_has(args, 'x') && !args_has(args, 'y') && !args_has(args, 'X') && !args_has(args, 'Y')) { x = wp->saved_float_xoff; @@ -2198,7 +2198,7 @@ window_pane_float_geometry(struct window *w, struct window_pane *wp, goto out; } - /* Default size */ + /* Default size. */ sx = w->sx / 2; sy = w->sy / 2; @@ -2215,7 +2215,7 @@ window_pane_float_geometry(struct window *w, struct window_pane *wp, return (-1); } - /* Position defaults to cascading when not defined */ + /* Position defaults to cascading when not defined. */ if (args_has(args, 'X')) { x = args_percentage_and_expand(args, 'X', 0, USHRT_MAX, w->sx, item, cause); From 57b17bbb706ed1352d5d737aafaa3c9dc71b8bc7 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 19 May 2026 10:54:35 +0100 Subject: [PATCH 119/167] window_pane_move is not used. --- format.c | 2 -- tmux.h | 1 - window.c | 9 --------- 3 files changed, 12 deletions(-) diff --git a/format.c b/format.c index b42b117b48..7ce26cd5c1 100644 --- a/format.c +++ b/format.c @@ -2263,7 +2263,6 @@ format_cb_pane_minimised_flag(struct format_tree *ft) return (NULL); } - /* Callback for pane_mode. */ static void * format_cb_pane_mode(struct format_tree *ft) @@ -4236,7 +4235,6 @@ format_strip(struct format_expand_state *es, const char *s) return (out); } - /* Skip until end. */ static const char * format_skip1(struct format_expand_state *es, const char *s, const char *end) diff --git a/tmux.h b/tmux.h index a18defe094..091d54c130 100644 --- a/tmux.h +++ b/tmux.h @@ -3445,7 +3445,6 @@ struct window_pane *window_pane_find_by_id_str(const char *); struct window_pane *window_pane_find_by_id(u_int); int window_pane_destroy_ready(struct window_pane *); void window_pane_resize(struct window_pane *, u_int, u_int); -void window_pane_move(struct window_pane *, int, int); int window_pane_set_mode(struct window_pane *, struct window_pane *, const struct window_mode *, struct cmd_find_state *, struct args *); diff --git a/window.c b/window.c index 051daed81f..61d85d9fb5 100644 --- a/window.c +++ b/window.c @@ -1219,15 +1219,6 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) wme->mode->resize(wme, sx, sy); } -void -window_pane_move(struct window_pane *wp, int xoff, int yoff) -{ - wp->xoff = xoff; - wp->yoff = yoff; - - log_debug("%s: %%%u resize %ux%u", __func__, wp->id, xoff, yoff); -} - int window_pane_set_mode(struct window_pane *wp, struct window_pane *swp, const struct window_mode *mode, struct cmd_find_state *fs, From 55fdfef9d1c7a7a8cefc9ed59f25bed3acb601b2 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 19 May 2026 13:19:21 +0100 Subject: [PATCH 120/167] Merge fixes. --- tmux.h | 3 --- window.c | 75 -------------------------------------------------------- 2 files changed, 78 deletions(-) diff --git a/tmux.h b/tmux.h index 274205b9a6..949d669686 100644 --- a/tmux.h +++ b/tmux.h @@ -1386,9 +1386,6 @@ struct window { u_int xpixel; u_int ypixel; - u_int last_new_pane_x; - u_int last_new_pane_y; - u_int new_sx; u_int new_sy; u_int new_xpixel; diff --git a/window.c b/window.c index 4cf2599e56..225fee1fec 100644 --- a/window.c +++ b/window.c @@ -2168,81 +2168,6 @@ window_pane_tiled_geometry(struct window *w, struct window_pane *wp, return (0); } -int -window_pane_float_geometry(struct window *w, struct window_pane *wp, - u_int *out_x, u_int *out_y, u_int *out_sx, u_int *out_sy, - struct cmdq_item *item, struct args *args, char **cause) -{ - u_int x, y, sx, sy; - const u_int cx = 4, cy = 2; - - if (wp != NULL && (wp->flags & PANE_SAVED_FLOAT) && - !args_has(args, 'x') && !args_has(args, 'y') && - !args_has(args, 'X') && !args_has(args, 'Y')) { - x = wp->saved_float_xoff; - y = wp->saved_float_yoff; - sx = wp->saved_float_sx; - sy = wp->saved_float_sy; - goto out; - } - - /* Default size. */ - sx = w->sx / 2; - sy = w->sy / 2; - - if (args_has(args, 'x')) { - sx = args_percentage_and_expand(args, 'x', 0, USHRT_MAX, w->sx, - item, cause); - if (*cause != NULL) - return (-1); - } - if (args_has(args, 'y')) { - sy = args_percentage_and_expand(args, 'y', 0, USHRT_MAX, w->sy, - item, cause); - if (*cause != NULL) - return (-1); - } - - /* Position defaults to cascading when not defined. */ - if (args_has(args, 'X')) { - x = args_percentage_and_expand(args, 'X', 0, USHRT_MAX, w->sx, - item, cause); - if (*cause != NULL) - return (-1); - } else { - if (w->last_new_pane_x == 0) - x = cx; - else { - x = w->last_new_pane_x + cx; - if (w->last_new_pane_x > w->sx) - x = cx; - } - w->last_new_pane_x = x; - } - if (args_has(args, 'Y')) { - y = args_percentage_and_expand(args, 'Y', 0, USHRT_MAX, w->sy, - item, cause); - if (*cause != NULL) - return (-1); - } else { - if (w->last_new_pane_y == 0) - y = cy; - else { - y = w->last_new_pane_y + cy; - if (w->last_new_pane_y > w->sy) - y = cy; - } - w->last_new_pane_y = y; - } - -out: - *out_x = x; - *out_y = y; - *out_sx = sx; - *out_sy = sy; - return (0); -} - /* Work out geometry for floating panes. */ int window_pane_floating_geometry(struct window *w, __unused struct window_pane *wp, From 2695c5305e593ba6aef50d71045fe8b216476b0b Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 19 May 2026 13:23:21 +0100 Subject: [PATCH 121/167] More merge fixes. --- window.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/window.c b/window.c index 225fee1fec..1907533243 100644 --- a/window.c +++ b/window.c @@ -569,7 +569,6 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) return (1); } - static int window_pane_get_palette(struct window_pane *wp, int c) { @@ -2120,7 +2119,7 @@ window_pane_tiled_geometry(struct window *w, struct window_pane *wp, int *out_size, int *out_flags, enum layout_type *out_type, struct cmdq_item *item, struct args *args, char **cause) { - int size, flags = *out_flags; + int size = -1, flags = *out_flags; enum layout_type type; u_int curval = 0; @@ -2128,7 +2127,6 @@ window_pane_tiled_geometry(struct window *w, struct window_pane *wp, if (args_has(args, 'h')) type = LAYOUT_LEFTRIGHT; - /* If the 'p' flag is dropped then this bit can be moved into 'l'. */ if (args_has(args, 'l') || args_has(args, 'p')) { if (args_has(args, 'f')) { if (type == LAYOUT_TOPBOTTOM) @@ -2143,7 +2141,6 @@ window_pane_tiled_geometry(struct window *w, struct window_pane *wp, } } - size = -1; if (args_has(args, 'l')) { size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, item, cause); @@ -2151,7 +2148,7 @@ window_pane_tiled_geometry(struct window *w, struct window_pane *wp, size = args_strtonum_and_expand(args, 'p', 0, 100, item, cause); if (cause == NULL) - size = curval * (size) / 100; + size = curval * size / 100; } if (*cause != NULL) return (-1); @@ -2164,7 +2161,6 @@ window_pane_tiled_geometry(struct window *w, struct window_pane *wp, *out_size = size; *out_flags = flags; *out_type = type; - return (0); } From f8e908b89c100e736bdf3dc13c5f490edd5ad97a Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 19 May 2026 13:26:27 +0100 Subject: [PATCH 122/167] More trivial merge errors. --- server-fn.c | 3 +-- tmux.1 | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server-fn.c b/server-fn.c index bf5a91fc83..16cda7565a 100644 --- a/server-fn.c +++ b/server-fn.c @@ -340,8 +340,7 @@ server_destroy_pane(struct window_pane *wp, int notify) case 0: break; case 2: - if (remain_on_exit == 2 && - WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0) + if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0) break; /* FALLTHROUGH */ case 1: diff --git a/tmux.1 b/tmux.1 index b22a305c71..1820935e66 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3448,6 +3448,7 @@ $ make 2>&1|tmux splitw \-dI & .Pp All other options have the same meaning as for the .Ic new\-window +command. .Tg nextl .It Ic next\-layout Op Fl t Ar target\-window .D1 Pq alias: Ic nextl From 0a7b008b2136d1275fc07a241d2465647f4cd7e7 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Sat, 16 May 2026 19:22:46 -0700 Subject: [PATCH 123/167] Changed minimised semantics to hide semantics --- Makefile.am | 2 +- cmd-minimise-pane.c => cmd-hide-pane.c | 99 +++++++++++++------------- cmd-split-window.c | 4 +- cmd-tile-float-pane.c | 32 ++++----- cmd.c | 8 +-- format.c | 10 +-- key-bindings.c | 4 +- layout.c | 54 +++++++------- screen-redraw.c | 8 +-- screen-write.c | 4 +- tmux.1 | 28 ++++---- tmux.h | 6 +- window-tree.c | 2 +- window.c | 24 +++---- 14 files changed, 143 insertions(+), 142 deletions(-) rename cmd-minimise-pane.c => cmd-hide-pane.c (61%) diff --git a/Makefile.am b/Makefile.am index fc054091c5..bea57e6506 100644 --- a/Makefile.am +++ b/Makefile.am @@ -102,6 +102,7 @@ dist_tmux_SOURCES = \ cmd-display-panes.c \ cmd-find-window.c \ cmd-find.c \ + cmd-hide-pane.c \ cmd-if-shell.c \ cmd-join-pane.c \ cmd-kill-pane.c \ @@ -117,7 +118,6 @@ dist_tmux_SOURCES = \ cmd-list-windows.c \ cmd-load-buffer.c \ cmd-lock-server.c \ - cmd-minimise-pane.c \ cmd-move-window.c \ cmd-new-session.c \ cmd-new-window.c \ diff --git a/cmd-minimise-pane.c b/cmd-hide-pane.c similarity index 61% rename from cmd-minimise-pane.c rename to cmd-hide-pane.c index 29807608e2..e9a9fe1d71 100644 --- a/cmd-minimise-pane.c +++ b/cmd-hide-pane.c @@ -1,7 +1,7 @@ /* $OpenBSD$ */ /* - * Copyright (c) 2009 Nicholas Marriott + * Copyright (c) 2026 Michael Grant * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,19 +24,19 @@ #include "tmux.h" /* - * Increase or decrease pane size. + * Hide or show panes. */ -static enum cmd_retval cmd_minimise_pane_minimise_exec(struct cmd *, struct cmdq_item *); -static enum cmd_retval cmd_minimise_pane_unminimise_exec(struct cmd *, struct cmdq_item *); +static enum cmd_retval cmd_hide_pane_hide_exec(struct cmd *, struct cmdq_item *); +static enum cmd_retval cmd_hide_pane_show_exec(struct cmd *, struct cmdq_item *); -static enum cmd_retval cmd_minimise_pane_minimise(struct window *, struct window_pane *, +static enum cmd_retval cmd_hide_pane_hide(struct window *, struct window_pane *, struct cmdq_item *); -static enum cmd_retval cmd_minimise_pane_unminimise(struct window *, struct window_pane *); +static enum cmd_retval cmd_hide_pane_show(struct window *, struct window_pane *); -const struct cmd_entry cmd_minimise_pane_entry = { - .name = "minimise-pane", - .alias = "minimize-pane", +const struct cmd_entry cmd_hide_pane_entry = { + .name = "hide-pane", + .alias = "hidep", .args = { "at:", 0, 1, NULL }, .usage = "[-a] " CMD_TARGET_PANE_USAGE, @@ -44,12 +44,12 @@ const struct cmd_entry cmd_minimise_pane_entry = { .target = { 't', CMD_FIND_PANE, 0 }, .flags = CMD_AFTERHOOK, - .exec = cmd_minimise_pane_minimise_exec + .exec = cmd_hide_pane_hide_exec }; -const struct cmd_entry cmd_unminimise_pane_entry = { - .name = "unminimise-pane", - .alias = "unminimize-pane", +const struct cmd_entry cmd_show_pane_entry = { + .name = "show-pane", + .alias = "showp", .args = { "at:", 0, 1, NULL }, .usage = "[-a] " CMD_TARGET_PANE_USAGE, @@ -57,28 +57,27 @@ const struct cmd_entry cmd_unminimise_pane_entry = { .target = { 't', CMD_FIND_PANE, 0 }, .flags = CMD_AFTERHOOK, - .exec = cmd_minimise_pane_unminimise_exec + .exec = cmd_hide_pane_show_exec }; static enum cmd_retval -cmd_minimise_pane_minimise_exec(struct cmd *self, struct cmdq_item *item) +cmd_hide_pane_hide_exec(struct cmd *self, struct cmdq_item *item) { - __attribute((unused)) struct args *args = cmd_get_args(self); - struct cmd_find_state *target = cmdq_get_target(item); - struct winlink *wl = target->wl; - struct window *w = wl->window; - struct window_pane *wp; - u_int id; - char *cause = NULL; - enum cmd_retval rv; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct winlink *wl = target->wl; + struct window *w = wl->window; + struct window_pane *wp, *active_pane = w->active; + u_int id; + char *cause = NULL; + enum cmd_retval rv; if (args_has(args, 'a')) { - struct window_pane *active_pane = w->active; TAILQ_FOREACH(wp, &w->z_index, zentry) { if (!window_pane_visible(wp) || wp == active_pane) continue; - rv = cmd_minimise_pane_minimise(w, wp, item); + rv = cmd_hide_pane_hide(w, wp, item); if (rv != CMD_RETURN_NORMAL) return (rv); } @@ -86,7 +85,8 @@ cmd_minimise_pane_minimise_exec(struct cmd *self, struct cmdq_item *item) } else { wp = target->wp; if (wp == NULL) { - id = args_strtonum_and_expand(args, 't', 0, INT_MAX, item, &cause); + id = args_strtonum_and_expand(args, 't', 0, INT_MAX, + item, &cause); if (cause != NULL) { cmdq_error(item, "%s target pane", cause); return (CMD_RETURN_ERROR); @@ -94,30 +94,30 @@ cmd_minimise_pane_minimise_exec(struct cmd *self, struct cmdq_item *item) wp = window_pane_find_by_id(id); } if (wp == NULL) { - cmdq_error(item, "No target pane to miminise."); + cmdq_error(item, "No target pane to hide."); return (CMD_RETURN_ERROR); } - return (cmd_minimise_pane_minimise(w, wp, item)); + return (cmd_hide_pane_hide(w, wp, item)); } } static enum cmd_retval -cmd_minimise_pane_unminimise_exec(struct cmd *self, struct cmdq_item *item) +cmd_hide_pane_show_exec(struct cmd *self, struct cmdq_item *item) { - __attribute((unused)) struct args *args = cmd_get_args(self); - struct cmd_find_state *target = cmdq_get_target(item); - struct winlink *wl = target->wl; - struct window *w = wl->window; - struct window_pane *wp; - u_int id; - char *cause = NULL; - enum cmd_retval rv; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct winlink *wl = target->wl; + struct window *w = wl->window; + struct window_pane *wp; + u_int id; + char *cause = NULL; + enum cmd_retval rv; if (args_has(args, 'a')) { TAILQ_FOREACH(wp, &w->z_index, zentry) { if (!window_pane_visible(wp)) continue; - rv = cmd_minimise_pane_unminimise(w, wp); + rv = cmd_hide_pane_show(w, wp); if (rv != CMD_RETURN_NORMAL) return (rv); } @@ -125,7 +125,8 @@ cmd_minimise_pane_unminimise_exec(struct cmd *self, struct cmdq_item *item) } else { wp = target->wp; if (wp == NULL) { - id = args_strtonum_and_expand(args, 't', 0, INT_MAX, item, &cause); + id = args_strtonum_and_expand(args, 't', 0, INT_MAX, + item, &cause); if (cause != NULL) { cmdq_error(item, "%s target pane", cause); return (CMD_RETURN_ERROR); @@ -133,20 +134,20 @@ cmd_minimise_pane_unminimise_exec(struct cmd *self, struct cmdq_item *item) wp = window_pane_find_by_id(id); } if (wp == NULL) { - cmdq_error(item, "No target pane to unmiminise."); + cmdq_error(item, "No target pane to show."); return (CMD_RETURN_ERROR); } - return (cmd_minimise_pane_unminimise(w, wp)); + return (cmd_hide_pane_show(w, wp)); } } static enum cmd_retval -cmd_minimise_pane_minimise(struct window *w, struct window_pane *wp, - __attribute__((unused)) struct cmdq_item *item) +cmd_hide_pane_hide(struct window *w, struct window_pane *wp, + __unused struct cmdq_item *item) { struct window_pane *pwp = NULL; - if (wp->flags & PANE_MINIMISED) + if (wp->flags & PANE_HIDDEN) return (CMD_RETURN_NORMAL); if (wp == w->active) { @@ -170,11 +171,11 @@ cmd_minimise_pane_minimise(struct window *w, struct window_pane *wp, } } - wp->flags |= PANE_MINIMISED; + wp->flags |= PANE_HIDDEN; if (w->layout_root != NULL) { wp->saved_layout_cell = wp->layout_cell; - layout_minimise_cell(w, wp->layout_cell); + layout_hide_cell(w, wp->layout_cell); layout_fix_offsets(w); layout_fix_panes(w, NULL); } @@ -200,15 +201,15 @@ cmd_minimise_pane_minimise(struct window *w, struct window_pane *wp, } static enum cmd_retval -cmd_minimise_pane_unminimise(struct window *w, struct window_pane *wp) +cmd_hide_pane_show(struct window *w, struct window_pane *wp) { - wp->flags &= ~PANE_MINIMISED; + wp->flags &= ~PANE_HIDDEN; /* Fix pane offsets and sizes. */ if (w->layout_root != NULL && wp->saved_layout_cell != NULL) { wp->layout_cell = wp->saved_layout_cell; wp->saved_layout_cell = NULL; - layout_unminimise_cell(w, wp->layout_cell); + layout_show_cell(w, wp->layout_cell); layout_fix_offsets(w); layout_fix_panes(w, NULL); } diff --git a/cmd-split-window.c b/cmd-split-window.c index e3cab8851c..34a840892f 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -110,8 +110,8 @@ cmd_split_window_get_tiled_cell(struct cmdq_item *item, struct args *args, return (NULL); } - if (wp->flags & PANE_MINIMISED) { - cmdq_error(item, "can't split a minimised pane"); + if (wp->flags & PANE_HIDDEN) { + cmdq_error(item, "can't split a hidden pane"); return (NULL); } diff --git a/cmd-tile-float-pane.c b/cmd-tile-float-pane.c index 6f3dd8ca4c..ec3d8f64f4 100644 --- a/cmd-tile-float-pane.c +++ b/cmd-tile-float-pane.c @@ -28,7 +28,7 @@ * tile-pane: insert a floating pane back into the tiled layout. * * saved_layout_cell is reused to remember the pane's tiled slot while it is - * floating, using the same mechanism as minimise-pane. The cell's wp pointer + * floating, using the same mechanism as hide-pane. The cell's wp pointer * is cleared while the pane is floating so that layout helpers treat the slot * as empty. */ @@ -163,8 +163,8 @@ cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "pane is already floating"); return (CMD_RETURN_ERROR); } - if (wp->flags & PANE_MINIMISED) { - cmdq_error(item, "can't float a minimised pane"); + if (wp->flags & PANE_HIDDEN) { + cmdq_error(item, "can't float a hidden pane"); return (CMD_RETURN_ERROR); } if (w->flags & WINDOW_ZOOMED) { @@ -229,7 +229,7 @@ cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) struct window_pane *wp = target->wp; struct window_pane *target_wp, *wpiter; struct layout_cell *float_lc, *lc; - int was_minimised; + int was_hidden; if (!(wp->flags & PANE_FLOATING)) { cmdq_error(item, "pane is not floating"); @@ -240,7 +240,7 @@ cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } - was_minimised = (wp->flags & PANE_MINIMISED) != 0; + was_hidden = (wp->flags & PANE_HIDDEN) != 0; /* * Save the floating geometry so we can restore it next time this pane @@ -254,10 +254,10 @@ cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) wp->flags |= PANE_SAVED_FLOAT; /* - * If the pane is also minimised, clear saved_layout_cell before + * If the pane is also hidden, clear saved_layout_cell before * freeing the floating cell — otherwise the pointer would dangle. */ - if (was_minimised) + if (was_hidden) wp->saved_layout_cell = NULL; /* @@ -270,17 +270,17 @@ cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) /* * Find the best tiled pane to split after, prefer a visible (non- - * minimised) tiled pane. If all tiled panes are minimised, fall back + * hidden) tiled pane. If all tiled panes are hidden, fall back * to any tiled pane so the new pane enters the existing tree rather * than becoming a disconnected root. */ target_wp = NULL; if (w->active != NULL && !(w->active->flags & PANE_FLOATING) && - !(w->active->flags & PANE_MINIMISED)) + !(w->active->flags & PANE_HIDDEN)) target_wp = w->active; if (target_wp == NULL) { TAILQ_FOREACH(wpiter, &w->last_panes, sentry) { - if (!(wpiter->flags & (PANE_FLOATING|PANE_MINIMISED)) && + if (!(wpiter->flags & (PANE_FLOATING|PANE_HIDDEN)) && window_pane_visible(wpiter)) { target_wp = wpiter; break; @@ -289,14 +289,14 @@ cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) } if (target_wp == NULL) { TAILQ_FOREACH(wpiter, &w->panes, entry) { - if (!(wpiter->flags & (PANE_FLOATING|PANE_MINIMISED)) && + if (!(wpiter->flags & (PANE_FLOATING|PANE_HIDDEN)) && window_pane_visible(wpiter)) { target_wp = wpiter; break; } } } - /* Fall back to any tiled pane (even minimised) to stay in the tree. */ + /* Fall back to any tiled pane (even hidden) to stay in the tree. */ if (target_wp == NULL) { TAILQ_FOREACH(wpiter, &w->panes, entry) { if (!(wpiter->flags & PANE_FLOATING)) { @@ -338,17 +338,17 @@ cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) } /* - * If the pane was minimised while floating, record its new tiled cell - * as the saved cell so unminimise can restore it correctly. + * If the pane was hidden while floating, record its new tiled cell + * as the saved cell so 'show' can restore it correctly. */ - if (was_minimised) + if (was_hidden) wp->saved_layout_cell = wp->layout_cell; wp->flags &= ~PANE_FLOATING; TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); - if (!(wp->flags & PANE_MINIMISED)) + if (!(wp->flags & PANE_HIDDEN)) window_set_active_pane(w, wp, 1); if (w->layout_root != NULL) diff --git a/cmd.c b/cmd.c index 72e0218517..3c812c8f84 100644 --- a/cmd.c +++ b/cmd.c @@ -70,7 +70,7 @@ extern const struct cmd_entry cmd_lock_client_entry; extern const struct cmd_entry cmd_lock_server_entry; extern const struct cmd_entry cmd_lock_session_entry; extern const struct cmd_entry cmd_float_pane_entry; -extern const struct cmd_entry cmd_minimise_pane_entry; +extern const struct cmd_entry cmd_hide_pane_entry; extern const struct cmd_entry cmd_move_pane_entry; extern const struct cmd_entry cmd_move_window_entry; extern const struct cmd_entry cmd_new_pane_entry; @@ -108,6 +108,7 @@ extern const struct cmd_entry cmd_show_environment_entry; extern const struct cmd_entry cmd_show_hooks_entry; extern const struct cmd_entry cmd_show_messages_entry; extern const struct cmd_entry cmd_show_options_entry; +extern const struct cmd_entry cmd_show_pane_entry; extern const struct cmd_entry cmd_show_prompt_history_entry; extern const struct cmd_entry cmd_show_window_options_entry; extern const struct cmd_entry cmd_source_file_entry; @@ -120,7 +121,6 @@ extern const struct cmd_entry cmd_switch_client_entry; extern const struct cmd_entry cmd_unbind_key_entry; extern const struct cmd_entry cmd_unlink_window_entry; extern const struct cmd_entry cmd_tile_pane_entry; -extern const struct cmd_entry cmd_unminimise_pane_entry; extern const struct cmd_entry cmd_wait_for_entry; const struct cmd_entry *cmd_table[] = { @@ -167,7 +167,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_lock_server_entry, &cmd_lock_session_entry, &cmd_float_pane_entry, - &cmd_minimise_pane_entry, + &cmd_hide_pane_entry, &cmd_move_pane_entry, &cmd_move_window_entry, &cmd_new_pane_entry, @@ -205,6 +205,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_show_hooks_entry, &cmd_show_messages_entry, &cmd_show_options_entry, + &cmd_show_pane_entry, &cmd_show_prompt_history_entry, &cmd_show_window_options_entry, &cmd_source_file_entry, @@ -217,7 +218,6 @@ const struct cmd_entry *cmd_table[] = { &cmd_unbind_key_entry, &cmd_unlink_window_entry, &cmd_tile_pane_entry, - &cmd_unminimise_pane_entry, &cmd_wait_for_entry, NULL }; diff --git a/format.c b/format.c index 7ce26cd5c1..5ff7fe81ce 100644 --- a/format.c +++ b/format.c @@ -2249,14 +2249,14 @@ format_cb_pane_marked_set(struct format_tree *ft) return (NULL); } -/* Callback for pane_minimised_flag. */ +/* Callback for pane_hidden_flag. */ static void * -format_cb_pane_minimised_flag(struct format_tree *ft) +format_cb_pane_hidden_flag(struct format_tree *ft) { struct window_pane *wp = ft->wp; if (wp != NULL) { - if (wp->flags & PANE_MINIMISED) + if (wp->flags & PANE_HIDDEN) return (xstrdup("1")); return (xstrdup("0")); } @@ -3460,8 +3460,8 @@ static const struct format_table_entry format_table[] = { { "pane_marked_set", FORMAT_TABLE_STRING, format_cb_pane_marked_set }, - { "pane_minimised_flag", FORMAT_TABLE_STRING, - format_cb_pane_minimised_flag + { "pane_hidden_flag", FORMAT_TABLE_STRING, + format_cb_pane_hidden_flag }, { "pane_mode", FORMAT_TABLE_STRING, format_cb_pane_mode diff --git a/key-bindings.c b/key-bindings.c index 49454ed759..b54fd3c5e6 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -351,7 +351,7 @@ key_bindings_init(void) { static const char *const defaults[] = { /* Prefix keys. */ - "bind -N 'Minimise pane' _ { minimise-pane }", + "bind -N 'Hide pane' _ { hide-pane }", "bind -N 'Send the prefix key' C-b { send-prefix }", "bind -N 'Rotate through the panes' C-o { rotate-window }", @@ -471,7 +471,7 @@ key_bindings_init(void) "bind -n MouseDrag1Border { resize-pane -M }", /* Mouse button 1 down on status line. */ - "bind -n MouseDown1Status { if -F '#{&&:#{pane_active},#{!#{pane_minimised_flag}}}' { minimise-pane -t= } { switch-client -t= } }", + "bind -n MouseDown1Status { if -F '#{&&:#{pane_active},#{!#{pane_hidden_flag}}}' { hide-pane -t= } { switch-client -t= } }", "bind -n C-MouseDown1Status { swap-window -t@ }", /* Mouse button 1 down on default pane-border-format */ diff --git a/layout.c b/layout.c index 0216a0362e..49f646ef66 100644 --- a/layout.c +++ b/layout.c @@ -270,7 +270,7 @@ layout_fix_offsets1(struct layout_cell *lc) TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (lcchild->type == LAYOUT_WINDOWPANE && lcchild->wp != NULL && - lcchild->wp->flags & PANE_MINIMISED) + lcchild->wp->flags & PANE_HIDDEN) continue; lcchild->xoff = xoff; lcchild->yoff = lc->yoff; @@ -283,7 +283,7 @@ layout_fix_offsets1(struct layout_cell *lc) TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (lcchild->type == LAYOUT_WINDOWPANE && lcchild->wp != NULL && - lcchild->wp->flags & PANE_MINIMISED) + lcchild->wp->flags & PANE_HIDDEN) continue; lcchild->xoff = lc->xoff; lcchild->yoff = yoff; @@ -536,7 +536,7 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, } /* - * Return the nearest sibling of lc that is not a minimised WINDOWPANE leaf, + * Return the nearest sibling of lc that is not a hidden WINDOWPANE leaf, * walking forward (direction=1) or backward (direction=0) in the parent's list. * Container cells (TOPBOTTOM/LEFTRIGHT) are never skipped. */ @@ -554,9 +554,9 @@ layout_active_neighbour(struct layout_cell *lc, int direction) if (lcother->type != LAYOUT_WINDOWPANE) return (lcother); /* container — not skipped */ if (lcother->wp == NULL || - (~lcother->wp->flags & PANE_MINIMISED)) + (~lcother->wp->flags & PANE_HIDDEN)) return (lcother); /* visible leaf */ - /* minimised leaf — keep walking */ + /* hidden leaf — keep walking */ if (direction) lcother = TAILQ_NEXT(lcother, entry); else @@ -566,12 +566,12 @@ layout_active_neighbour(struct layout_cell *lc, int direction) } /* - * Redistribute space equally among all visible (non-minimised WINDOWPANE) - * children of lcparent in the given direction. Minimised WINDOWPANE leaves + * Redistribute space equally among all visible (non-hidden WINDOWPANE) + * children of lcparent in the given direction. Hidden WINDOWPANE leaves * are skipped; their stored sizes are left untouched. Container children * have their own children resized proportionally via layout_resize_child_cells. * - * If all children happen to be minimised (n==0), nothing is done. + * If all children happen to be hidden (n==0), nothing is done. */ void layout_redistribute_cells(struct window *w, struct layout_cell *lcparent, @@ -585,7 +585,7 @@ layout_redistribute_cells(struct window *w, struct layout_cell *lcparent, TAILQ_FOREACH(lc, &lcparent->cells, entry) { if (lc->type == LAYOUT_WINDOWPANE && lc->wp != NULL && - (lc->wp->flags & PANE_MINIMISED)) + (lc->wp->flags & PANE_HIDDEN)) continue; n++; } @@ -608,7 +608,7 @@ layout_redistribute_cells(struct window *w, struct layout_cell *lcparent, TAILQ_FOREACH(lc, &lcparent->cells, entry) { if (lc->type == LAYOUT_WINDOWPANE && lc->wp != NULL && - (lc->wp->flags & PANE_MINIMISED)) + (lc->wp->flags & PANE_HIDDEN)) continue; target = each + (i < rem ? 1 : 0); if (type == LAYOUT_LEFTRIGHT) @@ -628,7 +628,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, { struct layout_cell *lcother, *lcparent; int direction; - int is_minimised; + int is_hidden; /* * If no parent, this is either a floating pane or the last @@ -652,15 +652,15 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, /* In tiled layouts, merge the space into the previous or next cell. */ if (lcparent->type != LAYOUT_FLOATING) { - is_minimised = (lc->wp != NULL && (lc->wp->flags & PANE_MINIMISED)); + is_hidden = (lc->wp != NULL && (lc->wp->flags & PANE_HIDDEN)); direction = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0; lcother = layout_active_neighbour(lc, direction); if (lcother == NULL) lcother = layout_active_neighbour(lc, !direction); if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT && - !is_minimised) + !is_hidden) layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); - else if (lcother != NULL && !is_minimised) + else if (lcother != NULL && !is_hidden) layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1); } @@ -685,15 +685,15 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, lc->yoff = 0; /* - * If the sole remaining child is a minimised + * If the sole remaining child is a hidden * WINDOWPANE, its stored size may be stale (it never * received the space that was given to the removed - * cell). Restore the full window size so that - * unminimise can reclaim the correct amount. + * cell). Restore the full window size so that + * 'show' can reclaim the correct amount. */ if (lc->type == LAYOUT_WINDOWPANE && lc->wp != NULL && - (lc->wp->flags & PANE_MINIMISED)) { + (lc->wp->flags & PANE_HIDDEN)) { lc->sx = lcparent->sx; lc->sy = lcparent->sy; } @@ -705,9 +705,9 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, } } -/* Minimise a cell and redistribute the space in tiled cells. */ +/* Hide a cell and redistribute the space in tiled cells. */ void -layout_minimise_cell(struct window *w, struct layout_cell *lc) +layout_hide_cell(struct window *w, struct layout_cell *lc) { struct layout_cell *lcother, *lcparent, *lcchild; u_int space = 0; @@ -719,7 +719,7 @@ layout_minimise_cell(struct window *w, struct layout_cell *lc) return; } - /* Merge the space into the nearest non-minimised sibling. */ + /* Merge the space into the nearest non-hidden sibling. */ { direction = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0; lcother = layout_active_neighbour(lc, direction); @@ -731,11 +731,11 @@ layout_minimise_cell(struct window *w, struct layout_cell *lc) else if (lcother != NULL) layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1); - /* If the parent cells are all minimised, minimise it too. */ + /* If the parent cells are all hidden, hide it too. */ if (lcparent != NULL) { TAILQ_FOREACH(lcchild, &lcparent->cells, entry) { if (lcchild->wp == NULL || - lcchild->wp->flags & PANE_MINIMISED) + lcchild->wp->flags & PANE_HIDDEN) continue; if (lcparent->type == LAYOUT_LEFTRIGHT) { space += lcchild->sx; @@ -744,13 +744,13 @@ layout_minimise_cell(struct window *w, struct layout_cell *lc) } } if (space == 0) - layout_minimise_cell(w, lcparent); + layout_hide_cell(w, lcparent); } } -/* Unminimise a cell and redistribute the space in tiled cells. */ +/* Show a cell and redistribute the space in tiled cells. */ void -layout_unminimise_cell(struct window *w, struct layout_cell *lc) +layout_show_cell(struct window *w, struct layout_cell *lc) { struct layout_cell *lcparent; @@ -762,7 +762,7 @@ layout_unminimise_cell(struct window *w, struct layout_cell *lc) /* * Redistribute the parent's space equally among all visible (non- - * minimised) children, including lc which has just been unminimised. + * hidden) children, including lc which has just been shown. * This ensures every pane at this level gets an equal share rather * than one pane losing most of its space to the restored pane. */ diff --git a/screen-redraw.c b/screen-redraw.c index c55a1ba5a0..c3dd3d8da4 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -461,14 +461,14 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; if (sb_pos == PANE_SCROLLBARS_LEFT) { - if (~wp->flags & PANE_MINIMISED && + if (~wp->flags & PANE_HIDDEN && ((int)px >= (int)wp->xoff - 1 - sb_w && (int)px <= wp->xoff + (int)wp->sx) && ((int)py >= (int)wp->yoff - 1 && (int)py <= wp->yoff + (int)wp->sy)) break; } else { /* PANE_SCROLLBARS_RIGHT or none. */ - if (~wp->flags & PANE_MINIMISED && + if (~wp->flags & PANE_HIDDEN && ((int)px >= (int)wp->xoff - 1 && (int)px <= wp->xoff + (int)wp->sx + sb_w) && ((int)py >= (int)wp->yoff - 1 && @@ -1165,7 +1165,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, u_int lb, rb, tb, bb; u_int i, s; - if (base_wp == NULL || base_wp->flags & PANE_MINIMISED) { + if (base_wp == NULL || base_wp->flags & PANE_HIDDEN) { if (r != NULL) { return (r); } else { @@ -1196,7 +1196,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, found_self = 0; TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { - if (wp->flags & PANE_MINIMISED) + if (wp->flags & PANE_HIDDEN) continue; if (wp == base_wp) { found_self = 1; diff --git a/screen-write.c b/screen-write.c index 2c7aa28c34..6e081fe60e 100644 --- a/screen-write.c +++ b/screen-write.c @@ -145,7 +145,7 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c) if (wp->layout_cell == NULL) return (0); - if (wp->flags & PANE_MINIMISED) + if (wp->flags & PANE_HIDDEN) return (0); if (wp->flags & (PANE_REDRAW|PANE_DROP)) @@ -1999,7 +1999,7 @@ screen_write_pane_obscured(struct window_pane *base_wp) continue; } if (found_self && wp->flags & PANE_FLOATING && - ! (wp->flags & PANE_MINIMISED) && + ! (wp->flags & PANE_HIDDEN) && ((wp->yoff >= base_wp->yoff && wp->yoff <= base_wp->yoff + (int)base_wp->sy) || (wp->yoff + (int)wp->sy >= base_wp->yoff && diff --git a/tmux.1 b/tmux.1 index 1820935e66..787f76b3ea 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3042,7 +3042,7 @@ are all omitted and the pane was previously returned to the tiled layout with .Ic tile\-pane , its last floating position and size are restored. -The pane must not already be floating or minimised, and the window must not +The pane must not already be floating or hidden, and the window must not be zoomed. .Tg joinp .It Xo Ic join\-pane @@ -3228,23 +3228,23 @@ or (time). .Fl r reverses the sort order. -.Tg minp -.It Xo Ic minimise\-pane +.Tg hidep +.It Xo Ic hide\-pane .Op Fl a .Op Fl t Ar target\-pane .Xc -.D1 Pq alias: Ic minimize\-pane +.D1 Pq alias: Ic hidep Hide .Ar target\-pane from the tiled layout without closing it. The pane continues to run but is no longer visible and does not occupy any screen space. -Minimised panes are shown in the status line and can be restored with -.Ic unminimise\-pane . +Hidden panes are shown in the status line and can be restored with +.Ic show\-pane . With .Fl a , -all visible panes in the window are minimised. -The pane must not already be minimised. +all visible panes in the window are hidden. +The pane must not already be hidden. .Tg movep .It Xo Ic move\-pane .Op Fl bdfhv @@ -3860,17 +3860,17 @@ a subsequent .Ic float\-pane command with no geometry options. The pane must be floating and the window must not be zoomed. -.Tg unminp -.It Xo Ic unminimise\-pane +.Tg showp +.It Xo Ic show\-pane .Op Fl t Ar target\-pane .Xc -.D1 Pq alias: Ic unminimize\-pane -Restore a minimised +.D1 Pq alias: Ic showp +Restore a hidden .Ar target\-pane to the tiled layout. Space is redistributed equally among all visible panes at the same layout level after the pane is restored. -The pane must be minimised. +The pane must be hidden. .Tg unlinkw .It Xo Ic unlink\-window .Op Fl k @@ -6670,7 +6670,7 @@ The following variables are available, where appropriate: .It Li "pane_left" Ta "" Ta "Left of pane" .It Li "pane_marked" Ta "" Ta "1 if this is the marked pane" .It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set" -.It Li "pane_minimised_flag" Ta "" Ta "1 if pane is minimised" +.It Li "pane_hidden_flag" Ta "" Ta "1 if pane is hidden" .It Li "pane_mode" Ta "" Ta "Name of pane mode, if any" .It Li "pane_path" Ta "" Ta "Path of pane (can be set by application)" .It Li "pane_pid" Ta "" Ta "PID of first process in pane" diff --git a/tmux.h b/tmux.h index 949d669686..f076eda55b 100644 --- a/tmux.h +++ b/tmux.h @@ -1279,7 +1279,7 @@ struct window_pane { #define PANE_THEMECHANGED 0x2000 #define PANE_UNSEENCHANGES 0x4000 #define PANE_REDRAWSCROLLBAR 0x8000 -#define PANE_MINIMISED 0x20000 +#define PANE_HIDDEN 0x20000 #define PANE_SAVED_FLOAT 0x80000 /* saved_float_* fields are valid */ /* Last floating position/size, saved when the pane is tiled. */ @@ -3507,8 +3507,8 @@ void layout_free_cell(struct layout_cell *); void layout_print_cell(struct layout_cell *, const char *, u_int); void layout_destroy_cell(struct window *, struct layout_cell *, struct layout_cell **); -void layout_minimise_cell(struct window *, struct layout_cell *); -void layout_unminimise_cell(struct window *, struct layout_cell *); +void layout_hide_cell(struct window *, struct layout_cell *); +void layout_show_cell(struct window *, struct layout_cell *); void layout_redistribute_cells(struct window *, struct layout_cell *, enum layout_type); void layout_resize_layout(struct window *, struct layout_cell *, diff --git a/window-tree.c b/window-tree.c index 0d6ca55345..b5ba7a4340 100644 --- a/window-tree.c +++ b/window-tree.c @@ -269,7 +269,7 @@ window_tree_build_window(struct session *s, struct winlink *wl, if (data->type == WINDOW_TREE_SESSION || data->type == WINDOW_TREE_WINDOW) { expanded = 0; - /* Without this, the only way to reach a minimised + /* Without this, the only way to reach a hidden * floating pane would be to first expand the window * manually (with the right-arrow key) and then press * its number — which is non-obvious and breaks the diff --git a/window.c b/window.c index 1907533243..c79f98eb3e 100644 --- a/window.c +++ b/window.c @@ -542,12 +542,12 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) w->active->active_point = next_active_point++; w->active->flags |= PANE_CHANGED; - if (wp->flags & PANE_MINIMISED) { - wp->flags &= ~PANE_MINIMISED; + if (wp->flags & PANE_HIDDEN) { + wp->flags &= ~PANE_HIDDEN; if (w->layout_root != NULL && wp->saved_layout_cell != NULL) { wp->layout_cell = wp->saved_layout_cell; wp->saved_layout_cell = NULL; - layout_unminimise_cell(w, wp->layout_cell); + layout_show_cell(w, wp->layout_cell); layout_fix_offsets(w); layout_fix_panes(w, NULL); } @@ -718,16 +718,16 @@ window_zoom(struct window_pane *wp) window_set_active_pane(w, wp, 1); wp->flags |= PANE_ZOOMED; - /* Bring pane above other tiled panes and minimise floating panes. */ + /* Bring pane above other tiled panes and hide floating panes. */ TAILQ_FOREACH(wp1, &w->z_index, zentry) { if (wp1 == wp) { - wp1->saved_flags |= (wp1->flags & PANE_MINIMISED); - wp1->flags &= ~PANE_MINIMISED; + wp1->saved_flags |= (wp1->flags & PANE_HIDDEN); + wp1->flags &= ~PANE_HIDDEN; continue; } if (wp1->flags & PANE_FLOATING) { - wp1->saved_flags |= (wp1->flags & PANE_MINIMISED); - wp1->flags |= PANE_MINIMISED; + wp1->saved_flags |= (wp1->flags & PANE_HIDDEN); + wp1->flags |= PANE_HIDDEN; continue; } break; @@ -761,7 +761,7 @@ window_unzoom(struct window *w, int notify) TAILQ_FOREACH(wp, &w->z_index, zentry) { if (wp->flags & PANE_FLOATING) { - wp->flags &= ~PANE_MINIMISED | (wp->saved_flags & PANE_MINIMISED); + wp->flags &= ~PANE_HIDDEN | (wp->saved_flags & PANE_HIDDEN); continue; } break; @@ -769,7 +769,7 @@ window_unzoom(struct window *w, int notify) TAILQ_FOREACH(wp, &w->panes, entry) { wp->layout_cell = wp->saved_layout_cell; - if (wp->flags & PANE_MINIMISED) + if (wp->flags & PANE_HIDDEN) wp->saved_layout_cell = wp->layout_cell; else wp->saved_layout_cell = NULL; @@ -1003,7 +1003,7 @@ window_pane_printable_flags(struct window_pane *wp) flags[pos++] = 'Z'; if (wp->flags & PANE_FLOATING) flags[pos++] = 'F'; - if (wp->flags & PANE_MINIMISED) + if (wp->flags & PANE_HIDDEN) flags[pos++] = 'm'; flags[pos] = '\0'; @@ -1382,7 +1382,7 @@ int window_pane_visible(struct window_pane *wp) { if (~wp->window->flags & WINDOW_ZOOMED && - ~wp->flags & PANE_MINIMISED) + ~wp->flags & PANE_HIDDEN) return (1); return (wp == wp->window->active); From 406ae3d8a69308ac4bb40692481967b9546751bd Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 20 May 2026 09:46:38 +0100 Subject: [PATCH 124/167] Remove some trivial differences. --- screen-redraw.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index c34b633ecf..745c30c5f9 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -115,7 +115,6 @@ screen_redraw_two_panes(struct window *w, enum layout_type *type) } if (count <= 1) return (0); - return (1); } @@ -920,7 +919,7 @@ screen_redraw_draw_border_arrows(struct screen_redraw_ctx *ctx, int i, if (wp == NULL) return; - if ((int)i != wp->xoff + 1 && (int)j != wp->yoff + 1) + if (i != wp->xoff + 1 && j != wp->yoff + 1) return; value = options_get_number(oo, "pane-border-indicators"); @@ -931,7 +930,7 @@ screen_redraw_draw_border_arrows(struct screen_redraw_ctx *ctx, int i, if (border == SCREEN_REDRAW_INSIDE) return; - if ((int)i == wp->xoff + 1) { + if (i == wp->xoff + 1) { if (border == SCREEN_REDRAW_OUTSIDE) { if (screen_redraw_two_panes(wp->window, &type)) { if (active == TAILQ_FIRST(&w->panes)) @@ -951,7 +950,7 @@ screen_redraw_draw_border_arrows(struct screen_redraw_ctx *ctx, int i, arrows = 1; } } - if ((int)j == wp->yoff + 1) { + if (j == wp->yoff + 1) { if (border == SCREEN_REDRAW_OUTSIDE) { if (screen_redraw_two_panes(wp->window, &type)) { if (active == TAILQ_FIRST(&w->panes)) From c56bc9ed05fa991ab56b0dfb192a758fb5dc0495 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 20 May 2026 13:41:13 +0100 Subject: [PATCH 125/167] Store pane ID for mouse last pane instead of a pointer to the pane. --- cmd-resize-pane.c | 19 +++++++++---------- server-client.c | 38 ++++++++++++++++++++++++-------------- tmux.h | 2 +- tty.c | 1 + window-copy.c | 5 +---- 5 files changed, 36 insertions(+), 29 deletions(-) diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index e1d6438d6e..b00c0d785c 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -78,13 +78,14 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) } if (args_has(args, 'M')) { - if (!event->m.valid || cmd_mouse_window(&event->m, &s) == NULL) + if (!event->m.valid) return (CMD_RETURN_NORMAL); - if (c == NULL || c->session != s) + wp = cmd_mouse_pane(&event->m, &s, NULL); + if (wp == NULL || c == NULL || c->session != s) return (CMD_RETURN_NORMAL); - if (c->tty.mouse_wp->flags & PANE_FLOATING) { - window_redraw_active_switch(w, c->tty.mouse_wp); - window_set_active_pane(w, c->tty.mouse_wp, 1); + if (wp->flags & PANE_FLOATING) { + window_redraw_active_switch(w, wp); + window_set_active_pane(w, wp, 1); c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_floating; cmd_resize_pane_mouse_update_floating(c, &event->m); } else { @@ -168,12 +169,13 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) int new_sx, new_sy; int new_xoff, new_yoff, resizes = 0; - wl = cmd_mouse_window(m, NULL); - if (wl == NULL) { + wp = cmd_mouse_pane(m, NULL, &wl); + if (wp == NULL) { c->tty.mouse_drag_update = NULL; return; } w = wl->window; + lc = wp->layout_cell; y = m->y + m->oy; x = m->x + m->ox; if (m->statusat == 0 && y >= m->statuslines) @@ -186,9 +188,6 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) else if (m->statusat > 0 && ly >= (u_int)m->statusat) ly = m->statusat - 1; - wp = c->tty.mouse_wp; - lc = wp->layout_cell; - log_debug("%s: %%%u resize_pane xoff=%d sx=%u xy=%ux%u lxy=%ux%u", __func__, wp->id, wp->xoff, wp->sx, x, y, lx, ly); if ((((int)lx == wp->xoff - 1) || ((int)lx == wp->xoff)) && diff --git a/server-client.c b/server-client.c index 7a812f95dc..ba6f3a8380 100644 --- a/server-client.c +++ b/server-client.c @@ -718,7 +718,7 @@ server_client_check_mouse(struct client *c, struct key_event *event) struct session *s = c->session, *fs; struct window *w = s->curw->window; struct winlink *fwl; - struct window_pane *wp, *fwp; + struct window_pane *wp, *fwp, *lwp = NULL; u_int x, y, sx, sy, px, py, n, sl_mpos = 0; u_int b, bn; int ignore = 0; @@ -731,6 +731,13 @@ server_client_check_mouse(struct client *c, struct key_event *event) log_debug("%s mouse %02x at %u,%u (last %u,%u) (%d)", c->name, m->b, m->x, m->y, m->lx, m->ly, c->tty.mouse_drag_flag); + /* Find last pane, if any. */ + if (c->tty.mouse_last_pane != -1) { + lwp = window_pane_find_by_id(c->tty.mouse_last_pane); + if (lwp != NULL) + log_debug("%s mouse last pane %%%u", c->name, lwp->id); + } + /* What type of event is this? */ if (event->key == KEYC_DOUBLECLICK) { type = KEYC_TYPE_DOUBLECLICK; @@ -875,9 +882,11 @@ server_client_check_mouse(struct client *c, struct key_event *event) */ if (loc == KEYC_MOUSE_LOCATION_NOWHERE) { if (c->tty.mouse_scrolling_flag) { - loc = KEYC_MOUSE_LOCATION_SCROLLBAR_SLIDER; - m->wp = c->tty.mouse_wp->id; - m->w = c->tty.mouse_wp->window->id; + if (lwp != NULL) { + loc = KEYC_MOUSE_LOCATION_SCROLLBAR_SLIDER; + m->wp = lwp->id; + m->w = lwp->window->id; + } } else { px = x; if (m->statusat == 0 && y >= m->statuslines) @@ -895,13 +904,13 @@ server_client_check_mouse(struct client *c, struct key_event *event) px = px + m->ox; py = py + m->oy; - if (type == KEYC_TYPE_MOUSEDRAG && - c->tty.mouse_wp != NULL) + if (type == KEYC_TYPE_MOUSEDRAG && lwp != NULL) { /* Use pane from last mouse event. */ - wp = c->tty.mouse_wp; - else + wp = lwp; + } else { /* Try inside the pane. */ wp = window_get_active_at(w, px, py); + } if (wp == NULL) return (KEYC_UNKNOWN); loc = server_client_check_mouse_in_pane(wp, px, py, @@ -979,8 +988,8 @@ server_client_check_mouse(struct client *c, struct key_event *event) */ type = KEYC_TYPE_MOUSEDRAGEND; c->tty.mouse_drag_flag = 0; - c->tty.mouse_wp = NULL; c->tty.mouse_slider_mpos = -1; + c->tty.mouse_last_pane = -1; } /* Convert to a key binding. */ @@ -1006,10 +1015,11 @@ server_client_check_mouse(struct client *c, struct key_event *event) * where the user grabbed. */ c->tty.mouse_drag_flag = MOUSE_BUTTONS(b) + 1; + /* Only change pane if not already dragging a pane border. */ - if (c->tty.mouse_wp == NULL) { - wp = window_get_active_at(w, px, py); - c->tty.mouse_wp = wp; + if (lwp == NULL) { + lwp = wp = window_get_active_at(w, px, py); + c->tty.mouse_last_pane = wp->id; } if (c->tty.mouse_scrolling_flag == 0 && loc == KEYC_MOUSE_LOCATION_SCROLLBAR_SLIDER) { @@ -2820,8 +2830,8 @@ server_client_remove_pane(struct window_pane *wp) RB_REMOVE(client_windows, &c->windows, cw); free(cw); } - if (c->tty.mouse_wp == wp) { - c->tty.mouse_wp = NULL; + if (c->tty.mouse_last_pane == (int)wp->id) { + c->tty.mouse_last_pane = -1; c->tty.mouse_drag_update = NULL; c->tty.mouse_scrolling_flag = 0; } diff --git a/tmux.h b/tmux.h index e58a583c91..4a6051c1e9 100644 --- a/tmux.h +++ b/tmux.h @@ -1719,7 +1719,7 @@ struct tty { int mouse_drag_flag; int mouse_scrolling_flag; int mouse_slider_mpos; - struct window_pane *mouse_wp; + int mouse_last_pane; void (*mouse_drag_update)(struct client *, struct mouse_event *); void (*mouse_drag_release)(struct client *, diff --git a/tty.c b/tty.c index 2079bffd05..6e6228041c 100644 --- a/tty.c +++ b/tty.c @@ -108,6 +108,7 @@ tty_init(struct tty *tty, struct client *c) tty->cstyle = SCREEN_CURSOR_DEFAULT; tty->ccolour = -1; tty->fg = tty->bg = -1; + tty->mouse_last_pane = -1; if (tcgetattr(c->fd, &tty->tio) != 0) return (-1); diff --git a/window-copy.c b/window-copy.c index 96e3bd113b..acb7cd8852 100644 --- a/window-copy.c +++ b/window-copy.c @@ -6507,10 +6507,7 @@ window_copy_drag_update(struct client *c, struct mouse_event *m) if (c == NULL) return; - if (c->tty.mouse_wp != NULL) - wp = c->tty.mouse_wp; - else - wp = cmd_mouse_pane(m, NULL, NULL); + wp = cmd_mouse_pane(m, NULL, NULL); if (wp == NULL) return; wme = TAILQ_FIRST(&wp->modes); From 1def5878844ca3876caced18893ff16908c545a8 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 20 May 2026 13:47:11 +0100 Subject: [PATCH 126/167] Remove code that was already applied. --- server-client.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/server-client.c b/server-client.c index ba6f3a8380..fcf5c8dd83 100644 --- a/server-client.c +++ b/server-client.c @@ -1415,14 +1415,6 @@ server_client_key_callback(struct cmdq_item *item, void *data) } if (c->flags & CLIENT_READONLY) goto out; - if (wp != NULL && - options_get_number(wp->options, "remain-on-exit") == 3 && - (wp->flags & PANE_EXITED) && - !KEYC_IS_MOUSE(key) && !KEYC_IS_PASTE(key)) { - options_set_number(wp->options, "remain-on-exit", 0); - server_destroy_pane(wp, 0); - goto out; - } if (wp != NULL) window_pane_key(wp, c, s, wl, key, m); goto out; From 243465386769eeffead0531df4b668b335676492 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Wed, 20 May 2026 12:17:50 -0700 Subject: [PATCH 127/167] fix: added conditionals and more finding logic to avoid displaying hidden panes. --- window.c | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/window.c b/window.c index c79f98eb3e..57ef2e92b1 100644 --- a/window.c +++ b/window.c @@ -843,26 +843,41 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, void window_lost_pane(struct window *w, struct window_pane *wp) { + struct window_pane *wpp, *twpp; + log_debug("%s: @%u pane %%%u", __func__, w->id, wp->id); if (wp == marked_pane.wp) server_clear_marked(); window_pane_stack_remove(&w->last_panes, wp); - if (wp == w->active) { - w->active = TAILQ_FIRST(&w->last_panes); - if (w->active == NULL) { - w->active = TAILQ_PREV(wp, window_panes, entry); - if (w->active == NULL) - w->active = TAILQ_NEXT(wp, entry); - } - if (w->active != NULL) { - window_pane_stack_remove(&w->last_panes, w->active); - w->active->flags |= PANE_CHANGED; - notify_window("window-pane-changed", w); - window_update_focus(w); + if (wp != w->active) + return; + + /* Try to find a good fit. */ + wpp = TAILQ_FIRST(&w->last_panes); + if (wpp == NULL || wpp->flags & PANE_HIDDEN) { + wpp = TAILQ_PREV(wp, window_panes, entry); + if (wpp == NULL || wpp->flags & PANE_HIDDEN) + wpp = TAILQ_NEXT(wp, entry); + } + /* Try to find any fit. */ + if (wpp == NULL || (wpp->flags & PANE_HIDDEN)) { + TAILQ_FOREACH_SAFE(wpp, &w->panes, entry, twpp) { + if (wpp != wp && (~wpp->flags & PANE_HIDDEN)) + break; } } + if (wpp != NULL && (wpp->flags & PANE_HIDDEN)) + wpp = NULL; + + w->active = wpp; + if (w->active != NULL) { + window_pane_stack_remove(&w->last_panes, w->active); + w->active->flags |= PANE_CHANGED; + notify_window("window-pane-changed", w); + window_update_focus(w); + } } void From e5abcd217c79c417fcb6672bf2ea971f4357ec70 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 22 May 2026 08:58:48 +0100 Subject: [PATCH 128/167] Start at the current pane in the zindex list for working out if a pane is obscured. --- screen-write.c | 78 +++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/screen-write.c b/screen-write.c index 6e081fe60e..b0c79b79fe 100644 --- a/screen-write.c +++ b/screen-write.c @@ -36,7 +36,7 @@ static int screen_write_overwrite(struct screen_write_ctx *, struct grid_cell *, u_int); static int screen_write_combine(struct screen_write_ctx *, const struct grid_cell *); -static int screen_write_pane_obscured(struct window_pane *); +static int screen_write_pane_is_obscured(struct window_pane *); struct screen_write_citem { u_int x; @@ -604,7 +604,7 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, break; grid_view_set_cell(ctx->s->grid, s->cx, s->cy, &gc); if (wp != NULL) { - if (! screen_redraw_is_visible(r, px)) + if (!screen_redraw_is_visible(r, px)) break; ttyctx.cell = &gc; tty_write(tty_cmd_cell, &ttyctx); @@ -1131,7 +1131,7 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); grid_view_insert_cells(s->grid, s->cx, s->cy, nx, bg); @@ -1165,7 +1165,7 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); grid_view_delete_cells(s->grid, s->cx, s->cy, nx, bg); @@ -1235,7 +1235,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); grid_view_insert_lines(gd, s->cy, ny, bg); @@ -1252,7 +1252,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); log_debug("%s: obscured=%d for pane %%%u", __func__, ttyctx.obscured, ctx->wp != NULL ? ctx->wp->id : 0); @@ -1292,7 +1292,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); grid_view_delete_lines(gd, s->cy, ny, bg); @@ -1309,7 +1309,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); log_debug("%s: obscured=%d for pane %%%u", __func__, ttyctx.obscured, ctx->wp != NULL ? ctx->wp->id : 0); @@ -1583,7 +1583,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); if (lines == 0) lines = 1; @@ -1646,7 +1646,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, s->cy + 1, sy - (s->cy + 1)); screen_write_collect_flush(ctx, 0, __func__); - if (! screen_write_pane_obscured(ctx->wp)) { + if (!screen_write_pane_is_obscured(ctx->wp)) { tty_write(tty_cmd_clearendofscreen, &ttyctx); return; } @@ -1722,7 +1722,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, 0, s->cy); screen_write_collect_flush(ctx, 0, __func__); - if (! screen_write_pane_obscured(ctx->wp)) { + if (!screen_write_pane_is_obscured(ctx->wp)) { tty_write(tty_cmd_clearstartofscreen, &ttyctx); return; } @@ -1796,7 +1796,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, 0, sy); - if (! screen_write_pane_obscured(ctx->wp)) { + if (!screen_write_pane_is_obscured(ctx->wp)) { tty_write(tty_cmd_clearscreen, &ttyctx); return; } @@ -1983,39 +1983,25 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) /* Return 1 if there is a floating window pane overlapping this pane. */ static int -screen_write_pane_obscured(struct window_pane *base_wp) -{ - struct window_pane *wp; - struct window *w; - int found_self = 0; - - if (base_wp == NULL) - return(0); - w = base_wp->window; - - TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { - if (wp == base_wp) { - found_self = 1; - continue; - } - if (found_self && wp->flags & PANE_FLOATING && - ! (wp->flags & PANE_HIDDEN) && - ((wp->yoff >= base_wp->yoff && - wp->yoff <= base_wp->yoff + (int)base_wp->sy) || - (wp->yoff + (int)wp->sy >= base_wp->yoff && - wp->yoff + wp->sy <= base_wp->yoff + base_wp->sy)) && - ((wp->xoff >= base_wp->xoff && - wp->xoff <= base_wp->xoff + (int)base_wp->sx) || - (wp->xoff + (int)wp->sx >= base_wp->xoff && - wp->xoff + wp->sx <= base_wp->xoff + base_wp->sx))) { - log_debug("%s: base %%%u obscured by %%%u " - "(xoff=%u sx=%u vs base xoff=%u sx=%u)", __func__, - base_wp->id, wp->id, - wp->xoff, wp->sx, base_wp->xoff, base_wp->sx); - return (1); - } - } - return (0); +screen_write_pane_is_obscured(struct window_pane *base_wp) +{ + struct window_pane *wp = base_wp; + + if (base_wp == NULL) + return (0); + while ((wp = TAILQ_PREV(wp, window_panes, zentry)) != NULL) { + if ((wp->flags & PANE_FLOATING) && + ((wp->yoff >= base_wp->yoff && + wp->yoff <= base_wp->yoff + (int)base_wp->sy) || + (wp->yoff + (int)wp->sy >= base_wp->yoff && + wp->yoff + wp->sy <= base_wp->yoff + base_wp->sy)) && + ((wp->xoff >= base_wp->xoff && + wp->xoff <= base_wp->xoff + (int)base_wp->sx) || + (wp->xoff + (int)wp->sx >= base_wp->xoff && + wp->xoff + wp->sx <= base_wp->xoff + base_wp->sx))) + return (1); + } + return (0); } /* Flush collected lines. */ @@ -2058,7 +2044,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy); ttyctx.num = ctx->scrolled; ttyctx.bg = ctx->bg; - ttyctx.obscured = screen_write_pane_obscured(wp); + ttyctx.obscured = screen_write_pane_is_obscured(wp); log_debug("%s: obscured=%d for pane %%%u", __func__, ttyctx.obscured, wp != NULL ? wp->id : 0); tty_write(tty_cmd_scrollup, &ttyctx); From 52ad3c8425e24bf693c63c773c3bffadc1845a5c Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 22 May 2026 09:03:51 +0100 Subject: [PATCH 129/167] Change pane_is_obscured to take the context since it always uses its pane. --- screen-write.c | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/screen-write.c b/screen-write.c index b0c79b79fe..f955c07c49 100644 --- a/screen-write.c +++ b/screen-write.c @@ -36,7 +36,7 @@ static int screen_write_overwrite(struct screen_write_ctx *, struct grid_cell *, u_int); static int screen_write_combine(struct screen_write_ctx *, const struct grid_cell *); -static int screen_write_pane_is_obscured(struct window_pane *); +static int screen_write_pane_is_obscured(struct screen_write_ctx *); struct screen_write_citem { u_int x; @@ -1131,7 +1131,7 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx); grid_view_insert_cells(s->grid, s->cx, s->cy, nx, bg); @@ -1165,7 +1165,7 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx); grid_view_delete_cells(s->grid, s->cx, s->cy, nx, bg); @@ -1235,7 +1235,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx); grid_view_insert_lines(gd, s->cy, ny, bg); @@ -1252,7 +1252,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx); log_debug("%s: obscured=%d for pane %%%u", __func__, ttyctx.obscured, ctx->wp != NULL ? ctx->wp->id : 0); @@ -1292,7 +1292,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx); grid_view_delete_lines(gd, s->cy, ny, bg); @@ -1309,7 +1309,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx); log_debug("%s: obscured=%d for pane %%%u", __func__, ttyctx.obscured, ctx->wp != NULL ? ctx->wp->id : 0); @@ -1583,7 +1583,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx->wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx); if (lines == 0) lines = 1; @@ -1646,7 +1646,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, s->cy + 1, sy - (s->cy + 1)); screen_write_collect_flush(ctx, 0, __func__); - if (!screen_write_pane_is_obscured(ctx->wp)) { + if (!screen_write_pane_is_obscured(ctx)) { tty_write(tty_cmd_clearendofscreen, &ttyctx); return; } @@ -1722,7 +1722,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, 0, s->cy); screen_write_collect_flush(ctx, 0, __func__); - if (!screen_write_pane_is_obscured(ctx->wp)) { + if (!screen_write_pane_is_obscured(ctx)) { tty_write(tty_cmd_clearstartofscreen, &ttyctx); return; } @@ -1796,7 +1796,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, 0, sy); - if (!screen_write_pane_is_obscured(ctx->wp)) { + if (!screen_write_pane_is_obscured(ctx)) { tty_write(tty_cmd_clearscreen, &ttyctx); return; } @@ -1983,22 +1983,22 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) /* Return 1 if there is a floating window pane overlapping this pane. */ static int -screen_write_pane_is_obscured(struct window_pane *base_wp) +screen_write_pane_is_obscured(struct screen_write_ctx *ctx) { - struct window_pane *wp = base_wp; + struct window_pane *wp = ctx->wp; - if (base_wp == NULL) + if (ctx->wp == NULL) return (0); while ((wp = TAILQ_PREV(wp, window_panes, zentry)) != NULL) { if ((wp->flags & PANE_FLOATING) && - ((wp->yoff >= base_wp->yoff && - wp->yoff <= base_wp->yoff + (int)base_wp->sy) || - (wp->yoff + (int)wp->sy >= base_wp->yoff && - wp->yoff + wp->sy <= base_wp->yoff + base_wp->sy)) && - ((wp->xoff >= base_wp->xoff && - wp->xoff <= base_wp->xoff + (int)base_wp->sx) || - (wp->xoff + (int)wp->sx >= base_wp->xoff && - wp->xoff + wp->sx <= base_wp->xoff + base_wp->sx))) + ((wp->yoff >= ctx->wp->yoff && + wp->yoff <= ctx->wp->yoff + (int)ctx->wp->sy) || + (wp->yoff + (int)wp->sy >= ctx->wp->yoff && + wp->yoff + wp->sy <= ctx->wp->yoff + ctx->wp->sy)) && + ((wp->xoff >= ctx->wp->xoff && + wp->xoff <= ctx->wp->xoff + (int)ctx->wp->sx) || + (wp->xoff + (int)wp->sx >= ctx->wp->xoff && + wp->xoff + wp->sx <= ctx->wp->xoff + ctx->wp->sx))) return (1); } return (0); @@ -2044,7 +2044,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy); ttyctx.num = ctx->scrolled; ttyctx.bg = ctx->bg; - ttyctx.obscured = screen_write_pane_is_obscured(wp); + ttyctx.obscured = screen_write_pane_is_obscured(ctx); log_debug("%s: obscured=%d for pane %%%u", __func__, ttyctx.obscured, wp != NULL ? wp->id : 0); tty_write(tty_cmd_scrollup, &ttyctx); From 8fa822207df9066174c749fff962bb88fecd5ff9 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 22 May 2026 09:07:38 +0100 Subject: [PATCH 130/167] Cache obscure flag in screen_write_ctx. --- screen-write.c | 12 +++++++++++- tmux.h | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/screen-write.c b/screen-write.c index f955c07c49..e83767a24f 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1989,6 +1989,14 @@ screen_write_pane_is_obscured(struct screen_write_ctx *ctx) if (ctx->wp == NULL) return (0); + + if (ctx->flags & SCREEN_WRITE_CHECKED_IF_OBSCURED) { + if (ctx->flags & SCREEN_WRITE_OBSCURED) + return (1); + return (0); + } + ctx->flags |= SCREEN_WRITE_CHECKED_IF_OBSCURED; + while ((wp = TAILQ_PREV(wp, window_panes, zentry)) != NULL) { if ((wp->flags & PANE_FLOATING) && ((wp->yoff >= ctx->wp->yoff && @@ -1998,8 +2006,10 @@ screen_write_pane_is_obscured(struct screen_write_ctx *ctx) ((wp->xoff >= ctx->wp->xoff && wp->xoff <= ctx->wp->xoff + (int)ctx->wp->sx) || (wp->xoff + (int)wp->sx >= ctx->wp->xoff && - wp->xoff + wp->sx <= ctx->wp->xoff + ctx->wp->sx))) + wp->xoff + wp->sx <= ctx->wp->xoff + ctx->wp->sx))) { + ctx->flags |= SCREEN_WRITE_OBSCURED; return (1); + } } return (0); } diff --git a/tmux.h b/tmux.h index 49cf384743..c88644965b 100644 --- a/tmux.h +++ b/tmux.h @@ -1055,6 +1055,8 @@ struct screen_write_ctx { int flags; #define SCREEN_WRITE_SYNC 0x1 +#define SCREEN_WRITE_OBSCURED 0x2 +#define SCREEN_WRITE_CHECKED_IF_OBSCURED 0x4 screen_write_init_ctx_cb init_ctx_cb; void *arg; From d3e8dd3623c07b2ef98b0b894cecf45896ab5023 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 22 May 2026 10:58:37 +0100 Subject: [PATCH 131/167] Use a flags bit for obscured rather than an int. --- screen-write.c | 148 +++++++++++++++++++++++-------------------------- tmux.h | 4 +- tty.c | 36 +++++------- 3 files changed, 84 insertions(+), 104 deletions(-) diff --git a/screen-write.c b/screen-write.c index dd178d9411..61c460da3d 100644 --- a/screen-write.c +++ b/screen-write.c @@ -36,7 +36,6 @@ static int screen_write_overwrite(struct screen_write_ctx *, struct grid_cell *, u_int); static int screen_write_combine(struct screen_write_ctx *, const struct grid_cell *); -static int screen_write_pane_is_obscured(struct screen_write_ctx *); struct screen_write_citem { u_int x; @@ -176,10 +175,43 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c) return (1); } +/* Return 1 if there is a floating window pane overlapping this pane. */ +static int +screen_write_pane_is_obscured(struct screen_write_ctx *ctx) +{ + struct window_pane *wp = ctx->wp; + + if (ctx->wp == NULL) + return (0); + + if (ctx->flags & SCREEN_WRITE_CHECKED_IF_OBSCURED) { + if (ctx->flags & SCREEN_WRITE_OBSCURED) + return (1); + return (0); + } + ctx->flags |= SCREEN_WRITE_CHECKED_IF_OBSCURED; + + while ((wp = TAILQ_PREV(wp, window_panes, zentry)) != NULL) { + if ((wp->flags & PANE_FLOATING) && + ((wp->yoff >= ctx->wp->yoff && + wp->yoff <= ctx->wp->yoff + (int)ctx->wp->sy) || + (wp->yoff + (int)wp->sy >= ctx->wp->yoff && + wp->yoff + wp->sy <= ctx->wp->yoff + ctx->wp->sy)) && + ((wp->xoff >= ctx->wp->xoff && + wp->xoff <= ctx->wp->xoff + (int)ctx->wp->sx) || + (wp->xoff + (int)wp->sx >= ctx->wp->xoff && + wp->xoff + wp->sx <= ctx->wp->xoff + ctx->wp->sx))) { + ctx->flags |= SCREEN_WRITE_OBSCURED; + return (1); + } + } + return (0); +} + /* Set up context for TTY command. */ static void screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, - int sync) + int is_sync, int check_obscured) { struct screen *s = ctx->s; @@ -194,6 +226,9 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, ttyctx->orlower = s->rlower; ttyctx->orupper = s->rupper; + if (check_obscured && screen_write_pane_is_obscured(ctx)) + ttyctx->flags |= TTY_CTX_PANE_OBSCURED; + memcpy(&ttyctx->defaults, &grid_default_cell, sizeof ttyctx->defaults); if (ctx->init_ctx_cb != NULL) { ctx->init_ctx_cb(ctx, ttyctx); @@ -225,7 +260,7 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, else { if (ctx->wp == NULL) ttyctx->flags |= TTY_CTX_OVERLAY_SYNC; - if (sync) + if (is_sync) ttyctx->flags |= TTY_CTX_SYNC; } tty_write(tty_cmd_syncstart, ttyctx); @@ -593,10 +628,9 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, if (yy >= gd->hsize + gd->sy) break; s->cx = cx; - screen_write_initctx(ctx, &ttyctx, 0); - if (wp != NULL) { + screen_write_initctx(ctx, &ttyctx, 0, 0); + if (wp != NULL) yoff = wp->yoff; - } r = screen_redraw_get_visible_ranges(wp, px, s->cy + yoff, nx, NULL); for (xx = px; xx < px + nx; xx++) { @@ -1104,7 +1138,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) s->rupper = 0; s->rlower = screen_size_y(s) - 1; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); screen_write_collect_clear(ctx, 0, screen_size_y(s) - 1); tty_write(tty_cmd_alignmenttest, &ttyctx); @@ -1133,9 +1167,8 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) ctx->wp->flags |= PANE_REDRAW; #endif - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx); grid_view_insert_cells(s->grid, s->cx, s->cy, nx, bg); @@ -1167,9 +1200,8 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) ctx->wp->flags |= PANE_REDRAW; #endif - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx); grid_view_delete_cells(s->grid, s->cx, s->cy, nx, bg); @@ -1201,7 +1233,7 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) ctx->wp->flags |= PANE_REDRAW; #endif - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); ttyctx.bg = bg; grid_view_clear(s->grid, s->cx, s->cy, nx, 1, bg); @@ -1237,9 +1269,8 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx); grid_view_insert_lines(gd, s->cy, ny, bg); @@ -1254,11 +1285,8 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx); - log_debug("%s: obscured=%d for pane %%%u", __func__, - ttyctx.obscured, ctx->wp != NULL ? ctx->wp->id : 0); if (s->cy < s->rupper || s->cy > s->rlower) grid_view_insert_lines(gd, s->cy, ny, bg); @@ -1294,9 +1322,8 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx); grid_view_delete_lines(gd, s->cy, ny, bg); @@ -1311,11 +1338,8 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx); - log_debug("%s: obscured=%d for pane %%%u", __func__, - ttyctx.obscured, ctx->wp != NULL ? ctx->wp->id : 0); if (s->cy < s->rupper || s->cy > s->rlower) grid_view_delete_lines(gd, s->cy, ny, bg); @@ -1474,7 +1498,7 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); screen_write_collect_flush(ctx, 0, __func__); - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 0); ttyctx.bg = bg; tty_write(tty_cmd_reverseindex, &ttyctx); @@ -1585,9 +1609,8 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) struct tty_ctx ttyctx; u_int i; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx); if (lines == 0) lines = 1; @@ -1631,7 +1654,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) ctx->wp->flags |= PANE_REDRAW; #endif - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; /* Scroll into history if it is enabled and clearing entire screen. */ @@ -1650,7 +1673,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, s->cy + 1, sy - (s->cy + 1)); screen_write_collect_flush(ctx, 0, __func__); - if (!screen_write_pane_is_obscured(ctx)) { + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) { tty_write(tty_cmd_clearendofscreen, &ttyctx); return; } @@ -1713,7 +1736,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) ctx->wp->flags |= PANE_REDRAW; #endif - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; if (s->cy > 0) @@ -1726,7 +1749,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, 0, s->cy); screen_write_collect_flush(ctx, 0, __func__); - if (!screen_write_pane_is_obscured(ctx)) { + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) { tty_write(tty_cmd_clearstartofscreen, &ttyctx); return; } @@ -1787,7 +1810,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) ctx->wp->flags |= PANE_REDRAW; #endif - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; /* Scroll into history if it is enabled. */ @@ -1800,7 +1823,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, 0, sy); - if (!screen_write_pane_is_obscured(ctx)) { + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) { tty_write(tty_cmd_clearscreen, &ttyctx); return; } @@ -1848,7 +1871,7 @@ screen_write_fullredraw(struct screen_write_ctx *ctx) screen_write_collect_flush(ctx, 0, __func__); - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 0); if (ttyctx.redraw_cb != NULL) ttyctx.redraw_cb(&ttyctx); } @@ -1985,39 +2008,6 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) TAILQ_INSERT_TAIL(&ctx->s->write_list[s->rlower].items, ci, entry); } -/* Return 1 if there is a floating window pane overlapping this pane. */ -static int -screen_write_pane_is_obscured(struct screen_write_ctx *ctx) -{ - struct window_pane *wp = ctx->wp; - - if (ctx->wp == NULL) - return (0); - - if (ctx->flags & SCREEN_WRITE_CHECKED_IF_OBSCURED) { - if (ctx->flags & SCREEN_WRITE_OBSCURED) - return (1); - return (0); - } - ctx->flags |= SCREEN_WRITE_CHECKED_IF_OBSCURED; - - while ((wp = TAILQ_PREV(wp, window_panes, zentry)) != NULL) { - if ((wp->flags & PANE_FLOATING) && - ((wp->yoff >= ctx->wp->yoff && - wp->yoff <= ctx->wp->yoff + (int)ctx->wp->sy) || - (wp->yoff + (int)wp->sy >= ctx->wp->yoff && - wp->yoff + wp->sy <= ctx->wp->yoff + ctx->wp->sy)) && - ((wp->xoff >= ctx->wp->xoff && - wp->xoff <= ctx->wp->xoff + (int)ctx->wp->sx) || - (wp->xoff + (int)wp->sx >= ctx->wp->xoff && - wp->xoff + wp->sx <= ctx->wp->xoff + ctx->wp->sx))) { - ctx->flags |= SCREEN_WRITE_OBSCURED; - return (1); - } - } - return (0); -} - /* Flush collected lines. */ static void screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, @@ -2053,14 +2043,11 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, if (ctx->scrolled > s->rlower - s->rupper + 1) ctx->scrolled = s->rlower - s->rupper + 1; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); if (wp != NULL && wp->yoff + wp->sy > wp->window->sy) ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy); ttyctx.num = ctx->scrolled; ttyctx.bg = ctx->bg; - ttyctx.obscured = screen_write_pane_is_obscured(ctx); - log_debug("%s: obscured=%d for pane %%%u", __func__, - ttyctx.obscured, wp != NULL ? wp->id : 0); tty_write(tty_cmd_scrollup, &ttyctx); if (wp != NULL) log_debug("%s: after scrollup, PANE_REDRAW=%d for %%%u", @@ -2147,15 +2134,16 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, continue; screen_write_set_cursor(ctx, wr_start, y); if (ci->type == CLEAR) { - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 0); ttyctx.bg = ci->bg; ttyctx.num = wr_length; tty_write(tty_cmd_clearcharacter, &ttyctx); } else { - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); ttyctx.cell = &ci->gc; - ttyctx.wrapped = ci->wrapped; + if (ci->wrapped) + ttyctx.flags |= TTY_CTX_WRAPPED; ttyctx.ptr = cl->data + wr_start; ttyctx.num = wr_length; tty_write(tty_cmd_cells, &ttyctx); @@ -2382,7 +2370,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) /* Sanity check cursor position. */ if (s->cx > sx - width || s->cy > sy - 1) return; - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); /* Handle overwriting of UTF-8 characters. */ gl = grid_get_line(s->grid, s->grid->hsize + s->cy); @@ -2622,7 +2610,7 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) * and what it is going to do now. */ screen_write_set_cursor(ctx, cx - n, cy); - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); ttyctx.cell = &last; if (force_wide) ttyctx.flags |= TTY_CTX_CELL_INVALIDATE; @@ -2711,7 +2699,7 @@ screen_write_setselection(struct screen_write_ctx *ctx, const char *clip, { struct tty_ctx ttyctx; - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); ttyctx.ptr = str; ttyctx.ptr2 = (void *)clip; ttyctx.num = len; @@ -2726,7 +2714,7 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len, { struct tty_ctx ttyctx; - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); if (allow_invisible_panes) ttyctx.flags |= TTY_CTX_INVISIBLE_PANES; ttyctx.ptr = str; @@ -2814,7 +2802,7 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, server_redraw_window_borders(wp->window); } - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 0); if (ttyctx.redraw_cb != NULL) ttyctx.redraw_cb(&ttyctx); } @@ -2838,7 +2826,7 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc, server_redraw_window_borders(wp->window); } - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 0); if (ttyctx.redraw_cb != NULL) ttyctx.redraw_cb(&ttyctx); } diff --git a/tmux.h b/tmux.h index c7bc244a22..ac7894ad30 100644 --- a/tmux.h +++ b/tmux.h @@ -1751,6 +1751,7 @@ struct tty_ctx { #define TTY_CTX_OVERLAY_SYNC 0x10 #define TTY_CTX_CELL_DRAW_LINE 0x20 #define TTY_CTX_CELL_INVALIDATE 0x40 +#define TTY_CTX_PANE_OBSCURED 0x80 u_int num; void *ptr; @@ -1787,9 +1788,6 @@ struct tty_ctx { u_int woy; u_int wsx; u_int wsy; - - /* tty partly obscured, it will need to be surgically scrolled. */ - u_int obscured; }; /* Saved message entry. */ diff --git a/tty.c b/tty.c index 946aad5c8f..121e3b85d9 100644 --- a/tty.c +++ b/tty.c @@ -1099,7 +1099,7 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) log_debug("%s: %s orlower=%u orupper=%u sy=%u large=%d", __func__, c->name, ctx->orlower, ctx->orupper, ctx->sy, tty_large_region(tty, ctx)); - if (tty_large_region(tty, ctx) && !ctx->obscured) { + if (tty_large_region(tty, ctx) && ~ctx->flags & TTY_CTX_PANE_OBSCURED) { log_debug("%s: %s large region redraw", __func__, c->name); ctx->redraw_cb(ctx); return; @@ -1433,7 +1433,7 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) if (~ctx->flags & TTY_CTX_WINDOW_BIGGER) { if (wp) { - if (ctx->obscured) { + if (ctx->flags & TTY_CTX_PANE_OBSCURED) { /* * Floating pane is present: use physical * coordinates and clip to visible ranges to @@ -1693,13 +1693,12 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || + if ((ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED)) || !tty_full_width(tty, ctx) || tty_fake_bce(tty, &ctx->defaults, ctx->bg) || (!tty_term_has(tty->term, TTYC_ICH) && !tty_term_has(tty->term, TTYC_ICH1)) || - c->overlay_check != NULL || - ctx->obscured) { + c->overlay_check != NULL) { tty_draw_pane(tty, ctx, ctx->ocy); return; } @@ -1717,13 +1716,12 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || + if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || !tty_full_width(tty, ctx) || tty_fake_bce(tty, &ctx->defaults, ctx->bg) || (!tty_term_has(tty->term, TTYC_DCH) && !tty_term_has(tty->term, TTYC_DCH1)) || - c->overlay_check != NULL || - ctx->obscured) { + c->overlay_check != NULL) { tty_draw_pane(tty, ctx, ctx->ocy); return; } @@ -1750,15 +1748,14 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || + if ((ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED)) || !tty_full_width(tty, ctx) || tty_fake_bce(tty, &ctx->defaults, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || !tty_term_has(tty->term, TTYC_IL1) || ctx->sx == 1 || ctx->sy == 1 || - c->overlay_check != NULL || - ctx->obscured) { + c->overlay_check != NULL) { tty_redraw_region(tty, ctx); return; } @@ -1779,15 +1776,14 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || + if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || !tty_full_width(tty, ctx) || tty_fake_bce(tty, &ctx->defaults, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || !tty_term_has(tty->term, TTYC_DL1) || ctx->sx == 1 || ctx->sy == 1 || - c->overlay_check != NULL || - ctx->obscured) { + c->overlay_check != NULL) { tty_redraw_region(tty, ctx); return; } @@ -1915,14 +1911,13 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) struct client *c = tty->client; u_int i; - if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || + if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || ctx->sx == 1 || ctx->sy == 1 || - c->overlay_check != NULL || - ctx->obscured) { + c->overlay_check != NULL) { tty_redraw_region(tty, ctx); return; } @@ -1955,7 +1950,7 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) u_int i; struct client *c = tty->client; - if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || + if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || @@ -1963,8 +1958,7 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) !tty_term_has(tty->term, TTYC_RIN)) || ctx->sx == 1 || ctx->sy == 1 || - c->overlay_check != NULL || - ctx->obscured) { + c->overlay_check != NULL) { tty_redraw_region(tty, ctx); return; } @@ -2058,7 +2052,7 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) { u_int i, j; - if (ctx->flags & TTY_CTX_WINDOW_BIGGER) { + if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED)) { ctx->redraw_cb(ctx); return; } From 55fbacb46922d50cf4fe5545d7f5df6a36f8919c Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 22 May 2026 13:07:54 +0100 Subject: [PATCH 132/167] Fix merge error. --- screen-write.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screen-write.c b/screen-write.c index 88413ca072..3708c5cde0 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2144,8 +2144,8 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ttyctx.cell = &ci->gc; if (ci->wrapped) ttyctx.flags |= TTY_CTX_WRAPPED; - ttyctx.ptr = cl->data + wr_start; - ttyctx.n = wr_length; + ttyctx.data.data = cl->data + wr_start; + ttyctx.data.size = wr_length; tty_write(tty_cmd_cells, &ttyctx); } items++; From 29a1a1f8b0c9ef3923c26e3ae9dd8ec8668350d1 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 22 May 2026 18:03:01 +0100 Subject: [PATCH 133/167] Check overlay/BCE first to avoid walking panes unless needed. --- tty.c | 63 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/tty.c b/tty.c index df14f6703f..7c064b0d87 100644 --- a/tty.c +++ b/tty.c @@ -1004,10 +1004,7 @@ tty_window_offset1(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy) else if (cy > w->sy - *sy) *oy = w->sy - *sy; else - /* cy-sy/2 was causing panned panes to scroll - * when the cursor was half way down the pane. - */ - *oy = cy - *sy + 1; /* cy - *sy / 2; */ + *oy = cy - *sy + 1; } c->pan_window = NULL; @@ -1096,17 +1093,14 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) * If region is large, schedule a redraw. In most cases this is likely * to be followed by some more scrolling. */ - log_debug("%s: %s orlower=%u orupper=%u sy=%u large=%d", __func__, - c->name, ctx->orlower, ctx->orupper, ctx->sy, - tty_large_region(tty, ctx)); if (tty_large_region(tty, ctx) && ~ctx->flags & TTY_CTX_PANE_OBSCURED) { log_debug("%s: %s large region redraw", __func__, c->name); ctx->redraw_cb(ctx); return; } - log_debug("%s: %s small redraw, drawing rows %u-%u", __func__, - c->name, ctx->orupper, ctx->orlower); + log_debug("%s: %s small region redraw (%u-%u)", __func__, c->name, + ctx->orupper, ctx->orlower); for (i = ctx->orupper; i <= ctx->orlower; i++) tty_draw_pane(tty, ctx, i); } @@ -1233,7 +1227,7 @@ tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py, if (tty_clamp_line(tty, ctx, px, py, nx, &l, &x, &rx, &ry)) { r = tty_check_overlay_range(tty, x, ry, rx); - for (i=0; i < r->used; i++) { + for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; @@ -1316,7 +1310,7 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, struct window_pane *wpl, *wp = ctx->arg; struct visible_ranges *r; struct visible_range *ri; - u_int i, yy, overlap = 0, oy = 0; + u_int i, yy, region = 1, oy = 0; char tmp[64]; log_debug("%s: %s, %u,%u at %u,%u", __func__, c->name, nx, ny, px, py); @@ -1325,23 +1319,30 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, if (nx == 0 || ny == 0) return; - /* Verify there's nothing overlapping in z-index before using BCE. */ - TAILQ_FOREACH(wpl, &w->z_index, zentry) { - if (wpl == wp || ~wpl->flags & PANE_FLOATING) - continue; - if ((int)wpl->xoff - 1 > (int)(px + nx) || - wpl->xoff + (int)wpl->sx + 1 < (int)px) - continue; - if ((int)wpl->yoff - 1 > (int)(py + ny) || - wpl->yoff + (int)wpl->sy + 1 < (int)py) - continue; - overlap++; - if (overlap > 0) break; + /* + * If there is an overlay or BCE is not available, cannot clear as a + * region. + */ + if (c->overlay_check != NULL || tty_fake_bce(tty, defaults, bg)) + region = 0; + else { + /* Any overlapping pane also means no region. */ + TAILQ_FOREACH(wpl, &w->z_index, zentry) { + if (wpl == wp || ~wpl->flags & PANE_FLOATING) + continue; + if ((int)wpl->xoff - 1 > (int)(px + nx) || + wpl->xoff + (int)wpl->sx + 1 < (int)px) + continue; + if ((int)wpl->yoff - 1 > (int)(py + ny) || + wpl->yoff + (int)wpl->sy + 1 < (int)py) + continue; + region = 0; + break; + } } - /* If genuine BCE is available, can try escape sequences. */ - if (!overlap && c->overlay_check == NULL && - !tty_fake_bce(tty, defaults, bg)) { + /* Clear as a region if possible. */ + if (region) { /* Use ED if clearing off the bottom of the terminal. */ if (px == 0 && px + nx >= tty->sx && @@ -1392,15 +1393,15 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, } } - if (c->session->statusat == 0) - oy = c->session->statuslines; - /* Couldn't use an escape sequence, loop over the lines. */ + if (c->session->statusat == 0) + oy = c->session->statuslines; for (yy = py; yy < py + ny; yy++) { r = tty_check_overlay_range(tty, px, yy - oy, nx); - for (i=0; i < r->used; i++) { + for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; - if (ri->nx == 0) continue; + if (ri->nx == 0) + continue; tty_clear_line(tty, defaults, yy, ri->px, ri->nx, bg); } } From 91b4e02805a8ad6bcfb71bb5c294ffffbafcaf18 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sat, 23 May 2026 06:15:12 +0100 Subject: [PATCH 134/167] Fix merge messup where i is no longer x, use px and ri->px instead here. --- tty.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tty.c b/tty.c index 121e3b85d9..58c809504b 100644 --- a/tty.c +++ b/tty.c @@ -1471,12 +1471,12 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) } if (tty_clamp_line(tty, ctx, 0, py, nx, &px, &x, &rx, &ry)) { if (wp) { - r = tty_check_overlay_range(tty, i, py, rx); + r = tty_check_overlay_range(tty, px, py, rx); for (i=0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; - tty_draw_line(tty, s, i, py, ri->nx, + tty_draw_line(tty, s, ri->px, py, ri->nx, x + ri->px, ry, &ctx->defaults, ctx->palette); } From 30866d06d586ba43ba8ec5fea0ae8ee94870ae7f Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 24 May 2026 09:58:30 +0100 Subject: [PATCH 135/167] Add visible range checks to screen_write.c functions and remove checks from tty.c. --- screen-write.c | 364 ++++++++++++++++++++++++++++++++++++++++++++----- tty.c | 5 +- 2 files changed, 331 insertions(+), 38 deletions(-) diff --git a/screen-write.c b/screen-write.c index dbce6a9db0..36bbcb28d7 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1118,7 +1118,9 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) struct screen *s = ctx->s; struct tty_ctx ttyctx; struct grid_cell gc; - u_int xx, yy; + u_int xx, yy, sx, xoff, yoff, cx, i, n; + struct visible_ranges *r; + struct visible_range *ri; memcpy(&gc, &grid_default_cell, sizeof gc); utf8_set(&gc.data, 'E'); @@ -1141,15 +1143,45 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) screen_write_initctx(ctx, &ttyctx, 1, 1); screen_write_collect_clear(ctx, 0, screen_size_y(s) - 1); - tty_write(tty_cmd_alignmenttest, &ttyctx); + + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { + tty_write(tty_cmd_alignmenttest, &ttyctx); + return; + } + + sx = screen_size_x(s); + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + + for (yy = 0; yy < screen_size_y(s); yy++) { + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff, yoff + yy, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + + cx = ri->px - xoff; + for (n = 0; n < ri->nx && cx < sx; n++, cx++) { + ttyctx.ocx = cx; + ttyctx.ocy = yy; + ttyctx.cell = &gc; + tty_write(tty_cmd_cell, &ttyctx); + } + } + } } /* Insert nx characters. */ void screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) { - struct screen *s = ctx->s; - struct tty_ctx ttyctx; + struct screen *s = ctx->s; + struct tty_ctx ttyctx; + u_int sx, xoff, yoff, cx, n, i; + struct visible_ranges *r; + struct visible_range *ri; + struct grid_cell gc; if (nx == 0) nx = 1; @@ -1174,15 +1206,44 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = nx; - tty_write(tty_cmd_insertcharacter, &ttyctx); + + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { + tty_write(tty_cmd_insertcharacter, &ttyctx); + return; + } + + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + sx = screen_size_x(s); + + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff + s->cx, yoff + s->cy, sx - s->cx, NULL); + + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + + cx = ri->px - xoff; + for (n = 0; n < ri->nx && cx < sx; n++, cx++) { + grid_view_get_cell(s->grid, cx, s->cy, &gc); + screen_write_set_cursor(ctx, cx, -1); + ttyctx.cell = &gc; + tty_write(tty_cmd_cell, &ttyctx); + } + } } /* Delete nx characters. */ void screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) { - struct screen *s = ctx->s; - struct tty_ctx ttyctx; + struct screen *s = ctx->s; + struct tty_ctx ttyctx; + u_int sx, xoff, yoff, cx, n, i; + struct visible_ranges *r; + struct visible_range *ri; + struct grid_cell gc; if (nx == 0) nx = 1; @@ -1207,15 +1268,44 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = nx; - tty_write(tty_cmd_deletecharacter, &ttyctx); + + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { + tty_write(tty_cmd_deletecharacter, &ttyctx); + return; + } + + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + sx = screen_size_x(s); + + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff + s->cx, yoff + s->cy, sx - s->cx, NULL); + + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + + cx = ri->px - xoff; + for (n = 0; n < ri->nx && cx < sx; n++, cx++) { + grid_view_get_cell(s->grid, cx, s->cy, &gc); + screen_write_set_cursor(ctx, cx, -1); + ttyctx.cell = &gc; + tty_write(tty_cmd_cell, &ttyctx); + } + } } /* Clear nx characters. */ void screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) { - struct screen *s = ctx->s; - struct tty_ctx ttyctx; + struct screen *s = ctx->s; + struct tty_ctx ttyctx; + u_int sx, xoff, yoff, cx, n, i; + struct visible_ranges *r; + struct visible_range *ri; + struct grid_cell gc; if (nx == 0) nx = 1; @@ -1233,26 +1323,57 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) ctx->wp->flags |= PANE_REDRAW; #endif - screen_write_initctx(ctx, &ttyctx, 0, 0); + screen_write_initctx(ctx, &ttyctx, 0, 1); ttyctx.bg = bg; grid_view_clear(s->grid, s->cx, s->cy, nx, 1, bg); screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = nx; - tty_write(tty_cmd_clearcharacter, &ttyctx); + + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { + tty_write(tty_cmd_clearcharacter, &ttyctx); + return; + } + + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + sx = screen_size_x(s); + + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff + s->cx, yoff + s->cy, sx - s->cx, NULL); + + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + + cx = ri->px - xoff; + for (n = 0; n < ri->nx && cx < sx; n++, cx++) { + grid_view_get_cell(s->grid, cx, s->cy, &gc); + screen_write_set_cursor(ctx, cx, -1); + ttyctx.cell = &gc; + tty_write(tty_cmd_cell, &ttyctx); + } + } } /* Insert ny lines. */ void screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) { - struct screen *s = ctx->s; - struct grid *gd = s->grid; - struct tty_ctx ttyctx; + struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct tty_ctx ttyctx; + u_int sx, sy, xoff, yoff, y, cx, i, n; + struct visible_ranges *r; + struct visible_range *ri; + struct grid_cell gc; #ifdef ENABLE_SIXEL - u_int sy = screen_size_y(s); + sy = screen_size_y(s); +#else + sy = screen_size_y(s); #endif if (ny == 0) @@ -1276,7 +1397,34 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - tty_write(tty_cmd_insertline, &ttyctx); + + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { + tty_write(tty_cmd_insertline, &ttyctx); + return; + } + + sx = screen_size_x(s); + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + + for (y = s->cy; y < sy; y++) { + screen_write_set_cursor(ctx, 0, y); + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff, yoff + y, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + + cx = ri->px - xoff; + for (n = 0; n < ri->nx && cx < sx; n++, cx++) { + grid_view_get_cell(gd, cx, y, &gc); + screen_write_set_cursor(ctx, cx, -1); + ttyctx.cell = &gc; + tty_write(tty_cmd_cell, &ttyctx); + } + } + } return; } @@ -1296,17 +1444,49 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - tty_write(tty_cmd_insertline, &ttyctx); + + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { + tty_write(tty_cmd_insertline, &ttyctx); + return; + } + + sx = screen_size_x(s); + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + + for (y = s->cy; y <= s->rlower; y++) { + screen_write_set_cursor(ctx, 0, y); + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff, yoff + y, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + + cx = ri->px - xoff; + for (n = 0; n < ri->nx && cx < sx; n++, cx++) { + grid_view_get_cell(gd, cx, y, &gc); + screen_write_set_cursor(ctx, cx, -1); + ttyctx.cell = &gc; + tty_write(tty_cmd_cell, &ttyctx); + } + } + } } /* Delete ny lines. */ void screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) { - struct screen *s = ctx->s; - struct grid *gd = s->grid; - struct tty_ctx ttyctx; - u_int sy = screen_size_y(s); + struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct tty_ctx ttyctx; + u_int sx, sy, xoff, yoff, y, cx, i, n; + struct visible_ranges *r; + struct visible_range *ri; + struct grid_cell gc; + + sy = screen_size_y(s); if (ny == 0) ny = 1; @@ -1329,7 +1509,34 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - tty_write(tty_cmd_deleteline, &ttyctx); + + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { + tty_write(tty_cmd_deleteline, &ttyctx); + return; + } + + sx = screen_size_x(s); + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + + for (y = s->cy; y < sy; y++) { + screen_write_set_cursor(ctx, 0, y); + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff, yoff + y, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + + cx = ri->px - xoff; + for (n = 0; n < ri->nx && cx < sx; n++, cx++) { + grid_view_get_cell(gd, cx, y, &gc); + screen_write_set_cursor(ctx, cx, -1); + ttyctx.cell = &gc; + tty_write(tty_cmd_cell, &ttyctx); + } + } + } return; } @@ -1348,7 +1555,34 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - tty_write(tty_cmd_deleteline, &ttyctx); + + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { + tty_write(tty_cmd_deleteline, &ttyctx); + return; + } + + sx = screen_size_x(s); + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + + for (y = s->cy; y <= s->rlower; y++) { + screen_write_set_cursor(ctx, 0, y); + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff, yoff + y, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + + cx = ri->px - xoff; + for (n = 0; n < ri->nx && cx < sx; n++, cx++) { + grid_view_get_cell(gd, cx, y, &gc); + screen_write_set_cursor(ctx, cx, -1); + ttyctx.cell = &gc; + tty_write(tty_cmd_cell, &ttyctx); + } + } + } } /* Clear line at cursor. */ @@ -1486,8 +1720,12 @@ screen_write_cursormove(struct screen_write_ctx *ctx, int px, int py, void screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct tty_ctx ttyctx; + struct screen *s = ctx->s; + struct tty_ctx ttyctx; + u_int sx, xoff, yoff, cx, i, n; + struct visible_ranges *r; + struct visible_range *ri; + struct grid_cell gc; if (s->cy == s->rupper) { #ifdef ENABLE_SIXEL @@ -1501,10 +1739,33 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) screen_write_initctx(ctx, &ttyctx, 1, 0); ttyctx.bg = bg; - tty_write(tty_cmd_reverseindex, &ttyctx); + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { + tty_write(tty_cmd_reverseindex, &ttyctx); + return; + } + + sx = screen_size_x(s); + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + + screen_write_set_cursor(ctx, 0, s->rupper); + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff, yoff + s->rupper, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + + cx = ri->px - xoff; + for (n = 0; n < ri->nx && cx < sx; n++, cx++) { + grid_view_get_cell(s->grid, cx, s->rupper, &gc); + screen_write_set_cursor(ctx, cx, -1); + ttyctx.cell = &gc; + tty_write(tty_cmd_cell, &ttyctx); + } + } } else if (s->cy > 0) screen_write_set_cursor(ctx, -1, s->cy - 1); - } /* Set scroll region. */ @@ -1604,10 +1865,13 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg) void screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) { - struct screen *s = ctx->s; - struct grid *gd = s->grid; - struct tty_ctx ttyctx; - u_int i; + struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct tty_ctx ttyctx; + u_int sx, xoff, yoff, y, cx, i, n; + struct visible_ranges *r; + struct visible_range *ri; + struct grid_cell gc; screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; @@ -1627,7 +1891,34 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = lines; - tty_write(tty_cmd_scrolldown, &ttyctx); + + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { + tty_write(tty_cmd_scrolldown, &ttyctx); + return; + } + + sx = screen_size_x(s); + xoff = ctx->wp->xoff; + yoff = ctx->wp->yoff; + + for (y = s->rupper; y <= s->rlower; y++) { + screen_write_set_cursor(ctx, 0, y); + r = screen_redraw_get_visible_ranges(ctx->wp, + xoff, yoff + y, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + + cx = ri->px - xoff; + for (n = 0; n < ri->nx && cx < sx; n++, cx++) { + grid_view_get_cell(gd, cx, y, &gc); + screen_write_set_cursor(ctx, cx, -1); + ttyctx.cell = &gc; + tty_write(tty_cmd_cell, &ttyctx); + } + } + } } /* Carriage return (cursor to start of line). */ @@ -2774,9 +3065,14 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, } screen_write_collect_flush(ctx, 0, __func__); - screen_write_initctx(ctx, &ttyctx, 0, 0); + screen_write_initctx(ctx, &ttyctx, 0, 1); ttyctx.image = image_store(s, si); + if ((ttyctx.flags & TTY_CTX_PANE_OBSCURED) && ctx->wp != NULL) { + ctx->wp->flags |= PANE_REDRAW; + return; + } + tty_write(tty_cmd_sixelimage, &ttyctx); screen_write_cursormove(ctx, 0, cy + y, 0); diff --git a/tty.c b/tty.c index e6fd3da7d4..710d15c5d7 100644 --- a/tty.c +++ b/tty.c @@ -1442,8 +1442,6 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) */ r = tty_check_overlay_range(tty, ctx->xoff, ctx->yoff + py, nx); - r = screen_redraw_get_visible_ranges(wp, - ctx->xoff, ctx->yoff + py, nx, r); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; @@ -2113,8 +2111,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - if (screen_redraw_is_visible(r, px)) - tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette, + tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette, ctx->s->hyperlinks); if (ctx->flags & TTY_CTX_CELL_INVALIDATE) From 791f77b8799e53010f2c91eb4c8607ef5f839ad8 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 24 May 2026 10:18:28 +0100 Subject: [PATCH 136/167] Some missing calls to tty_check_overlay_range. --- screen-redraw.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 522fd14dd3..a00d0fc313 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -712,7 +712,8 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) width = size - x; } - r = screen_redraw_get_visible_ranges(wp, x, yoff, width, NULL); + r = tty_check_overlay_range(tty, x, yoff, width); + r = screen_redraw_get_visible_ranges(wp, x, yoff, width, r); if (ctx->statustop) yoff += ctx->statuslines; for (i = 0; i < r->used; i++) { @@ -1329,7 +1330,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) __func__, c->name, wp->id, i, j, wx, wy, width); /* Get visible ranges of line before we draw it. */ - r = screen_redraw_get_visible_ranges(wp, wx, wy, width, NULL); + r = tty_check_overlay_range(tty, wx, wy, width); + r = screen_redraw_get_visible_ranges(wp, wx, wy, width, r); tty_default_colours(&defaults, wp); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; From 95c20aef209d321b7c354bacb85c94214263a464 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 24 May 2026 11:22:44 +0100 Subject: [PATCH 137/167] Fix the alignmenttest. --- tty.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tty.c b/tty.c index 710d15c5d7..41fd258011 100644 --- a/tty.c +++ b/tty.c @@ -2051,7 +2051,9 @@ tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) { - u_int i, j; + struct visible_ranges *r; + struct visible_range *ri; + u_int i, j, k, px, py, cx; if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED)) { ctx->redraw_cb(ctx); @@ -2064,10 +2066,21 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); + /* Get tty position from pane position for overlay check. */ + px = ctx->xoff - ctx->wox; + for (j = 0; j < ctx->sy; j++) { - tty_cursor_pane(tty, ctx, 0, j); - for (i = 0; i < ctx->sx; i++) - tty_putc(tty, 'E'); + py = ctx->yoff + j - ctx->woy; + r = tty_check_overlay_range(tty, px, py, ctx->sx); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + cx = ri->px - ctx->xoff + ctx->wox; + tty_cursor_pane(tty, ctx, cx, j); + for (k = 0; k < ri->nx; k++) + tty_putc(tty, 'E'); + } } } From 724d6d7c9525debb4c8e7ec54984c92a8a29e032 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 24 May 2026 11:43:35 +0100 Subject: [PATCH 138/167] Change to fall into redraw for alignment test. --- tty.c | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/tty.c b/tty.c index 41fd258011..d05eab78ec 100644 --- a/tty.c +++ b/tty.c @@ -2051,11 +2051,11 @@ tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) { - struct visible_ranges *r; - struct visible_range *ri; - u_int i, j, k, px, py, cx; + struct client *c = tty->client; + u_int i, j; - if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED)) { + if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || + c->overlay_check != NULL) { ctx->redraw_cb(ctx); return; } @@ -2066,21 +2066,10 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); - /* Get tty position from pane position for overlay check. */ - px = ctx->xoff - ctx->wox; - for (j = 0; j < ctx->sy; j++) { - py = ctx->yoff + j - ctx->woy; - r = tty_check_overlay_range(tty, px, py, ctx->sx); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - cx = ri->px - ctx->xoff + ctx->wox; - tty_cursor_pane(tty, ctx, cx, j); - for (k = 0; k < ri->nx; k++) - tty_putc(tty, 'E'); - } + tty_cursor_pane(tty, ctx, 0, j); + for (i = 0; i < ctx->sx; i++) + tty_putc(tty, 'E'); } } From 43ae6e0a6bb833f03ac6c5fed4a446e18e187173 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 24 May 2026 12:07:42 +0100 Subject: [PATCH 139/167] tty_clear_area should not work out popup ranges because tty_clear_line is going to do it anyway. --- screen-redraw.c | 6 +++--- tty.c | 13 ++----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index a00d0fc313..0a343f6915 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1498,13 +1498,13 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, } for (j = jmin; j < jmax; j++) { - wy = sb_y + j; /* window y coordinate */ - py = sb_tty_y + j;/* tty y coordinate */ + wy = sb_y + j; /* window y coordinate */ + py = sb_tty_y + j; /* tty y coordinate */ r = tty_check_overlay_range(tty, sb_x, wy, imax); r = screen_redraw_get_visible_ranges(wp, sb_x, wy, imax, r); for (i = imin; i < imax; i++) { px = sb_x + ox + i; /* tty x coordinate */ - wx = sb_x + i; /* window x coordinate */ + wx = sb_x + i; /* window x coordinate */ if (wx < xoff - (int)sb_w - (int)sb_pad || px >= sx || px < 0 || wy < yoff - 1 || diff --git a/tty.c b/tty.c index d05eab78ec..157efbcf3d 100644 --- a/tty.c +++ b/tty.c @@ -1394,17 +1394,8 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, } /* Couldn't use an escape sequence, loop over the lines. */ - if (c->session->statusat == 0) - oy = c->session->statuslines; - for (yy = py; yy < py + ny; yy++) { - r = tty_check_overlay_range(tty, px, yy - oy, nx); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - tty_clear_line(tty, defaults, yy, ri->px, ri->nx, bg); - } - } + for (yy = py; yy < py + ny; yy++) + tty_clear_line(tty, defaults, yy, px, nx, bg); } /* Clear an area in a pane. */ From fb9e47d8c9eddf62254f236d17736118a694671f Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 24 May 2026 12:10:46 +0100 Subject: [PATCH 140/167] Some chanegs I made in the wrong branch. --- tty.c | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/tty.c b/tty.c index 157efbcf3d..2950043e14 100644 --- a/tty.c +++ b/tty.c @@ -1306,11 +1306,9 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, { struct client *c = tty->client; const struct grid_cell *defaults = &ctx->defaults; - struct window *w = c->session->curw->window; - struct window_pane *wpl, *wp = ctx->arg; struct visible_ranges *r; struct visible_range *ri; - u_int i, yy, region = 1, oy = 0; + u_int i, yy, oy = 0; char tmp[64]; log_debug("%s: %s, %u,%u at %u,%u", __func__, c->name, nx, ny, px, py); @@ -1323,26 +1321,7 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, * If there is an overlay or BCE is not available, cannot clear as a * region. */ - if (c->overlay_check != NULL || tty_fake_bce(tty, defaults, bg)) - region = 0; - else { - /* Any overlapping pane also means no region. */ - TAILQ_FOREACH(wpl, &w->z_index, zentry) { - if (wpl == wp || ~wpl->flags & PANE_FLOATING) - continue; - if ((int)wpl->xoff - 1 > (int)(px + nx) || - wpl->xoff + (int)wpl->sx + 1 < (int)px) - continue; - if ((int)wpl->yoff - 1 > (int)(py + ny) || - wpl->yoff + (int)wpl->sy + 1 < (int)py) - continue; - region = 0; - break; - } - } - - /* Clear as a region if possible. */ - if (region) { + if (c->overlay_check == NULL && !tty_fake_bce(tty, defaults, bg)) { /* Use ED if clearing off the bottom of the terminal. */ if (px == 0 && px + nx >= tty->sx && @@ -1828,7 +1807,7 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) if (ctx->ocy != ctx->orupper) return; - if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || + if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || From f75b1e5f07e73ef3caf4fad795cecb11a993159c Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 24 May 2026 12:25:51 +0100 Subject: [PATCH 141/167] Do not check overlays in tty_draw_line_clear, the caller should be doing it. --- screen-redraw.c | 6 +++--- tty-draw.c | 26 ++++++++------------------ tty.c | 4 +--- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 0a343f6915..f31b2e6cf9 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1276,7 +1276,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct screen *s = wp->screen; struct colour_palette *palette = &wp->palette; struct grid_cell defaults; - u_int i, j, woy, wx, wy, py, width; + u_int i, j, k, woy, wx, wy, py, width; struct visible_ranges *r; struct visible_range *ri; @@ -1333,8 +1333,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) r = tty_check_overlay_range(tty, wx, wy, width); r = screen_redraw_get_visible_ranges(wp, wx, wy, width, r); tty_default_colours(&defaults, wp); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; + for (k = 0; k < r->used; k++) { + ri = &r->ranges[k]; if (ri->nx == 0) continue; tty_draw_line(tty, s, ri->px - wp->xoff, j, ri->nx, diff --git a/tty-draw.c b/tty-draw.c index 865fff604b..24d8b711bd 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -46,10 +46,6 @@ static void tty_draw_line_clear(struct tty *tty, u_int px, u_int py, u_int nx, const struct grid_cell *defaults, u_int bg, int wrapped) { - struct visible_ranges *r; - struct visible_range *rr; - u_int i; - /* Nothing to clear. */ if (nx == 0) return; @@ -82,20 +78,14 @@ tty_draw_line_clear(struct tty *tty, u_int px, u_int py, u_int nx, } /* Couldn't use an escape sequence, use spaces. */ - r = tty_check_overlay_range(tty, px, py, nx); - for (i = 0; i < r->used; i++) { - rr = &r->ranges[i]; - if (rr->nx != 0) { - if (rr->px != 0 || !wrapped) - tty_cursor(tty, rr->px, py); - if (rr->nx == 1) - tty_putc(tty, ' '); - else if (rr->nx == 2) - tty_putn(tty, " ", 2, 2); - else - tty_repeat_space(tty, rr->nx); - } - } + if (px != 0 || !wrapped) + tty_cursor(tty, px, py); + if (nx == 1) + tty_putc(tty, ' '); + else if (nx == 2) + tty_putn(tty, " ", 2, 2); + else + tty_repeat_space(tty, nx); } /* Draw a line from screen to tty. */ diff --git a/tty.c b/tty.c index 2950043e14..9d3ea5fb3c 100644 --- a/tty.c +++ b/tty.c @@ -1306,9 +1306,7 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, { struct client *c = tty->client; const struct grid_cell *defaults = &ctx->defaults; - struct visible_ranges *r; - struct visible_range *ri; - u_int i, yy, oy = 0; + u_int yy; char tmp[64]; log_debug("%s: %s, %u,%u at %u,%u", __func__, c->name, nx, ny, px, py); From 92dcb22e92b581208320c15c64d6bb47d485ac69 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 25 May 2026 08:17:57 +0100 Subject: [PATCH 142/167] Clean up in screen_redraw_draw_pane and fix an offset bug. --- screen-redraw.c | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index f31b2e6cf9..4301d82359 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -704,7 +704,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) /* Left not visible. */ l = ctx->ox - xoff; x = 0; - width = size - i; + width = size - l; } else { /* Right not visible. */ l = 0; @@ -1276,15 +1276,40 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct screen *s = wp->screen; struct colour_palette *palette = &wp->palette; struct grid_cell defaults; - u_int i, j, k, woy, wx, wy, py, width; + u_int j, k, woy, wx, wy, py, width; struct visible_ranges *r; struct visible_range *ri; + /* There are 3 coordinate spaces: + * window: (0 to w->sx-1, 0 to w->sy-1) + * tty: (0 to tty->sx-1, 0 to tty->sy-1) + * pane: (0 to wp->sx-1, 0 to wp->sy-1) + * + * Transformations: + * window <-> tty (x-axis): + * window_x = tty_x + ctx->ox + * tty_x = window_x - ctx->ox + * + * window <-> tty (y-axis): + * woy = (ctx->statustop) ? ctx->statuslines : 0 + * window_y = tty_y + ctx->oy - woy + * tty_y = woy + window_y - ctx->oy + * + * window <-> pane (x-axis): + * window_x = pane_x + wp->xoff + * pane_x = window_x - wp->xoff + * + * window <-> pane (y-axis): + * window_y = pane_y + wp->yoff + * pane_y = window_y - wp->yoff + */ + if (wp->base.mode & MODE_SYNC) screen_write_stop_sync(wp); log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); + /* Check if pane completely not visible. */ if (wp->xoff + (int)wp->sx <= ctx->ox || wp->xoff >= (int)ctx->ox + (int)ctx->sx) return; @@ -1306,28 +1331,22 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) if (wp->xoff >= (int)ctx->ox && wp->xoff + (int)wp->sx <= (int)ctx->ox + (int)ctx->sx) { /* All visible. */ - i = 0; wx = (u_int)(wp->xoff - (int)ctx->ox); width = wp->sx; } else if (wp->xoff < (int)ctx->ox && wp->xoff + (int)wp->sx > (int)ctx->ox + (int)ctx->sx) { /* Both left and right not visible. */ - i = ctx->ox; wx = 0; width = ctx->sx; } else if (wp->xoff < (int)ctx->ox) { /* Left not visible. */ - i = (u_int)((int)ctx->ox - wp->xoff); wx = 0; - width = wp->sx - i; + width = wp->sx - ((u_int)((int)ctx->ox - wp->xoff)); } else { /* Right not visible. */ - i = 0; wx = (u_int)(wp->xoff - (int)ctx->ox); width = ctx->sx - wx; } - log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", - __func__, c->name, wp->id, i, j, wx, wy, width); /* Get visible ranges of line before we draw it. */ r = tty_check_overlay_range(tty, wx, wy, width); @@ -1337,7 +1356,11 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) ri = &r->ranges[k]; if (ri->nx == 0) continue; - tty_draw_line(tty, s, ri->px - wp->xoff, j, ri->nx, + log_debug("%s: %s %%%u range pane (%u,%u) width %u, tty (%u,%u) width %u", + __func__, c->name, wp->id, + ri->px + (int)ctx->ox - wp->xoff, j, ri->nx, + ri->px, py, ri->nx); + tty_draw_line(tty, s, ri->px + (int)ctx->ox - wp->xoff, j, ri->nx, ri->px, py, &defaults, palette); } } From 6c1773542defa3f8f32640cd8b5a1dff1b3a4501 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 25 May 2026 15:49:22 +0100 Subject: [PATCH 143/167] Some trivial bits from master. --- layout-custom.c | 1 - tmux.h | 1 - tty.c | 6 +++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/layout-custom.c b/layout-custom.c index 426ecf298f..2942720c35 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -216,7 +216,6 @@ layout_parse(struct window *w, const char *layout, char **cause) if (floating_lc != NULL) ncells += layout_count_cells(floating_lc); if (npanes > ncells) { - /* Modify this to open a new pane */ xasprintf(cause, "have %u panes but need %u", npanes, ncells); goto fail; diff --git a/tmux.h b/tmux.h index 75cc6658d3..21afb60c35 100644 --- a/tmux.h +++ b/tmux.h @@ -1264,7 +1264,6 @@ struct window_pane { int yoff; int flags; - int saved_flags; #define PANE_REDRAW 0x1 #define PANE_DROP 0x2 #define PANE_FOCUSED 0x4 diff --git a/tty.c b/tty.c index 2db47cc1f7..0b5ae13526 100644 --- a/tty.c +++ b/tty.c @@ -1380,7 +1380,7 @@ static void tty_clear_pane_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, u_int ny, u_int px, u_int nx, u_int bg) { - u_int i, j, x, y, rx, ry; + u_int i, j, x, y, rx, ry; if (tty_clamp_area(tty, ctx, px, py, nx, ny, &i, &j, &x, &y, &rx, &ry)) tty_clear_area(tty, ctx, y, ry, x, rx, bg); @@ -2045,7 +2045,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) { const struct grid_cell *gcp = ctx->cell; struct screen *s = ctx->s; - struct visible_ranges *r = NULL; + struct visible_ranges *r; u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2081,7 +2081,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette, - ctx->s->hyperlinks); + ctx->s->hyperlinks); if (ctx->flags & TTY_CTX_CELL_INVALIDATE) tty_invalidate(tty); From 6513808720a9c51b3cd49a59f5d3e99c1218959c Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 25 May 2026 15:50:25 +0100 Subject: [PATCH 144/167] And a bad merge. --- cmd-swap-pane.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 217d35173d..a8e252e771 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -109,13 +109,6 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) dst_wp->flags ^= PANE_FLOATING; } - /* Swap PANE_FLOATING flag to keep each pane consistent with its new - * layout cell (floating cells have parent == NULL). */ - if ((src_wp->flags ^ dst_wp->flags) & PANE_FLOATING) { - src_wp->flags ^= PANE_FLOATING; - dst_wp->flags ^= PANE_FLOATING; - } - src_wp->window = dst_w; options_set_parent(src_wp->options, dst_w->options); src_wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); From 57b18d8a18243f71348a8fa12b9e3ff881b45387 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 26 May 2026 11:20:28 +0100 Subject: [PATCH 145/167] Move redraw of overlapped panes into common functions. --- screen-write.c | 351 +++++++++++-------------------------------------- tmux.h | 1 + tty.c | 16 +-- 3 files changed, 87 insertions(+), 281 deletions(-) diff --git a/screen-write.c b/screen-write.c index 36bbcb28d7..dabdf58ba5 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1111,6 +1111,48 @@ screen_write_backspace(struct screen_write_ctx *ctx) screen_write_set_cursor(ctx, cx, cy); } +/* Redraw all visible cells on a line. */ +static void +screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, + u_int yy) +{ + struct window_pane *wp = ctx->wp; + struct screen *s = ctx->s; + struct grid_cell gc; + u_int sx = screen_size_x(s),cx, i, n; + u_int xoff = wp->xoff, yoff = wp->yoff; + struct visible_ranges *r; + struct visible_range *ri; + + + r = screen_redraw_get_visible_ranges(wp, xoff, yoff + yy, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + + cx = ri->px - xoff; + for (n = 0; n < ri->nx && cx < sx; n++, cx++) { + grid_view_get_cell(s->grid, cx, s->cy, &gc); + ttyctx->ocx = cx; + ttyctx->ocy = yy; + ttyctx->cell = &gc; + tty_write(tty_cmd_cell, ttyctx); + } + } +} + +/* Redraw all visible cells in a pane. */ +static void +screen_write_redraw_pane(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx) +{ + struct screen *s = ctx->s; + u_int yy; + + for (yy = 0; yy < screen_size_y(s); yy++) + screen_write_redraw_line(ctx, ttyctx, yy); +} + /* VT100 alignment test. */ void screen_write_alignmenttest(struct screen_write_ctx *ctx) @@ -1118,9 +1160,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) struct screen *s = ctx->s; struct tty_ctx ttyctx; struct grid_cell gc; - u_int xx, yy, sx, xoff, yoff, cx, i, n; - struct visible_ranges *r; - struct visible_range *ri; + u_int xx, yy; memcpy(&gc, &grid_default_cell, sizeof gc); utf8_set(&gc.data, 'E'); @@ -1140,48 +1180,24 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) s->rupper = 0; s->rlower = screen_size_y(s) - 1; - screen_write_initctx(ctx, &ttyctx, 1, 1); - screen_write_collect_clear(ctx, 0, screen_size_y(s) - 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_alignmenttest, &ttyctx); return; } - sx = screen_size_x(s); - xoff = ctx->wp->xoff; - yoff = ctx->wp->yoff; - - for (yy = 0; yy < screen_size_y(s); yy++) { - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff, yoff + yy, sx, NULL); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - - cx = ri->px - xoff; - for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - ttyctx.ocx = cx; - ttyctx.ocy = yy; - ttyctx.cell = &gc; - tty_write(tty_cmd_cell, &ttyctx); - } - } - } + screen_write_redraw_pane(ctx, &ttyctx); } /* Insert nx characters. */ void screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) { - struct screen *s = ctx->s; - struct tty_ctx ttyctx; - u_int sx, xoff, yoff, cx, n, i; - struct visible_ranges *r; - struct visible_range *ri; - struct grid_cell gc; + struct screen *s = ctx->s; + struct tty_ctx ttyctx; if (nx == 0) nx = 1; @@ -1212,38 +1228,15 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) return; } - xoff = ctx->wp->xoff; - yoff = ctx->wp->yoff; - sx = screen_size_x(s); - - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff + s->cx, yoff + s->cy, sx - s->cx, NULL); - - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - - cx = ri->px - xoff; - for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - grid_view_get_cell(s->grid, cx, s->cy, &gc); - screen_write_set_cursor(ctx, cx, -1); - ttyctx.cell = &gc; - tty_write(tty_cmd_cell, &ttyctx); - } - } + screen_write_redraw_line(ctx, &ttyctx, s->cy); } /* Delete nx characters. */ void screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) { - struct screen *s = ctx->s; - struct tty_ctx ttyctx; - u_int sx, xoff, yoff, cx, n, i; - struct visible_ranges *r; - struct visible_range *ri; - struct grid_cell gc; + struct screen *s = ctx->s; + struct tty_ctx ttyctx; if (nx == 0) nx = 1; @@ -1274,38 +1267,15 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) return; } - xoff = ctx->wp->xoff; - yoff = ctx->wp->yoff; - sx = screen_size_x(s); - - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff + s->cx, yoff + s->cy, sx - s->cx, NULL); - - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - - cx = ri->px - xoff; - for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - grid_view_get_cell(s->grid, cx, s->cy, &gc); - screen_write_set_cursor(ctx, cx, -1); - ttyctx.cell = &gc; - tty_write(tty_cmd_cell, &ttyctx); - } - } + screen_write_redraw_line(ctx, &ttyctx, s->cy); } /* Clear nx characters. */ void screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) { - struct screen *s = ctx->s; - struct tty_ctx ttyctx; - u_int sx, xoff, yoff, cx, n, i; - struct visible_ranges *r; - struct visible_range *ri; - struct grid_cell gc; + struct screen *s = ctx->s; + struct tty_ctx ttyctx; if (nx == 0) nx = 1; @@ -1336,45 +1306,17 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) return; } - xoff = ctx->wp->xoff; - yoff = ctx->wp->yoff; - sx = screen_size_x(s); - - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff + s->cx, yoff + s->cy, sx - s->cx, NULL); - - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - - cx = ri->px - xoff; - for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - grid_view_get_cell(s->grid, cx, s->cy, &gc); - screen_write_set_cursor(ctx, cx, -1); - ttyctx.cell = &gc; - tty_write(tty_cmd_cell, &ttyctx); - } - } + screen_write_redraw_line(ctx, &ttyctx, s->cy); } /* Insert ny lines. */ void screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) { - struct screen *s = ctx->s; - struct grid *gd = s->grid; - struct tty_ctx ttyctx; - u_int sx, sy, xoff, yoff, y, cx, i, n; - struct visible_ranges *r; - struct visible_range *ri; - struct grid_cell gc; - -#ifdef ENABLE_SIXEL - sy = screen_size_y(s); -#else - sy = screen_size_y(s); -#endif + struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct tty_ctx ttyctx; + u_int sy = screen_size_y(s); if (ny == 0) ny = 1; @@ -1385,8 +1327,8 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) #endif if (s->cy < s->rupper || s->cy > s->rlower) { - if (ny > screen_size_y(s) - s->cy) - ny = screen_size_y(s) - s->cy; + if (ny > sy - s->cy) + ny = sy - s->cy; if (ny == 0) return; @@ -1403,28 +1345,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) return; } - sx = screen_size_x(s); - xoff = ctx->wp->xoff; - yoff = ctx->wp->yoff; - - for (y = s->cy; y < sy; y++) { - screen_write_set_cursor(ctx, 0, y); - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff, yoff + y, sx, NULL); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - - cx = ri->px - xoff; - for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - grid_view_get_cell(gd, cx, y, &gc); - screen_write_set_cursor(ctx, cx, -1); - ttyctx.cell = &gc; - tty_write(tty_cmd_cell, &ttyctx); - } - } - } + screen_write_redraw_pane(ctx, &ttyctx); return; } @@ -1442,7 +1363,6 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) grid_view_insert_lines_region(gd, s->rlower, s->cy, ny, bg); screen_write_collect_flush(ctx, 0, __func__); - ttyctx.n = ny; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { @@ -1450,43 +1370,17 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) return; } - sx = screen_size_x(s); - xoff = ctx->wp->xoff; - yoff = ctx->wp->yoff; - - for (y = s->cy; y <= s->rlower; y++) { - screen_write_set_cursor(ctx, 0, y); - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff, yoff + y, sx, NULL); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - - cx = ri->px - xoff; - for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - grid_view_get_cell(gd, cx, y, &gc); - screen_write_set_cursor(ctx, cx, -1); - ttyctx.cell = &gc; - tty_write(tty_cmd_cell, &ttyctx); - } - } - } + screen_write_redraw_pane(ctx, &ttyctx); } /* Delete ny lines. */ void screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) { - struct screen *s = ctx->s; - struct grid *gd = s->grid; - struct tty_ctx ttyctx; - u_int sx, sy, xoff, yoff, y, cx, i, n; - struct visible_ranges *r; - struct visible_range *ri; - struct grid_cell gc; - - sy = screen_size_y(s); + struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct tty_ctx ttyctx; + u_int sy = screen_size_y(s); if (ny == 0) ny = 1; @@ -1515,28 +1409,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) return; } - sx = screen_size_x(s); - xoff = ctx->wp->xoff; - yoff = ctx->wp->yoff; - - for (y = s->cy; y < sy; y++) { - screen_write_set_cursor(ctx, 0, y); - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff, yoff + y, sx, NULL); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - - cx = ri->px - xoff; - for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - grid_view_get_cell(gd, cx, y, &gc); - screen_write_set_cursor(ctx, cx, -1); - ttyctx.cell = &gc; - tty_write(tty_cmd_cell, &ttyctx); - } - } - } + screen_write_redraw_pane(ctx, &ttyctx); return; } @@ -1561,28 +1434,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) return; } - sx = screen_size_x(s); - xoff = ctx->wp->xoff; - yoff = ctx->wp->yoff; - - for (y = s->cy; y <= s->rlower; y++) { - screen_write_set_cursor(ctx, 0, y); - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff, yoff + y, sx, NULL); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - - cx = ri->px - xoff; - for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - grid_view_get_cell(gd, cx, y, &gc); - screen_write_set_cursor(ctx, cx, -1); - ttyctx.cell = &gc; - tty_write(tty_cmd_cell, &ttyctx); - } - } - } + screen_write_redraw_pane(ctx, &ttyctx); } /* Clear line at cursor. */ @@ -1720,12 +1572,8 @@ screen_write_cursormove(struct screen_write_ctx *ctx, int px, int py, void screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct tty_ctx ttyctx; - u_int sx, xoff, yoff, cx, i, n; - struct visible_ranges *r; - struct visible_range *ri; - struct grid_cell gc; + struct screen *s = ctx->s; + struct tty_ctx ttyctx; if (s->cy == s->rupper) { #ifdef ENABLE_SIXEL @@ -1736,7 +1584,7 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); screen_write_collect_flush(ctx, 0, __func__); - screen_write_initctx(ctx, &ttyctx, 1, 0); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { @@ -1744,26 +1592,7 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) return; } - sx = screen_size_x(s); - xoff = ctx->wp->xoff; - yoff = ctx->wp->yoff; - - screen_write_set_cursor(ctx, 0, s->rupper); - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff, yoff + s->rupper, sx, NULL); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - - cx = ri->px - xoff; - for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - grid_view_get_cell(s->grid, cx, s->rupper, &gc); - screen_write_set_cursor(ctx, cx, -1); - ttyctx.cell = &gc; - tty_write(tty_cmd_cell, &ttyctx); - } - } + screen_write_redraw_pane(ctx, &ttyctx); } else if (s->cy > 0) screen_write_set_cursor(ctx, -1, s->cy - 1); } @@ -1865,13 +1694,10 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg) void screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) { - struct screen *s = ctx->s; - struct grid *gd = s->grid; - struct tty_ctx ttyctx; - u_int sx, xoff, yoff, y, cx, i, n; - struct visible_ranges *r; - struct visible_range *ri; - struct grid_cell gc; + struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct tty_ctx ttyctx; + u_int i; screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; @@ -1897,28 +1723,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) return; } - sx = screen_size_x(s); - xoff = ctx->wp->xoff; - yoff = ctx->wp->yoff; - - for (y = s->rupper; y <= s->rlower; y++) { - screen_write_set_cursor(ctx, 0, y); - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff, yoff + y, sx, NULL); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - - cx = ri->px - xoff; - for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - grid_view_get_cell(gd, cx, y, &gc); - screen_write_set_cursor(ctx, cx, -1); - ttyctx.cell = &gc; - tty_write(tty_cmd_cell, &ttyctx); - } - } - } + screen_write_redraw_pane(ctx, &ttyctx); } /* Carriage return (cursor to start of line). */ diff --git a/tmux.h b/tmux.h index 21afb60c35..75cc6658d3 100644 --- a/tmux.h +++ b/tmux.h @@ -1264,6 +1264,7 @@ struct window_pane { int yoff; int flags; + int saved_flags; #define PANE_REDRAW 0x1 #define PANE_DROP 0x2 #define PANE_FOCUSED 0x4 diff --git a/tty.c b/tty.c index 0b5ae13526..761bd27ca8 100644 --- a/tty.c +++ b/tty.c @@ -1661,7 +1661,7 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - if ((ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED)) || + if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || !tty_full_width(tty, ctx) || tty_fake_bce(tty, &ctx->defaults, ctx->bg) || (!tty_term_has(tty->term, TTYC_ICH) && @@ -1684,7 +1684,7 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || + if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || !tty_full_width(tty, ctx) || tty_fake_bce(tty, &ctx->defaults, ctx->bg) || (!tty_term_has(tty->term, TTYC_DCH) && @@ -1716,7 +1716,7 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - if ((ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED)) || + if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || !tty_full_width(tty, ctx) || tty_fake_bce(tty, &ctx->defaults, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || @@ -1744,7 +1744,7 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || + if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || !tty_full_width(tty, ctx) || tty_fake_bce(tty, &ctx->defaults, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || @@ -1804,7 +1804,7 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) if (ctx->ocy != ctx->orupper) return; - if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || + if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || @@ -1879,7 +1879,7 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) struct client *c = tty->client; u_int i; - if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || + if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || @@ -1918,7 +1918,7 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) u_int i; struct client *c = tty->client; - if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || + if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || @@ -2021,7 +2021,7 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) struct client *c = tty->client; u_int i, j; - if (ctx->flags & (TTY_CTX_WINDOW_BIGGER|TTY_CTX_PANE_OBSCURED) || + if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) || c->overlay_check != NULL) { ctx->redraw_cb(ctx); return; From d89a4ab3df4ccaca49f290b655dd57502fad9589 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 26 May 2026 11:41:06 +0100 Subject: [PATCH 146/167] Redraw obscured panes instead of scrolling also. --- screen-write.c | 53 +++++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/screen-write.c b/screen-write.c index dabdf58ba5..6bd3fe9015 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1124,7 +1124,6 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, struct visible_ranges *r; struct visible_range *ri; - r = screen_redraw_get_visible_ranges(wp, xoff, yoff + yy, sx, NULL); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; @@ -1133,7 +1132,7 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, cx = ri->px - xoff; for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - grid_view_get_cell(s->grid, cx, s->cy, &gc); + grid_view_get_cell(s->grid, cx, yy, &gc); ttyctx->ocx = cx; ttyctx->ocy = yy; ttyctx->cell = &gc; @@ -2109,6 +2108,7 @@ static void screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, const char *from) { + struct window_pane *wp = ctx->wp; struct screen *s = ctx->s; struct screen_write_citem *ci, *tmp; struct screen_write_cline *cl; @@ -2120,37 +2120,30 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct tty_ctx ttyctx; struct visible_ranges *r; struct visible_range *ri; - struct window_pane *wp = ctx->wp; - if (s->mode & MODE_SYNC) { - for (y = 0; y < screen_size_y(s); y++) { - cl = &ctx->s->write_list[y]; - TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { - TAILQ_REMOVE(&cl->items, ci, entry); - screen_write_free_citem(ci); - } - } - return; - } + if (s->mode & MODE_SYNC) + goto discard; if (ctx->scrolled != 0) { + screen_write_initctx(ctx, &ttyctx, 1, 1); + if (ttyctx.flags & TTY_CTX_PANE_OBSCURED && wp != NULL) { + screen_write_redraw_pane(ctx, &ttyctx); + goto discard; + } + log_debug("%s: scrolled %u (region %u-%u)", __func__, ctx->scrolled, s->rupper, s->rlower); if (ctx->scrolled > s->rlower - s->rupper + 1) ctx->scrolled = s->rlower - s->rupper + 1; - screen_write_initctx(ctx, &ttyctx, 1, 1); if (wp != NULL && wp->yoff + wp->sy > wp->window->sy) - ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy); + ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy); ttyctx.n = ctx->scrolled; ttyctx.bg = ctx->bg; tty_write(tty_cmd_scrollup, &ttyctx); - if (wp != NULL) - log_debug("%s: after scrollup, PANE_REDRAW=%d for %%%u", - __func__, !!(wp->flags & PANE_REDRAW), wp->id); if (wp != NULL) - ctx->wp->flags |= PANE_REDRAWSCROLLBAR; + wp->flags |= PANE_REDRAWSCROLLBAR; } ctx->scrolled = 0; ctx->bg = 8; @@ -2183,16 +2176,6 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, NULL); last = UINT_MAX; - if (y == (u_int)s->cy || !TAILQ_EMPTY(&cl->items)) { - u_int dbg_cnt = 0; - struct screen_write_citem *dbg_ci; - TAILQ_FOREACH(dbg_ci, &cl->items, entry) dbg_cnt++; - log_debug("%s: row y=%u has %u items (wp_xoff=%d yoff=%d wsx=%u)", - __func__, y, dbg_cnt, - wp ? (int)wp->xoff : -1, - wp ? (int)wp->yoff : -1, - wsx); - } TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { log_debug("collect list: x=%u (last %u), y=%u, used=%u", ci->x, last, y, ci->used); @@ -2258,6 +2241,18 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, s->cx = cx; s->cy = cy; log_debug("%s: flushed %u items (%s)", __func__, items, from); + return; + +discard: + for (y = 0; y < screen_size_y(s); y++) { + cl = &ctx->s->write_list[y]; + TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { + TAILQ_REMOVE(&cl->items, ci, entry); + screen_write_free_citem(ci); + } + } + ctx->scrolled = 0; + ctx->bg = 8; } /* Insert an item on current line. */ From f9bd5eb79ea3e406acf04405476e53d31ad0c450 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 26 May 2026 12:48:16 +0100 Subject: [PATCH 147/167] If a pane is outside the window, treat as obscured. --- screen-write.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/screen-write.c b/screen-write.c index 6bd3fe9015..42daed2e9d 100644 --- a/screen-write.c +++ b/screen-write.c @@ -191,7 +191,15 @@ screen_write_pane_is_obscured(struct screen_write_ctx *ctx) } ctx->flags |= SCREEN_WRITE_CHECKED_IF_OBSCURED; - while ((wp = TAILQ_PREV(wp, window_panes, zentry)) != NULL) { + if (ctx->wp->xoff < 0 || + ctx->wp->yoff < 0 || + ctx->wp->xoff + ctx->wp->sx >= ctx->wp->window->sx || + ctx->wp->yoff + ctx->wp->sy >= ctx->wp->window->sy) { + ctx->flags |= SCREEN_WRITE_OBSCURED; + return (1); + } + + while ((wp = TAILQ_PREV(wp, window_panes, zentry)) != NULL) { if ((wp->flags & PANE_FLOATING) && ((wp->yoff >= ctx->wp->yoff && wp->yoff <= ctx->wp->yoff + (int)ctx->wp->sy) || From 46be03d88e3b60a4de6280a2dfd2610d088bab01 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 26 May 2026 13:02:07 +0100 Subject: [PATCH 148/167] Clamp range with to size of window (visible ranges cannot be outside window). --- screen-redraw.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/screen-redraw.c b/screen-redraw.c index c9f49b3d4f..2836260dac 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1155,6 +1155,10 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, return (&sr); } + w = base_wp->window; + if (px + width > w->sx) + width = w->sx - px; + if (r == NULL) { /* Start with the entire width of the range. */ server_client_ensure_ranges(&base_wp->r, 1); @@ -1164,7 +1168,6 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, r->used = 1; } - w = base_wp->window; sb = options_get_number(w->options, "pane-scrollbars"); sb_pos = options_get_number(w->options, "pane-scrollbars-position"); From 8f9e2c7db2c510e5d2b7aff8d85fa8f8c7e5c1f3 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 27 May 2026 14:30:27 +0100 Subject: [PATCH 149/167] Trivial tidying up of FP clears. --- screen-write.c | 115 ++++++++++++++++++++++++------------------------- 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/screen-write.c b/screen-write.c index 42daed2e9d..def791c248 100644 --- a/screen-write.c +++ b/screen-write.c @@ -27,6 +27,8 @@ static struct screen_write_citem *screen_write_collect_trim( struct screen_write_ctx *, u_int, u_int, u_int, int *); static void screen_write_collect_insert(struct screen_write_ctx *, struct screen_write_citem *); +static void screen_write_collect_insert_clear(struct screen_write_ctx *, + u_int, u_int, u_int); static void screen_write_collect_clear(struct screen_write_ctx *, u_int, u_int); static void screen_write_collect_scroll(struct screen_write_ctx *, u_int); @@ -1535,23 +1537,6 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_insert(ctx, ci); } -/* Clear part of a line from px for nx columns. */ -static void -screen_write_clearpartofline(struct screen_write_ctx *ctx, u_int px, u_int nx, - u_int bg) -{ - struct screen_write_citem *ci = ctx->item; - - if (nx == 0) - return; - - ci->x = px; - ci->used = nx; - ci->type = CLEAR; - ci->bg = bg; - screen_write_collect_insert(ctx, ci); -} - /* Move cursor to px,py. */ void screen_write_cursormove(struct screen_write_ctx *ctx, int px, int py, @@ -1748,7 +1733,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) struct grid *gd = s->grid; struct tty_ctx ttyctx; u_int sx = screen_size_x(s), sy = screen_size_y(s); - u_int y, i, xoff, yoff, cx_save, cy_save; + u_int y, i, xoff, yoff, ocx, ocy; struct visible_ranges *r; struct visible_range *ri; @@ -1782,8 +1767,8 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) } /* Can't just clear screen, must avoid floating windows. */ - cx_save = s->cx; - cy_save = s->cy; + ocx = s->cx; + ocy = s->cy; if (ctx->wp != NULL) { xoff = ctx->wp->xoff; yoff = ctx->wp->yoff; @@ -1792,35 +1777,33 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) yoff = 0; } - /* First line: visible ranges from the current cursor to end. */ + /* First line (containing the cursor). */ if (s->cx <= sx - 1) { - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff + s->cx, yoff + s->cy, sx - s->cx, NULL); + r = screen_redraw_get_visible_ranges(ctx->wp, xoff + s->cx, + yoff + s->cy, sx - s->cx, NULL); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; - screen_write_clearpartofline(ctx, - ri->px - xoff, ri->nx, bg); + screen_write_collect_insert_clear(ctx, ri->px - xoff, + ri->nx, bg); } } - /* Remaining lines: visible ranges across the full width. */ + /* Below cursor to bottom. */ for (y = s->cy + 1; y < sy; y++) { screen_write_set_cursor(ctx, 0, y); - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff, yoff + y, sx, NULL); + r = screen_redraw_get_visible_ranges(ctx->wp, xoff, yoff + y, + sx, NULL); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; - screen_write_clearpartofline(ctx, - ri->px - xoff, ri->nx, bg); + screen_write_collect_insert_clear(ctx, ri->px - xoff, + ri->nx, bg); } } - - screen_write_collect_flush(ctx, 0, __func__); - screen_write_set_cursor(ctx, cx_save, cy_save); + screen_write_set_cursor(ctx, ocx, ocy); } /* Clear to start of screen. */ @@ -1830,7 +1813,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) struct screen *s = ctx->s; struct tty_ctx ttyctx; u_int sx = screen_size_x(s); - u_int y, i, xoff, yoff, cx_save, cy_save; + u_int y, i, xoff, yoff, ocx, ocy; struct visible_ranges *r; struct visible_range *ri; @@ -1858,8 +1841,8 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) } /* Can't just clear screen, must avoid floating windows. */ - cx_save = s->cx; - cy_save = s->cy; + ocx = s->cx; + ocy = s->cy; if (ctx->wp != NULL) { xoff = ctx->wp->xoff; yoff = ctx->wp->yoff; @@ -1868,33 +1851,32 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) yoff = 0; } - /* Lines 0 to cy-1: visible ranges across the full width. */ + /* Top to above the cursor. */ for (y = 0; y < s->cy; y++) { screen_write_set_cursor(ctx, 0, y); - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff, yoff + y, sx, NULL); + r = screen_redraw_get_visible_ranges(ctx->wp, xoff, yoff + y, + sx, NULL); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; - screen_write_clearpartofline(ctx, - ri->px - xoff, ri->nx, bg); + screen_write_collect_insert_clear(ctx, ri->px - xoff, + ri->nx, bg); } } - /* Last line: visible ranges from 0 to cursor (inclusive). */ + /* Last line (containing the cursor). */ screen_write_set_cursor(ctx, 0, s->cy); - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff, yoff + cy_save, s->cx + 1, NULL); + r = screen_redraw_get_visible_ranges(ctx->wp, xoff, yoff + ocy, + s->cx + 1, NULL); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; - screen_write_clearpartofline(ctx, ri->px - xoff, ri->nx, bg); + screen_write_collect_insert_clear(ctx, ri->px - xoff, ri->nx, + bg); } - - screen_write_collect_flush(ctx, 0, __func__); - screen_write_set_cursor(ctx, cx_save, cy_save); + screen_write_set_cursor(ctx, ocx, ocy); } /* Clear entire screen. */ @@ -1904,7 +1886,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) struct screen *s = ctx->s; struct tty_ctx ttyctx; u_int sx = screen_size_x(s), sy = screen_size_y(s); - u_int y, i, xoff, yoff, cx_save, cy_save; + u_int y, i, xoff, yoff, ocx, ocy; struct visible_ranges *r; struct visible_range *ri; @@ -1932,8 +1914,8 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) } /* Can't just clear screen, must avoid floating windows. */ - cx_save = s->cx; - cy_save = s->cy; + ocx = s->cx; + ocy = s->cy; if (ctx->wp != NULL) { xoff = ctx->wp->xoff; yoff = ctx->wp->yoff; @@ -1942,21 +1924,20 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) yoff = 0; } + /* Clear every line. */ for (y = 0; y < sy; y++) { screen_write_set_cursor(ctx, 0, y); - r = screen_redraw_get_visible_ranges(ctx->wp, - xoff, yoff + y, sx, NULL); + r = screen_redraw_get_visible_ranges(ctx->wp, xoff, yoff + y, + sx, NULL); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; - screen_write_clearpartofline(ctx, - ri->px - xoff, ri->nx, bg); + screen_write_collect_insert_clear(ctx, ri->px - xoff, + ri->nx, bg); } } - - screen_write_collect_flush(ctx, 0, __func__); - screen_write_set_cursor(ctx, cx_save, cy_save); + screen_write_set_cursor(ctx, ocx, ocy); } /* Clear entire history. */ @@ -2264,7 +2245,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, } /* Insert an item on current line. */ -void +static void screen_write_collect_insert(struct screen_write_ctx *ctx, struct screen_write_citem *ci) { @@ -2281,6 +2262,22 @@ screen_write_collect_insert(struct screen_write_ctx *ctx, ctx->item = screen_write_get_citem(); } +/* Clear part of a line from px for nx columns. */ +static void +screen_write_collect_insert_clear(struct screen_write_ctx *ctx, u_int px, + u_int nx, u_int bg) +{ + struct screen_write_citem *ci = ctx->item; + + if (nx != 0) { + ci->x = px; + ci->used = nx; + ci->type = CLEAR; + ci->bg = bg; + screen_write_collect_insert(ctx, ci); + } +} + /* Finish and store collected cells. */ void screen_write_collect_end(struct screen_write_ctx *ctx) From 02caf7ce4a08dcb27ddf60615e3070f493430c93 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 27 May 2026 19:36:20 +0100 Subject: [PATCH 150/167] Remove a difference from master. --- options-table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options-table.c b/options-table.c index 021a29cedb..25f8c7c77e 100644 --- a/options-table.c +++ b/options-table.c @@ -1474,7 +1474,7 @@ const struct options_table_entry options_table[] = { { .name = "remain-on-exit-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "Command exited (" + .default_str = "Pane is dead (" "#{?#{!=:#{pane_dead_status},}," "status #{pane_dead_status},}" "#{?#{!=:#{pane_dead_signal},}," From c62b27f014ddd170b8acf9293caa92effafe76ad Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 27 May 2026 20:33:03 +0100 Subject: [PATCH 151/167] Add a helper to replace a loop. --- tmux.h | 1 + window-tree.c | 18 +++--------------- window.c | 12 ++++++++++++ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/tmux.h b/tmux.h index 75a96c999a..8c7e9f1180 100644 --- a/tmux.h +++ b/tmux.h @@ -3430,6 +3430,7 @@ struct window *window_create(u_int, u_int, u_int, u_int); void window_pane_set_event(struct window_pane *); struct window_pane *window_get_active_at(struct window *, u_int, u_int); struct window_pane *window_find_string(struct window *, const char *); +int window_has_floating_panes(struct window *); int window_has_pane(struct window *, struct window_pane *); int window_set_active_pane(struct window *, struct window_pane *, int); diff --git a/window-tree.c b/window-tree.c index b5ba7a4340..b58d0ffbc1 100644 --- a/window-tree.c +++ b/window-tree.c @@ -267,21 +267,9 @@ window_tree_build_window(struct session *s, struct winlink *wl, format_free(ft); if (data->type == WINDOW_TREE_SESSION || - data->type == WINDOW_TREE_WINDOW) { - expanded = 0; - /* Without this, the only way to reach a hidden - * floating pane would be to first expand the window - * manually (with the right-arrow key) and then press - * its number — which is non-obvious and breaks the - * expected workflow. - */ - TAILQ_FOREACH(fwp, &wl->window->panes, entry) { - if (fwp->flags & PANE_FLOATING) { - expanded = 1; - break; - } - } - } else + data->type == WINDOW_TREE_WINDOW) + expanded = window_has_floating_panes(wl->window); + else expanded = 1; mti = mode_tree_add(data->data, parent, item, (uint64_t)wl, name, text, expanded); diff --git a/window.c b/window.c index 3aa35f3291..29ad142106 100644 --- a/window.c +++ b/window.c @@ -461,6 +461,18 @@ window_pane_send_resize(struct window_pane *wp, u_int sx, u_int sy) fatal("ioctl failed"); } +int +window_has_floating_panes(struct window *w) +{ + struct window_pane *wp; + + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->flags & PANE_FLOATING) + return (1); + } + return (0); +} + int window_has_pane(struct window *w, struct window_pane *wp) { From bfa2ff2bdf2fb4e5da2e57003104c1a509f54af0 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 27 May 2026 20:33:41 +0100 Subject: [PATCH 152/167] Remove unused variable. --- window-tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/window-tree.c b/window-tree.c index b58d0ffbc1..49d68881d9 100644 --- a/window-tree.c +++ b/window-tree.c @@ -249,7 +249,7 @@ window_tree_build_window(struct session *s, struct winlink *wl, struct window_tree_itemdata *item; struct mode_tree_item *mti; char *name, *text; - struct window_pane *wp, *fwp, **l; + struct window_pane *wp, **l; u_int n, i; int expanded; struct format_tree *ft; From 52250d518d83c5b2dd5138e082b46e734d0dcf36 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 27 May 2026 21:33:50 +0100 Subject: [PATCH 153/167] Redraw line directly for wide cells in screen_write_cell, fix up selection and generally tidy. --- screen-write.c | 90 ++++++++++++++++++++++++++++++-------------------- tmux.h | 5 ++- tty.c | 9 +---- 3 files changed, 57 insertions(+), 47 deletions(-) diff --git a/screen-write.c b/screen-write.c index 5fd371a988..af91b9b003 100644 --- a/screen-write.c +++ b/screen-write.c @@ -632,25 +632,25 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, if (nx == 0 || ny == 0) return; + if (wp != NULL) + yoff = wp->yoff; for (yy = py; yy < py + ny; yy++) { if (yy >= gd->hsize + gd->sy) break; s->cx = cx; screen_write_initctx(ctx, &ttyctx, 0, 0); - if (wp != NULL) - yoff = wp->yoff; r = screen_redraw_get_visible_ranges(wp, px, s->cy + yoff, nx, NULL); for (xx = px; xx < px + nx; xx++) { gl = grid_get_line(gd, yy); - sgl = grid_get_line(ctx->s->grid, s->cy); + sgl = grid_get_line(s->grid, s->cy); if (xx >= gl->cellsize && s->cx >= sgl->cellsize) break; grid_get_cell(gd, xx, yy, &gc); if (xx + gc.data.width > px + nx) break; - grid_view_set_cell(ctx->s->grid, s->cx, s->cy, &gc); + grid_view_set_cell(s->grid, s->cx, s->cy, &gc); if (!screen_redraw_is_visible(r, px)) break; ttyctx.cell = &gc; @@ -1127,7 +1127,7 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, { struct window_pane *wp = ctx->wp; struct screen *s = ctx->s; - struct grid_cell gc; + struct grid_cell gc, ngc; u_int sx = screen_size_x(s), cx, i, n; u_int xoff = wp->xoff, yoff = wp->yoff; struct visible_ranges *r; @@ -1142,9 +1142,15 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, cx = ri->px - xoff; for (n = 0; n < ri->nx && cx < sx; n++, cx++) { grid_view_get_cell(s->grid, cx, yy, &gc); + if (~gc.flags & GRID_FLAG_SELECTED) + ttyctx->cell = &gc; + else { + screen_select_cell(s, &ngc, &gc); + ttyctx->cell = &ngc; + } + ttyctx->ocx = cx; ttyctx->ocy = yy; - ttyctx->cell = &gc; tty_write(tty_cmd_cell, ttyctx); } } @@ -2175,7 +2181,8 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, written = 0; for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; - if (ri->nx == 0) continue; + if (ri->nx == 0) + continue; r_start = ri->px; r_end = ri->px + ri->nx; ci_start = ci->x; @@ -2527,6 +2534,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) if (selected) skip = 0; + /* Get visible ranges for the character before moving the cursor. */ if (wp != NULL) yoff = wp->yoff; r = screen_redraw_get_visible_ranges(wp, s->cx, s->cy + yoff, width, @@ -2540,7 +2548,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) if (s->cx <= sx - not_wrap - width) screen_write_set_cursor(ctx, s->cx + width, -1); else - screen_write_set_cursor(ctx, sx - not_wrap, -1); + screen_write_set_cursor(ctx, sx - not_wrap, -1); /* Create space for character in insert mode. */ if (s->mode & MODE_INSERT) { @@ -2549,33 +2557,42 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) tty_write(tty_cmd_insertcharacter, &ttyctx); } - /* Write to the screen. */ - if (!skip && !(s->mode & MODE_SYNC)) { - if (selected) { - screen_select_cell(s, &tmp_gc, gc); - } else - memcpy(&tmp_gc, gc, sizeof tmp_gc); - ttyctx.cell = &tmp_gc; - if (redraw) - ttyctx.flags |= TTY_CTX_CELL_DRAW_LINE; - for (i=0, vis=0; i < r->used; i++) vis += r->ranges[i].nx; - if (vis < width) { - /* Wide character or tab partly obscured. Write - * spaces one by one in unobscured region(s). - */ - *tmp_gc.data.data = ' '; - tmp_gc.data.width = tmp_gc.data.size = - tmp_gc.data.have = 1; - for (i=0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) continue; - for (n = 0; n < ri->nx; n++) { - screen_write_set_cursor(ctx, ri->px + n, - -1); - tty_write(tty_cmd_cell, &ttyctx); - } - } - } else { + /* If not writing, done now. */ + if (skip || s->mode & MODE_SYNC) + return; + + /* Do a full line redraw if needed. */ + if (redraw) { + screen_write_redraw_line(ctx, &ttyctx, s->cy); + return; + } + + /* Work out the cell attributes. */ + if (selected) + screen_select_cell(s, &tmp_gc, gc); + else + memcpy(&tmp_gc, gc, sizeof tmp_gc); + ttyctx.cell = &tmp_gc; + + /* If the cell is fully visible, it can be written entirely. */ + for (i = 0, vis = 0; i < r->used; i++) + vis += r->ranges[i].nx; + if (vis >= width) { + tty_write(tty_cmd_cell, &ttyctx); + return; + } + + /* + * Otherwise this is a wide character or tab partly obscured. Write + * spaces in the visible regions. + */ + utf8_set(&tmp_gc.data, ' '); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; + for (n = 0; n < ri->nx; n++) { + ttyctx.ocx = ri->px + n; tty_write(tty_cmd_cell, &ttyctx); } } @@ -2589,6 +2606,7 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) struct window_pane *wp = ctx->wp; struct grid *gd = s->grid; const struct utf8_data *ud = &gc->data; + struct options *oo = global_options; u_int i, n, cx = s->cx, cy = s->cy, vis, yoff = 0; struct grid_cell last; struct tty_ctx ttyctx; @@ -2608,7 +2626,7 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) zero_width = 1; else if (utf8_is_vs(ud)) { zero_width = 1; - if (options_get_number(global_options, "variation-selector-always-wide")) + if (options_get_number(oo, "variation-selector-always-wide")) force_wide = 1; } else if (ud->width == 0) zero_width = 1; diff --git a/tmux.h b/tmux.h index 8c7e9f1180..4e3dfe0312 100644 --- a/tmux.h +++ b/tmux.h @@ -1751,9 +1751,8 @@ struct tty_ctx { #define TTY_CTX_WINDOW_BIGGER 0x4 #define TTY_CTX_SYNC 0x8 #define TTY_CTX_OVERLAY_SYNC 0x10 -#define TTY_CTX_CELL_DRAW_LINE 0x20 -#define TTY_CTX_CELL_INVALIDATE 0x40 -#define TTY_CTX_PANE_OBSCURED 0x80 +#define TTY_CTX_CELL_INVALIDATE 0x20 +#define TTY_CTX_PANE_OBSCURED 0x40 union { u_int n; diff --git a/tty.c b/tty.c index 6c654ef3fe..fb84186806 100644 --- a/tty.c +++ b/tty.c @@ -2054,14 +2054,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) (gcp->data.width == 1 && !tty_check_overlay(tty, px, py))) return; - if (ctx->flags & TTY_CTX_CELL_DRAW_LINE) { - tty_draw_line(tty, s, 0, s->cy, screen_size_x(s), - ctx->xoff - ctx->wox, py, &ctx->defaults, ctx->palette); - return; - } - - /* Handle partially obstructed wide characters. */ - if (gcp->data.width > 1) { + if (gcp->data.width > 1) { /* could be partially obscured */ r = tty_check_overlay_range(tty, px, py, gcp->data.width); for (i = 0; i < r->used; i++) vis += r->ranges[i].nx; From 1ed454d1023debeca1bb472a26c1ec92a0cd718d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 27 May 2026 21:41:20 +0100 Subject: [PATCH 154/167] Do not reduce the end position when removing padding. --- screen-redraw.c | 4 ++-- tty-draw.c | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index d53d71550a..8a830c71fa 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1353,8 +1353,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) ri = &r->ranges[k]; if (ri->nx == 0) continue; - log_debug("%s: %s %%%u range pane (%u,%u) width %u, tty (%u,%u) width %u", - __func__, c->name, wp->id, + log_debug("%s: %s %%%u range %u (%u,%u) width %u, tty (%u,%u) width %u", + __func__, c->name, wp->id, k, ri->px + (int)ctx->ox - wp->xoff, j, ri->nx, ri->px, py, ri->nx); tty_draw_line(tty, s, ri->px + (int)ctx->ox - wp->xoff, j, ri->nx, diff --git a/tty-draw.c b/tty-draw.c index 24d8b711bd..b617875486 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -181,7 +181,6 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, atx += cx; px += cx; nx -= cx; - ex -= cx; } /* Did the previous line wrap on to this one? */ From 97ee6e258728174ffffee11359db5b0ac637f137 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 28 May 2026 08:24:54 +0100 Subject: [PATCH 155/167] Break screen_write_collect_flush into some helper functions. --- screen-write.c | 232 ++++++++++++++++++++++++++----------------------- 1 file changed, 121 insertions(+), 111 deletions(-) diff --git a/screen-write.c b/screen-write.c index 31ef6e74c1..d9a5fafbc6 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2100,58 +2100,52 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) TAILQ_INSERT_TAIL(&ctx->s->write_list[s->rlower].items, ci, entry); } -/* Flush collected lines. */ -static void -screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, - const char *from) +/* Flush collected scrolling. */ +static int +screen_write_collect_flush_scrolled(struct screen_write_ctx *ctx) +{ + struct window_pane *wp = ctx->wp; + struct screen *s = ctx->s; + struct tty_ctx ttyctx; + + screen_write_initctx(ctx, &ttyctx, 1, 1); + if (ttyctx.flags & TTY_CTX_PANE_OBSCURED && wp != NULL) { + screen_write_redraw_pane(ctx, &ttyctx); + return 0; + } + + log_debug("%s: scrolled %u (region %u-%u)", __func__, ctx->scrolled, + s->rupper, s->rlower); + if (ctx->scrolled > s->rlower - s->rupper + 1) + ctx->scrolled = s->rlower - s->rupper + 1; + + if (wp != NULL && wp->yoff + wp->sy > wp->window->sy) + ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy); + ttyctx.n = ctx->scrolled; + ttyctx.bg = ctx->bg; + tty_write(tty_cmd_scrollup, &ttyctx); + + if (wp != NULL) + wp->flags |= PANE_REDRAWSCROLLBAR; + return 1; +} + +/* Flush a collected line. */ +static u_int +screen_write_collect_flush_line(struct screen_write_ctx *ctx, u_int y) { struct window_pane *wp = ctx->wp; struct screen *s = ctx->s; struct screen_write_citem *ci, *tmp; - struct screen_write_cline *cl; - u_int y, cx, cy, last, items = 0, i; - u_int wr_start, wr_end, wr_length, wsx, wsy; - int written; - int r_start, r_end, ci_start, ci_end; - int xoff, yoff; + struct screen_write_cline *cl = &s->write_list[y]; + u_int last = UINT_MAX, items = 0, wsx, wsy; + u_int w_start, w_end, w_length, i; + int xoff, yoff, written; + int r_start, r_end, c_start, c_end; struct tty_ctx ttyctx; struct visible_ranges *r; struct visible_range *ri; - if (s->mode & MODE_SYNC) - goto discard; - - if (ctx->scrolled != 0) { - screen_write_initctx(ctx, &ttyctx, 1, 1); - if (ttyctx.flags & TTY_CTX_PANE_OBSCURED && wp != NULL) { - screen_write_redraw_pane(ctx, &ttyctx); - goto discard; - } - - log_debug("%s: scrolled %u (region %u-%u)", __func__, - ctx->scrolled, s->rupper, s->rlower); - if (ctx->scrolled > s->rlower - s->rupper + 1) - ctx->scrolled = s->rlower - s->rupper + 1; - - if (wp != NULL && wp->yoff + wp->sy > wp->window->sy) - ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy); - ttyctx.n = ctx->scrolled; - ttyctx.bg = ctx->bg; - tty_write(tty_cmd_scrollup, &ttyctx); - - if (wp != NULL) - wp->flags |= PANE_REDRAWSCROLLBAR; - } - ctx->scrolled = 0; - ctx->bg = 8; - - if (scroll_only) - return; - - cx = s->cx; cy = s->cy; - - /* The xoff and width of window pane relative to the window we - * are writing to relative to the visible_ranges array. */ if (wp != NULL) { wsx = wp->window->sx; wsy = wp->window->sy; @@ -2163,79 +2157,95 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, xoff = 0; yoff = 0; } + if (y + yoff >= wsy) + return (0); - for (y = 0; y < screen_size_y(s); y++) { - if (y + yoff >= wsy) - continue; - cl = &ctx->s->write_list[y]; + r = screen_redraw_get_visible_ranges(wp, 0, y + yoff, wsx, NULL); + TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { + log_debug("collect list: x=%u (last %u), y=%u, used=%u", ci->x, + last, y, ci->used); + if (last != UINT_MAX && ci->x <= last) + fatalx("collect list bad order: %u <= %u", ci->x, last); - r = screen_redraw_get_visible_ranges(wp, 0, y + yoff, wsx, - NULL); + w_length = 0; + written = 0; + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) + continue; - last = UINT_MAX; - TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { - log_debug("collect list: x=%u (last %u), y=%u, used=%u", - ci->x, last, y, ci->used); - if (last != UINT_MAX && ci->x <= last) { - fatalx("collect list not in order: %u <= %u", - ci->x, last); - } - wr_length = 0; - written = 0; - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - r_start = ri->px; - r_end = ri->px + ri->nx; - ci_start = ci->x; - ci_end = ci->x + ci->used; - - if (ci_start + xoff > r_end || - ci_end + xoff < r_start) - continue; - - if (r_start > ci_start + xoff) - wr_start = ci_start + - (r_start - (ci_start + xoff)); - else - wr_start = ci_start; - if (ci_end + xoff > r_end) - wr_end = ci_end - - ((ci_end + xoff) - r_end); - else - wr_end = ci_end; - wr_length = wr_end - wr_start; - - if (wr_length <= 0) - continue; - screen_write_set_cursor(ctx, wr_start, y); - if (ci->type == CLEAR) { - screen_write_initctx(ctx, &ttyctx, 1, 0); - ttyctx.bg = ci->bg; - ttyctx.n = wr_length; - tty_write(tty_cmd_clearcharacter, - &ttyctx); - } else { - screen_write_initctx(ctx, &ttyctx, 0, 0); - ttyctx.cell = &ci->gc; - if (ci->wrapped) - ttyctx.flags |= TTY_CTX_WRAPPED; - ttyctx.data.data = cl->data + wr_start; - ttyctx.data.size = wr_length; - tty_write(tty_cmd_cells, &ttyctx); - } - items++; - written = 1; - } - if (written) { - last = ci->x; - TAILQ_REMOVE(&cl->items, ci, entry); - screen_write_free_citem(ci); + r_start = ri->px; + r_end = ri->px + ri->nx; + c_start = ci->x; + c_end = ci->x + ci->used; + + if (c_start + xoff > r_end || c_end + xoff < r_start) + continue; + if (r_start > c_start + xoff) + w_start = c_start + (r_start - c_start + xoff); + else + w_start = c_start; + if (c_end + xoff > r_end) + w_end = c_end - (c_end + xoff - r_end); + else + w_end = c_end; + w_length = w_end - w_start; + if (w_length <= 0) + continue; + + screen_write_set_cursor(ctx, w_start, y); + if (ci->type == CLEAR) { + screen_write_initctx(ctx, &ttyctx, 1, 0); + ttyctx.bg = ci->bg; + ttyctx.n = w_length; + tty_write(tty_cmd_clearcharacter, &ttyctx); + } else { + screen_write_initctx(ctx, &ttyctx, 0, 0); + ttyctx.cell = &ci->gc; + if (ci->wrapped) + ttyctx.flags |= TTY_CTX_WRAPPED; + ttyctx.data.data = cl->data + w_start; + ttyctx.data.size = w_length; + tty_write(tty_cmd_cells, &ttyctx); } + items++; + written = 1; } + if (written) { + last = ci->x; + TAILQ_REMOVE(&cl->items, ci, entry); + screen_write_free_citem(ci); + } + } + return (items); +} + +/* Flush collected lines. */ +static void +screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, + const char *from) +{ + struct screen *s = ctx->s; + u_int y, cx, cy, items = 0; + struct screen_write_citem *ci, *tmp; + struct screen_write_cline *cl; + + if (s->mode & MODE_SYNC) + goto discard; + + if (ctx->scrolled != 0) { + if (!screen_write_collect_flush_scrolled(ctx)) + goto discard; + ctx->scrolled = 0; } + ctx->bg = 8; + if (scroll_only) + return; + + cx = s->cx; cy = s->cy; + for (y = 0; y < screen_size_y(s); y++) + items += screen_write_collect_flush_line(ctx, y); s->cx = cx; s->cy = cy; log_debug("%s: flushed %u items (%s)", __func__, items, from); @@ -2243,7 +2253,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, discard: for (y = 0; y < screen_size_y(s); y++) { - cl = &ctx->s->write_list[y]; + cl = &s->write_list[y]; TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { TAILQ_REMOVE(&cl->items, ci, entry); screen_write_free_citem(ci); From 3f77cb95c5a13110ad5423c2bd6bfc73357b9c9a Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 28 May 2026 09:39:44 +0100 Subject: [PATCH 156/167] Change tty_draw_pane to draw in the right place I think. --- tty.c | 112 ++++++++++++++++++++++++---------------------------------- 1 file changed, 46 insertions(+), 66 deletions(-) diff --git a/tty.c b/tty.c index fb84186806..556a407588 100644 --- a/tty.c +++ b/tty.c @@ -1093,7 +1093,7 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) * If region is large, schedule a redraw. In most cases this is likely * to be followed by some more scrolling. */ - if (tty_large_region(tty, ctx) && ~ctx->flags & TTY_CTX_PANE_OBSCURED) { + if (tty_large_region(tty, ctx) || ctx->flags & TTY_CTX_PANE_OBSCURED) { log_debug("%s: %s large region redraw", __func__, c->name); ctx->redraw_cb(ctx); return; @@ -1128,6 +1128,17 @@ tty_clamp_line(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, { int xoff = ctx->rxoff + px; + /* + * px = x position in pane + * py = y position in pane + * nx = width + * + * i = new x position in pane + * x = x position on terminal + * rx = new width + * ry = y position on terminal + */ + if (!tty_is_visible(tty, ctx, px, py, nx, 1)) return (0); *ry = ctx->yoff + py - ctx->woy; @@ -1391,63 +1402,32 @@ static void tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) { struct screen *s = ctx->s; - struct window_pane *wp = ctx->arg; - struct visible_ranges *r = NULL; - struct visible_range *ri; - u_int nx = ctx->sx, i, px, x, rx, ry; + u_int nx = ctx->sx, i, x, rx, ry, j; + struct visible_ranges *r; + struct visible_range *rr; log_debug("%s: %s %u", __func__, tty->client->name, py); if (~ctx->flags & TTY_CTX_WINDOW_BIGGER) { - if (wp) { - if (ctx->flags & TTY_CTX_PANE_OBSCURED) { - /* - * Floating pane is present: use physical - * coordinates and clip to visible ranges to - * avoid drawing over the floating pane. - */ - r = tty_check_overlay_range(tty, ctx->xoff, - ctx->yoff + py, nx); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) continue; - tty_draw_line(tty, s, - ri->px - ctx->xoff, py, ri->nx, - ri->px, ctx->yoff + py, - &ctx->defaults, ctx->palette); - } - } else { - r = tty_check_overlay_range(tty, 0, - ctx->yoff + py, nx); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) continue; - tty_draw_line(tty, s, ri->px, py, - ri->nx, ctx->xoff + ri->px, - ctx->yoff + py, - &ctx->defaults, ctx->palette); - } - } - } else { - tty_draw_line(tty, s, 0, py, nx, ctx->xoff, - ctx->yoff + py, &ctx->defaults, ctx->palette); + r = tty_check_overlay_range(tty, ctx->xoff, ctx->yoff + py, nx); + for (j = 0; j < r->used; j++) { + rr = &r->ranges[j]; + if (rr->nx == 0) + continue; + tty_draw_line(tty, s, rr->px, py, rr->nx, + ctx->xoff + rr->px, ctx->yoff + py, &ctx->defaults, + ctx->palette); } return; } - if (tty_clamp_line(tty, ctx, 0, py, nx, &px, &x, &rx, &ry)) { - if (wp) { - r = tty_check_overlay_range(tty, px, py, rx); - for (i=0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - tty_draw_line(tty, s, ri->px, py, ri->nx, - x + ri->px, ry, &ctx->defaults, - ctx->palette); - } - } else { - tty_draw_line(tty, s, px, py, rx, x, ry, &ctx->defaults, - ctx->palette); + if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry)) { + r = tty_check_overlay_range(tty, x, ry, rx); + for (j = 0; j < r->used; j++) { + rr = &r->ranges[j]; + if (rr->nx == 0) + continue; + tty_draw_line(tty, s, i + rr->px, py, rr->nx, + x + rr->px, ry, &ctx->defaults, ctx->palette); } } } @@ -2360,21 +2340,21 @@ tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - if ((ctx->flags & TTY_CTX_OVERLAY_SYNC) && - (ctx->flags & TTY_CTX_SYNC)) { - /* - * This is an overlay and a command that moves the cursor so - * start synchronized updates. - */ - tty_sync_start(tty); - } else if (~ctx->flags & TTY_CTX_OVERLAY_SYNC) { - /* - * This is a pane. If there is an overlay, always start; - * otherwise, only if requested. - */ - if ((ctx->flags & TTY_CTX_SYNC) || c->overlay_draw != NULL) - tty_sync_start(tty); - } + if ((ctx->flags & TTY_CTX_OVERLAY_SYNC) && + (ctx->flags & TTY_CTX_SYNC)) { + /* + * This is an overlay and a command that moves the cursor so + * start synchronized updates. + */ + tty_sync_start(tty); + } else if (~ctx->flags & TTY_CTX_OVERLAY_SYNC) { + /* + * This is a pane. If there is an overlay, always start; + * otherwise, only if requested. + */ + if ((ctx->flags & TTY_CTX_SYNC) || c->overlay_draw != NULL) + tty_sync_start(tty); + } } void From 6dd288d4590942457602a77cef4291c1774dfd8a Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 1 Jun 2026 20:20:42 +0100 Subject: [PATCH 157/167] Fix bug: pane-border-status top, can't resize floating panes by dragging the border. --- window.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/window.c b/window.c index ba7abafd1d..97fc7f880e 100644 --- a/window.c +++ b/window.c @@ -658,16 +658,11 @@ window_get_active_at(struct window *w, u_int x, u_int y) continue; } } else { - /* Floating - include top or or left border. */ + /* Floating - include all borders. */ if ((int)x < xoff - 1 || x > xoff + sx) continue; - if (pane_status == PANE_STATUS_TOP) { - if ((int)y <= yoff - 2 || y > yoff + sy - 1) - continue; - } else { - if ((int)y < yoff - 1 || y > yoff + sy) - continue; - } + if ((int)y < yoff - 1 || y > yoff + sy) + continue; } return (wp); } From 446aa0828667272287004be7c00d9f994bddbb9b Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 1 Jun 2026 20:52:51 +0100 Subject: [PATCH 158/167] Bug fix vertical position of scrollbar had wrong vertical offset when status-position top. --- screen-redraw.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index bfdc92152f..74d0fc605e 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1482,6 +1482,7 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, int px, py, wx, wy, ox, oy, sx, sy, sb_tty_y; int xoff = wp->xoff; int yoff = wp->yoff; + int sb_wy = sb_y; /* window coordinates */ struct visible_ranges *r; /* @@ -1493,9 +1494,8 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, ox = ctx->ox; oy = ctx->oy; if (ctx->statustop) { - sb_y += ctx->statuslines; - sy += ctx->statuslines; /* height of window */ - oy += ctx->statuslines; /* top of window */ + sb_y += ctx->statuslines; /* tty coordinates */ + sy += ctx->statuslines; } gc = sb_style->gc; @@ -1527,8 +1527,8 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, } /* - * sb_y is a window coordinate; convert to tty coordinate by - * subtracting the pan offset oy. + * sb_y is in tty coordinate (window coord + statuslines when + * statustop). Subtract the pan offset oy to get the tty row. */ sb_tty_y = sb_y - oy; /* scrollbar top in tty coordinates */ if (sb_tty_y > (int)sy) { @@ -1550,7 +1550,7 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, } for (j = jmin; j < jmax; j++) { - wy = sb_y + j; /* window y coordinate */ + wy = sb_wy + j; /* window y coordinate */ py = sb_tty_y + j; /* tty y coordinate */ r = tty_check_overlay_range(tty, sb_x, wy, imax); r = screen_redraw_get_visible_ranges(wp, sb_x, wy, imax, r); From 7c908bca7a2b49819ed0af67ad268a8f9966b0de Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Wed, 3 Jun 2026 17:46:29 -0700 Subject: [PATCH 159/167] Fixed vestigal compile errors --- cmd-tile-float-pane.c | 18 ++++++++++-------- layout.c | 11 ++--------- screen-redraw.c | 2 +- tty.c | 6 +++--- window.c | 4 ++-- 5 files changed, 18 insertions(+), 23 deletions(-) diff --git a/cmd-tile-float-pane.c b/cmd-tile-float-pane.c index ec3d8f64f4..19dfa4fc90 100644 --- a/cmd-tile-float-pane.c +++ b/cmd-tile-float-pane.c @@ -159,7 +159,7 @@ cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item) u_int sx, sy; struct layout_cell *lc; - if (wp->flags & PANE_FLOATING) { + if (window_pane_is_floating(wp)) { cmdq_error(item, "pane is already floating"); return (CMD_RETURN_ERROR); } @@ -207,7 +207,7 @@ cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item) lc->sy = sy; layout_make_leaf(lc, wp); /* sets wp->layout_cell = lc, lc->wp = wp */ - wp->flags |= PANE_FLOATING; + lc->flags |= LAYOUT_CELL_FLOATING; TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); @@ -231,7 +231,7 @@ cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) struct layout_cell *float_lc, *lc; int was_hidden; - if (!(wp->flags & PANE_FLOATING)) { + if (!window_pane_is_floating(wp)) { cmdq_error(item, "pane is not floating"); return (CMD_RETURN_ERROR); } @@ -275,12 +275,13 @@ cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) * than becoming a disconnected root. */ target_wp = NULL; - if (w->active != NULL && !(w->active->flags & PANE_FLOATING) && + if (w->active != NULL && !window_pane_is_floating(w->active) && !(w->active->flags & PANE_HIDDEN)) target_wp = w->active; if (target_wp == NULL) { TAILQ_FOREACH(wpiter, &w->last_panes, sentry) { - if (!(wpiter->flags & (PANE_FLOATING|PANE_HIDDEN)) && + if (!(wpiter->flags & PANE_HIDDEN) && + !window_pane_is_floating(wpiter) && window_pane_visible(wpiter)) { target_wp = wpiter; break; @@ -289,7 +290,8 @@ cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) } if (target_wp == NULL) { TAILQ_FOREACH(wpiter, &w->panes, entry) { - if (!(wpiter->flags & (PANE_FLOATING|PANE_HIDDEN)) && + if (!(wpiter->flags & PANE_HIDDEN) && + !window_pane_is_floating(wpiter) && window_pane_visible(wpiter)) { target_wp = wpiter; break; @@ -299,7 +301,7 @@ cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) /* Fall back to any tiled pane (even hidden) to stay in the tree. */ if (target_wp == NULL) { TAILQ_FOREACH(wpiter, &w->panes, entry) { - if (!(wpiter->flags & PANE_FLOATING)) { + if (!window_pane_is_floating(wpiter)) { target_wp = wpiter; break; } @@ -344,7 +346,7 @@ cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) if (was_hidden) wp->saved_layout_cell = wp->layout_cell; - wp->flags &= ~PANE_FLOATING; + lc->flags &= ~LAYOUT_CELL_FLOATING; TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); diff --git a/layout.c b/layout.c index 84d98b0a84..41b8fee2fc 100644 --- a/layout.c +++ b/layout.c @@ -611,8 +611,6 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, struct layout_cell **lcroot) { struct layout_cell *lcother, *lcparent; - int direction; - int is_hidden; /* * If no parent, this is either a floating pane or the last @@ -641,9 +639,6 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, TAILQ_REMOVE(&lcparent->cells, lc, entry); layout_free_cell(lc); - if (lcparent->type == LAYOUT_FLOATING) - return; - /* * In tiled layouts, if the parent now has one cell, remove * the parent from the tree and replace it by that cell. @@ -687,10 +682,8 @@ layout_hide_cell(struct window *w, struct layout_cell *lc) int direction; lcparent = lc->parent; - if (lcparent == NULL || - lcparent->type == LAYOUT_FLOATING) { + if (lcparent == NULL) return; - } /* Merge the space into the nearest non-hidden sibling. */ { @@ -730,7 +723,7 @@ layout_show_cell(struct window *w, struct layout_cell *lc) if (lc == NULL) return; lcparent = lc->parent; - if (lcparent == NULL || lcparent->type == LAYOUT_FLOATING) + if (lcparent == NULL) return; /* diff --git a/screen-redraw.c b/screen-redraw.c index 51c3cc7894..c3ffe815e5 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -610,7 +610,7 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, else border_option = "pane-border-style"; style_apply(&gc, wp->options, border_option, ft); - if (wp->flags & PANE_FLOATING) + if (window_pane_is_floating(wp)) style_add(&gc, wp->options, "floating-pane-border-style", ft); fmt = options_get_string(wp->options, "pane-border-format"); diff --git a/tty.c b/tty.c index 73207b903a..c567238f10 100644 --- a/tty.c +++ b/tty.c @@ -2233,7 +2233,7 @@ tty_sixelimage_draw(struct tty *tty, const struct tty_ctx *ctx, breaks[nb++] = ry; TAILQ_FOREACH(fp, &w->z_index, zentry) { - if (~fp->flags & PANE_FLOATING) + if (!window_pane_is_floating(fp)) continue; fp_tb = (int)((fp->yoff > 0) ? fp->yoff - 1 : 0); fp_bb1 = (int)fp->yoff + (int)fp->sy + 1; @@ -3186,12 +3186,12 @@ tty_style_changed(struct window_pane *wp) tty_window_default_style(&wp->cached_active_gc, wp); style_add(&wp->cached_active_gc, oo, "window-active-style", ft); - if (wp->flags & PANE_FLOATING) + if (window_pane_is_floating(wp)) style_add(&wp->cached_active_gc, oo, "floating-pane-style", ft); tty_window_default_style(&wp->cached_gc, wp); style_add(&wp->cached_gc, oo, "window-style", ft); - if (wp->flags & PANE_FLOATING) + if (window_pane_is_floating(wp)) style_add(&wp->cached_active_gc, oo, "floating-pane-style", ft); format_free(ft); diff --git a/window.c b/window.c index a99d5109b3..e0417ec9f8 100644 --- a/window.c +++ b/window.c @@ -730,7 +730,7 @@ window_zoom(struct window_pane *wp) wp1->flags &= ~PANE_HIDDEN; continue; } - if (wp1->flags & PANE_FLOATING) { + if (window_pane_is_floating(wp1)) { wp1->saved_flags |= (wp1->flags & PANE_HIDDEN); wp1->flags |= PANE_HIDDEN; continue; @@ -765,7 +765,7 @@ window_unzoom(struct window *w, int notify) w->saved_layout_root = NULL; TAILQ_FOREACH(wp, &w->z_index, zentry) { - if (wp->flags & PANE_FLOATING) { + if (window_pane_is_floating(wp)) { wp->flags &= ~PANE_HIDDEN | (wp->saved_flags & PANE_HIDDEN); continue; } From 6766c8ec1d1391543e1e13276054b2debd8eb81c Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 8 Jun 2026 17:18:11 -0700 Subject: [PATCH 160/167] Initial commit. --- layout.c | 3 ++- tmux.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/layout.c b/layout.c index d6b6ca8607..a6fd703937 100644 --- a/layout.c +++ b/layout.c @@ -1328,7 +1328,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, u_int sx, u_int sy, int ox, int oy) +layout_floating_pane(struct window *w, struct layout_cell *lc, u_int sx, + u_int sy, int ox, int oy) { struct layout_cell *lc = w->layout_root, *lcnew, *lcparent; diff --git a/tmux.h b/tmux.h index d3122c8134..3c7f924bed 100644 --- a/tmux.h +++ b/tmux.h @@ -3552,8 +3552,8 @@ void layout_assign_pane(struct layout_cell *, struct window_pane *, int); struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type, int, int); -struct layout_cell *layout_floating_pane(struct window *, u_int, u_int, int, - int); +struct layout_cell *layout_floating_pane(struct window *, struct layout_cell *, + u_int, u_int, int, int); void layout_close_pane(struct window_pane *); int layout_spread_cell(struct window *, struct layout_cell *); void layout_spread_out(struct window_pane *); From 7568bff8e61079dbaf29f8e4e6cf2dbdb0f831ab Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 8 Jun 2026 18:59:07 -0700 Subject: [PATCH 161/167] Layout cells for floating panes are now inserted after the cell of a provided window pane. --- layout.c | 21 ++++++++++++--------- tmux.h | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/layout.c b/layout.c index a6fd703937..f453eaf85a 100644 --- a/layout.c +++ b/layout.c @@ -1328,12 +1328,16 @@ 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 layout_cell *lc, u_int sx, +layout_floating_pane(struct window *w, struct window_pane *wp, u_int sx, u_int sy, int ox, int oy) { - struct layout_cell *lc = w->layout_root, *lcnew, *lcparent; + struct layout_cell *lc = wp->layout_cell, *lcnew, *lcparent; - if (lc->type == LAYOUT_WINDOWPANE) { + if (lc == NULL) + lc = w->layout_root; + lcparent = lc->parent; + + if (lcparent == NULL) { /* * Adding a pane to a root that doesn't have a container. Must * create and insert a new root. @@ -1346,11 +1350,10 @@ layout_floating_pane(struct window *w, struct layout_cell *lc, u_int sx, /* Insert the old cell. */ lc->parent = lcparent; TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry); - } else - lcparent = w->layout_root; + } lcnew = layout_create_cell(lcparent); - TAILQ_INSERT_TAIL(&lcparent->cells, lcnew, entry); + TAILQ_INSERT_AFTER(&lcparent->cells, lc, lcnew, entry); lcnew->flags |= LAYOUT_CELL_FLOATING; layout_set_size(lcnew, sx, sy, ox, oy); @@ -1525,10 +1528,10 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, /* Get a new floating cell. */ struct layout_cell * layout_get_floating_cell(struct cmdq_item *item, struct args *args, - struct window *w, __unused struct window_pane *wp, char **cause) + struct window *w, struct window_pane *wp, char **cause) { struct layout_cell *lcnew; - int sx = w->sx / 2, sy = w->sy / 4; + u_int sx = w->sx / 2, sy = w->sy / 4; int ox = INT_MAX, oy = INT_MAX; if (args_has(args, 'x')) { @@ -1577,6 +1580,6 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, w->last_new_pane_y = oy; } - lcnew = layout_floating_pane(w, sx, sy, ox, oy); + lcnew = layout_floating_pane(w, wp, sx, sy, ox, oy); return (lcnew); } diff --git a/tmux.h b/tmux.h index 3c7f924bed..4e4e8e72f6 100644 --- a/tmux.h +++ b/tmux.h @@ -3552,7 +3552,7 @@ void layout_assign_pane(struct layout_cell *, struct window_pane *, int); struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type, int, int); -struct layout_cell *layout_floating_pane(struct window *, struct layout_cell *, +struct layout_cell *layout_floating_pane(struct window *, struct window_pane *, u_int, u_int, int, int); void layout_close_pane(struct window_pane *); int layout_spread_cell(struct window *, struct layout_cell *); From 2a1ad05671200efdfb63ca429f7affce74cba606 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Tue, 9 Jun 2026 13:28:38 -0700 Subject: [PATCH 162/167] There was an bug with the earlier commit, reproduction: "splitw; newp -t0; killp -t0". Added logic to handle previously unforseen states, like a floating cell before a tiled cell at the top of the screen. --- layout.c | 236 ++++++++++++++++++++++++++++++------------------------- tmux.h | 5 +- window.c | 28 +------ 3 files changed, 134 insertions(+), 135 deletions(-) diff --git a/layout.c b/layout.c index f453eaf85a..935c52326b 100644 --- a/layout.c +++ b/layout.c @@ -250,10 +250,7 @@ layout_fix_offsets1(struct layout_cell *lc) if (lc->type == LAYOUT_LEFTRIGHT) { xoff = lc->xoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (lcchild->flags & LAYOUT_CELL_FLOATING || - (lcchild->type == LAYOUT_WINDOWPANE && - lcchild->wp != NULL && - (lcchild->wp->flags & PANE_HIDDEN))) + if (!layout_cell_is_tiled(lcchild)) continue; lcchild->xoff = xoff; lcchild->yoff = lc->yoff; @@ -264,10 +261,7 @@ layout_fix_offsets1(struct layout_cell *lc) } else { yoff = lc->yoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (lcchild->flags & LAYOUT_CELL_FLOATING || - (lcchild->type == LAYOUT_WINDOWPANE && - lcchild->wp != NULL && - (lcchild->wp->flags & PANE_HIDDEN))) + if (!layout_cell_is_tiled(lcchild)) continue; lcchild->xoff = lc->xoff; lcchild->yoff = yoff; @@ -294,6 +288,29 @@ layout_fix_offsets(struct window *w) layout_fix_offsets1(lc); } +int +layout_cell_is_tiled(struct layout_cell *lc) +{ + return ((~lc->flags & LAYOUT_CELL_HIDDEN) && + (~lc->flags & LAYOUT_CELL_FLOATING)); +} + +static int +layout_cell_is_first_tiled(struct layout_cell *lc) +{ + struct layout_cell *lcchild, *lcparent = lc->parent; + + if (lcparent == NULL) + return (layout_cell_is_tiled(lc)); + + TAILQ_FOREACH(lcchild, &lcparent->cells, entry) { + if (layout_cell_is_tiled(lcchild)) + break; + } + + return (lcchild == lc); +} + /* Is this a top cell? */ static int layout_cell_is_top(struct window *w, struct layout_cell *lc) @@ -305,7 +322,7 @@ layout_cell_is_top(struct window *w, struct layout_cell *lc) if (next == NULL) return (0); if (next->type == LAYOUT_TOPBOTTOM && - lc != TAILQ_FIRST(&next->cells)) + !layout_cell_is_first_tiled(lc)) return (0); lc = next; } @@ -497,7 +514,7 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, /* Child cell runs in a different direction. */ if (lc->type != type) { TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (lcchild->flags & LAYOUT_CELL_FLOATING) + if (!layout_cell_is_tiled(lcchild)) continue; layout_resize_adjust(w, lcchild, type, change); } @@ -512,7 +529,7 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (change == 0) break; - if (lcchild->flags & LAYOUT_CELL_FLOATING) + if (!layout_cell_is_tiled(lcchild)) continue; if (change > 0) { layout_resize_adjust(w, lcchild, type, 1); @@ -527,36 +544,6 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, } } -/* - * Return the nearest sibling of lc that is not a hidden WINDOWPANE leaf, - * walking forward (direction=1) or backward (direction=0) in the parent's list. - * Container cells (TOPBOTTOM/LEFTRIGHT) are never skipped. - */ -static struct layout_cell * -layout_active_neighbour(struct layout_cell *lc, int direction) -{ - struct layout_cell *lcother; - - if (direction) - lcother = TAILQ_NEXT(lc, entry); - else - lcother = TAILQ_PREV(lc, layout_cells, entry); - - while (lcother != NULL) { - if (lcother->type != LAYOUT_WINDOWPANE) - return (lcother); /* container — not skipped */ - if (lcother->wp == NULL || - (~lcother->wp->flags & PANE_HIDDEN)) - return (lcother); /* visible leaf */ - /* hidden leaf — keep walking */ - if (direction) - lcother = TAILQ_NEXT(lcother, entry); - else - lcother = TAILQ_PREV(lcother, layout_cells, entry); - } - return (NULL); -} - /* * Redistribute space equally among all visible (non-hidden WINDOWPANE) * children of lcparent in the given direction. Hidden WINDOWPANE leaves @@ -613,14 +600,62 @@ layout_redistribute_cells(struct window *w, struct layout_cell *lcparent, } } -/* Destroy a cell and redistribute the space in tiled cells. */ +/* Helper function for layout_cell_get_neighbour. */ +static struct layout_cell * +layout_cell_get_neighbour_direction(struct layout_cell *lc, int direction) +{ + struct layout_cell *lcother = lc; + + while (1) { + if (direction) + lcother = TAILQ_NEXT(lcother, entry); + else + lcother = TAILQ_PREV(lcother, layout_cells, entry); + + if (lcother == NULL || layout_cell_is_tiled(lcother)) + return (lcother); + } +} + +/* + * Finds the nearest visible neighbour. A neighbour is a sibling cell drawn + * within the tiled layout. Prefers cells "before" the specified cell. + * This behavior defines how cell dimensions are redistributed when a cell is + * hidden/shown and floated/tiled. + */ +struct layout_cell * +layout_cell_get_neighbour(struct layout_cell *lc) +{ + struct layout_cell *lcother, *lcparent = lc->parent; + int direction = 0; + + if (lcparent == NULL) + return (NULL); + + if (lc == TAILQ_FIRST(&lcparent->cells)) + direction = 1; + + lcother = layout_cell_get_neighbour_direction(lc, direction); + if (lcother == NULL) + lcother = layout_cell_get_neighbour_direction(lc, !direction); + + return lcother; +} + + +/* Destroy a cell and redistribute the space if the cell was tiled. */ void layout_destroy_cell(struct window *w, struct layout_cell *lc, struct layout_cell **lcroot) { - struct layout_cell *lcother = NULL, *lcparent; + struct layout_cell *lcother, *lcparent; + int val; - /* If no parent, this is the last pane in a window. */ + /* + * If no parent, this is either a floating pane or the last + * pane so window close is imminent and there is no need to + * resize anything. + */ lcparent = lc->parent; if (lcparent == NULL) { if (lc->wp != NULL) @@ -629,30 +664,30 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, return; } - if (~lc->flags & LAYOUT_CELL_FLOATING) { - /* Merge the space into the previous or next cell. */ - if (lc == TAILQ_FIRST(&lcparent->cells)) - lcother = TAILQ_NEXT(lc, entry); - else - lcother = TAILQ_PREV(lc, layout_cells, entry); - } - if (lcother != NULL && (~lcother->flags & LAYOUT_CELL_FLOATING)) { - if (lcparent->type == LAYOUT_LEFTRIGHT) { - layout_resize_adjust(w, lcother, lcparent->type, - lc->sx + 1); - } else { - layout_resize_adjust(w, lcother, lcparent->type, - lc->sy + 1); - } + if (!layout_cell_is_tiled(lc)) { + TAILQ_REMOVE(&lcparent->cells, lc, entry); + layout_free_cell(lc); + goto out; } + lcother = layout_cell_get_neighbour(lc); + if (lcother != NULL) { + if (lcparent->type == LAYOUT_LEFTRIGHT) + val = lc->sx + 1; + else + val = lc->sy + 1; + layout_resize_adjust(w, lcother, lcparent->type, val); + } else + layout_hide_cell(w, lcparent); + /* Remove this from the parent's list. */ TAILQ_REMOVE(&lcparent->cells, lc, entry); layout_free_cell(lc); +out: /* - * In tiled layouts, if the parent now has one cell, remove - * the parent from the tree and replace it by that cell. + * If the parent now has one cell, remove the parent from the tree and + * replace it by that cell. */ lc = TAILQ_FIRST(&lcparent->cells); if (lc != NULL && TAILQ_NEXT(lc, entry) == NULL) { @@ -660,24 +695,8 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, lc->parent = lcparent->parent; if (lc->parent == NULL) { - if (~lc->flags & LAYOUT_CELL_FLOATING) { - lc->xoff = 0; - lc->yoff = 0; - } - - /* - * If the sole remaining child is a hidden - * WINDOWPANE, its stored size may be stale (it never - * received the space that was given to the removed - * cell). Restore the full window size so that - * 'show' can reclaim the correct amount. - */ - if (lc->type == LAYOUT_WINDOWPANE && - lc->wp != NULL && - (lc->wp->flags & PANE_HIDDEN)) { - lc->sx = lcparent->sx; - lc->sy = lcparent->sy; - } + if (layout_cell_is_tiled(lc)) + layout_set_size(lc, w->sx, w->sy, 0, 0); *lcroot = lc; } else TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry); @@ -686,43 +705,46 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, } } -/* Hide a cell and redistribute the space in tiled cells. */ + +/* Hide a cell. Space is redistributed to the nearest neighbour if the cell was + * tiled. + */ void layout_hide_cell(struct window *w, struct layout_cell *lc) { - struct layout_cell *lcother, *lcparent, *lcchild; - u_int space = 0; - int direction; + struct layout_cell *lcother, *lcparent, *lcchild; + u_int shown_children = 0; + int val; - lcparent = lc->parent; - if (lcparent == NULL) + if (lc == NULL) return; - /* Merge the space into the nearest non-hidden sibling. */ - { - direction = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0; - lcother = layout_active_neighbour(lc, direction); - if (lcother == NULL) - lcother = layout_active_neighbour(lc, !direction); + lcparent = lc->parent; + lc->flags |= LAYOUT_CELL_HIDDEN; + + /* Merge the space into the nearest neighbour. */ + lcother = layout_cell_get_neighbour(lc); + + if (lcother != NULL) { + if (lcparent->type == LAYOUT_LEFTRIGHT) + val = lc->sx + 1; + else + val = lc->sy + 1; + layout_resize_adjust(w, lcother, lcparent->type, val); } - if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) - layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); - else if (lcother != NULL) - layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1); - /* If the parent cells are all hidden, hide it too. */ + if (layout_cell_is_tiled(lc)) + layout_set_size(lc, 0, 0, 0, 0); + + /* If no children are tiled, hide the parent. */ if (lcparent != NULL) { TAILQ_FOREACH(lcchild, &lcparent->cells, entry) { - if (lcchild->wp == NULL || - lcchild->wp->flags & PANE_HIDDEN) - continue; - if (lcparent->type == LAYOUT_LEFTRIGHT) { - space += lcchild->sx; - } else if (lcparent->type == LAYOUT_TOPBOTTOM) { - space += lcchild->sy; + if (layout_cell_is_tiled(lcchild)) { + shown_children = 1; + break; } } - if (space == 0) + if (shown_children == 0) layout_hide_cell(w, lcparent); } } @@ -787,7 +809,7 @@ layout_resize(struct window *w, u_int sx, u_int sy) * out proportionately - this should leave the layout fitting the new * window size. */ - if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) + if (lc->type == LAYOUT_WINDOWPANE && !layout_cell_is_tiled(lc)) return; xchange = sx - lc->sx; xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT); @@ -1101,7 +1123,7 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) count = 0; previous = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (lcchild->flags & LAYOUT_CELL_FLOATING) + if (!layout_cell_is_tiled(lcchild)) continue; count++; if (lc->type == LAYOUT_LEFTRIGHT) @@ -1121,7 +1143,7 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) /* Resize children into the new size. */ idx = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (lcchild->flags & LAYOUT_CELL_FLOATING) + if (!layout_cell_is_tiled(lcchild)) continue; if (lc->type == LAYOUT_TOPBOTTOM) { lcchild->sx = lc->sx; diff --git a/tmux.h b/tmux.h index 4e4e8e72f6..47542d0e45 100644 --- a/tmux.h +++ b/tmux.h @@ -1486,7 +1486,8 @@ TAILQ_HEAD(layout_cells, layout_cell); struct layout_cell { enum layout_type type; -#define LAYOUT_CELL_FLOATING 0x1 +#define LAYOUT_CELL_FLOATING 0x1 +#define LAYOUT_CELL_HIDDEN 0x2 int flags; struct layout_cell *parent; @@ -3538,9 +3539,11 @@ 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 *); void layout_fix_offsets(struct window *); +int layout_cell_is_tiled(struct layout_cell *); void layout_fix_panes(struct window *, struct window_pane *); void layout_resize_adjust(struct window *, struct layout_cell *, enum layout_type, int); +struct layout_cell *layout_cell_get_neighbour(struct layout_cell *); void layout_init(struct window *, struct window_pane *); void layout_free(struct window *); void layout_resize(struct window *, u_int, u_int); diff --git a/window.c b/window.c index 458b234311..9b2a8bd9e6 100644 --- a/window.c +++ b/window.c @@ -751,21 +751,6 @@ window_zoom(struct window_pane *wp) window_set_active_pane(w, wp, 1); wp->flags |= PANE_ZOOMED; - /* Bring pane above other tiled panes and hide floating panes. */ - TAILQ_FOREACH(wp1, &w->z_index, zentry) { - if (wp1 == wp) { - wp1->saved_flags |= (wp1->flags & PANE_HIDDEN); - wp1->flags &= ~PANE_HIDDEN; - continue; - } - if (window_pane_is_floating(wp1)) { - wp1->saved_flags |= (wp1->flags & PANE_HIDDEN); - wp1->flags |= PANE_HIDDEN; - continue; - } - break; - } - TAILQ_FOREACH(wp1, &w->panes, entry) { wp1->saved_layout_cell = wp1->layout_cell; wp1->layout_cell = NULL; @@ -792,20 +777,9 @@ window_unzoom(struct window *w, int notify) w->layout_root = w->saved_layout_root; w->saved_layout_root = NULL; - TAILQ_FOREACH(wp, &w->z_index, zentry) { - if (window_pane_is_floating(wp)) { - wp->flags &= ~PANE_HIDDEN | (wp->saved_flags & PANE_HIDDEN); - continue; - } - break; - } - TAILQ_FOREACH(wp, &w->panes, entry) { wp->layout_cell = wp->saved_layout_cell; - if (wp->flags & PANE_HIDDEN) - wp->saved_layout_cell = wp->layout_cell; - else - wp->saved_layout_cell = NULL; + wp->saved_layout_cell = NULL; wp->flags &= ~PANE_ZOOMED; } layout_fix_panes(w, NULL); From e370ce5a003ba4ccf3328321b00c52f131b4ff93 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Tue, 9 Jun 2026 15:44:24 -0700 Subject: [PATCH 163/167] Added function comment. --- layout.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/layout.c b/layout.c index 935c52326b..853b0b6f5a 100644 --- a/layout.c +++ b/layout.c @@ -288,6 +288,10 @@ layout_fix_offsets(struct window *w) layout_fix_offsets1(lc); } +/* + * Not all cells are drawn within the tiled grid of a layout. This predicate + * isolates that logic. + */ int layout_cell_is_tiled(struct layout_cell *lc) { From 4fbc30267c853c2a4d3d3a63f7c8a7b4200877c1 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Wed, 10 Jun 2026 15:09:10 -0700 Subject: [PATCH 164/167] remove old logic. --- cmd-swap-pane.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 48785c9210..ca3300f3d2 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -79,7 +79,7 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) if (src_wp == dst_wp) goto out; - if (window_pane_is_floating(src_wp) || + if (window_pane_is_floating(src_wp) && window_pane_is_floating(dst_wp)) { cmdq_error(item, "cannot swap floating panes"); return (CMD_RETURN_ERROR); @@ -114,10 +114,6 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) dst_wp->layout_cell = src_lc; dst_lc->wp = src_wp; src_wp->layout_cell = dst_lc; - if (window_pane_is_floating(src_wp) != window_pane_is_floating(dst_wp)) { - src_wp->layout_cell->flags ^= LAYOUT_CELL_FLOATING; - dst_wp->layout_cell->flags ^= LAYOUT_CELL_FLOATING; - } src_wp->window = dst_w; options_set_parent(src_wp->options, dst_w->options); From b675c2149c892b8bf5fe6642a136acdb969f446d Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Wed, 10 Jun 2026 15:10:36 -0700 Subject: [PATCH 165/167] This guard doesn't need to be here. Why restrict this? --- cmd-swap-pane.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index ca3300f3d2..8a83a67afc 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -79,12 +79,6 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) if (src_wp == dst_wp) goto out; - if (window_pane_is_floating(src_wp) && - window_pane_is_floating(dst_wp)) { - cmdq_error(item, "cannot swap floating panes"); - return (CMD_RETURN_ERROR); - } - server_client_remove_pane(src_wp); server_client_remove_pane(dst_wp); From f58eb4dc0efb3a7b179d085b83eaa83a35e4e62f Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Thu, 11 Jun 2026 14:48:42 -0700 Subject: [PATCH 166/167] put the hidden panes format specifiers in the correct positions. --- format.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/format.c b/format.c index af251c3aed..99f9287dd9 100644 --- a/format.c +++ b/format.c @@ -2145,6 +2145,20 @@ format_cb_pane_height(struct format_tree *ft) return (NULL); } +/* Callback for pane_hidden_flag. */ +static void * +format_cb_pane_hidden_flag(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp != NULL) { + if (wp->flags & PANE_HIDDEN) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + /* Callback for pane_id. */ static void * format_cb_pane_id(struct format_tree *ft) @@ -2251,20 +2265,6 @@ format_cb_pane_marked_set(struct format_tree *ft) return (NULL); } -/* Callback for pane_hidden_flag. */ -static void * -format_cb_pane_hidden_flag(struct format_tree *ft) -{ - struct window_pane *wp = ft->wp; - - if (wp != NULL) { - if (wp->flags & PANE_HIDDEN) - return (xstrdup("1")); - return (xstrdup("0")); - } - return (NULL); -} - /* Callback for pane_mode. */ static void * format_cb_pane_mode(struct format_tree *ft) @@ -3464,6 +3464,9 @@ static const struct format_table_entry format_table[] = { { "pane_height", FORMAT_TABLE_STRING, format_cb_pane_height }, + { "pane_hidden_flag", FORMAT_TABLE_STRING, + format_cb_pane_hidden_flag + }, { "pane_id", FORMAT_TABLE_STRING, format_cb_pane_id }, @@ -3491,9 +3494,6 @@ static const struct format_table_entry format_table[] = { { "pane_marked_set", FORMAT_TABLE_STRING, format_cb_pane_marked_set }, - { "pane_hidden_flag", FORMAT_TABLE_STRING, - format_cb_pane_hidden_flag - }, { "pane_mode", FORMAT_TABLE_STRING, format_cb_pane_mode }, From d1fe0d449b3561fff2ce42c96cd995c013d44a4d Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Thu, 11 Jun 2026 14:55:43 -0700 Subject: [PATCH 167/167] Enabled swapping marked panes from the pane mouse menu. --- key-bindings.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/key-bindings.c b/key-bindings.c index 5ef7706c40..647971a2b4 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -65,7 +65,7 @@ " ''" \ " '#{?#{&&:#{!:#{pane_floating_flag}},#{>:#{window_panes},1}},Swap Up,}' 'u' {swap-pane -U}" \ " '#{?#{&&:#{!:#{pane_floating_flag}},#{>:#{window_panes},1}},Swap Down,}' 'd' {swap-pane -D}" \ - " '#{?#{!:#{pane_floating_flag}},#{?pane_marked_set,,-}Swap Marked,}' 's' {swap-pane}" \ + " '#{?pane_marked_set,,-}Swap Marked' 's' {swap-pane}" \ " ''" \ " 'Kill' 'X' {kill-pane}" \ " 'Respawn' 'R' {respawn-pane -k}" \