From 97b5f22a7075e8a124b01309e7ba0e3ed7e910c1 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 07:40:45 +0000 Subject: [PATCH 1/8] When redrawing a whole pane line, fall into tty_draw_line for any characters that are not plain ASCII. Fixes redraw issues with partial tabs and other wide characters when a pane is partly outside the window. --- screen-write.c | 62 +++++++++++++++++++++++++++++++++++++++----------- tmux.h | 1 + tty.c | 20 ++++++++++++++++ 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/screen-write.c b/screen-write.c index a5be7fe9..fbef8320 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1127,6 +1127,25 @@ screen_write_backspace(struct screen_write_ctx *ctx) screen_write_set_cursor(ctx, cx, cy); } +/* Is this cell a single ASCII character? */ +static int +screen_write_cell_is_single(const struct grid_cell *gc) +{ + if (gc->data.width != 1) + return (0); + if (gc->data.size != 1) + return (0); + if (*gc->data.data < 0x20 || *gc->data.data == 0x7f) + return (0); + if (gc->flags & GRID_FLAG_CLEARED) + return (0); + if (gc->flags & GRID_FLAG_PADDING) + return (0); + if (gc->flags & GRID_FLAG_TAB) + return (0); + return (1); +} + /* Redraw all visible cells on a line. */ static void screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, @@ -1135,11 +1154,14 @@ 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, ngc; - u_int sx = screen_size_x(s), cx, i, n; + u_int sx = screen_size_x(s), cx, i; int xoff = wp->xoff, yoff = wp->yoff; struct visible_ranges *r; struct visible_range *ri; + if (s->mode & MODE_SYNC) + return; + r = screen_redraw_get_visible_ranges(wp, xoff, yoff + yy, sx, NULL); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; @@ -1147,20 +1169,34 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, continue; 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; - } + if (cx >= sx) + continue; + if (cx + ri->nx > sx) + ttyctx->n = sx - cx; + else + ttyctx->n = ri->nx; + if (ttyctx->n == 0) + continue; + ttyctx->ocx = cx; + ttyctx->ocy = yy; - ttyctx->ocx = cx; - ttyctx->ocy = yy; - if (~s->mode & MODE_SYNC) - tty_write(tty_cmd_cell, ttyctx); + if (ttyctx->n != 1) { + tty_write(tty_cmd_redrawline, ttyctx); + continue; } + + grid_view_get_cell(s->grid, cx, yy, &gc); + if (!screen_write_cell_is_single(&gc)) { + tty_write(tty_cmd_redrawline, ttyctx); + continue; + } + if (~gc.flags & GRID_FLAG_SELECTED) + ttyctx->cell = &gc; + else { + screen_select_cell(s, &ngc, &gc); + ttyctx->cell = &ngc; + } + tty_write(tty_cmd_cell, ttyctx); } } diff --git a/tmux.h b/tmux.h index 20b3c228..5be1a809 100644 --- a/tmux.h +++ b/tmux.h @@ -2697,6 +2697,7 @@ void tty_write(void (*)(struct tty *, const struct tty_ctx *), void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *); void tty_cmd_cell(struct tty *, const struct tty_ctx *); void tty_cmd_cells(struct tty *, const struct tty_ctx *); +void tty_cmd_redrawline(struct tty *, const struct tty_ctx *); void tty_cmd_clearendofline(struct tty *, const struct tty_ctx *); void tty_cmd_clearendofscreen(struct tty *, const struct tty_ctx *); void tty_cmd_clearline(struct tty *, const struct tty_ctx *); diff --git a/tty.c b/tty.c index ef4dcd25..bb7c0ae8 100644 --- a/tty.c +++ b/tty.c @@ -1430,6 +1430,26 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) } } +void +tty_cmd_redrawline(struct tty *tty, const struct tty_ctx *ctx) +{ + u_int i, x, rx, ry, j; + struct visible_ranges *r; + struct visible_range *rr; + + if (tty_clamp_line(tty, ctx, ctx->ocx, ctx->ocy, ctx->n, + &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, ctx->s, i + rr->px - x, + ctx->ocy, rr->nx, rr->px, ry, &ctx->style_ctx); + } + } +} + /* Check if character needs to be mapped for codeset. */ const struct grid_cell * tty_check_codeset(struct tty *tty, const struct grid_cell *gc) From 7a18fa281db3745024d36d000e7bc45eac8978f1 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 08:16:05 +0000 Subject: [PATCH 2/8] Add top-floating and bottom-floating to pane-border-status to show status line only on floating panes. --- format.c | 6 ++---- options-table.c | 4 ++-- screen-redraw.c | 46 +++++++++++++++++++++++++++------------------- server-client.c | 2 +- tmux.1 | 7 ++++++- tmux.h | 4 +++- window.c | 28 ++++++++++++++++++++++++---- 7 files changed, 65 insertions(+), 32 deletions(-) diff --git a/format.c b/format.c index d0f8518f..4d58c40b 100644 --- a/format.c +++ b/format.c @@ -1143,15 +1143,13 @@ static void * format_cb_pane_at_top(struct format_tree *ft) { struct window_pane *wp = ft->wp; - struct window *w; int status, flag; char *value; if (wp == NULL) return (NULL); - w = wp->window; - status = window_get_pane_status(w); + status = window_pane_get_pane_status(wp); if (status == PANE_STATUS_TOP) flag = (wp->yoff == 1); else @@ -1173,7 +1171,7 @@ format_cb_pane_at_bottom(struct format_tree *ft) return (NULL); w = wp->window; - status = options_get_number(w->options, "pane-border-status"); + status = window_pane_get_pane_status(wp); if (status == PANE_STATUS_BOTTOM) flag = (wp->yoff + (int)wp->sy == (int)w->sy - 1); else diff --git a/options-table.c b/options-table.c index 90c8fb57..d24b90b3 100644 --- a/options-table.c +++ b/options-table.c @@ -71,7 +71,7 @@ static const char *options_table_pane_scrollbars_position_list[] = { "right", "left", NULL }; static const char *options_table_pane_status_list[] = { - "off", "top", "bottom", NULL + "off", "top", "bottom", "top-floating", "bottom-floating", NULL }; static const char *options_table_pane_border_indicators_list[] = { "off", "colour", "arrows", "both", NULL @@ -1374,7 +1374,7 @@ const struct options_table_entry options_table[] = { { .name = "pane-border-status", .type = OPTIONS_TABLE_CHOICE, - .scope = OPTIONS_TABLE_WINDOW, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .choices = options_table_pane_status_list, .default_num = PANE_STATUS_OFF, .text = "Position of the pane status lines." diff --git a/screen-redraw.c b/screen-redraw.c index 95436501..3f04e74c 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -125,7 +125,8 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, { struct options *oo = wp->window->options; int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy; - int hsplit = 0, vsplit = 0, pane_status = ctx->pane_status; + int hsplit = 0, vsplit = 0; + int pane_status = window_pane_get_pane_status(wp); int pane_scrollbars = ctx->pane_scrollbars, sb_w = 0; int sb_pos, sx = wp->sx, sy = wp->sy; enum layout_type split_type; @@ -296,7 +297,7 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, } /* Outside the window or on the window border? */ - if (ctx->pane_status == PANE_STATUS_BOTTOM) + if (window_pane_get_pane_status(wp) == PANE_STATUS_BOTTOM) sy--; if (px > sx || py > sy) return (0); @@ -327,7 +328,8 @@ screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx, { struct client *c = ctx->c; struct window *w = c->session->curw->window; - int pane_status = ctx->pane_status, borders = 0; + int pane_status = window_pane_get_pane_status(wp); + int borders = 0; int sx = w->sx, sy = w->sy; /* Is this outside the window? */ @@ -435,7 +437,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, int px, int py, struct window *w = c->session->curw->window; struct window_pane *wp, *start; int sx = w->sx, sy = w->sy; - int pane_status = ctx->pane_status; + int pane_status; int border, pane_scrollbars = ctx->pane_scrollbars; int pane_status_line, tiled_only = 0, left, right; int sb_pos = ctx->pane_scrollbars_pos, sb_w; @@ -511,6 +513,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, int px, int py, * Pane border status inside top/bottom border is CELL_INSIDE * so it doesn't get overdrawn by a border line. */ + pane_status = window_pane_get_pane_status(wp); if (pane_status != PANE_STATUS_OFF) { if (pane_status == PANE_STATUS_TOP) pane_status_line = wp->yoff - 1; @@ -593,13 +596,19 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, struct format_tree *ft; struct style_line_entry *sle = &wp->border_status_line; char *expanded; - int pane_status = rctx->pane_status, sb_w = 0; + int pane_status = window_pane_get_pane_status(wp); + int 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; + if (pane_status == PANE_STATUS_OFF) { + wp->status_size = 0; + return (0); + } + if (window_pane_show_scrollbar(wp, pane_scrollbars)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; @@ -673,7 +682,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) struct visible_ranges *r; struct visible_range *ri; u_int i, l, x, width, size; - int xoff, yoff; + int xoff, yoff, pane_status; log_debug("%s: %s @%u", __func__, c->name, w->id); @@ -683,7 +692,10 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) s = &wp->status_screen; size = wp->status_size; - if (ctx->pane_status == PANE_STATUS_TOP) + pane_status = window_pane_get_pane_status(wp); + if (pane_status == PANE_STATUS_OFF) + continue; + if (pane_status == PANE_STATUS_TOP) yoff = wp->yoff - 1; else yoff = wp->yoff + wp->sy; @@ -754,16 +766,14 @@ screen_redraw_update(struct screen_redraw_ctx *ctx, uint64_t flags) if (c->overlay_draw != NULL) flags |= CLIENT_REDRAWOVERLAY; - if (ctx->pane_status != PANE_STATUS_OFF) { - lines = ctx->pane_lines; - redraw = 0; - TAILQ_FOREACH(wp, &w->panes, entry) { - if (screen_redraw_make_pane_status(c, wp, ctx, lines)) - redraw = 1; - } - if (redraw) - flags |= CLIENT_REDRAWBORDERS; + lines = ctx->pane_lines; + redraw = 0; + TAILQ_FOREACH(wp, &w->panes, entry) { + if (screen_redraw_make_pane_status(c, wp, ctx, lines)) + redraw = 1; } + if (redraw) + flags |= CLIENT_REDRAWBORDERS; return (flags); } @@ -787,7 +797,6 @@ screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx) ctx->statustop = 1; ctx->statuslines = lines; - ctx->pane_status = window_get_pane_status(w); ctx->pane_lines = options_get_number(w->options, "pane-border-lines"); ctx->pane_scrollbars = options_get_number(w->options, @@ -824,8 +833,7 @@ screen_redraw_screen(struct client *c) if (flags & (CLIENT_REDRAWWINDOW|CLIENT_REDRAWBORDERS)) { log_debug("%s: redrawing borders", c->name); screen_redraw_draw_borders(&ctx); - if (ctx.pane_status != PANE_STATUS_OFF) - screen_redraw_draw_pane_status(&ctx); + screen_redraw_draw_pane_status(&ctx); screen_redraw_draw_pane_scrollbars(&ctx); } if (flags & CLIENT_REDRAWWINDOW) { diff --git a/server-client.c b/server-client.c index c3d04c3b..35a505b1 100644 --- a/server-client.c +++ b/server-client.c @@ -614,7 +614,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, sb = options_get_number(w->options, "pane-scrollbars"); sb_pos = options_get_number(w->options, "pane-scrollbars-position"); - pane_status = window_get_pane_status(w); + pane_status = window_pane_get_pane_status(wp); if (window_pane_show_scrollbar(wp, sb)) { sb_w = wp->scrollbar_style.width; diff --git a/tmux.1 b/tmux.1 index 87cdb70d..40f74d95 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5638,9 +5638,14 @@ and will fall back to standard ACS line drawing when UTF\-8 is not supported. .Pp .It Xo Ic pane\-border\-status -.Op Ic off | top | bottom +.Op Ic off | top | bottom | top\-floating | bottom\-floating .Xc Turn pane border status lines off or set their position. +If set to +.Ic top\-floating +or +.Ic bottom\-floating , +status lines are shown only on floating panes. .Pp .It Ic pane\-border\-style Ar style Set the pane border style for panes aside from the active pane. diff --git a/tmux.h b/tmux.h index 5be1a809..11590bc1 100644 --- a/tmux.h +++ b/tmux.h @@ -1078,7 +1078,6 @@ struct screen_redraw_ctx { u_int statuslines; int statustop; - int pane_status; enum pane_lines pane_lines; int pane_scrollbars; @@ -1416,6 +1415,8 @@ TAILQ_HEAD(winlink_stack, winlink); #define PANE_STATUS_OFF 0 #define PANE_STATUS_TOP 1 #define PANE_STATUS_BOTTOM 2 +#define PANE_STATUS_TOP_FLOATING 3 +#define PANE_STATUS_BOTTOM_FLOATING 4 /* Pane scrollbars option. */ #define PANE_SCROLLBARS_OFF 0 @@ -3474,6 +3475,7 @@ int window_get_bg_client(struct window_pane *); enum client_theme window_pane_get_theme(struct window_pane *); void window_pane_send_theme_update(struct window_pane *); int window_get_pane_status(struct window *); +int window_pane_get_pane_status(struct window_pane *); struct style_range *window_pane_status_get_range(struct window_pane *, u_int, u_int); int window_pane_is_floating(struct window_pane *); diff --git a/window.c b/window.c index 8b00dd1a..4ab5ca32 100644 --- a/window.c +++ b/window.c @@ -2126,16 +2126,14 @@ struct style_range * window_pane_status_get_range(struct window_pane *wp, u_int x, u_int y) { struct style_ranges *srs; - struct window *w; u_int line; int pane_status; if (wp == NULL) return (NULL); - w = wp->window; srs = &wp->border_status_line.ranges; - pane_status = window_get_pane_status(w); + pane_status = window_pane_get_pane_status(wp); if (pane_status == PANE_STATUS_TOP) line = wp->yoff - 1; else if (pane_status == PANE_STATUS_BOTTOM) @@ -2153,7 +2151,29 @@ window_pane_status_get_range(struct window_pane *wp, u_int x, u_int y) int window_get_pane_status(struct window *w) { - return (options_get_number(w->options, "pane-border-status")); + int status; + + status = options_get_number(w->options, "pane-border-status"); + if (status == PANE_STATUS_TOP_FLOATING || + status == PANE_STATUS_BOTTOM_FLOATING) + return (PANE_STATUS_OFF); + return (status); +} + +int +window_pane_get_pane_status(struct window_pane *wp) +{ + int status; + + if (!window_pane_is_floating(wp)) + return (window_get_pane_status(wp->window)); + + status = options_get_number(wp->options, "pane-border-status"); + if (status == PANE_STATUS_TOP_FLOATING) + return (PANE_STATUS_TOP); + if (status == PANE_STATUS_BOTTOM_FLOATING) + return (PANE_STATUS_BOTTOM); + return (status); } int From a41e6e2ed23f386da5630e6db379fadb477442c3 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 09:21:40 +0000 Subject: [PATCH 3/8] Add -B to new-pane to select the floating pane border. --- cmd-split-window.c | 24 ++++++++++++++++++++---- options-table.c | 2 +- screen-redraw.c | 7 ++++++- tmux.1 | 4 ++++ tmux.h | 1 + window.c | 12 ++++++++++++ 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 1c9eaae9..ee3eeb4c 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -39,9 +39,10 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:EfF:hIkl:Lm:p:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, - .usage = "[-bdefhIklPvWZ] [-c start-directory] [-e environment] " - "[-F format] [-l size] [-m message] [-p percentage] " + .args = { "bB:c:de:EfF:hIkl:Lm:p:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, + .usage = "[-bdefhIklPvWZ] [-B border-lines] " + "[-c start-directory] [-e environment] " + "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " "[-R inactive-border-style] [-T title] [-x width] [-y height] " "[-X x-position] [-Y y-position] " CMD_TARGET_PANE_USAGE " " @@ -85,9 +86,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) struct layout_cell *lc = NULL; struct cmd_find_state fs; int input, empty, is_floating, flags = 0; - const char *template, *style; + const char *template, *style, *value; char *cause = NULL, *cp, *title; + struct options_entry *oe; struct args_value *av; + enum pane_lines lines; u_int count = args_count(args); if (cmd_get_entry(self) == &cmd_new_pane_entry) @@ -188,6 +191,19 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } } + value = args_get(args, 'B'); + if (value != NULL) { + oe = options_get(new_wp->options, "pane-border-lines"); + lines = options_find_choice(options_table_entry(oe), value, + &cause); + if (cause != NULL) { + cmdq_error(item, "pane-border-lines %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + options_set_number(new_wp->options, "pane-border-lines", + lines); + } 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 d24b90b3..92b0519b 100644 --- a/options-table.c +++ b/options-table.c @@ -1365,7 +1365,7 @@ const struct options_table_entry options_table[] = { { .name = "pane-border-lines", .type = OPTIONS_TABLE_CHOICE, - .scope = OPTIONS_TABLE_WINDOW, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .choices = options_table_pane_border_lines_list, .default_num = PANE_LINES_SINGLE, .text = "Type of characters used to draw pane border lines. Some of " diff --git a/screen-redraw.c b/screen-redraw.c index 3f04e74c..ce98dd99 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1003,6 +1003,7 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) struct format_tree *ft; struct window_pane *wp, *active = server_client_get_pane(c); struct grid_cell gc; + enum pane_lines pane_lines; u_int cell_type; u_int x = ctx->ox + i, y = ctx->oy + j; int isolates; @@ -1034,7 +1035,11 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) screen_redraw_check_is(ctx, x, y, marked_pane.wp)) gc.attr ^= GRID_ATTR_REVERSE; } - screen_redraw_border_set(w, wp, ctx->pane_lines, cell_type, &gc); + if (wp == NULL) + pane_lines = ctx->pane_lines; + else + pane_lines = window_pane_get_pane_lines(wp); + screen_redraw_border_set(w, wp, pane_lines, cell_type, &gc); if (cell_type == CELL_TOPBOTTOM && (c->flags & CLIENT_UTF8) && diff --git a/tmux.1 b/tmux.1 index 40f74d95..59ed8c75 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3490,6 +3490,7 @@ but a different format may be specified with .Tg newp .It Xo Ic new\-pane .Op Fl bdefhIkPvWZ +.Op Fl B Ar border\-lines .Op Fl c Ar start\-directory .Op Fl e Ar environment .Op Fl F Ar format @@ -3521,6 +3522,9 @@ sets the border style when the pane is inactive (see .Sx STYLES ) . .Fl T sets the pane title. +.Fl B +sets the pane border lines for floating panes; see +.Ic pane\-border\-lines . .Pp .Fl h does a horizontal split and diff --git a/tmux.h b/tmux.h index 11590bc1..b70fdf06 100644 --- a/tmux.h +++ b/tmux.h @@ -3474,6 +3474,7 @@ int window_pane_get_bg_control_client(struct window_pane *); int window_get_bg_client(struct window_pane *); enum client_theme window_pane_get_theme(struct window_pane *); void window_pane_send_theme_update(struct window_pane *); +enum pane_lines window_pane_get_pane_lines(struct window_pane *); int window_get_pane_status(struct window *); int window_pane_get_pane_status(struct window_pane *); struct style_range *window_pane_status_get_range(struct window_pane *, u_int, diff --git a/window.c b/window.c index 4ab5ca32..b3a0a0ea 100644 --- a/window.c +++ b/window.c @@ -2148,6 +2148,18 @@ window_pane_status_get_range(struct window_pane *wp, u_int x, u_int y) return (style_ranges_get_range(srs, x - wp->xoff - 2)); } +enum pane_lines +window_pane_get_pane_lines(struct window_pane *wp) +{ + struct options *oo; + + if (!window_pane_is_floating(wp)) + oo = wp->window->options; + else + oo = wp->options; + return (options_get_number(oo, "pane-border-lines")); +} + int window_get_pane_status(struct window *w) { From 8ced9c332c659bfd94e6e2a48bd875579e1bb27c Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 11:45:51 +0000 Subject: [PATCH 4/8] Use correct x position when drawing clipped line. --- tty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tty.c b/tty.c index bb7c0ae8..5b66e2d1 100644 --- a/tty.c +++ b/tty.c @@ -1444,7 +1444,7 @@ tty_cmd_redrawline(struct tty *tty, const struct tty_ctx *ctx) rr = &r->ranges[j]; if (rr->nx == 0) continue; - tty_draw_line(tty, ctx->s, i + rr->px - x, + tty_draw_line(tty, ctx->s, ctx->ocx + i + rr->px - x, ctx->ocy, rr->nx, rr->px, ry, &ctx->style_ctx); } } From 057b688fc709576901f18c097b1275c54a9d762e Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 12:07:49 +0000 Subject: [PATCH 5/8] Tidy up screen_redraw_get_visible_ranges by using a couple of temporaries for start and end of range. --- screen-redraw.c | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index ce98dd99..6d151087 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1154,7 +1154,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, struct visible_range *ri; static struct visible_ranges sr = { NULL, 0, 0 }; int found_self, sb, sb_w, sb_pos; - u_int lb, rb, tb, bb; + int lb, rb, tb, bb, sx, ex; u_int i, s; if (py < 0 || width == 0) @@ -1207,11 +1207,10 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, bb = wp->yoff + wp->sy; if (!found_self || !window_pane_visible(wp) || - (u_int)py < tb || - (u_int)py > bb) + py < tb || + py > bb) continue; - if (!window_pane_is_floating(wp) && - ((u_int)py == tb || (u_int)py == bb)) + if (!window_pane_is_floating(wp) && (py == tb || py == bb)) continue; sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; @@ -1235,34 +1234,29 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, rb = wp->xoff + wp->sx; else /* PANE_SCROLLBARS_RIGHT or none. */ rb = wp->xoff + wp->sx + sb_w; - if (rb > w->sx) + if (rb > (int)w->sx) rb = w->sx - 1; - if (lb > ri->px && - lb < ri->px + ri->nx && - rb >= ri->px + ri->nx) - { + sx = ri->px; + ex = sx + ri->nx - 1; + if (lb > sx && lb <= ex && rb > ex) { /* * If the left edge of floating pane falls * inside this range and right edge covers up * to right of range, then shrink left edge of * range. */ - ri->nx = lb - ri->px; - } - else if (rb >= ri->px && - rb < ri->px + ri->nx && - lb <= ri->px) { + ri->nx = lb - sx; + } else if (rb >= sx && rb <= ex && lb <= sx) { /* * Else if the right edge of floating pane falls * inside of this range and left edge covers * the left of range, then move px forward to * right edge of pane. */ - ri->nx = ri->nx - (rb + 1 - ri->px); - ri->px = ri->px + (rb + 1 - ri->px); - } - else if (lb > ri->px && rb < ri->px + ri->nx) { + ri->nx = ex - rb; + ri->px = rb + 1; + } else if (lb > sx && rb <= ex) { /* * Else if pane fully inside range then split * into 2 ranges. @@ -1274,11 +1268,11 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, } ri = &r->ranges[i]; r->ranges[i + 1].px = rb + 1; - r->ranges[i + 1].nx = ri->px + ri->nx - (rb + 1); + r->ranges[i + 1].nx = ex - rb; /* ri->px was copied, unchanged. */ - ri->nx = lb - ri->px; + ri->nx = lb - sx; r->used++; - } else if (lb <= ri->px && rb >= ri->px + ri->nx) { + } else if (lb <= sx && rb > ex) { /* * If floating pane completely covers this range * then delete it (make it 0 length). From 0e6fe5a09786faf4c79b67c6eb48425c47e9c018 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 14:56:30 +0000 Subject: [PATCH 6/8] Convert cursor position back to pane coordinates for tty_cmd_cell. --- screen-write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen-write.c b/screen-write.c index fbef8320..ff1a713f 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2587,7 +2587,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) if (ri->nx == 0) continue; for (n = 0; n < ri->nx; n++) { - ttyctx.ocx = ri->px + n; + ttyctx.ocx = (int)ri->px - xoff + (int)n; tty_write(tty_cmd_cell, &ttyctx); } } From bb750b07bc0804f822357ec3ced7a258c0119890 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 15:05:12 +0000 Subject: [PATCH 7/8] If the client is blocked, defer the redraw because it may end up partially discarded leading to redraw artifacts. --- server-client.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server-client.c b/server-client.c index 35a505b1..a40c348b 100644 --- a/server-client.c +++ b/server-client.c @@ -2051,8 +2051,13 @@ server_client_check_redraw(struct client *c) } } } - if (needed && (left = EVBUFFER_LENGTH(tty->out)) != 0) { - log_debug("%s: redraw deferred (%zu left)", c->name, left); + left = EVBUFFER_LENGTH(tty->out); + if (needed && (left != 0 || (tty->flags & TTY_BLOCK))) { + if (left != 0) { + log_debug("%s: redraw deferred (%zu left)", c->name, + left); + } else + log_debug("%s: redraw deferred (blocked)", c->name); if (!evtimer_initialized(&ev)) evtimer_set(&ev, server_client_redraw_timer, NULL); if (!evtimer_pending(&ev, NULL)) { From b86bd1fcd0e0defa36e83ad37acf9eafb5d55459 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 17:34:25 +0000 Subject: [PATCH 8/8] Add pane-border-lines none like popups (-B to new-pane). --- cmd-join-pane.c | 28 ++++++++++++++++------------ layout.c | 3 ++- options-table.c | 2 +- screen-redraw.c | 44 +++++++++++++++++++++++++++++++++++--------- server-client.c | 7 +++++++ tmux.1 | 2 ++ tmux.h | 3 ++- window.c | 19 ++++++++++++++----- 8 files changed, 79 insertions(+), 29 deletions(-) diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 857aac0e..30f491f6 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -71,20 +71,24 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl, struct window_pane *owp; int wx = w->sx, wy = w->sy, px = lc->sx; int py = lc->sy, xoff = lc->xoff, yoff = lc->yoff; + int border = 1; + + if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE) + border = 0; if (strcmp(position, "top-left") == 0) { - xoff = 1; - yoff = 1; + xoff = border; + yoff = border; } else if (strcmp(position, "top-centre") == 0 || strcmp(position, "top-center") == 0) { xoff = (wx - px) / 2; - yoff = 1; + yoff = border; } else if (strcmp(position, "top-right") == 0) { - xoff = wx - px - 1; - yoff = 1; + xoff = wx - px - border; + yoff = border; } else if (strcmp(position, "centre-left") == 0 || strcmp(position, "center-left") == 0) { - xoff = 1; + xoff = border; yoff = (wy - py) / 2; } else if (strcmp(position, "centre") == 0 || strcmp(position, "center") == 0) { @@ -92,18 +96,18 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl, yoff = (wy - py) / 2; } else if (strcmp(position, "centre-right") == 0 || strcmp(position, "center-right") == 0) { - xoff = wx - px - 1; + xoff = wx - px - border; yoff = (wy - py) / 2; } else if (strcmp(position, "bottom-left") == 0) { - xoff = 1; - yoff = wy - py - 1; + xoff = border; + yoff = wy - py - border; } else if (strcmp(position, "bottom-centre") == 0 || strcmp(position, "bottom-center") == 0) { xoff = (wx - px) / 2; - yoff = wy - py - 1; + yoff = wy - py - border; } else if (strcmp(position, "bottom-right") == 0) { - xoff = wx - px - 1; - yoff = wy - py - 1; + xoff = wx - px - border; + yoff = wy - py - border; } else if (strcmp(position, "top-left-centre") == 0 || strcmp(position, "top-left-center") == 0) { xoff = wx / 4 - px / 2; diff --git a/layout.c b/layout.c index 686d1bdf..0e8a2a2a 100644 --- a/layout.c +++ b/layout.c @@ -712,7 +712,8 @@ layout_resize_floating_pane_to(struct window_pane *wp, enum layout_type type, return; } - if (size >= PANE_MINIMUM + 2) + if (window_pane_get_pane_lines(wp) != PANE_LINES_NONE && + size >= PANE_MINIMUM + 2) size -= 2; if (size < PANE_MINIMUM || size > PANE_MAXIMUM) { *cause = xstrdup("size is too big or too small"); diff --git a/options-table.c b/options-table.c index 92b0519b..ed90bd92 100644 --- a/options-table.c +++ b/options-table.c @@ -77,7 +77,7 @@ static const char *options_table_pane_border_indicators_list[] = { "off", "colour", "arrows", "both", NULL }; static const char *options_table_pane_border_lines_list[] = { - "single", "double", "heavy", "simple", "number", "spaces", NULL + "single", "double", "heavy", "simple", "number", "spaces", "none", NULL }; static const char *options_table_popup_border_lines_list[] = { "single", "double", "heavy", "simple", "rounded", "padded", "none", NULL diff --git a/screen-redraw.c b/screen-redraw.c index 6d151087..b2bfb824 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -87,6 +87,7 @@ screen_redraw_border_set(struct window *w, struct window_pane *wp, gc->attr &= ~GRID_ATTR_CHARSET; utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]); break; + case PANE_LINES_NONE: case PANE_LINES_SPACES: gc->attr &= ~GRID_ATTR_CHARSET; utf8_set(&gc->data, ' '); @@ -140,6 +141,10 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if (px >= wp->xoff && px < ex && py >= wp->yoff && py < ey) return (SCREEN_REDRAW_INSIDE); + if (window_pane_is_floating(wp) && + window_pane_get_pane_lines(wp) == PANE_LINES_NONE) + return (SCREEN_REDRAW_OUTSIDE); + /* Are scrollbars enabled? */ if (window_pane_show_scrollbar(wp, pane_scrollbars)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; @@ -1153,7 +1158,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, struct window *w; struct visible_range *ri; static struct visible_ranges sr = { NULL, 0, 0 }; - int found_self, sb, sb_w, sb_pos; + int found_self, sb, sb_w, sb_pos, no_border; int lb, rb, tb, bb, sx, ex; u_int i, s; @@ -1203,8 +1208,18 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, continue; } - tb = wp->yoff > 0 ? wp->yoff - 1 : 0; - bb = wp->yoff + wp->sy; + if (window_pane_is_floating(wp) && + window_pane_get_pane_lines(wp) == PANE_LINES_NONE) + no_border = 1; + else + no_border = 0; + if (no_border) { + tb = wp->yoff; + bb = wp->yoff + (int)wp->sy - 1; + } else { + tb = wp->yoff > 0 ? wp->yoff - 1 : 0; + bb = wp->yoff + (int)wp->sy; + } if (!found_self || !window_pane_visible(wp) || py < tb || @@ -1219,7 +1234,10 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (no_border) { + lb = wp->xoff; + rb = wp->xoff + (int)wp->sx - 1; + } else if (sb_pos == PANE_SCROLLBARS_LEFT) { if (wp->xoff > sb_w) lb = wp->xoff - 1 - sb_w; else @@ -1230,11 +1248,19 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, else lb = 0; } - if (sb_pos == PANE_SCROLLBARS_LEFT) - rb = wp->xoff + wp->sx; - else /* PANE_SCROLLBARS_RIGHT or none. */ - rb = wp->xoff + wp->sx + sb_w; - if (rb > (int)w->sx) + if (!no_border) { + if (sb_pos == PANE_SCROLLBARS_LEFT) + rb = wp->xoff + (int)wp->sx; + else /* PANE_SCROLLBARS_RIGHT or none. */ + rb = wp->xoff + (int)wp->sx + sb_w; + } + if (lb < 0) + lb = 0; + if (rb < 0) + continue; + if (no_border && rb >= (int)w->sx) + rb = w->sx - 1; + else if (!no_border && rb > (int)w->sx) rb = w->sx - 1; sx = ri->px; diff --git a/server-client.c b/server-client.c index a40c348b..2bb031bd 100644 --- a/server-client.c +++ b/server-client.c @@ -602,6 +602,7 @@ server_client_exec(struct client *c, const char *cmd) free(msg); } +/* Is the mouse inside a pane? */ static enum key_code_mouse_location server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, u_int *sl_mpos) @@ -659,6 +660,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, } else /* py > sl_bottom */ return (KEYC_MOUSE_LOCATION_SCROLLBAR_DOWN); } else if (window_pane_is_floating(wp) && + window_pane_get_pane_lines(wp) != PANE_LINES_NONE && (px == wp->xoff - 1 || py == wp->yoff - 1 || py == wp->yoff + (int)wp->sy)) { @@ -674,6 +676,9 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, if ((w->flags & WINDOW_ZOOMED) && (~fwp->flags & PANE_ZOOMED)) continue; + if (window_pane_is_floating(fwp) && + window_pane_get_pane_lines(fwp) == PANE_LINES_NONE) + continue; bdr_top = fwp->yoff - 1; bdr_bottom = fwp->yoff + fwp->sy; if (sb_pos == PANE_SCROLLBARS_LEFT) @@ -1510,12 +1515,14 @@ server_client_handle_key0(struct client *c, struct key_event *event, return (1); } +/* Handle key and insert at end of queue. */ int server_client_handle_key(struct client *c, struct key_event *event) { return (server_client_handle_key0(c, event, NULL, NULL)); } +/* Handle key and insert after another item. */ int server_client_handle_key_after(struct client *c, struct key_event *event, struct cmdq_item *after, struct cmdq_item **next) diff --git a/tmux.1 b/tmux.1 index 59ed8c75..2f092030 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5634,6 +5634,8 @@ simple ASCII characters the pane number .It spaces space characters +.It none +no border for floating panes .El .Pp .Ql double diff --git a/tmux.h b/tmux.h index b70fdf06..261b9962 100644 --- a/tmux.h +++ b/tmux.h @@ -1057,7 +1057,8 @@ enum pane_lines { PANE_LINES_HEAVY, PANE_LINES_SIMPLE, PANE_LINES_NUMBER, - PANE_LINES_SPACES + PANE_LINES_SPACES, + PANE_LINES_NONE }; /* Pane border indicator option. */ diff --git a/window.c b/window.c index b3a0a0ea..0187737a 100644 --- a/window.c +++ b/window.c @@ -667,11 +667,18 @@ window_get_active_at(struct window *w, u_int x, u_int y) continue; } } else { - /* Floating - include all borders. */ - if ((int)x < xoff - 1 || x > xoff + sx) - continue; - if ((int)y < yoff - 1 || y > yoff + sy) - continue; + if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE) { + if ((int)x < xoff || (int)x >= xoff + (int)sx) + continue; + if ((int)y < yoff || (int)y >= yoff + (int)sy) + continue; + } else { + /* Floating - include all borders. */ + if ((int)x < xoff - 1 || x > xoff + sx) + continue; + if ((int)y < yoff - 1 || y > yoff + sy) + continue; + } } return (wp); } @@ -2179,6 +2186,8 @@ window_pane_get_pane_status(struct window_pane *wp) if (!window_pane_is_floating(wp)) return (window_get_pane_status(wp->window)); + if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE) + return (PANE_STATUS_OFF); status = options_get_number(wp->options, "pane-border-status"); if (status == PANE_STATUS_TOP_FLOATING)