From 95afd7549c564c6584fad989947e2fddceb5fc87 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Jun 2026 08:47:45 +0000 Subject: [PATCH 01/71] Rewrite screen-redraw.c to make it tidier and more maintainable. A scene is generated and cached in the client: it holds positions and sizes of panes, borders and so on. The scene is invalidated when a pane is moved or resized or relevant option is changed. This scene is then drawn to the client as needed and text and colours are filled in. With Michael Grant. --- Makefile | 1 + cmd-capture-pane.c | 1 + cmd-display-panes.c | 39 +- cmd-rotate-window.c | 1 + cmd-swap-pane.c | 2 + layout.c | 29 +- menu.c | 3 +- options.c | 9 + popup.c | 4 +- screen-redraw.c | 2778 ++++++++++++++++++++++++------------------- screen-write.c | 24 +- server-client.c | 7 +- tmux.h | 86 +- window-border.c | 171 +++ window-copy.c | 2 +- window.c | 5 + 16 files changed, 1836 insertions(+), 1326 deletions(-) create mode 100644 window-border.c diff --git a/Makefile b/Makefile index ce873ba06..de8cd2414 100644 --- a/Makefile +++ b/Makefile @@ -125,6 +125,7 @@ SRCS= alerts.c \ tty.c \ utf8.c \ utf8-combined.c \ + window-border.c \ window-buffer.c \ window-client.c \ window-clock.c \ diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index 8f537b982..7820f3ce7 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -318,6 +318,7 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item) grid_clear_history(wp->base.grid); if (args_has(args, 'H')) screen_reset_hyperlinks(wp->screen); + server_redraw_window(wp->window); return (CMD_RETURN_NORMAL); } diff --git a/cmd-display-panes.c b/cmd-display-panes.c index 56e13333f..24195266e 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -48,6 +48,16 @@ struct cmd_display_panes_data { struct args_command_state *state; }; +struct cmd_display_panes_ctx { + struct client *c; + int ox; + int oy; + u_int sx; + u_int sy; + u_int statuslines; + int statustop; +}; + static enum args_parse_type cmd_display_panes_args_parse(__unused struct args *args, __unused u_int idx, __unused char **cause) @@ -56,7 +66,7 @@ cmd_display_panes_args_parse(__unused struct args *args, __unused u_int idx, } static void -cmd_display_panes_put(struct screen_redraw_ctx *ctx, +cmd_display_panes_put(struct cmd_display_panes_ctx *ctx, struct window_pane *wp, u_int cx, u_int cy, const char *buf, size_t len) { struct client *c = ctx->c; @@ -76,7 +86,7 @@ cmd_display_panes_put(struct screen_redraw_ctx *ctx, } static void -cmd_display_panes_draw_format(struct screen_redraw_ctx *ctx, +cmd_display_panes_draw_format(struct cmd_display_panes_ctx *ctx, struct window_pane *wp, u_int xoff, u_int yoff, u_int sx, const struct grid_cell *gc) { @@ -112,7 +122,7 @@ cmd_display_panes_draw_format(struct screen_redraw_ctx *ctx, } static void -cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, +cmd_display_panes_draw_pane(struct cmd_display_panes_ctx *ctx, struct window_pane *wp) { struct client *c = ctx->c; @@ -254,17 +264,30 @@ out: } static void -cmd_display_panes_draw(struct client *c, __unused void *data, - struct screen_redraw_ctx *ctx) +cmd_display_panes_draw(struct client *c, __unused void *data) { - struct window *w = c->session->curw->window; - struct window_pane *wp; + struct session *s = c->session; + struct window *w = s->curw->window; + struct window_pane *wp; + struct cmd_display_panes_ctx ctx; + u_int lines; log_debug("%s: %s @%u", __func__, c->name, w->id); + memset(&ctx, 0, sizeof ctx); + ctx.c = c; + tty_window_offset(&c->tty, &ctx.ox, &ctx.oy, &ctx.sx, &ctx.sy); + if (options_get_number(s->options, "status-position") == 0) { + lines = status_line_size(c); + if (c->message_string != NULL || c->prompt_string != NULL) + lines = (lines == 0 ? 1 : lines); + ctx.statuslines = lines; + ctx.statustop = 1; + } + TAILQ_FOREACH(wp, &w->panes, entry) { if (window_pane_is_visible(wp)) - cmd_display_panes_draw_pane(ctx, wp); + cmd_display_panes_draw_pane(&ctx, wp); } } diff --git a/cmd-rotate-window.c b/cmd-rotate-window.c index 0e2ed852d..c7459a9ae 100644 --- a/cmd-rotate-window.c +++ b/cmd-rotate-window.c @@ -109,6 +109,7 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item) window_set_active_pane(w, wp, 1); cmd_find_from_winlink_pane(current, wl, wp, 0); window_pop_zoom(w); + redraw_invalidate_scene(w); server_redraw_window(w); return (CMD_RETURN_NORMAL); diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 391ab7af9..f7519a0dd 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -149,9 +149,11 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) colour_palette_from_option(&src_wp->palette, src_wp->options); colour_palette_from_option(&dst_wp->palette, dst_wp->options); layout_fix_panes(src_w, NULL); + redraw_invalidate_scene(src_w); server_redraw_window(src_w); } layout_fix_panes(dst_w, NULL); + redraw_invalidate_scene(dst_w); server_redraw_window(dst_w); notify_window("window-layout-changed", src_w); diff --git a/layout.c b/layout.c index 1860d2988..cf103a7b8 100644 --- a/layout.c +++ b/layout.c @@ -405,7 +405,8 @@ layout_fix_panes(struct window *w, struct window_pane *skip) struct window_pane *wp; struct layout_cell *lc; int status, scrollbars, sb_pos, sb_w, sb_pad; - u_int sx, sy; + int old_xoff, old_yoff, changed = 0; + u_int sx, sy, old_sx, old_sy; status = window_get_pane_status(w); scrollbars = options_get_number(w->options, "pane-scrollbars"); @@ -415,6 +416,11 @@ layout_fix_panes(struct window *w, struct window_pane *skip) if ((lc = wp->layout_cell) == NULL || wp == skip) continue; + old_xoff = wp->xoff; + old_yoff = wp->yoff; + old_sx = wp->sx; + old_sy = wp->sy; + wp->xoff = lc->xoff; wp->yoff = lc->yoff; sx = lc->sx; @@ -452,7 +458,15 @@ layout_fix_panes(struct window *w, struct window_pane *skip) } window_pane_resize(wp, sx, sy); + + if (wp->xoff != old_xoff || + wp->yoff != old_yoff || + wp->sx != old_sx || + wp->sy != old_sy) + changed = 1; } + if (changed) + redraw_invalidate_scene(w); } /* Count the number of available cells in a layout. */ @@ -818,10 +832,16 @@ layout_resize_floating_pane_to(struct window_pane *wp, enum layout_type type, return; } - if (type == LAYOUT_TOPBOTTOM) + if (type == LAYOUT_TOPBOTTOM) { + if (lc->sy == size) + return; lc->sy = size; - else + } else { + if (lc->sx == size) + return; lc->sx = size; + } + redraw_invalidate_scene(wp->window); } /* Resize a floating pane relative to its current size. */ @@ -836,6 +856,8 @@ layout_resize_floating_pane(struct window_pane *wp, enum layout_type type, *cause = xstrdup("pane is not floating"); return; } + if (change == 0) + return; if (type == LAYOUT_TOPBOTTOM) { size = lc->sy + change; @@ -856,6 +878,7 @@ layout_resize_floating_pane(struct window_pane *wp, enum layout_type type, if (opposite) lc->xoff -= change; } + redraw_invalidate_scene(wp->window); } /* Resize a layout cell. */ diff --git a/menu.c b/menu.c index b124486b1..17f91b5da 100644 --- a/menu.c +++ b/menu.c @@ -257,8 +257,7 @@ menu_reapply_styles(struct menu_data *md, struct client *c) } void -menu_draw_cb(struct client *c, void *data, - __unused struct screen_redraw_ctx *rctx) +menu_draw_cb(struct client *c, void *data) { struct menu_data *md = data; struct tty *tty = &c->tty; diff --git a/options.c b/options.c index a847ad9c1..a9ef2f945 100644 --- a/options.c +++ b/options.c @@ -1219,6 +1219,15 @@ options_push_changes(const char *name) if (strcmp(name, "status") == 0 || strcmp(name, "status-interval") == 0) status_timer_start_all(); + if (strcmp(name, "status") == 0 || + strcmp(name, "status-position") == 0 || + strcmp(name, "pane-border-indicators") == 0 || + strcmp(name, "pane-border-lines") == 0 || + strcmp(name, "pane-border-status") == 0 || + strcmp(name, "pane-scrollbars") == 0 || + strcmp(name, "pane-scrollbars-position") == 0 || + strcmp(name, "pane-scrollbars-style") == 0) + redraw_invalidate_all_scenes(); if (strcmp(name, "monitor-silence") == 0) alerts_reset_all(); if (strcmp(name, "window-style") == 0 || diff --git a/popup.c b/popup.c index 91e2f68ec..72a8259cb 100644 --- a/popup.c +++ b/popup.c @@ -274,7 +274,7 @@ popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx) } static void -popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) +popup_draw_cb(struct client *c, void *data) { struct popup_data *pd = data; struct tty *tty = &c->tty; @@ -329,7 +329,7 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) if (pd->md != NULL) { c->overlay_check = NULL; c->overlay_data = NULL; - menu_draw_cb(c, pd->md, rctx); + menu_draw_cb(c, pd->md); } c->overlay_check = popup_check_cb; c->overlay_data = pd; diff --git a/screen-redraw.c b/screen-redraw.c index cae6edb4d..24072ea8c 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1,7 +1,7 @@ /* $OpenBSD$ */ /* - * Copyright (c) 2007 Nicholas Marriott + * 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 @@ -18,90 +18,396 @@ #include +#include #include #include #include "tmux.h" -static void screen_redraw_draw_borders(struct screen_redraw_ctx *); -static void screen_redraw_draw_panes(struct screen_redraw_ctx *); -static void screen_redraw_draw_status(struct screen_redraw_ctx *); -static void screen_redraw_draw_pane(struct screen_redraw_ctx *, - struct window_pane *); -static void screen_redraw_set_context(struct client *, - struct screen_redraw_ctx *); -static void screen_redraw_draw_pane_scrollbars(struct screen_redraw_ctx *); -static void screen_redraw_draw_scrollbar(struct screen_redraw_ctx *, - struct window_pane *, int, int, int, u_int, u_int, u_int); -static void screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *, - struct window_pane *); +/* + * Draw the visible area of a window to a client. + * + * A scene is built for the client and cached (in struct client). When the + * offset or size of the visible part of the window changes, the scene is + * invalidated. It is also invalidated when the generation number is increased; + * this is done at various points, such as when a pane is moved or resized. The + * scene only includes the part of the client used for the window: panes, pane + * status lines, borders, scrollbars, and any area outside the window. The + * client status line and overlay are not included. + * + * A scene is made from spans. A span is a horizontal run of cells on one + * visible line that can be drawn in the same way. Each span has a type, for + * example: pane content or pane border cells or pane status line. A span also + * includes enough additional data to draw it, but does not include items such + * as the style and content - those are determined when it is drawn. + * + * Scenes are built in two stages: + * + * 1) redraw_build_cells fills a temporary grid of struct redraw_build_cell + * objects, one per visible cell. It marks pane contents, scrollbars, + * borders, pane status lines and any cells outside the window. Border cells + * may belong to multiple panes, so they may be marked multiple times, with + * each pane adding its own state. + * + * 2) redraw_make_scene takes the grid of struct redraw_build_cell objects and + * converts them into spans by joining adjacent cells that have the same + * type and data. These spans together make up the scene (struct + * redraw_scene). + * + * Once generated, a scene is redrawn by looping over some or all of the spans + * (in redraw_draw), working out the style and content, and writing to the + * client terminal. Until it is invalidated, the scene may be redrawn multiple + * times without being rebuilt. + */ -#define START_ISOLATE "\342\201\246" -#define END_ISOLATE "\342\201\251" - -/* Border in relation to a pane. */ -enum screen_redraw_border_type { - SCREEN_REDRAW_OUTSIDE, - SCREEN_REDRAW_INSIDE, - SCREEN_REDRAW_BORDER_LEFT, - SCREEN_REDRAW_BORDER_RIGHT, - SCREEN_REDRAW_BORDER_TOP, - SCREEN_REDRAW_BORDER_BOTTOM +/* Type of span in the scene. */ +enum redraw_span_type { + REDRAW_SPAN_PANE, /* inside a pane */ + REDRAW_SPAN_OUTSIDE, /* outside the window */ + REDRAW_SPAN_EMPTY, /* inside the window but nothing visible */ + REDRAW_SPAN_STATUS, /* pane status line */ + REDRAW_SPAN_BORDER, /* pane border */ + REDRAW_SPAN_SCROLLBAR, /* pane scrollbar */ }; -#define BORDER_MARKERS " +,.-" +#define REDRAW_SPAN_TYPES 6 -/* Get cell border character. */ -static void -screen_redraw_border_set(struct window *w, struct window_pane *wp, - enum pane_lines pane_lines, int cell_type, struct grid_cell *gc) +/* Border connections to adjacent cells. */ +#define REDRAW_BORDER_L 0x1 +#define REDRAW_BORDER_R 0x2 +#define REDRAW_BORDER_U 0x4 +#define REDRAW_BORDER_D 0x8 + +/* Span flags. */ +#define REDRAW_BORDER_IS_ARROW 0x1 +#define REDRAW_SCROLLBAR_LEFT 0x2 +#define REDRAW_SCROLLBAR_RIGHT 0x4 + +/* Draw operations. */ +#define REDRAW_PANE 0x1 +#define REDRAW_OUTSIDE 0x2 +#define REDRAW_EMPTY 0x4 +#define REDRAW_PANE_BORDER 0x8 +#define REDRAW_PANE_STATUS 0x10 +#define REDRAW_PANE_SCROLLBAR 0x20 +#define REDRAW_STATUS 0x40 +#define REDRAW_OVERLAY 0x80 + +/* Draw everything. */ +#define REDRAW_ALL 0x7fffffff +#define REDRAW_IS_ALL(flags) ((flags) == REDRAW_ALL) + +/* UTF-8 isolate characters. */ +#define REDRAW_START_ISOLATE "\342\201\246" +#define REDRAW_END_ISOLATE "\342\201\251" + +/* Data for a span. */ +struct redraw_span_data { + enum redraw_span_type type; + + union { + struct { + /* Pane this span belongs to. */ + struct window_pane *wp; + + /* Position of span inside the pane. */ + u_int px; + u_int py; + } p; /* pane */ + + struct { + /* Adjacent panes on each side. */ + struct window_pane *top_wp; + struct window_pane *bottom_wp; + struct window_pane *left_wp; + struct window_pane *right_wp; + + /* + * The pane this span belongs if that is known when the + * scene is built. This is used for the half coloured + * active pane indicator. + */ + struct window_pane *style_wp; + + /* Cell type for this span. */ + int cell_type; + int cell_mask; + + /* Line styles for this span. */ + enum pane_lines top_lines; + enum pane_lines bottom_lines; + enum pane_lines left_lines; + enum pane_lines right_lines; + + /* Flags for this span. */ + int flags; + } b; /* pane border */ + + struct { + /* The pane and the offset into the status line. */ + struct window_pane *wp; + u_int offset; + int cell_type; + } st; /* pane status line */ + + struct { + /* Pane this span belongs to. */ + struct window_pane *wp; + + /* Line within the scrollbar. */ + u_int y; + + /* Full height of scrollbar. */ + u_int height; + + /* Flags for this span. */ + int flags; + } sb; /* pane scrollbar */ + }; +}; + +/* A span of cells of the same type inside a line. */ +struct redraw_span { + u_int x; + u_int width; + struct redraw_span_data data; + + TAILQ_ENTRY(redraw_span) entry; +}; +TAILQ_HEAD(redraw_spans, redraw_span); + +/* A visible line on the client. */ +struct redraw_line { + struct redraw_spans spans[REDRAW_SPAN_TYPES]; +}; + +/* A scene representing all the spans on the client. */ +struct redraw_scene { + struct client *c; + struct window *w; + struct redraw_line *lines; + + uint64_t generation; + u_int sx; + u_int sy; + u_int ox; + u_int oy; +}; + +/* Cell for building the scene. */ +struct redraw_build_cell { + struct redraw_span_data data; +}; + +static struct redraw_build_cell *redraw_cells; +static size_t redraw_ncells; + +/* Context for building the scene. */ +struct redraw_build_ctx { + struct client *c; + struct window *w; + + u_int ox; + u_int oy; + u_int sx; + u_int sy; + + int sb; + int sbp; + int ind; + + struct redraw_build_cell *cells; +}; + +/* Context for redrawing. */ +struct redraw_draw_ctx { + struct redraw_scene *scene; + + struct window_pane *active; + struct window_pane *marked; + + u_int status_lines; + enum pane_lines pane_lines; + struct grid_cell default_gc; + + int flags; +#define REDRAW_ISOLATES 0x1 +#define REDRAW_DEFAULT_SET 0x2 +#define REDRAW_STATUS_TOP 0x4 +}; + +/* Make redraw flags into a string. */ +static const char * +redraw_flags_to_string(int flags) { - u_int idx; + static char s[128]; - if (cell_type == CELL_OUTSIDE && w->fill_character != NULL) { - utf8_copy(&gc->data, &w->fill_character[0]); - return; - } - - switch (pane_lines) { - case PANE_LINES_NUMBER: - if (cell_type == CELL_OUTSIDE) { - gc->attr |= GRID_ATTR_CHARSET; - utf8_set(&gc->data, CELL_BORDERS[CELL_OUTSIDE]); - break; - } - gc->attr &= ~GRID_ATTR_CHARSET; - if (wp != NULL && window_pane_index(wp, &idx) == 0) - utf8_set(&gc->data, '0' + (idx % 10)); - else - utf8_set(&gc->data, '*'); - break; - case PANE_LINES_DOUBLE: - gc->attr &= ~GRID_ATTR_CHARSET; - utf8_copy(&gc->data, tty_acs_double_borders(cell_type)); - break; - case PANE_LINES_HEAVY: - gc->attr &= ~GRID_ATTR_CHARSET; - utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type)); - break; - case PANE_LINES_SIMPLE: - 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, ' '); - break; - default: - gc->attr |= GRID_ATTR_CHARSET; - utf8_set(&gc->data, CELL_BORDERS[cell_type]); - break; - } + *s = '\0'; + if (flags & REDRAW_STATUS) + strlcat(s, "status ", sizeof s); + if (flags & REDRAW_PANE) + strlcat(s, "pane ", sizeof s); + if (flags & REDRAW_PANE_BORDER) + strlcat(s, "border ", sizeof s); + if (flags & REDRAW_PANE_STATUS) + strlcat(s, "pane-status ", sizeof s); + if (flags & REDRAW_PANE_SCROLLBAR) + strlcat(s, "scrollbar ", sizeof s); + if (flags & REDRAW_OVERLAY) + strlcat(s, "overlay ", sizeof s); + if (REDRAW_IS_ALL(flags)) + strlcat(s, "all ", sizeof s); + if (*s != '\0') + s[strlen(s) - 1] = '\0'; + return (s); } -/* Return 1 if window has only two panes. */ +/* Get window offset and expand size to cover any part outside the window. */ +static void +redraw_get_window_offset(struct client *c, u_int *ox, u_int *oy, u_int *sx, + u_int *sy) +{ + u_int tty_sx, tty_sy; + + tty_window_offset(&c->tty, ox, oy, sx, sy); + + tty_sx = c->tty.sx; + tty_sy = c->tty.sy - status_line_size(c); + if (*sx < tty_sx) + *sx = tty_sx; + if (*sy < tty_sy) + *sy = tty_sy; +} + +/* Initialize the context for building scene. */ +static void +redraw_set_context(struct client *c, struct redraw_build_ctx *bctx) +{ + struct session *s = c->session; + struct window *w = s->curw->window; + struct options *oo = w->options; + + memset(bctx, 0, sizeof *bctx); + bctx->c = c; + bctx->w = w; + redraw_get_window_offset(c, &bctx->ox, &bctx->oy, &bctx->sx, &bctx->sy); + + bctx->sb = options_get_number(oo, "pane-scrollbars"); + bctx->sbp = options_get_number(oo, "pane-scrollbars-position"); + bctx->ind = options_get_number(oo, "pane-border-indicators"); +} + +/* Return a cell. */ +static struct redraw_build_cell * +redraw_get_build_cell(struct redraw_build_ctx *bctx, u_int x, u_int y) +{ + return (&bctx->cells[(y * bctx->sx) + x]); +} + +/* Reset cell to either empty or outside the window. */ +static void +redraw_reset_cell(struct redraw_build_ctx *bctx, u_int x, u_int y) +{ + struct redraw_build_cell *bc = redraw_get_build_cell(bctx, x, y); + struct window *w = bctx->w; + + memset(bc, 0, sizeof *bc); + if (bctx->ox + x <= w->sx && bctx->oy + y <= w->sy) + bc->data.type = REDRAW_SPAN_EMPTY; + else + bc->data.type = REDRAW_SPAN_OUTSIDE; +} + +/* Convert window position to scene position. Return 0 if outside the scene. */ static int -screen_redraw_two_panes(struct window *w, enum layout_type *type) +redraw_window_to_scene(struct redraw_build_ctx *bctx, int wx, int wy, + u_int *x, u_int *y) +{ + int sx, sy; + + if (wx < 0 || wy < 0) + return (0); + if ((u_int)wx > bctx->w->sx || (u_int)wy > bctx->w->sy) + return (0); + if (wx < (int)bctx->ox || wy < (int)bctx->oy) + return (0); + + sx = wx - (int)bctx->ox; + sy = wy - (int)bctx->oy; + + if ((u_int)sx >= bctx->sx || (u_int)sy >= bctx->sy) + return (0); + *x = sx; + *y = sy; + return (1); +} + +/* + * Convert pane position to scene position. Return 0 if outside the scene. A + * floating pane is clipped to the window edge. + */ +static int +redraw_pane_to_scene(struct redraw_build_ctx *bctx, struct window_pane *wp, + int px, int py, u_int *x, u_int *y) +{ + int wx = wp->xoff + px, wy = wp->yoff + py; + int left, right, top, bottom; + + if (window_pane_is_floating(wp)) { + left = wp->xoff - 1; + right = wp->xoff + wp->sx; + top = wp->yoff - 1; + bottom = wp->yoff + wp->sy; + + if (left < 0 && wx < 0) + return (0); + if (right > (int)bctx->w->sx && wx >= (int)bctx->w->sx) + return (0); + if (top < 0 && wy < 0) + return (0); + if (bottom > (int)bctx->w->sy && wy >= (int)bctx->w->sy) + return (0); + } + return (redraw_window_to_scene(bctx, wx, wy, x, y)); +} + +/* Convert redraw border mask to a border cell type. */ +static int +redraw_get_cell_type(int mask) +{ + switch (mask) { + case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_U|REDRAW_BORDER_D: + return (CELL_LRUD); + case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_U: + return (CELL_LRU); + case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_D: + return (CELL_LRD); + case REDRAW_BORDER_L|REDRAW_BORDER_R: + case REDRAW_BORDER_L: + case REDRAW_BORDER_R: + return (CELL_LR); + case REDRAW_BORDER_L|REDRAW_BORDER_U|REDRAW_BORDER_D: + return (CELL_ULD); + case REDRAW_BORDER_L|REDRAW_BORDER_U: + return (CELL_LU); + case REDRAW_BORDER_L|REDRAW_BORDER_D: + return (CELL_LD); + case REDRAW_BORDER_R|REDRAW_BORDER_U|REDRAW_BORDER_D: + return (CELL_URD); + case REDRAW_BORDER_R|REDRAW_BORDER_U: + return (CELL_RU); + case REDRAW_BORDER_R|REDRAW_BORDER_D: + return (CELL_RD); + case REDRAW_BORDER_U|REDRAW_BORDER_D: + case REDRAW_BORDER_U: + case REDRAW_BORDER_D: + return (CELL_UD); + } + return (CELL_NONE); +} + +/* Return if there are two panes for the border colour indicator. */ +static int +redraw_check_two_pane_colours(struct window *w, enum layout_type *type) { struct window_pane *wp; u_int count = 0; @@ -114,1165 +420,820 @@ screen_redraw_two_panes(struct window *w, enum layout_type *type) return (0); *type = wp->layout_cell->parent->type; } - if (count <= 1) - return (0); - return (1); + return (count == 2); } -/* Check if cell is on the border of a pane. */ -static enum screen_redraw_border_type -screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, - int px, int py) +/* Mark pane inside data. */ +static void +redraw_mark_pane_inside(struct redraw_build_ctx *bctx, 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; - 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, left, right; - enum layout_type split_type; + struct redraw_build_cell *bc; + u_int px, py, x, y; - if (pane_scrollbars != 0) - sb_pos = ctx->pane_scrollbars_pos; + for (py = 0; py < wp->sy; py++) { + for (px = 0; px < wp->sx; px++) { + if (!redraw_pane_to_scene(bctx, wp, px, py, &x, &y)) + continue; + bc = redraw_get_build_cell(bctx, x, y); + memset(bc, 0, sizeof *bc); + bc->data.type = REDRAW_SPAN_PANE; + bc->data.p.wp = wp; + bc->data.p.px = px; + bc->data.p.py = py; + } + } +} + +/* Mark scrollbar data. */ +static void +redraw_mark_pane_scrollbar(struct redraw_build_ctx *bctx, + struct window_pane *wp, int sb_w, int sb_left) +{ + struct redraw_build_cell *bc; + u_int x, y; + int wx, wy, sx, ex; + u_int sy; + + if (sb_w == 0) + return; + + if (sb_left) { + sx = wp->xoff - sb_w; + ex = wp->xoff - 1; + } else { + sx = wp->xoff + (int)wp->sx; + ex = sx + sb_w - 1; + } + + for (sy = 0; sy < wp->sy; sy++) { + wy = wp->yoff + (int)sy; + for (wx = sx; wx <= ex; wx++) { + if (!redraw_window_to_scene(bctx, wx, wy, &x, + &y)) + continue; + bc = redraw_get_build_cell(bctx, x, y); + memset(bc, 0, sizeof *bc); + bc->data.type = REDRAW_SPAN_SCROLLBAR; + bc->data.sb.wp = wp; + bc->data.sb.y = sy; + bc->data.sb.height = wp->sy; + if (sb_left) + bc->data.sb.flags |= REDRAW_SCROLLBAR_LEFT; + else + bc->data.sb.flags |= REDRAW_SCROLLBAR_RIGHT; + } + } +} + +/* + * Return if span data belongs to pane, that is: is the cell adjacent to this + * pane? + */ +static int +redraw_data_has_pane(struct redraw_span_data *data, struct window_pane *wp) +{ + if (data->b.top_wp == wp) + return (1); + if (data->b.bottom_wp == wp) + return (1); + if (data->b.left_wp == wp) + return (1); + if (data->b.right_wp == wp) + return (1); + return (0); +} + +/* + * Mark one border cell. If a non-border cell is marked as a border, replace + * it. If it is already a border and this is not a floating pane, merge the + * border mask and pane ownership. + */ +static void +redraw_mark_border_cell(struct redraw_build_ctx *bctx, int wx, int wy, + struct window_pane *wp, int top_owner, int bottom_owner, int mask, + enum pane_lines pane_lines, int floating) +{ + struct redraw_build_cell *bc; + u_int x, y; + int reset = 0; + + if (!redraw_window_to_scene(bctx, wx, wy, &x, &y)) + return; + bc = redraw_get_build_cell(bctx, x, y); + + /* + * If this is a tiled pane, only empty and border cells may be marked. + * Border cells are merged and empty cells are updated. + * + * Floating panes may mark any existing cell type. All cells are reset + * except borders that already belong to this pane, they need to be + * merged. + */ + if (!floating) { + if (bc->data.type == REDRAW_SPAN_EMPTY) + reset = 1; + else if (bc->data.type != REDRAW_SPAN_BORDER) + return; + } else { + if (bc->data.type != REDRAW_SPAN_BORDER || + !redraw_data_has_pane(&bc->data, wp)) + reset = 1; + } + if (reset) { + memset(bc, 0, sizeof *bc); + bc->data.type = REDRAW_SPAN_BORDER; + } + + if (top_owner) { + bc->data.b.top_wp = wp; + bc->data.b.top_lines = pane_lines; + } + if (bottom_owner) { + bc->data.b.bottom_wp = wp; + bc->data.b.bottom_lines = pane_lines; + } + + if (mask & (REDRAW_BORDER_U|REDRAW_BORDER_D)) { + if (wx < wp->xoff) { + bc->data.b.right_wp = wp; + bc->data.b.right_lines = pane_lines; + } else if (wx >= wp->xoff + (int)wp->sx) { + bc->data.b.left_wp = wp; + bc->data.b.left_lines = pane_lines; + } + } + + mask |= bc->data.b.cell_mask; + bc->data.b.cell_mask = mask; + bc->data.b.cell_type = redraw_get_cell_type(mask); +} + +/* + * Mark border cells for a pane status line, keeping the border cell type for + * drawing. + */ +static void +redraw_mark_border_status(struct redraw_build_ctx *bctx, struct window_pane *wp, + __unused int left, int right, int top, int bottom) +{ + struct redraw_build_cell *bc; + u_int x, y, off = 0; + int pane_status, wy, sx, ex, wx, cell_type; + + pane_status = window_pane_get_pane_status(wp); + if (pane_status == PANE_STATUS_OFF) + return; + if (pane_status == PANE_STATUS_TOP) + wy = top; else - sb_pos = 0; + wy = bottom; - /* Inside pane. */ - if (px >= wp->xoff && px < ex && py >= wp->yoff && py < ey) - return (SCREEN_REDRAW_INSIDE); + sx = wp->xoff + 2; + ex = right - 1; + if (sx > ex) + return; - if (window_pane_is_floating(wp) && - window_pane_get_pane_lines(wp) == PANE_LINES_NONE) - return (SCREEN_REDRAW_OUTSIDE); + for (wx = sx; wx <= ex; wx++, off++) { + if (!redraw_window_to_scene(bctx, wx, wy, &x, &y)) + continue; + bc = redraw_get_build_cell(bctx, x, y); + if (bc->data.type != REDRAW_SPAN_BORDER) + continue; + cell_type = bc->data.b.cell_type; - /* Are scrollbars enabled? */ - if (window_pane_show_scrollbar(wp, pane_scrollbars)) - sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; + bc->data.type = REDRAW_SPAN_STATUS; + bc->data.st.wp = wp; + bc->data.st.offset = off; + bc->data.st.cell_type = cell_type; + } +} - /* Floating pane borders. */ - if (window_pane_is_floating(wp)) { - left = wp->xoff - 1; - right = wp->xoff + sx; - if (sb_pos == PANE_SCROLLBARS_LEFT) +/* Mark existing border cells where indicator arrows will be drawn. */ +static void +redraw_mark_border_arrows(struct redraw_build_ctx *bctx, struct window_pane *wp, + int left, int right, int top, int bottom) +{ + struct redraw_build_cell *bc; + u_int x, y; + int wx, wy; + + if (bctx->ind != PANE_BORDER_ARROWS && bctx->ind != PANE_BORDER_BOTH) + return; + + wx = wp->xoff + 1; + if (wx >= left && wx <= right) { + wy = top; + if (redraw_window_to_scene(bctx, wx, wy, &x, &y)) { + bc = redraw_get_build_cell(bctx, x, y); + if (bc->data.type == REDRAW_SPAN_BORDER) + bc->data.b.flags |= REDRAW_BORDER_IS_ARROW; + } + wy = bottom; + if (redraw_window_to_scene(bctx, wx, wy, &x, &y)) { + bc = redraw_get_build_cell(bctx, x, y); + if (bc->data.type == REDRAW_SPAN_BORDER) + bc->data.b.flags |= REDRAW_BORDER_IS_ARROW; + } + } + + wy = wp->yoff + 1; + if (wy >= top && wy <= bottom) { + wx = left; + if (redraw_window_to_scene(bctx, wx, wy, &x, &y)) { + bc = redraw_get_build_cell(bctx, x, y); + if (bc->data.type == REDRAW_SPAN_BORDER) + bc->data.b.flags |= REDRAW_BORDER_IS_ARROW; + } + wx = right; + if (redraw_window_to_scene(bctx, wx, wy, &x, &y)) { + bc = redraw_get_build_cell(bctx, x, y); + if (bc->data.type == REDRAW_SPAN_BORDER) + bc->data.b.flags |= REDRAW_BORDER_IS_ARROW; + } + } +} + +/* Mark pane borders. */ +static void +redraw_mark_pane_borders(struct redraw_build_ctx *bctx, struct window_pane *wp, + int sb_w, int sb_left) +{ + enum pane_lines pane_lines = window_pane_get_pane_lines(wp); + int pane_status, left, right, top, bottom, wx, wy; + int mark_top, mark_bottom, mark_left, mark_right, mask = 0; + int floating = window_pane_is_floating(wp); + + if (floating && pane_lines == PANE_LINES_NONE) + return; + pane_status = window_pane_get_pane_status(wp); + + left = wp->xoff - 1; + right = wp->xoff + wp->sx; + if (sb_w != 0) { + if (sb_left) left -= sb_w; else right += sb_w; - if (py >= wp->yoff - 1 && py <= wp->yoff + sy) { - if (px == left) - return (SCREEN_REDRAW_BORDER_LEFT); - if (px == right) - return (SCREEN_REDRAW_BORDER_RIGHT); - } - if (px > left && px <= right) { - if (py == wp->yoff - 1) - return (SCREEN_REDRAW_BORDER_TOP); - if (py == wp->yoff + sy) - return (SCREEN_REDRAW_BORDER_BOTTOM); - } - return (SCREEN_REDRAW_OUTSIDE); } + top = wp->yoff - 1; + bottom = wp->yoff + wp->sy; - /* Get pane indicator. */ - switch (options_get_number(oo, "pane-border-indicators")) { - case PANE_BORDER_COLOUR: - case PANE_BORDER_BOTH: - if (screen_redraw_two_panes(wp->window, &split_type)) { - hsplit = (split_type == LAYOUT_LEFTRIGHT); - vsplit = (split_type == LAYOUT_TOPBOTTOM); - } - break; - } + mark_left = (left >= 0); + mark_right = (right <= (int)bctx->w->sx); + mark_top = (top >= 0); + mark_bottom = (bottom <= (int)bctx->w->sy); - /* - * Left/right borders. The 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 (sb_pos == PANE_SCROLLBARS_LEFT) { - if (wp->xoff - sb_w == 0 && px == sx + sb_w) { - if (!hsplit || (hsplit && py <= sy / 2)) - return (SCREEN_REDRAW_BORDER_RIGHT); - } - if (wp->xoff - sb_w != 0) { - if (px == wp->xoff - sb_w - 1 && - (!hsplit || (hsplit && py > sy / 2))) - return (SCREEN_REDRAW_BORDER_LEFT); - if (px == wp->xoff + sx + sb_w - 1) - return (SCREEN_REDRAW_BORDER_RIGHT); - } - } else { /* sb_pos == PANE_SCROLLBARS_RIGHT or disabled */ - if (wp->xoff == 0 && px == sx + sb_w) { - if (!hsplit || (hsplit && py <= sy / 2)) - return (SCREEN_REDRAW_BORDER_RIGHT); - } - if (wp->xoff != 0) { - if (px == wp->xoff - 1 && - (!hsplit || (hsplit && py > sy / 2))) - return (SCREEN_REDRAW_BORDER_LEFT); - if (px == wp->xoff + sx + sb_w) - return (SCREEN_REDRAW_BORDER_RIGHT); - } - } - } - - /* Top/bottom borders. */ - if (vsplit && pane_status == PANE_STATUS_OFF) { - if (wp->yoff == 0 && py == sy && px <= sx / 2) - return (SCREEN_REDRAW_BORDER_BOTTOM); - if (wp->yoff != 0 && py == wp->yoff - 1 && px > sx / 2) - return (SCREEN_REDRAW_BORDER_TOP); + if (floating) { + if (left < 0) + left = 0; + if (right > (int)bctx->w->sx) + right = (int)bctx->w->sx - 1; + if (top < 0) + top = 0; + if (bottom > (int)bctx->w->sy) + bottom = (int)bctx->w->sy - 1; } else { - if (sb_pos == PANE_SCROLLBARS_LEFT) { - if ((wp->xoff - sb_w == 0 || px >= wp->xoff - sb_w) && - (px <= ex || (sb_w != 0 && px < ex + sb_w))) { - if (pane_status != PANE_STATUS_BOTTOM && - wp->yoff != 0 && py == wp->yoff - 1) - return (SCREEN_REDRAW_BORDER_TOP); - if (pane_status != PANE_STATUS_TOP && py == ey) - return (SCREEN_REDRAW_BORDER_BOTTOM); - } - } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ - if ((wp->xoff == 0 || px >= wp->xoff) && - (px <= ex || (sb_w != 0 && px < ex + sb_w))) { - if (pane_status != PANE_STATUS_BOTTOM && - wp->yoff != 0 && - py == wp->yoff - 1) - return (SCREEN_REDRAW_BORDER_TOP); - if (pane_status != PANE_STATUS_TOP && py == ey) - return (SCREEN_REDRAW_BORDER_BOTTOM); - } - } - } - - /* Outside pane. */ - return (SCREEN_REDRAW_OUTSIDE); -} - -/* Check a single cell position. */ -static int -screen_redraw_cell_border1(struct screen_redraw_ctx *ctx, int sb_pos, int sb_w, - struct window_pane *wp, int px, int py) -{ - if (sb_pos == PANE_SCROLLBARS_LEFT) { - if ((px < wp->xoff - 1 - sb_w || - px > wp->xoff + (int)wp->sx) && - (py < wp->yoff - 1 || - py > wp->yoff + (int)wp->sy)) - return (-1); - } else { /* PANE_SCROLLBARS_RIGHT or off. */ - if ((px < wp->xoff - 1 || - px > wp->xoff + (int)wp->sx + sb_w) && - (py < wp->yoff - 1 || - py > wp->yoff + (int)wp->sy)) - return (-1); - } - switch (screen_redraw_pane_border(ctx, wp, px, py)) { - case SCREEN_REDRAW_INSIDE: - return (0); - case SCREEN_REDRAW_OUTSIDE: - return (-1); - default: - return (1); - } -} - -/* Check if a cell is on a border. */ -static int -screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, - int px, int py) -{ - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - struct window_pane *wp2; - int sx = w->sx, sy = w->sy, sb_w, sb_pos, n; - - 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; - - /* For floating panes, only check the pane itself. */ - if (window_pane_is_floating(wp)) { - n = screen_redraw_cell_border1(ctx, sb_pos, sb_w, wp, px, py); - if (n == -1) - return (0); - return (n); - } - - /* Outside the window or on the window border? */ - if (window_pane_get_pane_status(wp) == PANE_STATUS_BOTTOM) - sy--; - if (px > sx || py > sy) - return (0); - if (px == sx || py == sy) - return (1); - - /* - * If checking a cell from a tiled pane, ignore floating panes because - * two side-by-side or top-bottom panes share a border which is used to - * do split colouring. Essentially, treat all tiled panes as being in a - * single z-index. - */ - TAILQ_FOREACH(wp2, &w->z_index, zentry) { - if (!window_pane_is_visible(wp2) || window_pane_is_floating(wp2)) - continue; - n = screen_redraw_cell_border1(ctx, sb_pos, sb_w, wp2, px, py); - if (n != -1) - return (n); - } - - return (0); -} - -/* Work out type of border cell from surrounding cells. */ -static int -screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx, - struct window_pane *wp, int px, int py) -{ - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - int pane_status = window_pane_get_pane_status(wp); - int borders = 0; - int sx = w->sx, sy = w->sy; - - /* Is this outside the window? */ - if (pane_status == PANE_STATUS_BOTTOM) - sy--; - if (px > sx || py > sy) - return (CELL_OUTSIDE); - - /* - * Construct a bitmask of whether the cells to the left (bit 8), right, - * top, and bottom (bit 1) of this cell are borders. - * - * bits 8 4 2 1: 2 - * 8 + 4 - * 1 - */ - if (!window_pane_is_floating(wp)) { - 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 (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; - } - } - - /* - * Figure out what kind of border this cell is. Only one bit set - * doesn't make sense (can't have a border cell with no others - * connected). - */ - switch (borders) { - case 15: /* 1111, left right top bottom */ - return (CELL_JOIN); - case 14: /* 1110, left right top */ - return (CELL_BOTTOMJOIN); - case 13: /* 1101, left right bottom */ - return (CELL_TOPJOIN); - case 12: /* 1100, left right */ - return (CELL_LEFTRIGHT); - case 11: /* 1011, left top bottom */ - return (CELL_RIGHTJOIN); - case 10: /* 1010, left top */ - return (CELL_BOTTOMRIGHT); - case 9: /* 1001, left bottom */ - return (CELL_TOPRIGHT); - case 7: /* 0111, right top bottom */ - return (CELL_LEFTJOIN); - case 6: /* 0110, right top */ - return (CELL_BOTTOMLEFT); - case 5: /* 0101, right bottom */ - return (CELL_TOPLEFT); - case 3: /* 0011, top bottom */ - return (CELL_TOPBOTTOM); - } - return (CELL_OUTSIDE); -} - -/* Check if cell inside a pane. */ -static int -screen_redraw_check_cell(struct screen_redraw_ctx *ctx, int px, int py, - struct window_pane **wpp) -{ - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - struct window_pane *wp, *start; - int sx = w->sx, sy = w->sy; - 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; - - *wpp = NULL; - - /* Outside the pane. */ - if (px > sx || py > sy) - return (CELL_OUTSIDE); - - /* Find pane highest in z-index at this point. */ - TAILQ_FOREACH(wp, &w->z_index, zentry) { - if (window_pane_is_floating(wp) && (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) { - if ((px >= wp->xoff - 1 - sb_w && - px <= wp->xoff + (int)wp->sx) && - (py >= wp->yoff - 1 && - py <= wp->yoff + (int)wp->sy)) - break; - } else { /* PANE_SCROLLBARS_RIGHT or none. */ - if ((px >= wp->xoff - 1 && - px <= wp->xoff + (int)wp->sx + sb_w) && - (py >= wp->yoff - 1 && - py <= wp->yoff + (int)wp->sy)) - break; - } - } - if (wp != NULL) - start = wp; - else - start = wp = server_client_get_pane(c); - if (wp == NULL) - return (CELL_OUTSIDE); - - /* On the window border. */ - if (px == sx || py == sy) - return (screen_redraw_type_of_cell(ctx, wp, px, py)); - - /* - * If this is a tiled pane, then only check other tiled panes. This is - * necessary if there are two side-by-side or top-bottom panes with a - * shared border and half the shared border is the active border. - */ - if (!window_pane_is_floating(wp)) - tiled_only = 1; - do { /* loop until back to wp == start */ - if (!window_pane_is_visible(wp)) - goto next; - if (tiled_only && window_pane_is_floating(wp)) - goto next; - *wpp = wp; - - sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - if (sb_pos == PANE_SCROLLBARS_LEFT) { - if ((px < wp->xoff - 1 - sb_w || - px > wp->xoff + (int)wp->sx) && - (py < wp->yoff - 1 || - py > wp->yoff + (int)wp->sy)) - goto next; - } else { /* PANE_SCROLLBARS_RIGHT or none */ - if ((px < wp->xoff - 1 || - px > wp->xoff + (int)wp->sx + sb_w) && - (py < wp->yoff - 1 || - py > wp->yoff + (int)wp->sy)) - goto next; - } - - /* - * 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; - else - pane_status_line = wp->yoff + (int)wp->sy; - left = wp->xoff + 2; - right = wp->xoff + 2 + (int)wp->status_size - 1; - if (py == pane_status_line + (int)ctx->oy && - px >= left && - 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; - if ((wp->yoff == 0 && py < (int)wp->sy) || - (py >= wp->yoff && - py < wp->yoff + (int)wp->sy)) { - /* Check if px lies within a scrollbar. */ - if ((sb_pos == PANE_SCROLLBARS_RIGHT && - (px >= wp->xoff + (int)wp->sx && - px < wp->xoff + (int)wp->sx + sb_w)) || - (sb_pos == PANE_SCROLLBARS_LEFT && - (px >= wp->xoff - sb_w && - px < wp->xoff))) - return (CELL_SCROLLBAR); - } - } - - /* - * If definitely inside, return. If not on border, skip. - * Otherwise work out the cell. - */ - border = screen_redraw_pane_border(ctx, wp, px, py); - if (border == SCREEN_REDRAW_INSIDE) - return (CELL_INSIDE); - if (border == SCREEN_REDRAW_OUTSIDE) - goto next; - return (screen_redraw_type_of_cell(ctx, wp, px, py)); - - next: - wp = TAILQ_NEXT(wp, zentry); - if (wp == NULL) - wp = TAILQ_FIRST(&w->z_index); - } while (wp != start); - - return (CELL_OUTSIDE); -} - -/* Check if the border of a particular pane. */ -static int -screen_redraw_check_is(struct screen_redraw_ctx *ctx, int px, int py, - struct window_pane *wp) -{ - 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); - return (0); -} - -/* Update pane status. */ -static int -screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, - struct screen_redraw_ctx *rctx, enum pane_lines pane_lines) -{ - struct window *w = wp->window; - struct grid_cell gc; - const char *fmt, *border_option; - struct format_tree *ft; - struct style_line_entry *sle = &wp->border_status_line; - char *expanded; - 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; - - 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)) - border_option = "pane-active-border-style"; - else - border_option = "pane-border-style"; - style_apply(&gc, wp->options, border_option, ft); - fmt = options_get_string(wp->options, "pane-border-format"); - - expanded = format_expand_time(ft, fmt); - if (wp->sx < 4) - width = 0; - else - width = wp->sx + sb_w - 2; - max_width = (int)w->sx - (wp->xoff + 2); - 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); - wp->status_screen.mode = 0; - - screen_write_start(&ctx, &wp->status_screen); - - for (i = 0; i < width; i++) { - px = wp->xoff + 2 + i; if (pane_status == PANE_STATUS_TOP) - py = wp->yoff - 1; - else - py = wp->yoff + wp->sy; - 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); + mark_bottom = 0; + else if (pane_status == PANE_STATUS_BOTTOM) + mark_top = 0; } - gc.attr &= ~GRID_ATTR_CHARSET; - screen_write_cursormove(&ctx, 0, 0, 0); - style_ranges_free(&sle->ranges); - format_draw(&ctx, &gc, width, expanded, &sle->ranges, 0); - - screen_write_stop(&ctx); - format_free(ft); - - free(sle->expanded); - sle->expanded = expanded; - - if (grid_compare(wp->status_screen.grid, old.grid) == 0) { - screen_free(&old); - return (0); - } - screen_free(&old); - return (1); -} - -/* Draw pane status. */ -static void -screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) -{ - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - struct tty *tty = &c->tty; - struct window_pane *wp; - struct screen *s; - struct visible_ranges *r; - struct visible_range *ri; - u_int i, l, x, width, size; - int xoff, yoff, pane_status; - - log_debug("%s: %s @%u", __func__, c->name, w->id); - - TAILQ_FOREACH(wp, &w->panes, entry) { - if (!window_pane_is_visible(wp)) - continue; - s = &wp->status_screen; - - size = wp->status_size; - 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; - xoff = wp->xoff + 2; - - if (xoff + (int)size <= ctx->ox || - xoff >= ctx->ox + (int)ctx->sx || - yoff < ctx->oy || - yoff >= ctx->oy + (int)ctx->sy) - continue; - - if (xoff >= ctx->ox && xoff + size <= ctx->ox + ctx->sx) { - /* All visible. */ - l = 0; - x = xoff - ctx->ox; - width = size; - } else if (xoff < ctx->ox && xoff + size > ctx->ox + ctx->sx) { - /* Both left and right not visible. */ - l = ctx->ox - xoff; - x = 0; - width = ctx->sx; - } else if (xoff < ctx->ox) { - /* Left not visible. */ - l = ctx->ox - xoff; - x = 0; - width = size - l; - } else { - /* Right not visible. */ - l = 0; - x = xoff - ctx->ox; - width = ctx->sx - x; - } - - r = tty_check_overlay_range(tty, x, yoff, width); - r = window_visible_ranges(wp, x, yoff, width, r); - if (ctx->statustop) - yoff += ctx->statuslines; - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - if (ri->nx == 0) - continue; - tty_draw_line(tty, s, l + (ri->px - x), 0, ri->nx, - ri->px, yoff - ctx->oy, NULL); + if (mark_top) { + for (wx = left; wx <= right; wx++) { + mask = 0; + if (wx > left) + mask |= REDRAW_BORDER_L; + if (wx < right) + mask |= REDRAW_BORDER_R; + redraw_mark_border_cell(bctx, wx, top, wp, 0, 1, mask, + pane_lines, floating); } } - tty_cursor(tty, 0, 0); -} - -/* Update status line and change flags if unchanged. */ -static uint64_t -screen_redraw_update(struct screen_redraw_ctx *ctx, uint64_t flags) -{ - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - struct window_pane *wp; - int redraw; - enum pane_lines lines; - - if (c->message_string != NULL) - redraw = status_message_redraw(c); - else if (c->prompt_string != NULL) - redraw = status_prompt_redraw(c); - else - redraw = status_redraw(c); - if (!redraw && (~flags & CLIENT_REDRAWSTATUSALWAYS)) - flags &= ~CLIENT_REDRAWSTATUS; - - if (c->overlay_draw != NULL) - flags |= CLIENT_REDRAWOVERLAY; - - 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 (mark_bottom) { + for (wx = left; wx <= right; wx++) { + mask = 0; + if (wx > left) + mask |= REDRAW_BORDER_L; + if (wx < right) + mask |= REDRAW_BORDER_R; + redraw_mark_border_cell(bctx, wx, bottom, wp, 1, 0, + mask, pane_lines, floating); + } + } + if (mark_left) { + for (wy = top; wy <= bottom; wy++) { + mask = 0; + if (wy > top) + mask |= REDRAW_BORDER_U; + if (wy < bottom) + mask |= REDRAW_BORDER_D; + redraw_mark_border_cell(bctx, left, wy, wp, 0, 0, mask, + pane_lines, floating); + } + } + if (mark_right) { + for (wy = top; wy <= bottom; wy++) { + mask = 0; + if (wy > top) + mask |= REDRAW_BORDER_U; + if (wy < bottom) + mask |= REDRAW_BORDER_D; + redraw_mark_border_cell(bctx, right, wy, wp, 0, 0, mask, + pane_lines, floating); + } } - if (redraw) - flags |= CLIENT_REDRAWBORDERS; - return (flags); + redraw_mark_border_status(bctx, wp, left, right, top, bottom); + redraw_mark_border_arrows(bctx, wp, left, right, top, bottom); } -/* Set up redraw context. */ +/* + * Mark an entire pane in the build grid. Floating panes overwrite anything + * already below them. + */ static void -screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx) +redraw_mark_pane(struct redraw_build_ctx *bctx, struct window_pane *wp) { - struct session *s = c->session; - struct options *oo = s->options; - struct window *w = s->curw->window; - u_int lines; - - memset(ctx, 0, sizeof *ctx); - ctx->c = c; - - lines = status_line_size(c); - if (c->message_string != NULL || c->prompt_string != NULL) - lines = (lines == 0) ? 1 : lines; - if (lines != 0 && options_get_number(oo, "status-position") == 0) - ctx->statustop = 1; - ctx->statuslines = lines; - - ctx->pane_lines = options_get_number(w->options, "pane-border-lines"); - - ctx->pane_scrollbars = options_get_number(w->options, - "pane-scrollbars"); - ctx->pane_scrollbars_pos = options_get_number(w->options, - "pane-scrollbars-position"); - - tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, &ctx->sx, &ctx->sy); - - log_debug("%s: %s @%u ox=%u oy=%u sx=%u sy=%u %u/%d", __func__, c->name, - w->id, ctx->ox, ctx->oy, ctx->sx, ctx->sy, ctx->statuslines, - ctx->statustop); -} - -/* Redraw entire screen. */ -void -screen_redraw_screen(struct client *c) -{ - struct screen_redraw_ctx ctx; - uint64_t flags; - - if (c->flags & CLIENT_SUSPENDED) - return; - - screen_redraw_set_context(c, &ctx); - - flags = screen_redraw_update(&ctx, c->flags); - if ((flags & CLIENT_ALLREDRAWFLAGS) == 0) - return; - - tty_sync_start(&c->tty); - tty_update_mode(&c->tty, c->tty.mode, NULL); - - if (flags & (CLIENT_REDRAWWINDOW|CLIENT_REDRAWBORDERS)) { - log_debug("%s: redrawing borders", c->name); - screen_redraw_draw_borders(&ctx); - screen_redraw_draw_pane_status(&ctx); - screen_redraw_draw_pane_scrollbars(&ctx); - } - if (flags & CLIENT_REDRAWWINDOW) { - log_debug("%s: redrawing panes", c->name); - screen_redraw_draw_panes(&ctx); - screen_redraw_draw_pane_scrollbars(&ctx); - } - if (ctx.statuslines != 0 && - (flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))) { - log_debug("%s: redrawing status", c->name); - screen_redraw_draw_status(&ctx); - } - if (c->overlay_draw != NULL && (flags & CLIENT_REDRAWOVERLAY)) { - log_debug("%s: redrawing overlay", c->name); - c->overlay_draw(c, c->overlay_data, &ctx); - } - - tty_reset(&c->tty); -} - -/* Redraw a single pane and its scrollbar. */ -void -screen_redraw_pane(struct client *c, struct window_pane *wp, - int redraw_scrollbar_only) -{ - struct screen_redraw_ctx ctx; + int sb_w = 0, sb_left = 0; if (!window_pane_is_visible(wp)) return; - screen_redraw_set_context(c, &ctx); - tty_sync_start(&c->tty); - tty_update_mode(&c->tty, c->tty.mode, NULL); + if (window_pane_show_scrollbar(wp, bctx->sb)) + sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; + if (sb_w != 0 && bctx->sbp == PANE_SCROLLBARS_LEFT) + sb_left = 1; - if (!redraw_scrollbar_only) - screen_redraw_draw_pane(&ctx, wp); - - if (window_pane_show_scrollbar(wp, ctx.pane_scrollbars)) - screen_redraw_draw_pane_scrollbar(&ctx, wp); - - tty_reset(&c->tty); + redraw_mark_pane_inside(bctx, wp); + redraw_mark_pane_borders(bctx, wp, sb_w, sb_left); + redraw_mark_pane_scrollbar(bctx, wp, sb_w, sb_left); } -/* Get border cell style. */ +/* Choose the pane that will provide the border style for two-pane layouts. */ static void -screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x, - u_int y, struct window_pane *wp, struct grid_cell *ngc) +redraw_mark_two_pane_colours(struct redraw_build_ctx *bctx) { - struct client *c = ctx->c; - struct session *s = c->session; - struct window_pane *active = server_client_get_pane(c); - struct grid_cell *gc; - const char *border_option; - struct format_tree *ft; - int *flag; + struct redraw_build_cell *bc; + struct redraw_span_data *sd; + enum layout_type type; + u_int x, y, wx, wy; - if ((window_pane_is_floating(wp) && wp == active) || - (!window_pane_is_floating(wp) && - screen_redraw_check_is(ctx, x, y, active))) { - flag = &wp->active_border_gc_set; - gc = &wp->active_border_gc; - border_option = "pane-active-border-style"; - } else { - flag = &wp->border_gc_set; - gc = &wp->border_gc; - border_option = "pane-border-style"; - } - - if (!*flag) { - ft = format_create_defaults(NULL, c, s, s->curw, wp); - style_apply(gc, wp->options, border_option, ft); - format_free(ft); - *flag = 1; - } - memcpy(ngc, gc, sizeof *ngc); -} - -/* Draw arrow indicator if enabled. */ -static void -screen_redraw_draw_border_arrows(struct screen_redraw_ctx *ctx, int i, - int j, u_int cell_type, struct window_pane *wp, - struct window_pane *active, struct grid_cell *gc) -{ - struct client *c = ctx->c; - struct session *s = c->session; - struct window *w = s->curw->window; - struct options *oo = w->options; - u_int x = ctx->ox + i, y = ctx->oy + j; - int value, arrows = 0, border; - enum layout_type type; - - if (wp == NULL) + if (bctx->ind != PANE_BORDER_COLOUR && bctx->ind != PANE_BORDER_BOTH) return; - if (i != wp->xoff + 1 && j != wp->yoff + 1) + if (!redraw_check_two_pane_colours(bctx->w, &type)) return; - if (wp != active) { - if (window_pane_is_floating(active)) - return; - if (window_pane_is_floating(wp)) - return; - } + for (y = 0; y < bctx->sy; y++) { + for (x = 0; x < bctx->sx; x++) { + bc = redraw_get_build_cell(bctx, x, y); + if (bc->data.type != REDRAW_SPAN_BORDER) + continue; + sd = &bc->data; - value = options_get_number(oo, "pane-border-indicators"); - if (value != PANE_BORDER_ARROWS && value != PANE_BORDER_BOTH) - return; + wx = bctx->ox + x; + wy = bctx->oy + y; - border = screen_redraw_pane_border(ctx, active, x, y); - if (border == SCREEN_REDRAW_INSIDE) - return; - - if (i == wp->xoff + 1) { - if (border == SCREEN_REDRAW_OUTSIDE) { - if (screen_redraw_two_panes(wp->window, &type)) { - if (active == TAILQ_FIRST(&w->panes)) - border = SCREEN_REDRAW_BORDER_BOTTOM; + if (type == LAYOUT_LEFTRIGHT && + sd->b.left_wp != NULL && + sd->b.right_wp != NULL) { + if (wy <= bctx->w->sy / 2) + sd->b.style_wp = sd->b.left_wp; else - border = SCREEN_REDRAW_BORDER_TOP; - arrows = 1; - } - } else { - if (cell_type == CELL_LEFTRIGHT) - arrows = 1; - else if (cell_type == CELL_TOPJOIN && - border == SCREEN_REDRAW_BORDER_BOTTOM) - arrows = 1; - else if (cell_type == CELL_BOTTOMJOIN && - border == SCREEN_REDRAW_BORDER_TOP) - arrows = 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)) - border = SCREEN_REDRAW_BORDER_RIGHT; + sd->b.style_wp = sd->b.right_wp; + } else if (type == LAYOUT_TOPBOTTOM && + sd->b.top_wp != NULL && + sd->b.bottom_wp != NULL) { + if (wx <= bctx->w->sx / 2) + sd->b.style_wp = sd->b.top_wp; else - border = SCREEN_REDRAW_BORDER_LEFT; - arrows = 1; + sd->b.style_wp = sd->b.bottom_wp; } - } else { - if (cell_type == CELL_TOPBOTTOM) - arrows = 1; - else if (cell_type == CELL_LEFTJOIN && - border == SCREEN_REDRAW_BORDER_RIGHT) - arrows = 1; - else if (cell_type == CELL_RIGHTJOIN && - border == SCREEN_REDRAW_BORDER_LEFT) - arrows = 1; } } - if (arrows) { - gc->attr |= GRID_ATTR_CHARSET; - utf8_set(&gc->data, BORDER_MARKERS[border]); - } } -/* Draw a border cell. */ -static void -screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) +/* Return true if two adjacent build cells can be joined into one span. */ +static int +redraw_compare_data(struct redraw_build_cell *a, struct redraw_build_cell *b) { - 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; - enum pane_lines pane_lines; - u_int cell_type; - u_int x = ctx->ox + i, y = ctx->oy + j; - int isolates; - struct visible_ranges *r; + struct redraw_span_data *ad = &a->data, *bd = &b->data; - if (c->overlay_check != NULL) { - r = c->overlay_check(c, c->overlay_data, x, y, 1); - if (server_client_ranges_is_empty(r)) - return; + if (ad->type != bd->type) + return (0); + + switch (ad->type) { + case REDRAW_SPAN_PANE: + if (ad->p.wp != bd->p.wp || + ad->p.py != bd->p.py || + ad->p.px + 1 != bd->p.px) + return (0); + return (1); + case REDRAW_SPAN_BORDER: + if (ad->b.top_wp != bd->b.top_wp || + ad->b.bottom_wp != bd->b.bottom_wp || + ad->b.left_wp != bd->b.left_wp || + ad->b.right_wp != bd->b.right_wp || + ad->b.style_wp != bd->b.style_wp || + ad->b.top_lines != bd->b.top_lines || + ad->b.bottom_lines != bd->b.bottom_lines || + ad->b.left_lines != bd->b.left_lines || + ad->b.right_lines != bd->b.right_lines || + ad->b.cell_type != bd->b.cell_type || + ad->b.cell_mask != bd->b.cell_mask || + ad->b.flags != bd->b.flags) + return (0); + if (ad->b.flags & REDRAW_BORDER_IS_ARROW) + return (0); + return (1); + case REDRAW_SPAN_STATUS: + if (ad->st.wp != bd->st.wp || + ad->st.offset + 1 != bd->st.offset || + ad->st.cell_type != bd->st.cell_type) + return (0); + return (1); + case REDRAW_SPAN_SCROLLBAR: + if (ad->sb.wp != bd->sb.wp || + ad->sb.y != bd->sb.y || + ad->sb.height != bd->sb.height || + ad->sb.flags != bd->sb.flags) + return (0); + return (1); + case REDRAW_SPAN_OUTSIDE: + case REDRAW_SPAN_EMPTY: + return (1); + } + return (0); +} + +/* Build the temporary cells for a redraw scene. */ +static void +redraw_build_cells(struct redraw_build_ctx *bctx) +{ + struct window *w = bctx->w; + struct window_pane *wp; + size_t ncells; + u_int x, y; + + if (bctx->sx != 0 && bctx->sy > SIZE_MAX / bctx->sx) + fatalx("%s: too many cells", __func__); + ncells = (size_t)bctx->sx * bctx->sy; + if (ncells > redraw_ncells) { + redraw_cells = xreallocarray(redraw_cells, ncells, + sizeof *redraw_cells); + redraw_ncells = ncells; } - cell_type = screen_redraw_check_cell(ctx, x, y, &wp); - if (cell_type == CELL_INSIDE || cell_type == CELL_SCROLLBAR) + bctx->cells = redraw_cells; + for (y = 0; y < bctx->sy; y++) { + for (x = 0; x < bctx->sx; x++) + redraw_reset_cell(bctx, x, y); + } + + TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) + redraw_mark_pane(bctx, wp); + redraw_mark_two_pane_colours(bctx); +} + +/* + * Build and return a redraw scene for a client. The caller owns the scene and + * must free it with redraw_free_scene. + */ +static struct redraw_scene * +redraw_make_scene(struct client *c) +{ + struct session *s = c->session; + struct window *w = s->curw->window; + struct redraw_build_ctx bctx; + struct redraw_scene *scene; + struct redraw_build_cell *bc, *last; + struct redraw_line *line; + struct redraw_span *span; + enum redraw_span_type type; + u_int x, y, x0; + + if (c->flags & CLIENT_SUSPENDED) + return (NULL); + + redraw_set_context(c, &bctx); + + log_debug("%s: building @%u scene (%ux%u %u,%u; generation %llu)", + c->name, w->id, bctx.sx, bctx.sy, bctx.ox, bctx.oy, + (unsigned long long)w->redraw_scene_generation); + + redraw_build_cells(&bctx); + + scene = xcalloc(1, sizeof *scene); + scene->c = c; + scene->w = w; + scene->lines = xcalloc(bctx.sy, sizeof *scene->lines); + scene->generation = w->redraw_scene_generation; + scene->sx = bctx.sx; + scene->sy = bctx.sy; + scene->ox = bctx.ox; + scene->oy = bctx.oy; + + for (y = 0; y < bctx.sy; y++) { + line = &scene->lines[y]; + for (type = 0; type < REDRAW_SPAN_TYPES; type++) + TAILQ_INIT(&line->spans[type]); + + x = 0; + while (x < bctx.sx) { + x0 = x; + last = redraw_get_build_cell(&bctx, x, y); + x++; + + while (x < bctx.sx) { + bc = redraw_get_build_cell(&bctx, x, y); + if (!redraw_compare_data(last, bc)) + break; + last = bc; + x++; + } + bc = redraw_get_build_cell(&bctx, x0, y); + type = bc->data.type; + + span = xcalloc(1, sizeof *span); + span->x = x0; + span->width = x - x0; + span->data = bc->data; + + TAILQ_INSERT_TAIL(&line->spans[type], span, entry); + } + } + + log_debug("%s: finished building @%u scene", c->name, w->id); + return (scene); +} + +/* Free a scene. */ +void +redraw_free_scene(struct redraw_scene *scene) +{ + struct redraw_spans *spans; + struct redraw_span *span, *span1; + u_int y, type; + + if (scene == NULL) return; - 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); - style_add(&ctx->no_pane_gc, oo, "pane-border-style", - ft); - format_free(ft); - ctx->no_pane_gc_set = 1; + for (y = 0; y < scene->sy; y++) { + for (type = 0; type < REDRAW_SPAN_TYPES; type++) { + spans = &scene->lines[y].spans[type]; + TAILQ_FOREACH_SAFE(span, spans, entry, span1) { + TAILQ_REMOVE(spans, span, entry); + free(span); + } } - memcpy(&gc, &ctx->no_pane_gc, sizeof gc); - } else { - screen_redraw_draw_borders_style(ctx, x, y, wp, &gc); - if (server_is_marked(s, s->curw, marked_pane.wp) && - screen_redraw_check_is(ctx, x, y, marked_pane.wp)) - gc.attr ^= GRID_ATTR_REVERSE; } - 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) && - tty_term_has(tty->term, TTYC_BIDI)) - isolates = 1; - else - isolates = 0; - - if (ctx->statustop) - tty_cursor(tty, i, ctx->statuslines + j); - else - tty_cursor(tty, i, j); - if (isolates) - tty_puts(tty, END_ISOLATE); - - screen_redraw_draw_border_arrows(ctx, i, j, cell_type, wp, active, &gc); - - tty_cell(tty, &gc, NULL); - if (isolates) - tty_puts(tty, START_ISOLATE); + free(scene->lines); + free(scene); } -/* Draw the borders. */ -static void -screen_redraw_draw_borders(struct screen_redraw_ctx *ctx) +/* Mark a window's cached redraw scenes as out of date. */ +void +redraw_invalidate_scene(struct window *w) { - struct client *c = ctx->c; - struct session *s = c->session; - struct window *w = s->curw->window; - struct window_pane *wp; - u_int i, j; - - log_debug("%s: %s @%u", __func__, c->name, w->id); - - TAILQ_FOREACH(wp, &w->panes, entry) { - wp->border_gc_set = 0; - wp->active_border_gc_set = 0; - } - - for (j = 0; j < c->tty.sy - ctx->statuslines; j++) { - for (i = 0; i < c->tty.sx; i++) - screen_redraw_draw_borders_cell(ctx, i, j); - } + w->redraw_scene_generation++; } -/* Draw the panes. */ -static void -screen_redraw_draw_panes(struct screen_redraw_ctx *ctx) +/* Mark all cached redraw scenes as out of date. */ +void +redraw_invalidate_all_scenes(void) { - struct client *c = ctx->c; + struct window *w; + + RB_FOREACH(w, windows, &windows) + redraw_invalidate_scene(w); +} + +/* Get the cached redraw scene, rebuilding it if needed. */ +static struct redraw_scene * +redraw_get_scene(struct client *c) +{ + struct redraw_scene *scene = c->redraw_scene; struct window *w = c->session->curw->window; - struct window_pane *wp; + const char *reason = NULL; + u_int ox, oy, sx, sy; - log_debug("%s: %s @%u", __func__, c->name, w->id); - - TAILQ_FOREACH(wp, &w->panes, entry) { - if (window_pane_is_visible(wp)) - screen_redraw_draw_pane(ctx, wp); + redraw_get_window_offset(c, &ox, &oy, &sx, &sy); + if (scene == NULL) + reason = "missing"; + else if (scene->w != w) + reason = "window changed"; + else if (scene->generation != w->redraw_scene_generation) + reason = "generation changed"; + else if (scene->ox != ox || scene->oy != oy) + reason = "offset changed"; + else if (scene->sx != sx || scene->sy != sy) + reason = "size changed"; + if (reason != NULL) { + log_debug("%s: @%u scene invalid: %s", c->name, w->id, reason); + redraw_free_scene(scene); + scene = redraw_make_scene(c); + c->redraw_scene = scene; } + return (scene); } -/* Draw the status line. */ +/* Draw a pane span. */ static void -screen_redraw_draw_status(struct screen_redraw_ctx *ctx) +redraw_draw_pane_span(struct redraw_draw_ctx *dctx, + struct redraw_span *span, u_int x, u_int y, u_int n) { - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - struct tty *tty = &c->tty; - struct screen *s = c->status.active; - u_int i, y; - - log_debug("%s: %s @%u", __func__, c->name, w->id); - - if (ctx->statustop) - y = 0; - else - 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, NULL); -} - - -/* Draw one pane. */ -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 redraw_scene *scene = dctx->scene; + struct client *c = scene->c; struct tty *tty = &c->tty; + struct window_pane *wp = span->data.p.wp; struct screen *s = wp->screen; - struct colour_palette *palette = &wp->palette; struct grid_cell defaults; struct tty_style_ctx style_ctx; - u_int j, k, woy, wx, wy, py, width; - struct visible_ranges *r; - struct visible_range *ri; + u_int px, py; - /* - * 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 - */ + tty_default_colours(&defaults, wp, &style_ctx.dim); + style_ctx.defaults = &defaults; + style_ctx.palette = &wp->palette; + style_ctx.hyperlinks = s->hyperlinks; - if (wp->base.mode & MODE_SYNC) - screen_write_stop_sync(wp); + px = span->data.p.px + (x - span->x); + py = span->data.p.py; + tty_draw_line(tty, s, px, py, n, x, y, &style_ctx); +} - log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); +/* Get default border style for spans without a pane. */ +static void +redraw_get_default_border_style(struct redraw_draw_ctx *dctx, + struct grid_cell *gc, enum pane_lines *pane_lines) +{ + struct redraw_scene *scene = dctx->scene; + struct client *c = scene->c; + struct session *s = c->session; + struct options *oo = scene->w->options; + struct format_tree *ft; + struct grid_cell *dgc = &dctx->default_gc; - /* Check if pane completely not visible. */ - if (wp->xoff + (int)wp->sx <= ctx->ox || - wp->xoff >= (int)ctx->ox + (int)ctx->sx) + if (~dctx->flags & REDRAW_DEFAULT_SET) { + ft = format_create_defaults(NULL, c, s, s->curw, NULL); + memcpy(dgc, &grid_default_cell, sizeof *dgc); + style_add(dgc, oo, "pane-border-style", ft); + format_free(ft); + dctx->pane_lines = options_get_number(oo, "pane-border-lines"); + dctx->flags |= REDRAW_DEFAULT_SET; + } + memcpy(gc, dgc, sizeof *gc); + *pane_lines = dctx->pane_lines; +} + +/* + * For this border span, pick the pane whose border style should colour it. + * Prefer an explicitly assigned style owner, then the active adjacent pane, + * then any adjacent pane. + */ +static struct window_pane * +redraw_get_pane_for_border_style(struct redraw_draw_ctx *dctx, + struct redraw_span *span) +{ + struct window_pane *active = dctx->active; + + if (span->data.type != REDRAW_SPAN_BORDER) + return (NULL); + + if (span->data.b.style_wp != NULL) + return (span->data.b.style_wp); + if (active != NULL && redraw_data_has_pane(&span->data, active)) + return (active); + + if (span->data.b.top_wp != NULL) + return (span->data.b.top_wp); + if (span->data.b.bottom_wp != NULL) + return (span->data.b.bottom_wp); + if (span->data.b.left_wp != NULL) + return (span->data.b.left_wp); + if (span->data.b.right_wp != NULL) + return (span->data.b.right_wp); + return (NULL); +} + +/* Draw arrow indicator if this border span is an arrow cell. */ +static void +redraw_draw_border_arrow(struct redraw_draw_ctx *dctx, + struct redraw_span *span, struct grid_cell *gc) +{ + struct window_pane *active = dctx->active; + char ch; + + if (span->data.type != REDRAW_SPAN_BORDER || active == NULL) + return; + if (~span->data.b.flags & REDRAW_BORDER_IS_ARROW) return; - if (ctx->statustop) - woy = ctx->statuslines; + if (span->data.b.left_wp == active) + ch = ','; + else if (span->data.b.right_wp == active) + ch = '+'; + else if (span->data.b.top_wp == active) + ch = '-'; + else if (span->data.b.bottom_wp == active) + ch = '.'; else - woy = 0; - for (j = 0; j < wp->sy; j++) { - 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 */ - if (py > tty->sy) { - /* Continue if this line is off of tty. */ - continue; - } - if (wp->xoff >= (int)ctx->ox && - wp->xoff + (int)wp->sx <= (int)ctx->ox + (int)ctx->sx) { - /* All visible. */ - 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. */ - wx = 0; - width = ctx->sx; - } else if (wp->xoff < (int)ctx->ox) { - /* Left not visible. */ - wx = 0; - width = wp->sx - ((u_int)((int)ctx->ox - wp->xoff)); - } else { - /* Right not visible. */ - wx = (u_int)(wp->xoff - (int)ctx->ox); - width = ctx->sx - wx; - } + return; - /* Set up the default style. */ - tty_default_colours(&defaults, wp, &style_ctx.dim); - style_ctx.defaults = &defaults; - style_ctx.palette = palette; - style_ctx.hyperlinks = s->hyperlinks; - - /* Get visible ranges of line before we draw it. */ - r = tty_check_overlay_range(tty, wx, wy, width); - r = window_visible_ranges(wp, wx, wy, width, r); - for (k = 0; k < r->used; k++) { - ri = &r->ranges[k]; - if (ri->nx == 0) - continue; - 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, ri->px, py, &style_ctx); - } - } + utf8_set(&gc->data, ch); + gc->attr |= GRID_ATTR_CHARSET; } -/* Draw the panes scrollbars */ +/* Draw a border span. */ static void -screen_redraw_draw_pane_scrollbars(struct screen_redraw_ctx *ctx) +redraw_draw_border_span(struct redraw_draw_ctx *dctx, + struct redraw_span *span, u_int x, u_int y, u_int n) { - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - struct window_pane *wp; + struct redraw_scene *scene = dctx->scene; + struct client *c = scene->c; + struct tty *tty = &c->tty; + struct window *w = scene->w; + struct window_pane *wp = NULL; + struct grid_cell gc; + enum pane_lines pane_lines; + u_int i, cell_type; + int isolates = 0; - log_debug("%s: %s @%u", __func__, c->name, w->id); + if (span->data.type != REDRAW_SPAN_BORDER) + cell_type = CELL_NONE; + else { + wp = redraw_get_pane_for_border_style(dctx, span); + cell_type = span->data.b.cell_type; + } - TAILQ_FOREACH(wp, &w->panes, entry) { - if (window_pane_show_scrollbar(wp, ctx->pane_scrollbars) && - window_pane_is_visible(wp)) - screen_redraw_draw_pane_scrollbar(ctx, wp); + if (wp == NULL) { + redraw_get_default_border_style(dctx, &gc, &pane_lines); + window_get_border_cell(w, NULL, pane_lines, cell_type, &gc); + } else { + window_pane_get_border_style(wp, c, &gc); + window_pane_get_border_cell(wp, cell_type, &gc); + } + + if (span->data.type == REDRAW_SPAN_BORDER && + dctx->marked != NULL && + redraw_data_has_pane(&span->data, dctx->marked)) + gc.attr ^= GRID_ATTR_REVERSE; + redraw_draw_border_arrow(dctx, span, &gc); + + if (cell_type == CELL_UD && (dctx->flags & REDRAW_ISOLATES)) + isolates = 1; + tty_cursor(tty, x, y); + if (isolates) + tty_puts(tty, REDRAW_END_ISOLATE); + for (i = 0; i < n; i++) + tty_cell(tty, &gc, NULL); + if (isolates) + tty_puts(tty, REDRAW_START_ISOLATE); +} + +/* Draw a pane status span. */ +static void +redraw_draw_status_span(struct redraw_draw_ctx *dctx, + struct redraw_span *span, u_int x, u_int y, u_int n) +{ + struct redraw_scene *scene = dctx->scene; + struct client *c = scene->c; + struct tty *tty = &c->tty; + struct window_pane *wp = span->data.st.wp; + struct screen *s = &wp->status_screen; + u_int px, sx = screen_size_x(s); + + px = span->data.st.offset + (x - span->x); + if (px < sx) { + if (n > sx - px) + n = sx - px; + tty_draw_line(tty, s, px, 0, n, x, y, NULL); } } -/* Calculate position and size of pane scrollbar. */ -void -screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx, - struct window_pane *wp) +/* Draw a scrollbar span. */ +static void +redraw_draw_scrollbar_span(struct redraw_draw_ctx *dctx, + struct redraw_span *span, u_int x, u_int y, u_int n) { - struct screen *s = wp->screen; - double percent_view; - u_int sb = ctx->pane_scrollbars, total_height, sb_h = wp->sy; - 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; - int sb_x, sb_y = (int)wp->yoff; /* sb top */ + struct redraw_scene *scene = dctx->scene; + struct window_pane *wp = span->data.sb.wp; + struct screen *s = wp->screen; + struct tty *tty = &scene->c->tty; + struct style *sb_style = &wp->scrollbar_style; + struct grid_cell gc, slgc, *gcp; + double pct_view; + u_int total_height, slider_h, slider_y; + u_int sb_h = span->data.sb.height; + u_int sb_y = span->data.sb.y; + u_int i, off, sb_w, sb_pad; + int cm_y, cm_size; if (window_pane_mode(wp) == WINDOW_PANE_NO_MODE) { - if (sb == PANE_SCROLLBARS_MODAL) - return; - /* Show slider at the bottom of the scrollbar. */ total_height = screen_size_y(s) + screen_hsize(s); - percent_view = (double)sb_h / total_height; - slider_h = (double)sb_h * percent_view; + if (total_height == 0) + return; + pct_view = (double)sb_h / total_height; + slider_h = (double)sb_h * pct_view; slider_y = sb_h - slider_h; } else { if (TAILQ_FIRST(&wp->modes) == NULL) @@ -1280,138 +1241,451 @@ screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx, if (window_copy_get_current_offset(wp, &cm_y, &cm_size) == 0) return; 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)); + if (total_height == 0) + return; + pct_view = (double)sb_h / total_height; + slider_h = (double)sb_h * pct_view; + slider_y = (sb_h + 1) * ((double)cm_y / total_height); } - if (sb_pos == PANE_SCROLLBARS_LEFT) - sb_x = xoff - sb_w - sb_pad; - else - sb_x = xoff + wp->sx; - if (slider_h < 1) slider_h = 1; if (slider_y >= sb_h) slider_y = sb_h - 1; - screen_redraw_draw_scrollbar(ctx, wp, sb_pos, sb_x, sb_y, sb_h, - slider_h, slider_y); - - /* Store current position and height of the slider */ - wp->sb_slider_y = slider_y; /* top of slider y pos in scrollbar */ - 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, - u_int slider_h, u_int slider_y) -{ - struct client *c = ctx->c; - struct tty *tty = &c->tty; - struct grid_cell gc, slgc, *gcp; - 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, sb_tty_y; - int xoff = wp->xoff; - int yoff = wp->yoff; - int sb_wy = sb_y; /* window coordinates */ - struct visible_ranges *r; - - /* - * Size and offset of window relative to tty. Status at top offsets - * window downward. - */ - sx = ctx->sx; - sy = tty->sy - ctx->statuslines; - ox = ctx->ox; - oy = ctx->oy; - if (ctx->statustop) { - sb_y += ctx->statuslines; /* tty coordinates */ - sy += ctx->statuslines; - } + wp->sb_slider_y = slider_y; + wp->sb_slider_h = slider_h; gc = sb_style->gc; memcpy(&slgc, &gc, sizeof slgc); slgc.fg = gc.bg; slgc.bg = gc.fg; - if (sb_x + (int)sb_w < 0 || sb_x >= sx || sb_y >= sy) { - /* Whole scrollbar is off screen. */ - return; - } - if (sb_x < 0) { - /* Part of scrollbar on screen. */ - imin = - sb_x; - } - imax = sb_w + sb_pad; - if ((int)imax + sb_x > sx) { - if (sb_x > sx) { - /* Whole scrollbar off screen. */ - return; - } - imax = sx - sb_x; - } - jmax = sb_h; - if ((int)jmax + sb_y > sy) { - if (sb_y >= sy) - return; - jmax = sy - sb_y; - } + sb_w = sb_style->width; + sb_pad = sb_style->pad; + off = x - span->x; - /* - * 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) { - /* Whole scrollbar is off screen. */ - return; - } - 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 scrollbar above visible area. */ - return; - } - jmax = sb_h; - if (sb_tty_y + (int)jmax > (int)sy) { - /* Clip to height of tty. */ - jmax = sy - sb_tty_y; - } - - for (j = jmin; j < jmax; j++) { - 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 = window_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 */ - if (wx < xoff - (int)sb_w - (int)sb_pad || - px >= sx || px < 0 || - wy < yoff - 1 || - py >= sy || py < 0 || - !window_position_is_visible(r, wx)) - continue; - tty_cursor(tty, px, py); - if ((sb_pos == PANE_SCROLLBARS_LEFT && - i >= sb_w && i < sb_w + sb_pad) || - (sb_pos == PANE_SCROLLBARS_RIGHT && - i < sb_pad)) + tty_cursor(tty, x, y); + for (i = 0; i < n; i++) { + if (span->data.sb.flags & REDRAW_SCROLLBAR_LEFT) { + if (off + i >= sb_w && off + i < sb_w + sb_pad) { tty_cell(tty, &grid_default_cell, NULL); - else { - if (j >= slider_y && j < slider_y + slider_h) - gcp = &slgc; - else - gcp = &gc; - tty_cell(tty, gcp, NULL); + continue; + } + } else { + if (off + i < sb_pad) { + tty_cell(tty, &grid_default_cell, NULL); + continue; + } + } + + if (sb_y >= slider_y && sb_y < slider_y + slider_h) + gcp = &slgc; + else + gcp = &gc; + tty_cell(tty, gcp, NULL); + } +} + +/* Draw a span. */ +static void +redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span, + u_int y) +{ + struct redraw_scene *scene = dctx->scene; + struct redraw_span_data *data = &span->data; + enum redraw_span_type type = data->type; + struct client *c = scene->c; + struct tty *tty = &c->tty; + struct visible_ranges *r; + struct visible_range *rr; + u_int i, x, n; + + if (type == REDRAW_SPAN_STATUS && ~data->st.wp->flags & PANE_NEWSTATUS) + return; + + r = tty_check_overlay_range(tty, span->x, y, span->width); + for (i = 0; i < r->used; i++) { + rr = &r->ranges[i]; + if (rr->nx == 0) + continue; + x = rr->px; + n = rr->nx; + + switch (span->data.type) { + case REDRAW_SPAN_PANE: + redraw_draw_pane_span(dctx, span, x, y, n); + break; + case REDRAW_SPAN_BORDER: + case REDRAW_SPAN_EMPTY: + case REDRAW_SPAN_OUTSIDE: + redraw_draw_border_span(dctx, span, x, y, n); + break; + case REDRAW_SPAN_STATUS: + redraw_draw_status_span(dctx, span, x, y, n); + break; + case REDRAW_SPAN_SCROLLBAR: + redraw_draw_scrollbar_span(dctx, span, x, y, n); + break; + } + } +} + +/* Draw pane lines. */ +static void +redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp, + int flags) +{ + struct redraw_scene *scene = dctx->scene; + struct redraw_line *line; + struct redraw_spans *spans; + struct redraw_span *span; + u_int cy; + int y, top, bottom; + + top = wp->yoff - (int)scene->oy; + if (top < 0) + top = 0; + bottom = wp->yoff + (int)wp->sy - (int)scene->oy; + if (bottom < 0) + bottom = 0; + if (bottom > (int)scene->sy) + bottom = scene->sy; + + for (y = top; y < bottom; y++) { + line = &scene->lines[y]; + if (dctx->flags & REDRAW_STATUS_TOP) + cy = dctx->status_lines + y; + else + cy = y; + if (flags & REDRAW_PANE) { + spans = &line->spans[REDRAW_SPAN_PANE]; + TAILQ_FOREACH(span, spans, entry) { + if (span->data.p.wp == wp) + redraw_draw_span(dctx, span, cy); + } + } + if (flags & REDRAW_PANE_SCROLLBAR) { + spans = &line->spans[REDRAW_SPAN_SCROLLBAR]; + TAILQ_FOREACH(span, spans, entry) { + if (span->data.sb.wp == wp) + redraw_draw_span(dctx, span, cy); } } } } + +/* Draw lines. */ +static void +redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags) +{ + struct redraw_scene *scene = dctx->scene; + struct redraw_line *line; + struct redraw_spans *spans; + struct redraw_span *span; + u_int y, cy, type; + + for (y = 0; y < scene->sy; y++) { + line = &scene->lines[y]; + if (dctx->flags & REDRAW_STATUS_TOP) + cy = dctx->status_lines + y; + else + cy = y; + for (type = 0; type < REDRAW_SPAN_TYPES; type++) { + if (!REDRAW_IS_ALL(flags)) { + switch (type) { + case REDRAW_SPAN_PANE: + if (~flags & REDRAW_PANE) + continue; + break; + case REDRAW_SPAN_OUTSIDE: + if (~flags & REDRAW_OUTSIDE) + continue; + break; + case REDRAW_SPAN_EMPTY: + if (~flags & REDRAW_EMPTY) + continue; + break; + case REDRAW_SPAN_BORDER: + if (~flags & REDRAW_PANE_BORDER) + continue; + break; + case REDRAW_SPAN_STATUS: + if (~flags & REDRAW_PANE_STATUS) + continue; + break; + case REDRAW_SPAN_SCROLLBAR: + if (~flags & REDRAW_PANE_SCROLLBAR) + continue; + break; + default: + continue; + } + } + spans = &line->spans[type]; + TAILQ_FOREACH(span, spans, entry) + redraw_draw_span(dctx, span, cy); + } + } +} + +/* Get line for pane status line. */ +static int +redraw_pane_status_line(struct redraw_draw_ctx *dctx, + struct window_pane *wp, u_int *line) +{ + struct redraw_scene *scene = dctx->scene; + int pane_status, wy; + + pane_status = window_pane_get_pane_status(wp); + if (pane_status == PANE_STATUS_OFF) + return (0); + + if (pane_status == PANE_STATUS_TOP) + wy = (int)wp->yoff - 1; + else + wy = (int)wp->yoff + wp->sy; + if (wy < 0 || wy < (int)scene->oy) + return (0); + if ((u_int)wy >= scene->oy + scene->sy) + return (0); + *line = wy - scene->oy; + return (1); +} + +/* Get available width for pane status line. */ +static u_int +redraw_pane_status_width(struct redraw_draw_ctx *dctx, + struct window_pane *wp, struct redraw_span **first) +{ + struct redraw_scene *scene = dctx->scene; + struct redraw_span *span; + u_int y, width = 0, end; + + if (!redraw_pane_status_line(dctx, wp, &y)) + return (0); + + *first = NULL; + TAILQ_FOREACH(span, &scene->lines[y].spans[REDRAW_SPAN_STATUS], entry) { + if (span->data.st.wp == wp) { + if (*first == NULL) + *first = span; + end = span->data.st.offset + span->width; + if (end > width) + width = end; + } + } + return (width); +} + +/* Set up draw context. */ +static void +redraw_set_draw_context(struct redraw_draw_ctx *dctx, + struct redraw_scene *scene) +{ + struct client *c = scene->c; + struct session *s = c->session; + struct options *oo = s->options; + struct tty *tty = &c->tty; + u_int lines; + + memset(dctx, 0, sizeof *dctx); + dctx->scene = scene; + + if (server_is_marked(s, s->curw, marked_pane.wp)) + dctx->marked = marked_pane.wp; + dctx->active = server_client_get_pane(c); + + lines = status_line_size(c); + if (options_get_number(oo, "status-position") == 0) + dctx->flags |= REDRAW_STATUS_TOP; + dctx->status_lines = lines; + + if ((c->flags & CLIENT_UTF8) && tty_term_has(tty->term, TTYC_BIDI)) + dctx->flags |= REDRAW_ISOLATES; +} + +/* Draw scene to client. */ +static void +redraw_draw(struct client *c, struct window_pane *wp, int flags) +{ + struct redraw_draw_ctx dctx; + struct session *s = c->session; + struct window *w = s->curw->window; + struct tty *tty = &c->tty; + struct screen *sl; + struct redraw_scene *scene; + struct window_pane *loop; + u_int width, i, y, lines; + struct redraw_span *first; + int redraw; + + if (c->flags & CLIENT_SUSPENDED) + return; + + if (flags & REDRAW_STATUS) { + if (c->message_string != NULL) + redraw = status_message_redraw(c); + else if (c->prompt_string != NULL) + redraw = status_prompt_redraw(c); + else + redraw = status_redraw(c); + if (!redraw && !REDRAW_IS_ALL(flags)) { + flags &= ~REDRAW_STATUS; + if (flags == 0) + return; + } + } + + if (log_get_level() != 0) { + log_debug("%s: starting @%u redraw (%s)", c->name, w->id, + redraw_flags_to_string(flags)); + } + + scene = redraw_get_scene(c); + if (scene == NULL) + return; + redraw_set_draw_context(&dctx, scene); + + if (flags & (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS)) { + TAILQ_FOREACH(loop, &scene->w->panes, entry) { + loop->border_gc_set = 0; + loop->active_border_gc_set = 0; + } + } + + if (flags & REDRAW_PANE_STATUS) { + redraw = 0; + TAILQ_FOREACH(loop, &scene->w->panes, entry) { + if (REDRAW_IS_ALL(flags)) + loop->flags |= PANE_NEWSTATUS; + else + loop->flags &= ~PANE_NEWSTATUS; + + width = redraw_pane_status_width(&dctx, loop, + &first); + if (width == 0) + continue; + + if (window_make_pane_status(loop, c, width, first)) { + loop->flags |= PANE_NEWSTATUS; + redraw = 1; + } + } + if (!redraw && !REDRAW_IS_ALL(flags)) { + flags &= ~REDRAW_PANE_STATUS; + if (flags == 0) + return; + } + } + + if (flags & REDRAW_PANE) { + if (wp != NULL) { + if (wp->base.mode & MODE_SYNC) + screen_write_stop_sync(wp); + } else { + TAILQ_FOREACH(loop, &scene->w->panes, entry) { + if (!window_pane_is_visible(loop)) + continue; + if (loop->base.mode & MODE_SYNC) + screen_write_stop_sync(loop); + } + } + } + tty_sync_start(tty); + tty_update_mode(tty, 0, NULL); + + if (wp != NULL) + redraw_draw_pane_lines(&dctx, wp, flags); + else + redraw_draw_lines(&dctx, flags); + + if (flags & REDRAW_STATUS) { + lines = dctx.status_lines; + if (c->message_string != NULL || c->prompt_string != NULL) + lines = (lines == 0 ? 1 : lines); + if (dctx.flags & REDRAW_STATUS_TOP) + y = 0; + else + y = c->tty.sy - lines; + sl = c->status.active; + for (i = 0; i < lines; i++) + tty_draw_line(tty, sl, 0, i, UINT_MAX, 0, y + i, NULL); + } + if (c->overlay_draw != NULL && (flags & REDRAW_OVERLAY)) + c->overlay_draw(c, c->overlay_data); + + tty_reset(tty); + tty_sync_end(tty); + + log_debug("%s: finished @%u redraw", c->name, scene->w->id); + +} + +/* Get border cell type beneath status cell at offset x in pane status line. */ +int +redraw_get_status_border_cell_type(struct redraw_span **spanp, u_int x) +{ + struct redraw_span *span = *spanp; + struct window_pane *wp; + u_int start, end; + + if (span == NULL || span->data.type != REDRAW_SPAN_STATUS) + return (CELL_LR); + wp = span->data.st.wp; + for (; span != NULL; span = TAILQ_NEXT(span, entry)) { + if (span->data.type != REDRAW_SPAN_STATUS) + continue; + if (span->data.st.wp != wp) + continue; + + start = span->data.st.offset; + end = start + span->width; + if (x >= start && x < end) { + *spanp = span; + return (span->data.st.cell_type); + } + + if (start > x) { + *spanp = span; + break; + } + } + if (span == NULL) + *spanp = NULL; + return (CELL_LR); +} + +/* Draw screen. */ +void +redraw_screen(struct client *c) +{ + int flags = 0; + + if (c->flags & CLIENT_REDRAWWINDOW) + redraw_draw(c, NULL, REDRAW_ALL); + else { + if (c->flags & CLIENT_REDRAWBORDERS) + flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS); + if (c->flags & CLIENT_REDRAWSTATUS) + flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS); + if (c->flags & CLIENT_REDRAWOVERLAY) + flags |= REDRAW_OVERLAY; + if (flags != 0) + redraw_draw(c, NULL, flags); + } +} + +/* Draw a single pane. */ +void +redraw_pane(struct client *c, struct window_pane *wp) +{ + redraw_draw(c, wp, REDRAW_PANE|REDRAW_PANE_SCROLLBAR); +} + +/* Draw a pane's scrollbar. */ +void +redraw_pane_scrollbar(struct client *c, struct window_pane *wp) +{ + redraw_draw(c, wp, REDRAW_PANE_SCROLLBAR); +} diff --git a/screen-write.c b/screen-write.c index f239be9a7..f00619f3f 100644 --- a/screen-write.c +++ b/screen-write.c @@ -729,19 +729,19 @@ screen_write_hline(struct screen_write_ctx *ctx, u_int nx, int left, int right, gc.attr |= GRID_ATTR_CHARSET; if (left) - screen_write_box_border_set(lines, CELL_LEFTJOIN, &gc); + screen_write_box_border_set(lines, CELL_URD, &gc); else - screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); + screen_write_box_border_set(lines, CELL_LR, &gc); screen_write_cell(ctx, &gc); - screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); + screen_write_box_border_set(lines, CELL_LR, &gc); for (i = 1; i < nx - 1; i++) screen_write_cell(ctx, &gc); if (right) - screen_write_box_border_set(lines, CELL_RIGHTJOIN, &gc); + screen_write_box_border_set(lines, CELL_ULD, &gc); else - screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); + screen_write_box_border_set(lines, CELL_LR, &gc); screen_write_cell(ctx, &gc); screen_write_set_cursor(ctx, cx, cy); @@ -844,26 +844,26 @@ screen_write_box(struct screen_write_ctx *ctx, u_int nx, u_int ny, gc.flags |= GRID_FLAG_NOPALETTE; /* Draw top border */ - screen_write_box_border_set(lines, CELL_TOPLEFT, &gc); + screen_write_box_border_set(lines, CELL_RD, &gc); screen_write_cell(ctx, &gc); - screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); + screen_write_box_border_set(lines, CELL_LR, &gc); for (i = 1; i < nx - 1; i++) screen_write_cell(ctx, &gc); - screen_write_box_border_set(lines, CELL_TOPRIGHT, &gc); + screen_write_box_border_set(lines, CELL_LD, &gc); screen_write_cell(ctx, &gc); /* Draw bottom border */ screen_write_set_cursor(ctx, cx, cy + ny - 1); - screen_write_box_border_set(lines, CELL_BOTTOMLEFT, &gc); + screen_write_box_border_set(lines, CELL_RU, &gc); screen_write_cell(ctx, &gc); - screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); + screen_write_box_border_set(lines, CELL_LR, &gc); for (i = 1; i < nx - 1; i++) screen_write_cell(ctx, &gc); - screen_write_box_border_set(lines, CELL_BOTTOMRIGHT, &gc); + screen_write_box_border_set(lines, CELL_LU, &gc); screen_write_cell(ctx, &gc); /* Draw sides */ - screen_write_box_border_set(lines, CELL_TOPBOTTOM, &gc); + screen_write_box_border_set(lines, CELL_UD, &gc); for (i = 1; i < ny - 1; i++) { /* left side */ screen_write_set_cursor(ctx, cx, cy + i); diff --git a/server-client.c b/server-client.c index a37733583..ecd94e3db 100644 --- a/server-client.c +++ b/server-client.c @@ -534,6 +534,7 @@ server_client_free(__unused int fd, __unused short events, void *arg) log_debug("free client %p (%d references)", c, c->references); + redraw_free_scene(c->redraw_scene); cmdq_free(c->queue); if (c->references == 0) { @@ -2105,11 +2106,11 @@ server_client_check_redraw(struct client *c) if (wp->flags & PANE_REDRAW) { log_debug("%s: redraw pane %%%u", __func__, wp->id); - screen_redraw_pane(c, wp, 0); + redraw_pane(c, wp); } else if (wp->flags & PANE_REDRAWSCROLLBAR) { log_debug("%s: redraw scrollbar %%%u", __func__, wp->id); - screen_redraw_pane(c, wp, 1); + redraw_pane_scrollbar(c, wp); } } } @@ -2124,7 +2125,7 @@ server_client_check_redraw(struct client *c) server_client_set_path(c); } server_client_set_progress_bar(c); - screen_redraw_screen(c); + redraw_screen(c); } /* Put the tty back how it was. */ diff --git a/tmux.h b/tmux.h index efa4dd30f..a453aef76 100644 --- a/tmux.h +++ b/tmux.h @@ -62,6 +62,8 @@ struct mouse_event; struct options; struct options_array_item; struct options_entry; +struct redraw_scene; +struct redraw_span; struct screen_write_citem; struct screen_write_cline; struct screen_write_ctx; @@ -787,20 +789,23 @@ struct colour_palette { #define GRID_STRING_USED_ONLY 0x8 #define GRID_STRING_EMPTY_CELLS 0x10 -/* Cell positions. */ +/* + * Cell border characters. Border cells are named for the directions they + * connect to: U for up, D for down, L for left and R for right. + */ #define CELL_INSIDE 0 -#define CELL_TOPBOTTOM 1 -#define CELL_LEFTRIGHT 2 -#define CELL_TOPLEFT 3 -#define CELL_TOPRIGHT 4 -#define CELL_BOTTOMLEFT 5 -#define CELL_BOTTOMRIGHT 6 -#define CELL_TOPJOIN 7 -#define CELL_BOTTOMJOIN 8 -#define CELL_LEFTJOIN 9 -#define CELL_RIGHTJOIN 10 -#define CELL_JOIN 11 -#define CELL_OUTSIDE 12 +#define CELL_UD 1 +#define CELL_LR 2 +#define CELL_RD 3 +#define CELL_LD 4 +#define CELL_RU 5 +#define CELL_LU 6 +#define CELL_LRD 7 +#define CELL_LRU 8 +#define CELL_URD 9 +#define CELL_ULD 10 +#define CELL_LRUD 11 +#define CELL_NONE 12 #define CELL_SCROLLBAR 13 /* Cell borders. */ @@ -1079,27 +1084,6 @@ enum pane_lines { #define WINDOW_PANE_COPY_MODE 1 #define WINDOW_PANE_VIEW_MODE 2 -/* Screen redraw context. */ -struct screen_redraw_ctx { - struct client *c; - - u_int statuslines; - int statustop; - - enum pane_lines pane_lines; - - int pane_scrollbars; - int pane_scrollbars_pos; - - struct grid_cell no_pane_gc; - int no_pane_gc_set; - - u_int sx; - u_int sy; - int ox; - int oy; -}; - /* Screen size. */ #define screen_size_x(s) ((s)->grid->sx) #define screen_size_y(s) ((s)->grid->sy) @@ -1250,7 +1234,7 @@ struct window_pane { #define PANE_FOCUSED 0x4 #define PANE_VISITED 0x8 #define PANE_ZOOMED 0x10 -/* 0x20 unused */ +#define PANE_NEWSTATUS 0x20 #define PANE_INPUTOFF 0x40 #define PANE_CHANGED 0x80 #define PANE_EXITED 0x100 @@ -1306,7 +1290,6 @@ struct window_pane { struct screen base; struct screen status_screen; - size_t status_size; TAILQ_HEAD(, window_mode_entry) modes; @@ -1371,6 +1354,8 @@ struct window { u_int new_xpixel; u_int new_ypixel; + uint64_t redraw_scene_generation; + u_int last_new_pane_x; u_int last_new_pane_y; @@ -2008,12 +1993,11 @@ RB_HEAD(client_windows, client_window); /* 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 *, +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 *, - struct screen_redraw_ctx *); +typedef void (*overlay_draw_cb)(struct client *, void *); 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 *); @@ -2059,6 +2043,8 @@ struct client { size_t discarded; size_t redraw; + struct redraw_scene *redraw_scene; + struct event repeat_timer; struct event click_timer; @@ -3338,8 +3324,13 @@ void screen_write_alternateoff(struct screen_write_ctx *, struct grid_cell *, int); /* screen-redraw.c */ -void screen_redraw_screen(struct client *); -void screen_redraw_pane(struct client *, struct window_pane *, int); +void redraw_screen(struct client *); +void redraw_pane(struct client *, struct window_pane *); +void redraw_pane_scrollbar(struct client *, struct window_pane *); +void redraw_free_scene(struct redraw_scene *); +void redraw_invalidate_scene(struct window *); +void redraw_invalidate_all_scenes(void); +int redraw_get_status_border_cell_type(struct redraw_span **, u_int); /* screen.c */ void screen_init(struct screen *, u_int, u_int, u_int); @@ -3487,6 +3478,16 @@ struct style_range *window_pane_status_get_range(struct window_pane *, u_int, u_int); int window_pane_is_floating(struct window_pane *); +/* window-border.c */ +void window_get_border_cell(struct window *, struct window_pane *, + enum pane_lines, int, struct grid_cell *); +void window_pane_get_border_cell(struct window_pane *, int, + struct grid_cell *); +void window_pane_get_border_style(struct window_pane *, + struct client *, struct grid_cell *); +int window_make_pane_status(struct window_pane *, struct client *, + u_int, struct redraw_span *); + /* window-visible.c */ int window_position_is_visible(struct visible_ranges *, u_int); struct visible_ranges *window_visible_ranges(struct window_pane *, int, int, @@ -3783,8 +3784,7 @@ int menu_display(struct menu *, int, int, struct cmdq_item *, 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_draw_cb(struct client *, void *, - struct screen_redraw_ctx *); +void menu_draw_cb(struct client *, void *); void menu_free_cb(struct client *, void *); int menu_key_cb(struct client *, void *, struct key_event *); diff --git a/window-border.c b/window-border.c new file mode 100644 index 000000000..5420f4b97 --- /dev/null +++ b/window-border.c @@ -0,0 +1,171 @@ +/* $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 + +#include "tmux.h" + +/* Get border cell. */ +void +window_get_border_cell(struct window *w, struct window_pane *wp, + enum pane_lines pane_lines, int cell_type, struct grid_cell *gc) +{ + u_int idx; + + if (cell_type == CELL_NONE && w->fill_character != NULL) { + utf8_copy(&gc->data, &w->fill_character[0]); + return; + } + + switch (pane_lines) { + case PANE_LINES_NUMBER: + if (cell_type == CELL_NONE) { + gc->attr |= GRID_ATTR_CHARSET; + utf8_set(&gc->data, CELL_BORDERS[CELL_NONE]); + break; + } + gc->attr &= ~GRID_ATTR_CHARSET; + if (wp != NULL && window_pane_index(wp, &idx) == 0) + utf8_set(&gc->data, '0' + (idx % 10)); + else + utf8_set(&gc->data, '*'); + break; + case PANE_LINES_DOUBLE: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, tty_acs_double_borders(cell_type)); + break; + case PANE_LINES_HEAVY: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type)); + break; + case PANE_LINES_SIMPLE: + 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, ' '); + break; + default: + gc->attr |= GRID_ATTR_CHARSET; + utf8_set(&gc->data, CELL_BORDERS[cell_type]); + break; + } +} + +/* Get pane border cell. */ +void +window_pane_get_border_cell(struct window_pane *wp, int cell_type, + struct grid_cell *gc) +{ + enum pane_lines pane_lines = window_pane_get_pane_lines(wp); + + window_get_border_cell(wp->window, wp, pane_lines, cell_type, gc); +} + +/* Get pane border style. */ +void +window_pane_get_border_style(struct window_pane *wp, struct client *c, + struct grid_cell *gc) +{ + struct session *s = c->session; + struct format_tree *ft; + const char *option; + struct grid_cell *saved; + int *flag; + + if (wp == server_client_get_pane(c)) { + flag = &wp->active_border_gc_set; + saved = &wp->active_border_gc; + option = "pane-active-border-style"; + } else { + flag = &wp->border_gc_set; + saved = &wp->border_gc; + option = "pane-border-style"; + } + + if (!*flag) { + ft = format_create_defaults(NULL, c, s, s->curw, wp); + style_apply(saved, wp->options, option, ft); + format_free(ft); + *flag = 1; + } + memcpy(gc, saved, sizeof *gc); +} + +/* Build pane status line. */ +int +window_make_pane_status(struct window_pane *wp, struct client *c, u_int width, + struct redraw_span *span) +{ + struct grid_cell gc; + const char *fmt; + struct format_tree *ft; + struct style_line_entry *sle = &wp->border_status_line; + struct screen_write_ctx ctx; + struct screen old; + char *expanded; + u_int i; + enum pane_lines pane_lines; + int pane_status, cell_type; + + pane_status = window_pane_get_pane_status(wp); + if (pane_status == PANE_STATUS_OFF || width == 0) + return (0); + + ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS); + format_defaults(ft, c, c->session, c->session->curw, wp); + + fmt = options_get_string(wp->options, "pane-border-format"); + expanded = format_expand_time(ft, fmt); + + memcpy(&old, &wp->status_screen, sizeof old); + screen_init(&wp->status_screen, width, 1, 0); + wp->status_screen.mode = 0; + screen_write_start(&ctx, &wp->status_screen); + + window_pane_get_border_style(wp, c, &gc); + pane_lines = window_pane_get_pane_lines(wp); + for (i = 0; i < width; i++) { + cell_type = redraw_get_status_border_cell_type(&span, i); + window_get_border_cell(wp->window, wp, pane_lines, cell_type, &gc); + screen_write_cell(&ctx, &gc); + } + gc.attr &= ~GRID_ATTR_CHARSET; + + screen_write_cursormove(&ctx, 0, 0, 0); + style_ranges_free(&sle->ranges); + format_draw(&ctx, &gc, width, expanded, &sle->ranges, 0); + + screen_write_stop(&ctx); + format_free(ft); + + free(sle->expanded); + sle->expanded = expanded; + + if (grid_compare(wp->status_screen.grid, old.grid) == 0) { + screen_free(&old); + return (0); + } + screen_free(&old); + return (1); +} diff --git a/window-copy.c b/window-copy.c index 272e2e9e7..4ba791e75 100644 --- a/window-copy.c +++ b/window-copy.c @@ -800,7 +800,7 @@ window_copy_scroll1(struct window_mode_entry *wme, struct window_pane *wp, return; /* - * See screen_redraw_draw_pane_scrollbar - this is the inverse of the + * See redraw_draw_pane_scrollbar - this is the inverse of the * formula used there. */ new_offset = new_slider_y * ((float)(size + sb_height) / sb_height); diff --git a/window.c b/window.c index 61deb7210..8862e65da 100644 --- a/window.c +++ b/window.c @@ -755,6 +755,7 @@ window_zoom(struct window_pane *wp) w->flags |= WINDOW_ZOOMED; notify_window("window-layout-changed", w); + redraw_invalidate_scene(w); return (0); } @@ -781,6 +782,7 @@ window_unzoom(struct window *w, int notify) if (notify) notify_window("window-layout-changed", w); + redraw_invalidate_scene(w); return (0); } @@ -837,6 +839,7 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, else { TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); } + redraw_invalidate_scene(w); return (wp); } @@ -863,6 +866,7 @@ window_lost_pane(struct window *w, struct window_pane *wp) window_update_focus(w); } } + redraw_invalidate_scene(w); } void @@ -871,6 +875,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); + redraw_invalidate_scene(w); window_pane_destroy(wp); } From 49dff83a0079476d70b589f9e2257f34041e0429 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Jun 2026 10:17:08 +0000 Subject: [PATCH 02/71] Set check callback for menus so they aren't overwritten by sync. --- menu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/menu.c b/menu.c index 17f91b5da..756bb5693 100644 --- a/menu.c +++ b/menu.c @@ -635,7 +635,7 @@ menu_display(struct menu *menu, int flags, int starting_choice, style, selected_style, border_style, fs, cb, data); if (md == NULL) return (-1); - server_client_set_overlay(c, 0, NULL, menu_mode_cb, menu_draw_cb, - menu_key_cb, menu_free_cb, menu_resize_cb, md); + server_client_set_overlay(c, 0, menu_check_cb, menu_mode_cb, + menu_draw_cb, menu_key_cb, menu_free_cb, menu_resize_cb, md); return (0); } From d8370864f3b1e1d233d5f7bf7ccc93605d613aa4 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Jun 2026 12:39:56 +0000 Subject: [PATCH 03/71] Memory leak from someone in GitHub issue 5259. --- window-customize.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/window-customize.c b/window-customize.c index 71ca206fb..a31acc8ba 100644 --- a/window-customize.c +++ b/window-customize.c @@ -740,8 +740,10 @@ window_customize_draw_option(struct window_customize_modedata *data, if (strcmp(expanded, value) != 0) { if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &grid_default_cell, "This expands to: %s", - expanded)) + expanded)) { + free(expanded); goto out; + } } free(expanded); } From 02b37b3c690d2d77199e5836be7cf6cb62fd1489 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Jun 2026 13:57:33 +0000 Subject: [PATCH 04/71] Fix scrollbar pad to work even if not set in style. --- layout.c | 2 +- style.c | 30 +++++++++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/layout.c b/layout.c index cf103a7b8..6fe75a9ed 100644 --- a/layout.c +++ b/layout.c @@ -441,7 +441,7 @@ layout_fix_panes(struct window *w, struct window_pane *skip) if (sb_pad < 0) sb_pad = 0; if (sb_pos == PANE_SCROLLBARS_LEFT) { - if ((int)sx - sb_w < PANE_MINIMUM) { + if ((int)sx - sb_w - sb_pad < PANE_MINIMUM) { wp->xoff = wp->xoff + (int)sx - PANE_MINIMUM; sx = PANE_MINIMUM; diff --git a/style.c b/style.c index b79d32916..796600253 100644 --- a/style.c +++ b/style.c @@ -454,22 +454,26 @@ void style_set_scrollbar_style_from_option(struct style *sb_style, struct options *oo) { - struct style *sy; + const struct options_table_entry *oe; + struct options_entry *o; + const char *s; - sy = options_string_to_style(oo, "pane-scrollbars-style", NULL); - if (sy == NULL) { - style_set(sb_style, &grid_default_cell); + style_set(sb_style, &grid_default_cell); + o = options_get(oo, "pane-scrollbars-style"); + if (o == NULL) + fatalx("missing pane-scrollbars-style"); + oe = options_table_entry(o); + if (style_parse(sb_style, &grid_default_cell, oe->default_str) != 0) + fatalx("bad pane-scrollbars-style default"); + + s = options_get_string(oo, "pane-scrollbars-style"); + if (s != NULL && style_parse(sb_style, &grid_default_cell, s) != 0) + style_parse(sb_style, &grid_default_cell, oe->default_str); + if (sb_style->width < 1) sb_style->width = PANE_SCROLLBARS_DEFAULT_WIDTH; + if (sb_style->pad < 0) sb_style->pad = PANE_SCROLLBARS_DEFAULT_PADDING; - utf8_set(&sb_style->gc.data, PANE_SCROLLBARS_CHARACTER); - } else { - style_copy(sb_style, sy); - if (sb_style->width < 1) - sb_style->width = PANE_SCROLLBARS_DEFAULT_WIDTH; - if (sb_style->pad < 0) - sb_style->pad = PANE_SCROLLBARS_DEFAULT_PADDING; - utf8_set(&sb_style->gc.data, PANE_SCROLLBARS_CHARACTER); - } + utf8_set(&sb_style->gc.data, PANE_SCROLLBARS_CHARACTER); } /* Initialize style ranges. */ From 2a26c738bdd9b87c25c02a9ed37b2def8e0e163b Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Jun 2026 19:39:01 +0000 Subject: [PATCH 05/71] Add z modifier to m operator for fuzzy match. --- format.c | 24 +++++++++++++++++++++++- tmux.1 | 10 ++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/format.c b/format.c index dbb8bce80..797f64659 100644 --- a/format.c +++ b/format.c @@ -4507,6 +4507,25 @@ format_build_modifiers(struct format_expand_state *es, const char **s, return (list); } +/* Fuzzy match strings. */ +static int +format_fuzzy_match(const char *pattern, const char *text, int icase) +{ + while (*pattern != '\0') { + if (*text == '\0') + return (0); + if (icase) { + if (tolower((u_char)*pattern) == tolower((u_char)*text)) + pattern++; + } else { + if (*pattern == *text) + pattern++; + } + text++; + } + return (1); +} + /* Match against an fnmatch(3) pattern or regular expression. */ static char * format_match(struct format_modifier *fm, const char *pattern, const char *text) @@ -4517,7 +4536,10 @@ format_match(struct format_modifier *fm, const char *pattern, const char *text) if (fm->argc >= 1) s = fm->argv[0]; - if (strchr(s, 'r') == NULL) { + if (strchr(s, 'z') != NULL) { + if (!format_fuzzy_match(pattern, text, strchr(s, 'i') != NULL)) + return (xstrdup("0")); + } else if (strchr(s, 'r') == NULL) { if (strchr(s, 'i') != NULL) flags |= FNM_CASEFOLD; if (fnmatch(pattern, text, flags) != 0) diff --git a/tmux.1 b/tmux.1 index a06017a67..da5d126e9 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6379,13 +6379,19 @@ An optional argument specifies flags: .Ql r means the pattern is a regular expression instead of the default .Xr glob 7 -pattern, and +pattern, +.Ql z +means the pattern is a fuzzy match, that is, it matches if its characters +appear in the string in order but not necessarily consecutively, +and .Ql i means to ignore case. For example: .Ql #{m:*foo*,#{host}} or -.Ql #{m/ri:\[ha]A,MYVAR} . +.Ql #{m/ri:\[ha]A,MYVAR} +or +.Ql #{m/z:abc,aXbXc} . A .Ql C performs a search for a From 265f22be49a9424c181f80f0bdb7e9a2f42b50d3 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Jun 2026 19:43:22 +0000 Subject: [PATCH 06/71] Make cursor movement exit incremental prompt (more like emacs). --- status.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/status.c b/status.c index 2dfad5198..0e01dbff3 100644 --- a/status.c +++ b/status.c @@ -1424,6 +1424,22 @@ status_prompt_backward_word(struct client *c, const char *separators) c->prompt_index = idx; } +/* Should this key exit incremental prompt? */ +static int +status_prompt_incremental_exit_key(key_code key) +{ + switch (key) { + case KEYC_UP: + case KEYC_DOWN: + case KEYC_LEFT: + case KEYC_RIGHT: + case KEYC_PPAGE: + case KEYC_NPAGE: + return (1); + } + return (0); +} + /* Handle keys in prompt. */ int status_prompt_key(struct client *c, key_code key) @@ -1482,6 +1498,15 @@ status_prompt_key(struct client *c, key_code key) } process_key: + if ((c->prompt_flags & PROMPT_INCREMENTAL) && + status_prompt_incremental_exit_key(key)) { + s = utf8_tocstr(c->prompt_buffer); + c->prompt_inputcb(c, c->prompt_data, s, 1); + status_prompt_clear(c); + free(s); + return (1); + } + switch (key) { case KEYC_LEFT: case 'b'|KEYC_CTRL: From 7654b518f101d62ca61743baa11df0d3220db6f9 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 23 Jun 2026 08:36:07 +0100 Subject: [PATCH 07/71] Add to .gitignore. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index bf012d5f8..46e3ad595 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ *~ .deps/ .dirstamp +.serena/ +AGENTS.md Makefile Makefile.in aclocal.m4 From 51b787e449087b5b0a81c67096d062ef117e6d41 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 23 Jun 2026 09:05:44 +0100 Subject: [PATCH 08/71] Fix SIXEL build. --- Makefile | 1897 ----------------------------------------------- screen-redraw.c | 14 +- tmux.h | 2 +- tty.c | 8 +- 4 files changed, 14 insertions(+), 1907 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index 0de252fbd..000000000 --- a/Makefile +++ /dev/null @@ -1,1897 +0,0 @@ -# Makefile.in generated by automake 1.18.1 from Makefile.am. -# Makefile. Generated from Makefile.in by configure. - -# Copyright (C) 1994-2025 Free Software Foundation, Inc. - -# This Makefile.in is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY, to the extent permitted by law; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. - - - - -am__is_gnu_make = { \ - if test -z '$(MAKELEVEL)'; then \ - false; \ - elif test -n '$(MAKE_HOST)'; then \ - true; \ - elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ - true; \ - else \ - false; \ - fi; \ -} -am__make_running_with_option = \ - case $${target_option-} in \ - ?) ;; \ - *) echo "am__make_running_with_option: internal error: invalid" \ - "target option '$${target_option-}' specified" >&2; \ - exit 1;; \ - esac; \ - has_opt=no; \ - sane_makeflags=$$MAKEFLAGS; \ - if $(am__is_gnu_make); then \ - sane_makeflags=$$MFLAGS; \ - else \ - case $$MAKEFLAGS in \ - *\\[\ \ ]*) \ - bs=\\; \ - sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ - | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ - esac; \ - fi; \ - skip_next=no; \ - strip_trailopt () \ - { \ - flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ - }; \ - for flg in $$sane_makeflags; do \ - test $$skip_next = yes && { skip_next=no; continue; }; \ - case $$flg in \ - *=*|--*) continue;; \ - -*I) strip_trailopt 'I'; skip_next=yes;; \ - -*I?*) strip_trailopt 'I';; \ - -*O) strip_trailopt 'O'; skip_next=yes;; \ - -*O?*) strip_trailopt 'O';; \ - -*l) strip_trailopt 'l'; skip_next=yes;; \ - -*l?*) strip_trailopt 'l';; \ - -[dEDm]) skip_next=yes;; \ - -[JT]) skip_next=yes;; \ - esac; \ - case $$flg in \ - *$$target_option*) has_opt=yes; break;; \ - esac; \ - done; \ - test $$has_opt = yes -am__make_dryrun = (target_option=n; $(am__make_running_with_option)) -am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) -am__rm_f = rm -f $(am__rm_f_notfound) -am__rm_rf = rm -rf $(am__rm_f_notfound) -pkgdatadir = $(datadir)/tmux -pkgincludedir = $(includedir)/tmux -pkglibdir = $(libdir)/tmux -pkglibexecdir = $(libexecdir)/tmux -am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd -install_sh_DATA = $(install_sh) -c -m 644 -install_sh_PROGRAM = $(install_sh) -c -install_sh_SCRIPT = $(install_sh) -c -INSTALL_HEADER = $(INSTALL_DATA) -transform = $(program_transform_name) -NORMAL_INSTALL = : -PRE_INSTALL = : -POST_INSTALL = : -NORMAL_UNINSTALL = : -PRE_UNINSTALL = : -POST_UNINSTALL = : -build_triplet = amd64-unknown-freebsd15.0 -host_triplet = amd64-unknown-freebsd15.0 -bin_PROGRAMS = tmux$(EXEEXT) - -# Set flags for gcc. -am__append_1 = -std=gnu99 -am__append_2 = -O2 -#am__append_3 = -O0 -am__append_4 = -g3 -ggdb -Wno-long-long \ - -Wall -W -Wformat=2 \ - -Wno-use-after-free \ - -Wmissing-prototypes \ - -Wstrict-prototypes \ - -Wmissing-declarations \ - -Wwrite-strings -Wshadow \ - -Wpointer-arith -Wsign-compare \ - -Wundef -Wbad-function-cast \ - -Winline -Wno-cast-align \ - -Wdeclaration-after-statement \ - -Wno-pointer-sign -Wno-attributes \ - -Wno-unused-result -Wno-format-y2k \ - -Wno-unknown-warning-option \ - -Wno-maybe-uninitialized -#am__append_5 = -Wno-deprecated-declarations -Wno-macro-redefined -am__append_6 = -DDEBUG -am__append_7 = -iquote. -#am__append_8 = -fsanitize=address -#am__append_9 = -fsanitize=address - -# Set flags for Solaris. -#am__append_10 = -D_XPG6 -##am__append_11 = -D_XPG4_2 - -# Set flags for Sun CC. -#am__append_12 = -erroff=E_EMPTY_DECLARATION - -# Set _LINUX_SOURCE_COMPAT for AIX for malloc(0). -#am__append_13 = -D_LINUX_SOURCE_COMPAT=1 - -# Set flags for NetBSD. -#am__append_14 = -D_OPENBSD_SOURCE - -# Set flags for Haiku. -#am__append_15 = -D_BSD_SOURCE - -# Set flags for Cygwin. -#am__append_16 = -DTMUX_SOCK_PERM=0 - -# Add compat file for forkpty. -#am__append_17 = compat/forkpty-freebsd.c - -# Add compat file for systemd. -#am__append_18 = compat/systemd.c - -# Add compat file for utf8proc. -#am__append_19 = compat/utf8proc.c - -# Enable sixel support. -#am__append_20 = image.c image-sixel.c -#check_PROGRAMS = fuzz/input-fuzzer$(EXEEXT) \ -# fuzz/cmd-parse-fuzzer$(EXEEXT) \ -# fuzz/format-fuzzer$(EXEEXT) \ -# fuzz/style-fuzzer$(EXEEXT) -subdir = . -ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 -am__aclocal_m4_deps = $(top_srcdir)/configure.ac -am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ - $(ACLOCAL_M4) -DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \ - $(am__configure_deps) $(am__DIST_COMMON) -am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ - configure.lineno config.status.lineno -mkinstalldirs = $(install_sh) -d -CONFIG_CLEAN_FILES = -CONFIG_CLEAN_VPATH_FILES = -am__installdirs = "$(DESTDIR)$(bindir)" -PROGRAMS = $(bin_PROGRAMS) -LIBOBJDIR = compat/ -am__dirstamp = $(am__leading_dot)dirstamp -fuzz_cmd_parse_fuzzer_SOURCES = fuzz/cmd-parse-fuzzer.c -fuzz_cmd_parse_fuzzer_OBJECTS = fuzz/cmd-parse-fuzzer.$(OBJEXT) -#fuzz_cmd_parse_fuzzer_DEPENDENCIES = $(LDADD) -fuzz_cmd_parse_fuzzer_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(fuzz_cmd_parse_fuzzer_LDFLAGS) $(LDFLAGS) -o $@ -fuzz_format_fuzzer_SOURCES = fuzz/format-fuzzer.c -fuzz_format_fuzzer_OBJECTS = fuzz/format-fuzzer.$(OBJEXT) -#fuzz_format_fuzzer_DEPENDENCIES = $(LDADD) -fuzz_format_fuzzer_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(fuzz_format_fuzzer_LDFLAGS) $(LDFLAGS) -o $@ -fuzz_input_fuzzer_SOURCES = fuzz/input-fuzzer.c -fuzz_input_fuzzer_OBJECTS = fuzz/input-fuzzer.$(OBJEXT) -#fuzz_input_fuzzer_DEPENDENCIES = $(LDADD) -fuzz_input_fuzzer_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(fuzz_input_fuzzer_LDFLAGS) $(LDFLAGS) -o $@ -fuzz_style_fuzzer_SOURCES = fuzz/style-fuzzer.c -fuzz_style_fuzzer_OBJECTS = fuzz/style-fuzzer.$(OBJEXT) -#fuzz_style_fuzzer_DEPENDENCIES = $(LDADD) -fuzz_style_fuzzer_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(fuzz_style_fuzzer_LDFLAGS) $(LDFLAGS) -o $@ -am__dist_tmux_SOURCES_DIST = alerts.c arguments.c attributes.c cfg.c \ - client.c cmd-attach-session.c cmd-bind-key.c cmd-break-pane.c \ - cmd-capture-pane.c cmd-choose-tree.c cmd-command-prompt.c \ - cmd-confirm-before.c cmd-copy-mode.c cmd-detach-client.c \ - cmd-display-menu.c cmd-display-message.c cmd-display-panes.c \ - cmd-find-window.c cmd-find.c cmd-if-shell.c cmd-join-pane.c \ - cmd-kill-pane.c cmd-kill-server.c cmd-kill-session.c \ - cmd-kill-window.c cmd-list-buffers.c cmd-list-clients.c \ - cmd-list-commands.c cmd-list-keys.c cmd-list-panes.c \ - cmd-list-sessions.c cmd-list-windows.c cmd-load-buffer.c \ - cmd-lock-server.c cmd-move-window.c cmd-new-session.c \ - cmd-new-window.c cmd-parse.y cmd-paste-buffer.c \ - cmd-pipe-pane.c cmd-queue.c cmd-refresh-client.c \ - cmd-rename-session.c cmd-rename-window.c cmd-resize-pane.c \ - cmd-resize-window.c cmd-respawn-pane.c cmd-respawn-window.c \ - cmd-rotate-window.c cmd-run-shell.c cmd-save-buffer.c \ - cmd-select-layout.c cmd-select-pane.c cmd-select-window.c \ - cmd-send-keys.c cmd-server-access.c cmd-set-buffer.c \ - cmd-set-environment.c cmd-set-option.c cmd-show-environment.c \ - cmd-show-messages.c cmd-show-options.c \ - cmd-show-prompt-history.c cmd-source-file.c cmd-split-window.c \ - cmd-swap-pane.c cmd-swap-window.c cmd-switch-client.c \ - cmd-unbind-key.c cmd-wait-for.c cmd.c colour.c compat.h \ - control-notify.c control.c environ.c file.c format.c \ - format-draw.c grid-reader.c grid-view.c grid.c hyperlinks.c \ - input-keys.c input.c job.c key-bindings.c key-string.c \ - layout-custom.c layout-set.c layout.c log.c menu.c mode-tree.c \ - names.c notify.c options-table.c options.c paste.c popup.c \ - proc.c regsub.c resize.c screen-redraw.c screen-write.c \ - screen.c server-acl.c server-client.c server-fn.c server.c \ - session.c sort.c spawn.c status.c style.c tmux.c tmux.h \ - tmux-protocol.h tty-acs.c tty-draw.c tty-features.c tty-keys.c \ - tty-term.c tty.c utf8-combined.c utf8.c window-border.c \ - window-buffer.c window-client.c window-clock.c window-copy.c \ - window-customize.c window-tree.c window-visible.c window.c \ - xmalloc.c xmalloc.h image.c image-sixel.c -#am__objects_1 = image.$(OBJEXT) \ -# image-sixel.$(OBJEXT) -dist_tmux_OBJECTS = alerts.$(OBJEXT) arguments.$(OBJEXT) \ - attributes.$(OBJEXT) cfg.$(OBJEXT) client.$(OBJEXT) \ - cmd-attach-session.$(OBJEXT) cmd-bind-key.$(OBJEXT) \ - cmd-break-pane.$(OBJEXT) cmd-capture-pane.$(OBJEXT) \ - cmd-choose-tree.$(OBJEXT) cmd-command-prompt.$(OBJEXT) \ - cmd-confirm-before.$(OBJEXT) cmd-copy-mode.$(OBJEXT) \ - cmd-detach-client.$(OBJEXT) cmd-display-menu.$(OBJEXT) \ - cmd-display-message.$(OBJEXT) cmd-display-panes.$(OBJEXT) \ - cmd-find-window.$(OBJEXT) cmd-find.$(OBJEXT) \ - cmd-if-shell.$(OBJEXT) cmd-join-pane.$(OBJEXT) \ - cmd-kill-pane.$(OBJEXT) cmd-kill-server.$(OBJEXT) \ - cmd-kill-session.$(OBJEXT) cmd-kill-window.$(OBJEXT) \ - cmd-list-buffers.$(OBJEXT) cmd-list-clients.$(OBJEXT) \ - cmd-list-commands.$(OBJEXT) cmd-list-keys.$(OBJEXT) \ - cmd-list-panes.$(OBJEXT) cmd-list-sessions.$(OBJEXT) \ - cmd-list-windows.$(OBJEXT) cmd-load-buffer.$(OBJEXT) \ - cmd-lock-server.$(OBJEXT) cmd-move-window.$(OBJEXT) \ - cmd-new-session.$(OBJEXT) cmd-new-window.$(OBJEXT) \ - cmd-parse.$(OBJEXT) cmd-paste-buffer.$(OBJEXT) \ - cmd-pipe-pane.$(OBJEXT) cmd-queue.$(OBJEXT) \ - cmd-refresh-client.$(OBJEXT) cmd-rename-session.$(OBJEXT) \ - cmd-rename-window.$(OBJEXT) cmd-resize-pane.$(OBJEXT) \ - cmd-resize-window.$(OBJEXT) cmd-respawn-pane.$(OBJEXT) \ - cmd-respawn-window.$(OBJEXT) cmd-rotate-window.$(OBJEXT) \ - cmd-run-shell.$(OBJEXT) cmd-save-buffer.$(OBJEXT) \ - cmd-select-layout.$(OBJEXT) cmd-select-pane.$(OBJEXT) \ - cmd-select-window.$(OBJEXT) cmd-send-keys.$(OBJEXT) \ - cmd-server-access.$(OBJEXT) cmd-set-buffer.$(OBJEXT) \ - cmd-set-environment.$(OBJEXT) cmd-set-option.$(OBJEXT) \ - cmd-show-environment.$(OBJEXT) cmd-show-messages.$(OBJEXT) \ - cmd-show-options.$(OBJEXT) cmd-show-prompt-history.$(OBJEXT) \ - cmd-source-file.$(OBJEXT) cmd-split-window.$(OBJEXT) \ - cmd-swap-pane.$(OBJEXT) cmd-swap-window.$(OBJEXT) \ - cmd-switch-client.$(OBJEXT) cmd-unbind-key.$(OBJEXT) \ - cmd-wait-for.$(OBJEXT) cmd.$(OBJEXT) colour.$(OBJEXT) \ - control-notify.$(OBJEXT) control.$(OBJEXT) environ.$(OBJEXT) \ - file.$(OBJEXT) format.$(OBJEXT) format-draw.$(OBJEXT) \ - grid-reader.$(OBJEXT) grid-view.$(OBJEXT) grid.$(OBJEXT) \ - hyperlinks.$(OBJEXT) input-keys.$(OBJEXT) input.$(OBJEXT) \ - job.$(OBJEXT) key-bindings.$(OBJEXT) key-string.$(OBJEXT) \ - layout-custom.$(OBJEXT) layout-set.$(OBJEXT) layout.$(OBJEXT) \ - log.$(OBJEXT) menu.$(OBJEXT) mode-tree.$(OBJEXT) \ - names.$(OBJEXT) notify.$(OBJEXT) options-table.$(OBJEXT) \ - options.$(OBJEXT) paste.$(OBJEXT) popup.$(OBJEXT) \ - proc.$(OBJEXT) regsub.$(OBJEXT) resize.$(OBJEXT) \ - screen-redraw.$(OBJEXT) screen-write.$(OBJEXT) \ - screen.$(OBJEXT) server-acl.$(OBJEXT) server-client.$(OBJEXT) \ - server-fn.$(OBJEXT) server.$(OBJEXT) session.$(OBJEXT) \ - sort.$(OBJEXT) spawn.$(OBJEXT) status.$(OBJEXT) \ - style.$(OBJEXT) tmux.$(OBJEXT) tty-acs.$(OBJEXT) \ - tty-draw.$(OBJEXT) tty-features.$(OBJEXT) tty-keys.$(OBJEXT) \ - tty-term.$(OBJEXT) tty.$(OBJEXT) utf8-combined.$(OBJEXT) \ - utf8.$(OBJEXT) window-border.$(OBJEXT) window-buffer.$(OBJEXT) \ - window-client.$(OBJEXT) window-clock.$(OBJEXT) \ - window-copy.$(OBJEXT) window-customize.$(OBJEXT) \ - window-tree.$(OBJEXT) window-visible.$(OBJEXT) \ - window.$(OBJEXT) xmalloc.$(OBJEXT) $(am__objects_1) -#am__objects_2 = \ -# compat/forkpty-freebsd.$(OBJEXT) -#am__objects_3 = compat/systemd.$(OBJEXT) -#am__objects_4 = compat/utf8proc.$(OBJEXT) -nodist_tmux_OBJECTS = osdep-freebsd.$(OBJEXT) $(am__objects_2) \ - $(am__objects_3) $(am__objects_4) -tmux_OBJECTS = $(dist_tmux_OBJECTS) $(nodist_tmux_OBJECTS) -tmux_LDADD = $(LDADD) -tmux_DEPENDENCIES = $(LIBOBJS) -AM_V_P = $(am__v_P_$(V)) -am__v_P_ = $(am__v_P_$(AM_DEFAULT_VERBOSITY)) -am__v_P_0 = false -am__v_P_1 = : -AM_V_GEN = $(am__v_GEN_$(V)) -am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) -am__v_GEN_0 = @echo " GEN " $@; -am__v_GEN_1 = -AM_V_at = $(am__v_at_$(V)) -am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) -am__v_at_0 = @ -am__v_at_1 = -DEFAULT_INCLUDES = -I. -depcomp = $(SHELL) $(top_srcdir)/etc/depcomp -am__maybe_remake_depfiles = depfiles -am__depfiles_remade = ./$(DEPDIR)/alerts.Po ./$(DEPDIR)/arguments.Po \ - ./$(DEPDIR)/attributes.Po ./$(DEPDIR)/cfg.Po \ - ./$(DEPDIR)/client.Po ./$(DEPDIR)/cmd-attach-session.Po \ - ./$(DEPDIR)/cmd-bind-key.Po ./$(DEPDIR)/cmd-break-pane.Po \ - ./$(DEPDIR)/cmd-capture-pane.Po ./$(DEPDIR)/cmd-choose-tree.Po \ - ./$(DEPDIR)/cmd-command-prompt.Po \ - ./$(DEPDIR)/cmd-confirm-before.Po ./$(DEPDIR)/cmd-copy-mode.Po \ - ./$(DEPDIR)/cmd-detach-client.Po \ - ./$(DEPDIR)/cmd-display-menu.Po \ - ./$(DEPDIR)/cmd-display-message.Po \ - ./$(DEPDIR)/cmd-display-panes.Po \ - ./$(DEPDIR)/cmd-find-window.Po ./$(DEPDIR)/cmd-find.Po \ - ./$(DEPDIR)/cmd-if-shell.Po ./$(DEPDIR)/cmd-join-pane.Po \ - ./$(DEPDIR)/cmd-kill-pane.Po ./$(DEPDIR)/cmd-kill-server.Po \ - ./$(DEPDIR)/cmd-kill-session.Po ./$(DEPDIR)/cmd-kill-window.Po \ - ./$(DEPDIR)/cmd-list-buffers.Po \ - ./$(DEPDIR)/cmd-list-clients.Po \ - ./$(DEPDIR)/cmd-list-commands.Po ./$(DEPDIR)/cmd-list-keys.Po \ - ./$(DEPDIR)/cmd-list-panes.Po ./$(DEPDIR)/cmd-list-sessions.Po \ - ./$(DEPDIR)/cmd-list-windows.Po ./$(DEPDIR)/cmd-load-buffer.Po \ - ./$(DEPDIR)/cmd-lock-server.Po ./$(DEPDIR)/cmd-move-window.Po \ - ./$(DEPDIR)/cmd-new-session.Po ./$(DEPDIR)/cmd-new-window.Po \ - ./$(DEPDIR)/cmd-parse.Po ./$(DEPDIR)/cmd-paste-buffer.Po \ - ./$(DEPDIR)/cmd-pipe-pane.Po ./$(DEPDIR)/cmd-queue.Po \ - ./$(DEPDIR)/cmd-refresh-client.Po \ - ./$(DEPDIR)/cmd-rename-session.Po \ - ./$(DEPDIR)/cmd-rename-window.Po \ - ./$(DEPDIR)/cmd-resize-pane.Po \ - ./$(DEPDIR)/cmd-resize-window.Po \ - ./$(DEPDIR)/cmd-respawn-pane.Po \ - ./$(DEPDIR)/cmd-respawn-window.Po \ - ./$(DEPDIR)/cmd-rotate-window.Po ./$(DEPDIR)/cmd-run-shell.Po \ - ./$(DEPDIR)/cmd-save-buffer.Po \ - ./$(DEPDIR)/cmd-select-layout.Po \ - ./$(DEPDIR)/cmd-select-pane.Po \ - ./$(DEPDIR)/cmd-select-window.Po ./$(DEPDIR)/cmd-send-keys.Po \ - ./$(DEPDIR)/cmd-server-access.Po ./$(DEPDIR)/cmd-set-buffer.Po \ - ./$(DEPDIR)/cmd-set-environment.Po \ - ./$(DEPDIR)/cmd-set-option.Po \ - ./$(DEPDIR)/cmd-show-environment.Po \ - ./$(DEPDIR)/cmd-show-messages.Po \ - ./$(DEPDIR)/cmd-show-options.Po \ - ./$(DEPDIR)/cmd-show-prompt-history.Po \ - ./$(DEPDIR)/cmd-source-file.Po ./$(DEPDIR)/cmd-split-window.Po \ - ./$(DEPDIR)/cmd-swap-pane.Po ./$(DEPDIR)/cmd-swap-window.Po \ - ./$(DEPDIR)/cmd-switch-client.Po ./$(DEPDIR)/cmd-unbind-key.Po \ - ./$(DEPDIR)/cmd-wait-for.Po ./$(DEPDIR)/cmd.Po \ - ./$(DEPDIR)/colour.Po ./$(DEPDIR)/control-notify.Po \ - ./$(DEPDIR)/control.Po ./$(DEPDIR)/environ.Po \ - ./$(DEPDIR)/file.Po ./$(DEPDIR)/format-draw.Po \ - ./$(DEPDIR)/format.Po ./$(DEPDIR)/grid-reader.Po \ - ./$(DEPDIR)/grid-view.Po ./$(DEPDIR)/grid.Po \ - ./$(DEPDIR)/hyperlinks.Po ./$(DEPDIR)/image-sixel.Po \ - ./$(DEPDIR)/image.Po ./$(DEPDIR)/input-keys.Po \ - ./$(DEPDIR)/input.Po ./$(DEPDIR)/job.Po \ - ./$(DEPDIR)/key-bindings.Po ./$(DEPDIR)/key-string.Po \ - ./$(DEPDIR)/layout-custom.Po ./$(DEPDIR)/layout-set.Po \ - ./$(DEPDIR)/layout.Po ./$(DEPDIR)/log.Po ./$(DEPDIR)/menu.Po \ - ./$(DEPDIR)/mode-tree.Po ./$(DEPDIR)/names.Po \ - ./$(DEPDIR)/notify.Po ./$(DEPDIR)/options-table.Po \ - ./$(DEPDIR)/options.Po ./$(DEPDIR)/osdep-freebsd.Po \ - ./$(DEPDIR)/paste.Po ./$(DEPDIR)/popup.Po ./$(DEPDIR)/proc.Po \ - ./$(DEPDIR)/regsub.Po ./$(DEPDIR)/resize.Po \ - ./$(DEPDIR)/screen-redraw.Po ./$(DEPDIR)/screen-write.Po \ - ./$(DEPDIR)/screen.Po ./$(DEPDIR)/server-acl.Po \ - ./$(DEPDIR)/server-client.Po ./$(DEPDIR)/server-fn.Po \ - ./$(DEPDIR)/server.Po ./$(DEPDIR)/session.Po \ - ./$(DEPDIR)/sort.Po ./$(DEPDIR)/spawn.Po ./$(DEPDIR)/status.Po \ - ./$(DEPDIR)/style.Po ./$(DEPDIR)/tmux.Po \ - ./$(DEPDIR)/tty-acs.Po ./$(DEPDIR)/tty-draw.Po \ - ./$(DEPDIR)/tty-features.Po ./$(DEPDIR)/tty-keys.Po \ - ./$(DEPDIR)/tty-term.Po ./$(DEPDIR)/tty.Po \ - ./$(DEPDIR)/utf8-combined.Po ./$(DEPDIR)/utf8.Po \ - ./$(DEPDIR)/window-border.Po ./$(DEPDIR)/window-buffer.Po \ - ./$(DEPDIR)/window-client.Po ./$(DEPDIR)/window-clock.Po \ - ./$(DEPDIR)/window-copy.Po ./$(DEPDIR)/window-customize.Po \ - ./$(DEPDIR)/window-tree.Po ./$(DEPDIR)/window-visible.Po \ - ./$(DEPDIR)/window.Po ./$(DEPDIR)/xmalloc.Po \ - compat/$(DEPDIR)/asprintf.Po compat/$(DEPDIR)/base64.Po \ - compat/$(DEPDIR)/cfmakeraw.Po \ - compat/$(DEPDIR)/clock_gettime.Po \ - compat/$(DEPDIR)/closefrom.Po \ - compat/$(DEPDIR)/daemon-darwin.Po compat/$(DEPDIR)/daemon.Po \ - compat/$(DEPDIR)/err.Po compat/$(DEPDIR)/explicit_bzero.Po \ - compat/$(DEPDIR)/fdforkpty.Po compat/$(DEPDIR)/fgetln.Po \ - compat/$(DEPDIR)/forkpty-freebsd.Po \ - compat/$(DEPDIR)/freezero.Po \ - compat/$(DEPDIR)/getdtablecount.Po \ - compat/$(DEPDIR)/getdtablesize.Po compat/$(DEPDIR)/getline.Po \ - compat/$(DEPDIR)/getopt_long.Po compat/$(DEPDIR)/getpeereid.Po \ - compat/$(DEPDIR)/getprogname.Po compat/$(DEPDIR)/htonll.Po \ - compat/$(DEPDIR)/imsg-buffer.Po compat/$(DEPDIR)/imsg.Po \ - compat/$(DEPDIR)/memmem.Po compat/$(DEPDIR)/ntohll.Po \ - compat/$(DEPDIR)/reallocarray.Po \ - compat/$(DEPDIR)/recallocarray.Po compat/$(DEPDIR)/setenv.Po \ - compat/$(DEPDIR)/setproctitle.Po \ - compat/$(DEPDIR)/strcasestr.Po compat/$(DEPDIR)/strlcat.Po \ - compat/$(DEPDIR)/strlcpy.Po compat/$(DEPDIR)/strndup.Po \ - compat/$(DEPDIR)/strnlen.Po compat/$(DEPDIR)/strsep.Po \ - compat/$(DEPDIR)/strtonum.Po compat/$(DEPDIR)/systemd.Po \ - compat/$(DEPDIR)/unvis.Po compat/$(DEPDIR)/utf8proc.Po \ - compat/$(DEPDIR)/vis.Po fuzz/$(DEPDIR)/cmd-parse-fuzzer.Po \ - fuzz/$(DEPDIR)/format-fuzzer.Po fuzz/$(DEPDIR)/input-fuzzer.Po \ - fuzz/$(DEPDIR)/style-fuzzer.Po -am__mv = mv -f -COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ - $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -AM_V_CC = $(am__v_CC_$(V)) -am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) -am__v_CC_0 = @echo " CC " $@; -am__v_CC_1 = -CCLD = $(CC) -LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ -AM_V_CCLD = $(am__v_CCLD_$(V)) -am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) -am__v_CCLD_0 = @echo " CCLD " $@; -am__v_CCLD_1 = -am__yacc_c2h = sed -e s/cc$$/hh/ -e s/cpp$$/hpp/ -e s/cxx$$/hxx/ \ - -e s/c++$$/h++/ -e s/c$$/h/ -YACCCOMPILE = $(YACC) $(AM_YFLAGS) $(YFLAGS) -AM_V_YACC = $(am__v_YACC_$(V)) -am__v_YACC_ = $(am__v_YACC_$(AM_DEFAULT_VERBOSITY)) -am__v_YACC_0 = @echo " YACC " $@; -am__v_YACC_1 = -YLWRAP = $(top_srcdir)/etc/ylwrap -SOURCES = fuzz/cmd-parse-fuzzer.c fuzz/format-fuzzer.c \ - fuzz/input-fuzzer.c fuzz/style-fuzzer.c $(dist_tmux_SOURCES) \ - $(nodist_tmux_SOURCES) $(dist_EXTRA_tmux_SOURCES) -DIST_SOURCES = fuzz/cmd-parse-fuzzer.c fuzz/format-fuzzer.c \ - fuzz/input-fuzzer.c fuzz/style-fuzzer.c \ - $(am__dist_tmux_SOURCES_DIST) $(dist_EXTRA_tmux_SOURCES) -am__can_run_installinfo = \ - case $$AM_UPDATE_INFO_DIR in \ - n|no|NO) false;; \ - *) (install-info --version) >/dev/null 2>&1;; \ - esac -am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) -# Read a list of newline-separated strings from the standard input, -# and print each of them once, without duplicates. Input order is -# *not* preserved. -am__uniquify_input = $(AWK) '\ - BEGIN { nonempty = 0; } \ - { items[$$0] = 1; nonempty = 1; } \ - END { if (nonempty) { for (i in items) print i; }; } \ -' -# Make sure the list of sources is unique. This is necessary because, -# e.g., the same source file might be shared among _SOURCES variables -# for different programs/libraries. -am__define_uniq_tagged_files = \ - list='$(am__tagged_files)'; \ - unique=`for i in $$list; do \ - if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ - done | $(am__uniquify_input)` -AM_RECURSIVE_TARGETS = cscope -am__DIST_COMMON = $(srcdir)/Makefile.in \ - $(top_srcdir)/compat/asprintf.c $(top_srcdir)/compat/base64.c \ - $(top_srcdir)/compat/cfmakeraw.c \ - $(top_srcdir)/compat/clock_gettime.c \ - $(top_srcdir)/compat/closefrom.c \ - $(top_srcdir)/compat/daemon-darwin.c \ - $(top_srcdir)/compat/daemon.c $(top_srcdir)/compat/err.c \ - $(top_srcdir)/compat/explicit_bzero.c \ - $(top_srcdir)/compat/fdforkpty.c $(top_srcdir)/compat/fgetln.c \ - $(top_srcdir)/compat/freezero.c \ - $(top_srcdir)/compat/getdtablecount.c \ - $(top_srcdir)/compat/getdtablesize.c \ - $(top_srcdir)/compat/getline.c \ - $(top_srcdir)/compat/getopt_long.c \ - $(top_srcdir)/compat/getpeereid.c \ - $(top_srcdir)/compat/getprogname.c \ - $(top_srcdir)/compat/htonll.c \ - $(top_srcdir)/compat/imsg-buffer.c $(top_srcdir)/compat/imsg.c \ - $(top_srcdir)/compat/memmem.c $(top_srcdir)/compat/ntohll.c \ - $(top_srcdir)/compat/reallocarray.c \ - $(top_srcdir)/compat/recallocarray.c \ - $(top_srcdir)/compat/setenv.c \ - $(top_srcdir)/compat/setproctitle.c \ - $(top_srcdir)/compat/strcasestr.c \ - $(top_srcdir)/compat/strlcat.c $(top_srcdir)/compat/strlcpy.c \ - $(top_srcdir)/compat/strndup.c $(top_srcdir)/compat/strnlen.c \ - $(top_srcdir)/compat/strsep.c $(top_srcdir)/compat/strtonum.c \ - $(top_srcdir)/compat/unvis.c $(top_srcdir)/compat/vis.c \ - $(top_srcdir)/etc/compile $(top_srcdir)/etc/config.guess \ - $(top_srcdir)/etc/config.sub $(top_srcdir)/etc/depcomp \ - $(top_srcdir)/etc/install-sh $(top_srcdir)/etc/missing \ - $(top_srcdir)/etc/ylwrap COPYING README cmd-parse.c \ - etc/compile etc/config.guess etc/config.sub etc/depcomp \ - etc/install-sh etc/missing etc/ylwrap -DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) -distdir = $(PACKAGE)-$(VERSION) -top_distdir = $(distdir) -am__remove_distdir = \ - if test -d "$(distdir)"; then \ - find "$(distdir)" -type d ! -perm -700 -exec chmod u+rwx {} ';' \ - ; rm -rf "$(distdir)" \ - || { sleep 5 && rm -rf "$(distdir)"; }; \ - else :; fi -am__post_remove_distdir = $(am__remove_distdir) -DIST_ARCHIVES = $(distdir).tar.gz -GZIP_ENV = -9 -DIST_TARGETS = dist-gzip -# Exists only to be overridden by the user if desired. -AM_DISTCHECK_DVI_TARGET = dvi -distuninstallcheck_listfiles = find . -type f -print -am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ - | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' -distcleancheck_listfiles = \ - find . \( -type f -a \! \ - \( -name .nfs* -o -name .smb* -o -name .__afs* \) \) -print -ACLOCAL = ${SHELL} '/home/n6tadam/tmux-portable/etc/missing' aclocal-1.18 -AMTAR = $${TAR-tar} -AM_CFLAGS = $(am__append_1) $(am__append_2) \ - $(am__append_3) $(am__append_4) $(am__append_5) \ - $(am__append_8) $(am__append_12) - -# Preprocessor flags. -AM_CPPFLAGS = -I/usr/local/include \ - -DTMUX_VERSION='"next-3.7"' \ - -DTMUX_CONF='"$(sysconfdir)/tmux.conf:~/.tmux.conf:$$XDG_CONFIG_HOME/tmux/tmux.conf:~/.config/tmux/tmux.conf"' \ - -DTMUX_LOCK_CMD='"lock -np"' \ - -DTMUX_TERM='"tmux-256color"' $(am__append_6) $(am__append_7) \ - $(am__append_10) $(am__append_11) $(am__append_13) \ - $(am__append_14) $(am__append_15) $(am__append_16) -AM_DEFAULT_VERBOSITY = 1 -AM_LDFLAGS = $(am__append_9) -AUTOCONF = ${SHELL} '/home/n6tadam/tmux-portable/etc/missing' autoconf -AUTOHEADER = ${SHELL} '/home/n6tadam/tmux-portable/etc/missing' autoheader -AUTOMAKE = ${SHELL} '/home/n6tadam/tmux-portable/etc/missing' automake-1.18 -AWK = nawk -CC = cc -CCDEPMODE = depmode=gcc3 -CFLAGS = -CPP = cc -E -CPPFLAGS = -CSCOPE = cscope -CTAGS = ctags -CYGPATH_W = echo -DEFAULT_LOCK_CMD = lock -np -DEFAULT_TERM = tmux-256color -DEFS = -DPACKAGE_NAME=\"tmux\" -DPACKAGE_TARNAME=\"tmux\" -DPACKAGE_VERSION=\"next-3.7\" -DPACKAGE_STRING=\"tmux\ next-3.7\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DPACKAGE=\"tmux\" -DVERSION=\"next-3.7\" -DHAVE_STDIO_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_STRINGS_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTD_H=1 -DHAVE_WCHAR_H=1 -DSTDC_HEADERS=1 -D_ALL_SOURCE=1 -D_COSMO_SOURCE=1 -D_DARWIN_C_SOURCE=1 -D_GNU_SOURCE=1 -D_HPUX_ALT_XOPEN_SOCKET_API=1 -D_NETBSD_SOURCE=1 -D_OPENBSD_SOURCE=1 -D_POSIX_PTHREAD_SEMANTICS=1 -D__STDC_WANT_IEC_60559_ATTRIBS_EXT__=1 -D__STDC_WANT_IEC_60559_BFP_EXT__=1 -D__STDC_WANT_IEC_60559_DFP_EXT__=1 -D__STDC_WANT_IEC_60559_EXT__=1 -D__STDC_WANT_IEC_60559_FUNCS_EXT__=1 -D__STDC_WANT_IEC_60559_TYPES_EXT__=1 -D__STDC_WANT_LIB_EXT2__=1 -D__STDC_WANT_MATH_SPEC_FUNCS__=1 -D_TANDEM_SOURCE=1 -D__EXTENSIONS__=1 -DHAVE_BITSTRING_H=1 -DHAVE_DIRENT_H=1 -DHAVE_FCNTL_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_LIBPROC_H=1 -DHAVE_LIBUTIL_H=1 -DHAVE_PATHS_H=1 -DHAVE_STDINT_H=1 -DHAVE_SYS_TREE_H=1 -DHAVE_SYS_SIGNAME=1 -DHAVE_LIBM=1 -DHAVE_DIRFD=1 -DHAVE_FLOCK=1 -DHAVE_SYSCONF=1 -DHAVE_ASPRINTF=1 -DHAVE_CFMAKERAW=1 -DHAVE_CLOCK_GETTIME=1 -DHAVE_CLOSEFROM=1 -DHAVE_EXPLICIT_BZERO=1 -DHAVE_FGETLN=1 -DHAVE_GETDTABLESIZE=1 -DHAVE_GETPEEREID=1 -DHAVE_GETLINE=1 -DHAVE_GETPROGNAME=1 -DHAVE_MEMMEM=1 -DHAVE_SETENV=1 -DHAVE_SETPROCTITLE=1 -DHAVE_STRCASESTR=1 -DHAVE_STRLCAT=1 -DHAVE_STRLCPY=1 -DHAVE_STRNDUP=1 -DHAVE_STRSEP=1 -DHAVE_STRTONUM=1 -DHAVE_EVENT2_EVENT_H=1 -DHAVE_NCURSES_H=1 -DHAVE_TIPARM=1 -DHAVE_TIPARM_S=1 -DHAVE_B64_NTOP=1 -DHAVE_LIBXNET=1 -DHAVE_DAEMON=1 -DHAVE_FORKPTY=1 -DHAVE_QUEUE_H=1 -DHAVE___PROGNAME=1 -DEPDIR = .deps -ECHO_C = -ECHO_N = -n -ECHO_T = -EGREP = /usr/bin/grep -E -ETAGS = etags -EXEEXT = -FUZZING_LIBS = -GREP = /usr/bin/grep -INSTALL = /usr/bin/install -c -INSTALL_DATA = ${INSTALL} -m 644 -INSTALL_PROGRAM = ${INSTALL} -INSTALL_SCRIPT = ${INSTALL} -INSTALL_STRIP_PROGRAM = $(install_sh) -c -s -JEMALLOC_CFLAGS = -JEMALLOC_LIBS = -LDFLAGS = -LIBEVENT_CFLAGS = -LIBEVENT_CORE_CFLAGS = -I/usr/local/include -LIBEVENT_CORE_LIBS = -L/usr/local/lib -levent_core -LIBEVENT_LIBS = -LIBNCURSESW_CFLAGS = -LIBNCURSESW_LIBS = -LIBNCURSES_CFLAGS = -LIBNCURSES_LIBS = -LIBOBJS = ${LIBOBJDIR}freezero$U.o ${LIBOBJDIR}getdtablecount$U.o ${LIBOBJDIR}htonll$U.o ${LIBOBJDIR}ntohll$U.o ${LIBOBJDIR}recallocarray$U.o ${LIBOBJDIR}getopt_long$U.o ${LIBOBJDIR}imsg$U.o ${LIBOBJDIR}imsg-buffer$U.o ${LIBOBJDIR}vis$U.o ${LIBOBJDIR}unvis$U.o ${LIBOBJDIR}fdforkpty$U.o -LIBS = -lutil -lxnet -fstack-protector-strong -ltinfow -L/usr/local/lib -levent_core -lm -LIBTINFOW_CFLAGS = -LIBTINFOW_LIBS = -fstack-protector-strong -ltinfow -LIBTINFO_CFLAGS = -LIBTINFO_LIBS = -LIBUTF8PROC_CFLAGS = -LIBUTF8PROC_LIBS = -LTLIBOBJS = ${LIBOBJDIR}freezero$U.lo ${LIBOBJDIR}getdtablecount$U.lo ${LIBOBJDIR}htonll$U.lo ${LIBOBJDIR}ntohll$U.lo ${LIBOBJDIR}recallocarray$U.lo ${LIBOBJDIR}getopt_long$U.lo ${LIBOBJDIR}imsg$U.lo ${LIBOBJDIR}imsg-buffer$U.lo ${LIBOBJDIR}vis$U.lo ${LIBOBJDIR}unvis$U.lo ${LIBOBJDIR}fdforkpty$U.lo -MAKEINFO = ${SHELL} '/home/n6tadam/tmux-portable/etc/missing' makeinfo -MANFORMAT = mdoc -MKDIR_P = /usr/local/bin/gmkdir -p -OBJEXT = o -PACKAGE = tmux -PACKAGE_BUGREPORT = -PACKAGE_NAME = tmux -PACKAGE_STRING = tmux next-3.7 -PACKAGE_TARNAME = tmux -PACKAGE_URL = -PACKAGE_VERSION = next-3.7 -PATH_SEPARATOR = : -PKG_CONFIG = /usr/local/bin/pkg-config -PKG_CONFIG_LIBDIR = -PKG_CONFIG_PATH = -PLATFORM = freebsd -SET_MAKE = -SHELL = /bin/sh -STRIP = -SYSTEMD_CFLAGS = -SYSTEMD_LIBS = -VERSION = next-3.7 -XOPEN_DEFINES = -YACC = byacc -YFLAGS = -abs_builddir = /home/n6tadam/tmux-portable -abs_srcdir = /home/n6tadam/tmux-portable -abs_top_builddir = /home/n6tadam/tmux-portable -abs_top_srcdir = /home/n6tadam/tmux-portable -ac_ct_CC = -am__include = include -am__leading_dot = . -am__quote = -am__rm_f_notfound = -am__tar = tar --format=ustar -chf - "$$tardir" -am__untar = tar -xf - -am__xargs_n = xargs -n -bindir = ${exec_prefix}/bin -build = amd64-unknown-freebsd15.0 -build_alias = -build_cpu = amd64 -build_os = freebsd15.0 -build_vendor = unknown -builddir = . -datadir = ${datarootdir} -datarootdir = ${prefix}/share -docdir = ${datarootdir}/doc/${PACKAGE_TARNAME} -dvidir = ${docdir} -exec_prefix = ${prefix} -found_vlock = -found_yacc = yes -host = amd64-unknown-freebsd15.0 -host_alias = -host_cpu = amd64 -host_os = freebsd15.0 -host_vendor = unknown -htmldir = ${docdir} -includedir = ${prefix}/include -infodir = ${datarootdir}/info -install_sh = ${SHELL} /home/n6tadam/tmux-portable/etc/install-sh -libdir = ${exec_prefix}/lib -libexecdir = ${exec_prefix}/libexec -localedir = ${datarootdir}/locale -localstatedir = ${prefix}/var -mandir = ${datarootdir}/man -mkdir_p = $(MKDIR_P) -oldincludedir = /usr/include -pdfdir = ${docdir} -prefix = /usr/local -program_transform_name = s,x,x, -psdir = ${docdir} -runstatedir = ${localstatedir}/run -sbindir = ${exec_prefix}/sbin -sharedstatedir = ${prefix}/com -srcdir = . -sysconfdir = /etc -target_alias = -top_build_prefix = -top_builddir = . -top_srcdir = . -CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c - -# Distribution tarball options. -EXTRA_DIST = \ - CHANGES README README.ja COPYING example_tmux.conf \ - osdep-*.c mdoc2man.awk tmux.1 - -dist_EXTRA_tmux_SOURCES = compat/*.[ch] - -# Additional object files. -LDADD = $(LIBOBJS) - -# List of sources. -dist_tmux_SOURCES = alerts.c arguments.c attributes.c cfg.c client.c \ - cmd-attach-session.c cmd-bind-key.c cmd-break-pane.c \ - cmd-capture-pane.c cmd-choose-tree.c cmd-command-prompt.c \ - cmd-confirm-before.c cmd-copy-mode.c cmd-detach-client.c \ - cmd-display-menu.c cmd-display-message.c cmd-display-panes.c \ - cmd-find-window.c cmd-find.c cmd-if-shell.c cmd-join-pane.c \ - cmd-kill-pane.c cmd-kill-server.c cmd-kill-session.c \ - cmd-kill-window.c cmd-list-buffers.c cmd-list-clients.c \ - cmd-list-commands.c cmd-list-keys.c cmd-list-panes.c \ - cmd-list-sessions.c cmd-list-windows.c cmd-load-buffer.c \ - cmd-lock-server.c cmd-move-window.c cmd-new-session.c \ - cmd-new-window.c cmd-parse.y cmd-paste-buffer.c \ - cmd-pipe-pane.c cmd-queue.c cmd-refresh-client.c \ - cmd-rename-session.c cmd-rename-window.c cmd-resize-pane.c \ - cmd-resize-window.c cmd-respawn-pane.c cmd-respawn-window.c \ - cmd-rotate-window.c cmd-run-shell.c cmd-save-buffer.c \ - cmd-select-layout.c cmd-select-pane.c cmd-select-window.c \ - cmd-send-keys.c cmd-server-access.c cmd-set-buffer.c \ - cmd-set-environment.c cmd-set-option.c cmd-show-environment.c \ - cmd-show-messages.c cmd-show-options.c \ - cmd-show-prompt-history.c cmd-source-file.c cmd-split-window.c \ - cmd-swap-pane.c cmd-swap-window.c cmd-switch-client.c \ - cmd-unbind-key.c cmd-wait-for.c cmd.c colour.c compat.h \ - control-notify.c control.c environ.c file.c format.c \ - format-draw.c grid-reader.c grid-view.c grid.c hyperlinks.c \ - input-keys.c input.c job.c key-bindings.c key-string.c \ - layout-custom.c layout-set.c layout.c log.c menu.c mode-tree.c \ - names.c notify.c options-table.c options.c paste.c popup.c \ - proc.c regsub.c resize.c screen-redraw.c screen-write.c \ - screen.c server-acl.c server-client.c server-fn.c server.c \ - session.c sort.c spawn.c status.c style.c tmux.c tmux.h \ - tmux-protocol.h tty-acs.c tty-draw.c tty-features.c tty-keys.c \ - tty-term.c tty.c utf8-combined.c utf8.c window-border.c \ - window-buffer.c window-client.c window-clock.c window-copy.c \ - window-customize.c window-tree.c window-visible.c window.c \ - xmalloc.c xmalloc.h $(am__append_20) -nodist_tmux_SOURCES = osdep-freebsd.c $(am__append_17) \ - $(am__append_18) $(am__append_19) -#fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS) -#fuzz_input_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS) -#fuzz_cmd_parse_fuzzer_LDFLAGS = $(FUZZING_LIBS) -#fuzz_cmd_parse_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS) -#fuzz_format_fuzzer_LDFLAGS = $(FUZZING_LIBS) -#fuzz_format_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS) -#fuzz_style_fuzzer_LDFLAGS = $(FUZZING_LIBS) -#fuzz_style_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS) -all: all-am - -.SUFFIXES: -.SUFFIXES: .c .o .obj .y -am--refresh: Makefile - @: -$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) - @for dep in $?; do \ - case '$(am__configure_deps)' in \ - *$$dep*) \ - echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ - $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ - && exit 0; \ - exit 1;; \ - esac; \ - done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ - $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --foreign Makefile -Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status - @case '$?' in \ - *config.status*) \ - echo ' $(SHELL) ./config.status'; \ - $(SHELL) ./config.status;; \ - *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles);; \ - esac; - -$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) - $(SHELL) ./config.status --recheck - -$(top_srcdir)/configure: $(am__configure_deps) - $(am__cd) $(srcdir) && $(AUTOCONF) -$(ACLOCAL_M4): $(am__aclocal_m4_deps) - $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) -$(am__aclocal_m4_deps): -install-binPROGRAMS: $(bin_PROGRAMS) - @$(NORMAL_INSTALL) - @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ - if test -n "$$list"; then \ - echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ - $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ - fi; \ - for p in $$list; do echo "$$p $$p"; done | \ - sed 's/$(EXEEXT)$$//' | \ - while read p p1; do if test -f $$p \ - ; then echo "$$p"; echo "$$p"; else :; fi; \ - done | \ - sed -e 'p;s,.*/,,;n;h' \ - -e 's|.*|.|' \ - -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ - sed 'N;N;N;s,\n, ,g' | \ - $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ - { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ - if ($$2 == $$4) files[d] = files[d] " " $$1; \ - else { print "f", $$3 "/" $$4, $$1; } } \ - END { for (d in files) print "f", d, files[d] }' | \ - while read type dir files; do \ - if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ - test -z "$$files" || { \ - echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ - $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ - } \ - ; done - -uninstall-binPROGRAMS: - @$(NORMAL_UNINSTALL) - @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ - files=`for p in $$list; do echo "$$p"; done | \ - sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ - -e 's/$$/$(EXEEXT)/' \ - `; \ - test -n "$$list" || exit 0; \ - echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ - cd "$(DESTDIR)$(bindir)" && $(am__rm_f) $$files - -clean-binPROGRAMS: - -$(am__rm_f) $(bin_PROGRAMS) - -clean-checkPROGRAMS: - -$(am__rm_f) $(check_PROGRAMS) -compat/$(am__dirstamp): - @$(MKDIR_P) compat/ - @: >>compat/$(am__dirstamp) -compat/asprintf.$(OBJEXT): compat/$(am__dirstamp) -compat/base64.$(OBJEXT): compat/$(am__dirstamp) -compat/cfmakeraw.$(OBJEXT): compat/$(am__dirstamp) -compat/clock_gettime.$(OBJEXT): compat/$(am__dirstamp) -compat/closefrom.$(OBJEXT): compat/$(am__dirstamp) -compat/daemon-darwin.$(OBJEXT): compat/$(am__dirstamp) -compat/daemon.$(OBJEXT): compat/$(am__dirstamp) -compat/err.$(OBJEXT): compat/$(am__dirstamp) -compat/explicit_bzero.$(OBJEXT): compat/$(am__dirstamp) -compat/fdforkpty.$(OBJEXT): compat/$(am__dirstamp) -compat/fgetln.$(OBJEXT): compat/$(am__dirstamp) -compat/freezero.$(OBJEXT): compat/$(am__dirstamp) -compat/getdtablecount.$(OBJEXT): compat/$(am__dirstamp) -compat/getdtablesize.$(OBJEXT): compat/$(am__dirstamp) -compat/getline.$(OBJEXT): compat/$(am__dirstamp) -compat/getopt_long.$(OBJEXT): compat/$(am__dirstamp) -compat/getpeereid.$(OBJEXT): compat/$(am__dirstamp) -compat/getprogname.$(OBJEXT): compat/$(am__dirstamp) -compat/htonll.$(OBJEXT): compat/$(am__dirstamp) -compat/imsg-buffer.$(OBJEXT): compat/$(am__dirstamp) -compat/imsg.$(OBJEXT): compat/$(am__dirstamp) -compat/memmem.$(OBJEXT): compat/$(am__dirstamp) -compat/ntohll.$(OBJEXT): compat/$(am__dirstamp) -compat/reallocarray.$(OBJEXT): compat/$(am__dirstamp) -compat/recallocarray.$(OBJEXT): compat/$(am__dirstamp) -compat/setenv.$(OBJEXT): compat/$(am__dirstamp) -compat/setproctitle.$(OBJEXT): compat/$(am__dirstamp) -compat/strcasestr.$(OBJEXT): compat/$(am__dirstamp) -compat/strlcat.$(OBJEXT): compat/$(am__dirstamp) -compat/strlcpy.$(OBJEXT): compat/$(am__dirstamp) -compat/strndup.$(OBJEXT): compat/$(am__dirstamp) -compat/strnlen.$(OBJEXT): compat/$(am__dirstamp) -compat/strsep.$(OBJEXT): compat/$(am__dirstamp) -compat/strtonum.$(OBJEXT): compat/$(am__dirstamp) -compat/unvis.$(OBJEXT): compat/$(am__dirstamp) -compat/vis.$(OBJEXT): compat/$(am__dirstamp) -fuzz/$(am__dirstamp): - @$(MKDIR_P) fuzz - @: >>fuzz/$(am__dirstamp) -fuzz/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) fuzz/$(DEPDIR) - @: >>fuzz/$(DEPDIR)/$(am__dirstamp) -fuzz/cmd-parse-fuzzer.$(OBJEXT): fuzz/$(am__dirstamp) \ - fuzz/$(DEPDIR)/$(am__dirstamp) - -fuzz/cmd-parse-fuzzer$(EXEEXT): $(fuzz_cmd_parse_fuzzer_OBJECTS) $(fuzz_cmd_parse_fuzzer_DEPENDENCIES) $(EXTRA_fuzz_cmd_parse_fuzzer_DEPENDENCIES) fuzz/$(am__dirstamp) - @rm -f fuzz/cmd-parse-fuzzer$(EXEEXT) - $(AM_V_CCLD)$(fuzz_cmd_parse_fuzzer_LINK) $(fuzz_cmd_parse_fuzzer_OBJECTS) $(fuzz_cmd_parse_fuzzer_LDADD) $(LIBS) -fuzz/format-fuzzer.$(OBJEXT): fuzz/$(am__dirstamp) \ - fuzz/$(DEPDIR)/$(am__dirstamp) - -fuzz/format-fuzzer$(EXEEXT): $(fuzz_format_fuzzer_OBJECTS) $(fuzz_format_fuzzer_DEPENDENCIES) $(EXTRA_fuzz_format_fuzzer_DEPENDENCIES) fuzz/$(am__dirstamp) - @rm -f fuzz/format-fuzzer$(EXEEXT) - $(AM_V_CCLD)$(fuzz_format_fuzzer_LINK) $(fuzz_format_fuzzer_OBJECTS) $(fuzz_format_fuzzer_LDADD) $(LIBS) -fuzz/input-fuzzer.$(OBJEXT): fuzz/$(am__dirstamp) \ - fuzz/$(DEPDIR)/$(am__dirstamp) - -fuzz/input-fuzzer$(EXEEXT): $(fuzz_input_fuzzer_OBJECTS) $(fuzz_input_fuzzer_DEPENDENCIES) $(EXTRA_fuzz_input_fuzzer_DEPENDENCIES) fuzz/$(am__dirstamp) - @rm -f fuzz/input-fuzzer$(EXEEXT) - $(AM_V_CCLD)$(fuzz_input_fuzzer_LINK) $(fuzz_input_fuzzer_OBJECTS) $(fuzz_input_fuzzer_LDADD) $(LIBS) -fuzz/style-fuzzer.$(OBJEXT): fuzz/$(am__dirstamp) \ - fuzz/$(DEPDIR)/$(am__dirstamp) - -fuzz/style-fuzzer$(EXEEXT): $(fuzz_style_fuzzer_OBJECTS) $(fuzz_style_fuzzer_DEPENDENCIES) $(EXTRA_fuzz_style_fuzzer_DEPENDENCIES) fuzz/$(am__dirstamp) - @rm -f fuzz/style-fuzzer$(EXEEXT) - $(AM_V_CCLD)$(fuzz_style_fuzzer_LINK) $(fuzz_style_fuzzer_OBJECTS) $(fuzz_style_fuzzer_LDADD) $(LIBS) -compat/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) compat/$(DEPDIR) - @: >>compat/$(DEPDIR)/$(am__dirstamp) -compat/forkpty-freebsd.$(OBJEXT): compat/$(am__dirstamp) \ - compat/$(DEPDIR)/$(am__dirstamp) -compat/systemd.$(OBJEXT): compat/$(am__dirstamp) \ - compat/$(DEPDIR)/$(am__dirstamp) -compat/utf8proc.$(OBJEXT): compat/$(am__dirstamp) \ - compat/$(DEPDIR)/$(am__dirstamp) - -tmux$(EXEEXT): $(tmux_OBJECTS) $(tmux_DEPENDENCIES) $(EXTRA_tmux_DEPENDENCIES) - @rm -f tmux$(EXEEXT) - $(AM_V_CCLD)$(LINK) $(tmux_OBJECTS) $(tmux_LDADD) $(LIBS) - -mostlyclean-compile: - -rm -f *.$(OBJEXT) - -rm -f compat/*.$(OBJEXT) - -rm -f fuzz/*.$(OBJEXT) - -distclean-compile: - -rm -f *.tab.c - -include ./$(DEPDIR)/alerts.Po # am--include-marker -include ./$(DEPDIR)/arguments.Po # am--include-marker -include ./$(DEPDIR)/attributes.Po # am--include-marker -include ./$(DEPDIR)/cfg.Po # am--include-marker -include ./$(DEPDIR)/client.Po # am--include-marker -include ./$(DEPDIR)/cmd-attach-session.Po # am--include-marker -include ./$(DEPDIR)/cmd-bind-key.Po # am--include-marker -include ./$(DEPDIR)/cmd-break-pane.Po # am--include-marker -include ./$(DEPDIR)/cmd-capture-pane.Po # am--include-marker -include ./$(DEPDIR)/cmd-choose-tree.Po # am--include-marker -include ./$(DEPDIR)/cmd-command-prompt.Po # am--include-marker -include ./$(DEPDIR)/cmd-confirm-before.Po # am--include-marker -include ./$(DEPDIR)/cmd-copy-mode.Po # am--include-marker -include ./$(DEPDIR)/cmd-detach-client.Po # am--include-marker -include ./$(DEPDIR)/cmd-display-menu.Po # am--include-marker -include ./$(DEPDIR)/cmd-display-message.Po # am--include-marker -include ./$(DEPDIR)/cmd-display-panes.Po # am--include-marker -include ./$(DEPDIR)/cmd-find-window.Po # am--include-marker -include ./$(DEPDIR)/cmd-find.Po # am--include-marker -include ./$(DEPDIR)/cmd-if-shell.Po # am--include-marker -include ./$(DEPDIR)/cmd-join-pane.Po # am--include-marker -include ./$(DEPDIR)/cmd-kill-pane.Po # am--include-marker -include ./$(DEPDIR)/cmd-kill-server.Po # am--include-marker -include ./$(DEPDIR)/cmd-kill-session.Po # am--include-marker -include ./$(DEPDIR)/cmd-kill-window.Po # am--include-marker -include ./$(DEPDIR)/cmd-list-buffers.Po # am--include-marker -include ./$(DEPDIR)/cmd-list-clients.Po # am--include-marker -include ./$(DEPDIR)/cmd-list-commands.Po # am--include-marker -include ./$(DEPDIR)/cmd-list-keys.Po # am--include-marker -include ./$(DEPDIR)/cmd-list-panes.Po # am--include-marker -include ./$(DEPDIR)/cmd-list-sessions.Po # am--include-marker -include ./$(DEPDIR)/cmd-list-windows.Po # am--include-marker -include ./$(DEPDIR)/cmd-load-buffer.Po # am--include-marker -include ./$(DEPDIR)/cmd-lock-server.Po # am--include-marker -include ./$(DEPDIR)/cmd-move-window.Po # am--include-marker -include ./$(DEPDIR)/cmd-new-session.Po # am--include-marker -include ./$(DEPDIR)/cmd-new-window.Po # am--include-marker -include ./$(DEPDIR)/cmd-parse.Po # am--include-marker -include ./$(DEPDIR)/cmd-paste-buffer.Po # am--include-marker -include ./$(DEPDIR)/cmd-pipe-pane.Po # am--include-marker -include ./$(DEPDIR)/cmd-queue.Po # am--include-marker -include ./$(DEPDIR)/cmd-refresh-client.Po # am--include-marker -include ./$(DEPDIR)/cmd-rename-session.Po # am--include-marker -include ./$(DEPDIR)/cmd-rename-window.Po # am--include-marker -include ./$(DEPDIR)/cmd-resize-pane.Po # am--include-marker -include ./$(DEPDIR)/cmd-resize-window.Po # am--include-marker -include ./$(DEPDIR)/cmd-respawn-pane.Po # am--include-marker -include ./$(DEPDIR)/cmd-respawn-window.Po # am--include-marker -include ./$(DEPDIR)/cmd-rotate-window.Po # am--include-marker -include ./$(DEPDIR)/cmd-run-shell.Po # am--include-marker -include ./$(DEPDIR)/cmd-save-buffer.Po # am--include-marker -include ./$(DEPDIR)/cmd-select-layout.Po # am--include-marker -include ./$(DEPDIR)/cmd-select-pane.Po # am--include-marker -include ./$(DEPDIR)/cmd-select-window.Po # am--include-marker -include ./$(DEPDIR)/cmd-send-keys.Po # am--include-marker -include ./$(DEPDIR)/cmd-server-access.Po # am--include-marker -include ./$(DEPDIR)/cmd-set-buffer.Po # am--include-marker -include ./$(DEPDIR)/cmd-set-environment.Po # am--include-marker -include ./$(DEPDIR)/cmd-set-option.Po # am--include-marker -include ./$(DEPDIR)/cmd-show-environment.Po # am--include-marker -include ./$(DEPDIR)/cmd-show-messages.Po # am--include-marker -include ./$(DEPDIR)/cmd-show-options.Po # am--include-marker -include ./$(DEPDIR)/cmd-show-prompt-history.Po # am--include-marker -include ./$(DEPDIR)/cmd-source-file.Po # am--include-marker -include ./$(DEPDIR)/cmd-split-window.Po # am--include-marker -include ./$(DEPDIR)/cmd-swap-pane.Po # am--include-marker -include ./$(DEPDIR)/cmd-swap-window.Po # am--include-marker -include ./$(DEPDIR)/cmd-switch-client.Po # am--include-marker -include ./$(DEPDIR)/cmd-unbind-key.Po # am--include-marker -include ./$(DEPDIR)/cmd-wait-for.Po # am--include-marker -include ./$(DEPDIR)/cmd.Po # am--include-marker -include ./$(DEPDIR)/colour.Po # am--include-marker -include ./$(DEPDIR)/control-notify.Po # am--include-marker -include ./$(DEPDIR)/control.Po # am--include-marker -include ./$(DEPDIR)/environ.Po # am--include-marker -include ./$(DEPDIR)/file.Po # am--include-marker -include ./$(DEPDIR)/format-draw.Po # am--include-marker -include ./$(DEPDIR)/format.Po # am--include-marker -include ./$(DEPDIR)/grid-reader.Po # am--include-marker -include ./$(DEPDIR)/grid-view.Po # am--include-marker -include ./$(DEPDIR)/grid.Po # am--include-marker -include ./$(DEPDIR)/hyperlinks.Po # am--include-marker -include ./$(DEPDIR)/image-sixel.Po # am--include-marker -include ./$(DEPDIR)/image.Po # am--include-marker -include ./$(DEPDIR)/input-keys.Po # am--include-marker -include ./$(DEPDIR)/input.Po # am--include-marker -include ./$(DEPDIR)/job.Po # am--include-marker -include ./$(DEPDIR)/key-bindings.Po # am--include-marker -include ./$(DEPDIR)/key-string.Po # am--include-marker -include ./$(DEPDIR)/layout-custom.Po # am--include-marker -include ./$(DEPDIR)/layout-set.Po # am--include-marker -include ./$(DEPDIR)/layout.Po # am--include-marker -include ./$(DEPDIR)/log.Po # am--include-marker -include ./$(DEPDIR)/menu.Po # am--include-marker -include ./$(DEPDIR)/mode-tree.Po # am--include-marker -include ./$(DEPDIR)/names.Po # am--include-marker -include ./$(DEPDIR)/notify.Po # am--include-marker -include ./$(DEPDIR)/options-table.Po # am--include-marker -include ./$(DEPDIR)/options.Po # am--include-marker -include ./$(DEPDIR)/osdep-freebsd.Po # am--include-marker -include ./$(DEPDIR)/paste.Po # am--include-marker -include ./$(DEPDIR)/popup.Po # am--include-marker -include ./$(DEPDIR)/proc.Po # am--include-marker -include ./$(DEPDIR)/regsub.Po # am--include-marker -include ./$(DEPDIR)/resize.Po # am--include-marker -include ./$(DEPDIR)/screen-redraw.Po # am--include-marker -include ./$(DEPDIR)/screen-write.Po # am--include-marker -include ./$(DEPDIR)/screen.Po # am--include-marker -include ./$(DEPDIR)/server-acl.Po # am--include-marker -include ./$(DEPDIR)/server-client.Po # am--include-marker -include ./$(DEPDIR)/server-fn.Po # am--include-marker -include ./$(DEPDIR)/server.Po # am--include-marker -include ./$(DEPDIR)/session.Po # am--include-marker -include ./$(DEPDIR)/sort.Po # am--include-marker -include ./$(DEPDIR)/spawn.Po # am--include-marker -include ./$(DEPDIR)/status.Po # am--include-marker -include ./$(DEPDIR)/style.Po # am--include-marker -include ./$(DEPDIR)/tmux.Po # am--include-marker -include ./$(DEPDIR)/tty-acs.Po # am--include-marker -include ./$(DEPDIR)/tty-draw.Po # am--include-marker -include ./$(DEPDIR)/tty-features.Po # am--include-marker -include ./$(DEPDIR)/tty-keys.Po # am--include-marker -include ./$(DEPDIR)/tty-term.Po # am--include-marker -include ./$(DEPDIR)/tty.Po # am--include-marker -include ./$(DEPDIR)/utf8-combined.Po # am--include-marker -include ./$(DEPDIR)/utf8.Po # am--include-marker -include ./$(DEPDIR)/window-border.Po # am--include-marker -include ./$(DEPDIR)/window-buffer.Po # am--include-marker -include ./$(DEPDIR)/window-client.Po # am--include-marker -include ./$(DEPDIR)/window-clock.Po # am--include-marker -include ./$(DEPDIR)/window-copy.Po # am--include-marker -include ./$(DEPDIR)/window-customize.Po # am--include-marker -include ./$(DEPDIR)/window-tree.Po # am--include-marker -include ./$(DEPDIR)/window-visible.Po # am--include-marker -include ./$(DEPDIR)/window.Po # am--include-marker -include ./$(DEPDIR)/xmalloc.Po # am--include-marker -include compat/$(DEPDIR)/asprintf.Po # am--include-marker -include compat/$(DEPDIR)/base64.Po # am--include-marker -include compat/$(DEPDIR)/cfmakeraw.Po # am--include-marker -include compat/$(DEPDIR)/clock_gettime.Po # am--include-marker -include compat/$(DEPDIR)/closefrom.Po # am--include-marker -include compat/$(DEPDIR)/daemon-darwin.Po # am--include-marker -include compat/$(DEPDIR)/daemon.Po # am--include-marker -include compat/$(DEPDIR)/err.Po # am--include-marker -include compat/$(DEPDIR)/explicit_bzero.Po # am--include-marker -include compat/$(DEPDIR)/fdforkpty.Po # am--include-marker -include compat/$(DEPDIR)/fgetln.Po # am--include-marker -include compat/$(DEPDIR)/forkpty-freebsd.Po # am--include-marker -include compat/$(DEPDIR)/freezero.Po # am--include-marker -include compat/$(DEPDIR)/getdtablecount.Po # am--include-marker -include compat/$(DEPDIR)/getdtablesize.Po # am--include-marker -include compat/$(DEPDIR)/getline.Po # am--include-marker -include compat/$(DEPDIR)/getopt_long.Po # am--include-marker -include compat/$(DEPDIR)/getpeereid.Po # am--include-marker -include compat/$(DEPDIR)/getprogname.Po # am--include-marker -include compat/$(DEPDIR)/htonll.Po # am--include-marker -include compat/$(DEPDIR)/imsg-buffer.Po # am--include-marker -include compat/$(DEPDIR)/imsg.Po # am--include-marker -include compat/$(DEPDIR)/memmem.Po # am--include-marker -include compat/$(DEPDIR)/ntohll.Po # am--include-marker -include compat/$(DEPDIR)/reallocarray.Po # am--include-marker -include compat/$(DEPDIR)/recallocarray.Po # am--include-marker -include compat/$(DEPDIR)/setenv.Po # am--include-marker -include compat/$(DEPDIR)/setproctitle.Po # am--include-marker -include compat/$(DEPDIR)/strcasestr.Po # am--include-marker -include compat/$(DEPDIR)/strlcat.Po # am--include-marker -include compat/$(DEPDIR)/strlcpy.Po # am--include-marker -include compat/$(DEPDIR)/strndup.Po # am--include-marker -include compat/$(DEPDIR)/strnlen.Po # am--include-marker -include compat/$(DEPDIR)/strsep.Po # am--include-marker -include compat/$(DEPDIR)/strtonum.Po # am--include-marker -include compat/$(DEPDIR)/systemd.Po # am--include-marker -include compat/$(DEPDIR)/unvis.Po # am--include-marker -include compat/$(DEPDIR)/utf8proc.Po # am--include-marker -include compat/$(DEPDIR)/vis.Po # am--include-marker -include fuzz/$(DEPDIR)/cmd-parse-fuzzer.Po # am--include-marker -include fuzz/$(DEPDIR)/format-fuzzer.Po # am--include-marker -include fuzz/$(DEPDIR)/input-fuzzer.Po # am--include-marker -include fuzz/$(DEPDIR)/style-fuzzer.Po # am--include-marker - -$(am__depfiles_remade): - @$(MKDIR_P) $(@D) - @: >>$@ - -am--depfiles: $(am__depfiles_remade) - -.c.o: - $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ - $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ - $(am__mv) $$depbase.Tpo $$depbase.Po -# $(AM_V_CC)source='$<' object='$@' libtool=no \ -# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ -# $(AM_V_CC_no)$(COMPILE) -c -o $@ $< - -.c.obj: - $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ - $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ - $(am__mv) $$depbase.Tpo $$depbase.Po -# $(AM_V_CC)source='$<' object='$@' libtool=no \ -# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ -# $(AM_V_CC_no)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` - -.y.c: - $(AM_V_YACC)$(am__skipyacc) $(SHELL) $(YLWRAP) $< y.tab.c $@ y.tab.h `echo $@ | $(am__yacc_c2h)` y.output $*.output -- $(YACCCOMPILE) - -ID: $(am__tagged_files) - $(am__define_uniq_tagged_files); mkid -fID $$unique -tags: tags-am -TAGS: tags - -tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) - set x; \ - here=`pwd`; \ - $(am__define_uniq_tagged_files); \ - shift; \ - if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ - test -n "$$unique" || unique=$$empty_fix; \ - if test $$# -gt 0; then \ - $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - "$$@" $$unique; \ - else \ - $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - $$unique; \ - fi; \ - fi -ctags: ctags-am - -CTAGS: ctags -ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) - $(am__define_uniq_tagged_files); \ - test -z "$(CTAGS_ARGS)$$unique" \ - || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ - $$unique - -GTAGS: - here=`$(am__cd) $(top_builddir) && pwd` \ - && $(am__cd) $(top_srcdir) \ - && gtags -i $(GTAGS_ARGS) "$$here" -cscope: cscope.files - test ! -s cscope.files \ - || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS) -clean-cscope: - -rm -f cscope.files -cscope.files: clean-cscope cscopelist -cscopelist: cscopelist-am - -cscopelist-am: $(am__tagged_files) - list='$(am__tagged_files)'; \ - case "$(srcdir)" in \ - [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ - *) sdir=$(subdir)/$(srcdir) ;; \ - esac; \ - for i in $$list; do \ - if test -f "$$i"; then \ - echo "$(subdir)/$$i"; \ - else \ - echo "$$sdir/$$i"; \ - fi; \ - done >> $(top_builddir)/cscope.files - -distclean-tags: - -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags - -rm -f cscope.out cscope.in.out cscope.po.out cscope.files - -distdir: $(BUILT_SOURCES) - $(MAKE) $(AM_MAKEFLAGS) distdir-am - -distdir-am: $(DISTFILES) - $(am__remove_distdir) - $(AM_V_at)$(MKDIR_P) "$(distdir)" - @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ - topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ - list='$(DISTFILES)'; \ - dist_files=`for file in $$list; do echo $$file; done | \ - sed -e "s|^$$srcdirstrip/||;t" \ - -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ - case $$dist_files in \ - */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ - sort -u` ;; \ - esac; \ - for file in $$dist_files; do \ - if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ - if test -d $$d/$$file; then \ - dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ - if test -d "$(distdir)/$$file"; then \ - find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ - fi; \ - if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ - cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ - find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ - fi; \ - cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ - else \ - test -f "$(distdir)/$$file" \ - || cp -p $$d/$$file "$(distdir)/$$file" \ - || exit 1; \ - fi; \ - done - -test -n "$(am__skip_mode_fix)" \ - || find "$(distdir)" -type d ! -perm -755 \ - -exec chmod u+rwx,go+rx {} \; -o \ - ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ - ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ - ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ - || chmod -R a+r "$(distdir)" -dist-gzip: distdir - tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz - $(am__post_remove_distdir) - -dist-bzip2: distdir - tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 - $(am__post_remove_distdir) - -dist-bzip3: distdir - tardir=$(distdir) && $(am__tar) | bzip3 -c >$(distdir).tar.bz3 - $(am__post_remove_distdir) - -dist-lzip: distdir - tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz - $(am__post_remove_distdir) - -dist-xz: distdir - tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz - $(am__post_remove_distdir) - -dist-zstd: distdir - tardir=$(distdir) && $(am__tar) | zstd -c $${ZSTD_CLEVEL-$${ZSTD_OPT--19}} >$(distdir).tar.zst - $(am__post_remove_distdir) - -dist-tarZ: distdir - @echo WARNING: "Support for distribution archives compressed with" \ - "legacy program 'compress' is deprecated." >&2 - @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 - tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z - $(am__post_remove_distdir) - -dist-shar: distdir - @echo WARNING: "Support for shar distribution archives is" \ - "deprecated." >&2 - @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 - shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz - $(am__post_remove_distdir) - -dist-zip: distdir - -rm -f $(distdir).zip - zip -rq $(distdir).zip $(distdir) - $(am__post_remove_distdir) - -dist dist-all: - $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:' - $(am__post_remove_distdir) - -# This target untars the dist file and tries a VPATH configuration. Then -# it guarantees that the distribution is self-contained by making another -# tarfile. -distcheck: dist - case '$(DIST_ARCHIVES)' in \ - *.tar.gz*) \ - eval GZIP= gzip -dc $(distdir).tar.gz | $(am__untar) ;;\ - *.tar.bz2*) \ - bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ - *.tar.bz3*) \ - bzip3 -dc $(distdir).tar.bz3 | $(am__untar) ;;\ - *.tar.lz*) \ - lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ - *.tar.xz*) \ - xz -dc $(distdir).tar.xz | $(am__untar) ;;\ - *.tar.Z*) \ - uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ - *.shar.gz*) \ - eval GZIP= gzip -dc $(distdir).shar.gz | unshar ;;\ - *.zip*) \ - unzip $(distdir).zip ;;\ - *.tar.zst*) \ - zstd -dc $(distdir).tar.zst | $(am__untar) ;;\ - esac - chmod -R a-w $(distdir) - chmod u+w $(distdir) - mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst - chmod a-w $(distdir) - test -d $(distdir)/_build || exit 0; \ - dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ - && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ - && am__cwd=`pwd` \ - && $(am__cd) $(distdir)/_build/sub \ - && ../../configure \ - $(AM_DISTCHECK_CONFIGURE_FLAGS) \ - $(DISTCHECK_CONFIGURE_FLAGS) \ - --srcdir=../.. --prefix="$$dc_install_base" \ - && $(MAKE) $(AM_MAKEFLAGS) \ - && $(MAKE) $(AM_MAKEFLAGS) $(AM_DISTCHECK_DVI_TARGET) \ - && $(MAKE) $(AM_MAKEFLAGS) check \ - && $(MAKE) $(AM_MAKEFLAGS) install \ - && $(MAKE) $(AM_MAKEFLAGS) installcheck \ - && $(MAKE) $(AM_MAKEFLAGS) uninstall \ - && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ - distuninstallcheck \ - && chmod -R a-w "$$dc_install_base" \ - && ({ \ - (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ - && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ - && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ - && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ - distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ - } || { rm -rf "$$dc_destdir"; exit 1; }) \ - && rm -rf "$$dc_destdir" \ - && $(MAKE) $(AM_MAKEFLAGS) dist \ - && rm -rf $(DIST_ARCHIVES) \ - && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ - && cd "$$am__cwd" \ - || exit 1 - $(am__post_remove_distdir) - @(echo "$(distdir) archives ready for distribution: "; \ - list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ - sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' -distuninstallcheck: - @test -n '$(distuninstallcheck_dir)' || { \ - echo 'ERROR: trying to run $@ with an empty' \ - '$$(distuninstallcheck_dir)' >&2; \ - exit 1; \ - }; \ - $(am__cd) '$(distuninstallcheck_dir)' || { \ - echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \ - exit 1; \ - }; \ - test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \ - || { echo "ERROR: files left after uninstall:" ; \ - if test -n "$(DESTDIR)"; then \ - echo " (check DESTDIR support)"; \ - fi ; \ - $(distuninstallcheck_listfiles) ; \ - exit 1; } >&2 -distcleancheck: distclean - @if test '$(srcdir)' = . ; then \ - echo "ERROR: distcleancheck can only run from a VPATH build" ; \ - exit 1 ; \ - fi - @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ - || { echo "ERROR: files left in build directory after distclean:" ; \ - $(distcleancheck_listfiles) ; \ - exit 1; } >&2 -check-am: all-am - $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS) -check: check-am -all-am: Makefile $(PROGRAMS) -installdirs: - for dir in "$(DESTDIR)$(bindir)"; do \ - test -z "$$dir" || $(MKDIR_P) "$$dir"; \ - done -install: install-am -install-exec: install-exec-am -install-data: install-data-am -uninstall: uninstall-am - -install-am: all-am - @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am - -installcheck: installcheck-am -install-strip: - if test -z '$(STRIP)'; then \ - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - install; \ - else \ - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ - fi -mostlyclean-generic: - -$(am__rm_f) compat/asprintf.$(OBJEXT) - -$(am__rm_f) compat/base64.$(OBJEXT) - -$(am__rm_f) compat/cfmakeraw.$(OBJEXT) - -$(am__rm_f) compat/clock_gettime.$(OBJEXT) - -$(am__rm_f) compat/closefrom.$(OBJEXT) - -$(am__rm_f) compat/daemon-darwin.$(OBJEXT) - -$(am__rm_f) compat/daemon.$(OBJEXT) - -$(am__rm_f) compat/err.$(OBJEXT) - -$(am__rm_f) compat/explicit_bzero.$(OBJEXT) - -$(am__rm_f) compat/fdforkpty.$(OBJEXT) - -$(am__rm_f) compat/fgetln.$(OBJEXT) - -$(am__rm_f) compat/freezero.$(OBJEXT) - -$(am__rm_f) compat/getdtablecount.$(OBJEXT) - -$(am__rm_f) compat/getdtablesize.$(OBJEXT) - -$(am__rm_f) compat/getline.$(OBJEXT) - -$(am__rm_f) compat/getopt_long.$(OBJEXT) - -$(am__rm_f) compat/getpeereid.$(OBJEXT) - -$(am__rm_f) compat/getprogname.$(OBJEXT) - -$(am__rm_f) compat/htonll.$(OBJEXT) - -$(am__rm_f) compat/imsg-buffer.$(OBJEXT) - -$(am__rm_f) compat/imsg.$(OBJEXT) - -$(am__rm_f) compat/memmem.$(OBJEXT) - -$(am__rm_f) compat/ntohll.$(OBJEXT) - -$(am__rm_f) compat/reallocarray.$(OBJEXT) - -$(am__rm_f) compat/recallocarray.$(OBJEXT) - -$(am__rm_f) compat/setenv.$(OBJEXT) - -$(am__rm_f) compat/setproctitle.$(OBJEXT) - -$(am__rm_f) compat/strcasestr.$(OBJEXT) - -$(am__rm_f) compat/strlcat.$(OBJEXT) - -$(am__rm_f) compat/strlcpy.$(OBJEXT) - -$(am__rm_f) compat/strndup.$(OBJEXT) - -$(am__rm_f) compat/strnlen.$(OBJEXT) - -$(am__rm_f) compat/strsep.$(OBJEXT) - -$(am__rm_f) compat/strtonum.$(OBJEXT) - -$(am__rm_f) compat/unvis.$(OBJEXT) - -$(am__rm_f) compat/vis.$(OBJEXT) - -clean-generic: - -$(am__rm_f) $(CLEANFILES) - -distclean-generic: - -$(am__rm_f) $(CONFIG_CLEAN_FILES) - -test . = "$(srcdir)" || $(am__rm_f) $(CONFIG_CLEAN_VPATH_FILES) - -$(am__rm_f) compat/$(DEPDIR)/$(am__dirstamp) - -$(am__rm_f) compat/$(am__dirstamp) - -$(am__rm_f) fuzz/$(DEPDIR)/$(am__dirstamp) - -$(am__rm_f) fuzz/$(am__dirstamp) - -maintainer-clean-generic: - @echo "This command is intended for maintainers to use" - @echo "it deletes files that may require special tools to rebuild." - -$(am__rm_f) cmd-parse.c -clean: clean-am - -clean-am: clean-binPROGRAMS clean-checkPROGRAMS clean-generic \ - mostlyclean-am - -distclean: distclean-am - -rm -f $(am__CONFIG_DISTCLEAN_FILES) - -rm -f ./$(DEPDIR)/alerts.Po - -rm -f ./$(DEPDIR)/arguments.Po - -rm -f ./$(DEPDIR)/attributes.Po - -rm -f ./$(DEPDIR)/cfg.Po - -rm -f ./$(DEPDIR)/client.Po - -rm -f ./$(DEPDIR)/cmd-attach-session.Po - -rm -f ./$(DEPDIR)/cmd-bind-key.Po - -rm -f ./$(DEPDIR)/cmd-break-pane.Po - -rm -f ./$(DEPDIR)/cmd-capture-pane.Po - -rm -f ./$(DEPDIR)/cmd-choose-tree.Po - -rm -f ./$(DEPDIR)/cmd-command-prompt.Po - -rm -f ./$(DEPDIR)/cmd-confirm-before.Po - -rm -f ./$(DEPDIR)/cmd-copy-mode.Po - -rm -f ./$(DEPDIR)/cmd-detach-client.Po - -rm -f ./$(DEPDIR)/cmd-display-menu.Po - -rm -f ./$(DEPDIR)/cmd-display-message.Po - -rm -f ./$(DEPDIR)/cmd-display-panes.Po - -rm -f ./$(DEPDIR)/cmd-find-window.Po - -rm -f ./$(DEPDIR)/cmd-find.Po - -rm -f ./$(DEPDIR)/cmd-if-shell.Po - -rm -f ./$(DEPDIR)/cmd-join-pane.Po - -rm -f ./$(DEPDIR)/cmd-kill-pane.Po - -rm -f ./$(DEPDIR)/cmd-kill-server.Po - -rm -f ./$(DEPDIR)/cmd-kill-session.Po - -rm -f ./$(DEPDIR)/cmd-kill-window.Po - -rm -f ./$(DEPDIR)/cmd-list-buffers.Po - -rm -f ./$(DEPDIR)/cmd-list-clients.Po - -rm -f ./$(DEPDIR)/cmd-list-commands.Po - -rm -f ./$(DEPDIR)/cmd-list-keys.Po - -rm -f ./$(DEPDIR)/cmd-list-panes.Po - -rm -f ./$(DEPDIR)/cmd-list-sessions.Po - -rm -f ./$(DEPDIR)/cmd-list-windows.Po - -rm -f ./$(DEPDIR)/cmd-load-buffer.Po - -rm -f ./$(DEPDIR)/cmd-lock-server.Po - -rm -f ./$(DEPDIR)/cmd-move-window.Po - -rm -f ./$(DEPDIR)/cmd-new-session.Po - -rm -f ./$(DEPDIR)/cmd-new-window.Po - -rm -f ./$(DEPDIR)/cmd-parse.Po - -rm -f ./$(DEPDIR)/cmd-paste-buffer.Po - -rm -f ./$(DEPDIR)/cmd-pipe-pane.Po - -rm -f ./$(DEPDIR)/cmd-queue.Po - -rm -f ./$(DEPDIR)/cmd-refresh-client.Po - -rm -f ./$(DEPDIR)/cmd-rename-session.Po - -rm -f ./$(DEPDIR)/cmd-rename-window.Po - -rm -f ./$(DEPDIR)/cmd-resize-pane.Po - -rm -f ./$(DEPDIR)/cmd-resize-window.Po - -rm -f ./$(DEPDIR)/cmd-respawn-pane.Po - -rm -f ./$(DEPDIR)/cmd-respawn-window.Po - -rm -f ./$(DEPDIR)/cmd-rotate-window.Po - -rm -f ./$(DEPDIR)/cmd-run-shell.Po - -rm -f ./$(DEPDIR)/cmd-save-buffer.Po - -rm -f ./$(DEPDIR)/cmd-select-layout.Po - -rm -f ./$(DEPDIR)/cmd-select-pane.Po - -rm -f ./$(DEPDIR)/cmd-select-window.Po - -rm -f ./$(DEPDIR)/cmd-send-keys.Po - -rm -f ./$(DEPDIR)/cmd-server-access.Po - -rm -f ./$(DEPDIR)/cmd-set-buffer.Po - -rm -f ./$(DEPDIR)/cmd-set-environment.Po - -rm -f ./$(DEPDIR)/cmd-set-option.Po - -rm -f ./$(DEPDIR)/cmd-show-environment.Po - -rm -f ./$(DEPDIR)/cmd-show-messages.Po - -rm -f ./$(DEPDIR)/cmd-show-options.Po - -rm -f ./$(DEPDIR)/cmd-show-prompt-history.Po - -rm -f ./$(DEPDIR)/cmd-source-file.Po - -rm -f ./$(DEPDIR)/cmd-split-window.Po - -rm -f ./$(DEPDIR)/cmd-swap-pane.Po - -rm -f ./$(DEPDIR)/cmd-swap-window.Po - -rm -f ./$(DEPDIR)/cmd-switch-client.Po - -rm -f ./$(DEPDIR)/cmd-unbind-key.Po - -rm -f ./$(DEPDIR)/cmd-wait-for.Po - -rm -f ./$(DEPDIR)/cmd.Po - -rm -f ./$(DEPDIR)/colour.Po - -rm -f ./$(DEPDIR)/control-notify.Po - -rm -f ./$(DEPDIR)/control.Po - -rm -f ./$(DEPDIR)/environ.Po - -rm -f ./$(DEPDIR)/file.Po - -rm -f ./$(DEPDIR)/format-draw.Po - -rm -f ./$(DEPDIR)/format.Po - -rm -f ./$(DEPDIR)/grid-reader.Po - -rm -f ./$(DEPDIR)/grid-view.Po - -rm -f ./$(DEPDIR)/grid.Po - -rm -f ./$(DEPDIR)/hyperlinks.Po - -rm -f ./$(DEPDIR)/image-sixel.Po - -rm -f ./$(DEPDIR)/image.Po - -rm -f ./$(DEPDIR)/input-keys.Po - -rm -f ./$(DEPDIR)/input.Po - -rm -f ./$(DEPDIR)/job.Po - -rm -f ./$(DEPDIR)/key-bindings.Po - -rm -f ./$(DEPDIR)/key-string.Po - -rm -f ./$(DEPDIR)/layout-custom.Po - -rm -f ./$(DEPDIR)/layout-set.Po - -rm -f ./$(DEPDIR)/layout.Po - -rm -f ./$(DEPDIR)/log.Po - -rm -f ./$(DEPDIR)/menu.Po - -rm -f ./$(DEPDIR)/mode-tree.Po - -rm -f ./$(DEPDIR)/names.Po - -rm -f ./$(DEPDIR)/notify.Po - -rm -f ./$(DEPDIR)/options-table.Po - -rm -f ./$(DEPDIR)/options.Po - -rm -f ./$(DEPDIR)/osdep-freebsd.Po - -rm -f ./$(DEPDIR)/paste.Po - -rm -f ./$(DEPDIR)/popup.Po - -rm -f ./$(DEPDIR)/proc.Po - -rm -f ./$(DEPDIR)/regsub.Po - -rm -f ./$(DEPDIR)/resize.Po - -rm -f ./$(DEPDIR)/screen-redraw.Po - -rm -f ./$(DEPDIR)/screen-write.Po - -rm -f ./$(DEPDIR)/screen.Po - -rm -f ./$(DEPDIR)/server-acl.Po - -rm -f ./$(DEPDIR)/server-client.Po - -rm -f ./$(DEPDIR)/server-fn.Po - -rm -f ./$(DEPDIR)/server.Po - -rm -f ./$(DEPDIR)/session.Po - -rm -f ./$(DEPDIR)/sort.Po - -rm -f ./$(DEPDIR)/spawn.Po - -rm -f ./$(DEPDIR)/status.Po - -rm -f ./$(DEPDIR)/style.Po - -rm -f ./$(DEPDIR)/tmux.Po - -rm -f ./$(DEPDIR)/tty-acs.Po - -rm -f ./$(DEPDIR)/tty-draw.Po - -rm -f ./$(DEPDIR)/tty-features.Po - -rm -f ./$(DEPDIR)/tty-keys.Po - -rm -f ./$(DEPDIR)/tty-term.Po - -rm -f ./$(DEPDIR)/tty.Po - -rm -f ./$(DEPDIR)/utf8-combined.Po - -rm -f ./$(DEPDIR)/utf8.Po - -rm -f ./$(DEPDIR)/window-border.Po - -rm -f ./$(DEPDIR)/window-buffer.Po - -rm -f ./$(DEPDIR)/window-client.Po - -rm -f ./$(DEPDIR)/window-clock.Po - -rm -f ./$(DEPDIR)/window-copy.Po - -rm -f ./$(DEPDIR)/window-customize.Po - -rm -f ./$(DEPDIR)/window-tree.Po - -rm -f ./$(DEPDIR)/window-visible.Po - -rm -f ./$(DEPDIR)/window.Po - -rm -f ./$(DEPDIR)/xmalloc.Po - -rm -f compat/$(DEPDIR)/asprintf.Po - -rm -f compat/$(DEPDIR)/base64.Po - -rm -f compat/$(DEPDIR)/cfmakeraw.Po - -rm -f compat/$(DEPDIR)/clock_gettime.Po - -rm -f compat/$(DEPDIR)/closefrom.Po - -rm -f compat/$(DEPDIR)/daemon-darwin.Po - -rm -f compat/$(DEPDIR)/daemon.Po - -rm -f compat/$(DEPDIR)/err.Po - -rm -f compat/$(DEPDIR)/explicit_bzero.Po - -rm -f compat/$(DEPDIR)/fdforkpty.Po - -rm -f compat/$(DEPDIR)/fgetln.Po - -rm -f compat/$(DEPDIR)/forkpty-freebsd.Po - -rm -f compat/$(DEPDIR)/freezero.Po - -rm -f compat/$(DEPDIR)/getdtablecount.Po - -rm -f compat/$(DEPDIR)/getdtablesize.Po - -rm -f compat/$(DEPDIR)/getline.Po - -rm -f compat/$(DEPDIR)/getopt_long.Po - -rm -f compat/$(DEPDIR)/getpeereid.Po - -rm -f compat/$(DEPDIR)/getprogname.Po - -rm -f compat/$(DEPDIR)/htonll.Po - -rm -f compat/$(DEPDIR)/imsg-buffer.Po - -rm -f compat/$(DEPDIR)/imsg.Po - -rm -f compat/$(DEPDIR)/memmem.Po - -rm -f compat/$(DEPDIR)/ntohll.Po - -rm -f compat/$(DEPDIR)/reallocarray.Po - -rm -f compat/$(DEPDIR)/recallocarray.Po - -rm -f compat/$(DEPDIR)/setenv.Po - -rm -f compat/$(DEPDIR)/setproctitle.Po - -rm -f compat/$(DEPDIR)/strcasestr.Po - -rm -f compat/$(DEPDIR)/strlcat.Po - -rm -f compat/$(DEPDIR)/strlcpy.Po - -rm -f compat/$(DEPDIR)/strndup.Po - -rm -f compat/$(DEPDIR)/strnlen.Po - -rm -f compat/$(DEPDIR)/strsep.Po - -rm -f compat/$(DEPDIR)/strtonum.Po - -rm -f compat/$(DEPDIR)/systemd.Po - -rm -f compat/$(DEPDIR)/unvis.Po - -rm -f compat/$(DEPDIR)/utf8proc.Po - -rm -f compat/$(DEPDIR)/vis.Po - -rm -f fuzz/$(DEPDIR)/cmd-parse-fuzzer.Po - -rm -f fuzz/$(DEPDIR)/format-fuzzer.Po - -rm -f fuzz/$(DEPDIR)/input-fuzzer.Po - -rm -f fuzz/$(DEPDIR)/style-fuzzer.Po - -rm -f Makefile -distclean-am: clean-am distclean-compile distclean-generic \ - distclean-tags - -dvi: dvi-am - -dvi-am: - -html: html-am - -html-am: - -info: info-am - -info-am: - -install-data-am: - -install-dvi: install-dvi-am - -install-dvi-am: - -install-exec-am: install-binPROGRAMS - @$(NORMAL_INSTALL) - $(MAKE) $(AM_MAKEFLAGS) install-exec-hook -install-html: install-html-am - -install-html-am: - -install-info: install-info-am - -install-info-am: - -install-man: - -install-pdf: install-pdf-am - -install-pdf-am: - -install-ps: install-ps-am - -install-ps-am: - -installcheck-am: - -maintainer-clean: maintainer-clean-am - -rm -f $(am__CONFIG_DISTCLEAN_FILES) - -rm -rf $(top_srcdir)/autom4te.cache - -rm -f ./$(DEPDIR)/alerts.Po - -rm -f ./$(DEPDIR)/arguments.Po - -rm -f ./$(DEPDIR)/attributes.Po - -rm -f ./$(DEPDIR)/cfg.Po - -rm -f ./$(DEPDIR)/client.Po - -rm -f ./$(DEPDIR)/cmd-attach-session.Po - -rm -f ./$(DEPDIR)/cmd-bind-key.Po - -rm -f ./$(DEPDIR)/cmd-break-pane.Po - -rm -f ./$(DEPDIR)/cmd-capture-pane.Po - -rm -f ./$(DEPDIR)/cmd-choose-tree.Po - -rm -f ./$(DEPDIR)/cmd-command-prompt.Po - -rm -f ./$(DEPDIR)/cmd-confirm-before.Po - -rm -f ./$(DEPDIR)/cmd-copy-mode.Po - -rm -f ./$(DEPDIR)/cmd-detach-client.Po - -rm -f ./$(DEPDIR)/cmd-display-menu.Po - -rm -f ./$(DEPDIR)/cmd-display-message.Po - -rm -f ./$(DEPDIR)/cmd-display-panes.Po - -rm -f ./$(DEPDIR)/cmd-find-window.Po - -rm -f ./$(DEPDIR)/cmd-find.Po - -rm -f ./$(DEPDIR)/cmd-if-shell.Po - -rm -f ./$(DEPDIR)/cmd-join-pane.Po - -rm -f ./$(DEPDIR)/cmd-kill-pane.Po - -rm -f ./$(DEPDIR)/cmd-kill-server.Po - -rm -f ./$(DEPDIR)/cmd-kill-session.Po - -rm -f ./$(DEPDIR)/cmd-kill-window.Po - -rm -f ./$(DEPDIR)/cmd-list-buffers.Po - -rm -f ./$(DEPDIR)/cmd-list-clients.Po - -rm -f ./$(DEPDIR)/cmd-list-commands.Po - -rm -f ./$(DEPDIR)/cmd-list-keys.Po - -rm -f ./$(DEPDIR)/cmd-list-panes.Po - -rm -f ./$(DEPDIR)/cmd-list-sessions.Po - -rm -f ./$(DEPDIR)/cmd-list-windows.Po - -rm -f ./$(DEPDIR)/cmd-load-buffer.Po - -rm -f ./$(DEPDIR)/cmd-lock-server.Po - -rm -f ./$(DEPDIR)/cmd-move-window.Po - -rm -f ./$(DEPDIR)/cmd-new-session.Po - -rm -f ./$(DEPDIR)/cmd-new-window.Po - -rm -f ./$(DEPDIR)/cmd-parse.Po - -rm -f ./$(DEPDIR)/cmd-paste-buffer.Po - -rm -f ./$(DEPDIR)/cmd-pipe-pane.Po - -rm -f ./$(DEPDIR)/cmd-queue.Po - -rm -f ./$(DEPDIR)/cmd-refresh-client.Po - -rm -f ./$(DEPDIR)/cmd-rename-session.Po - -rm -f ./$(DEPDIR)/cmd-rename-window.Po - -rm -f ./$(DEPDIR)/cmd-resize-pane.Po - -rm -f ./$(DEPDIR)/cmd-resize-window.Po - -rm -f ./$(DEPDIR)/cmd-respawn-pane.Po - -rm -f ./$(DEPDIR)/cmd-respawn-window.Po - -rm -f ./$(DEPDIR)/cmd-rotate-window.Po - -rm -f ./$(DEPDIR)/cmd-run-shell.Po - -rm -f ./$(DEPDIR)/cmd-save-buffer.Po - -rm -f ./$(DEPDIR)/cmd-select-layout.Po - -rm -f ./$(DEPDIR)/cmd-select-pane.Po - -rm -f ./$(DEPDIR)/cmd-select-window.Po - -rm -f ./$(DEPDIR)/cmd-send-keys.Po - -rm -f ./$(DEPDIR)/cmd-server-access.Po - -rm -f ./$(DEPDIR)/cmd-set-buffer.Po - -rm -f ./$(DEPDIR)/cmd-set-environment.Po - -rm -f ./$(DEPDIR)/cmd-set-option.Po - -rm -f ./$(DEPDIR)/cmd-show-environment.Po - -rm -f ./$(DEPDIR)/cmd-show-messages.Po - -rm -f ./$(DEPDIR)/cmd-show-options.Po - -rm -f ./$(DEPDIR)/cmd-show-prompt-history.Po - -rm -f ./$(DEPDIR)/cmd-source-file.Po - -rm -f ./$(DEPDIR)/cmd-split-window.Po - -rm -f ./$(DEPDIR)/cmd-swap-pane.Po - -rm -f ./$(DEPDIR)/cmd-swap-window.Po - -rm -f ./$(DEPDIR)/cmd-switch-client.Po - -rm -f ./$(DEPDIR)/cmd-unbind-key.Po - -rm -f ./$(DEPDIR)/cmd-wait-for.Po - -rm -f ./$(DEPDIR)/cmd.Po - -rm -f ./$(DEPDIR)/colour.Po - -rm -f ./$(DEPDIR)/control-notify.Po - -rm -f ./$(DEPDIR)/control.Po - -rm -f ./$(DEPDIR)/environ.Po - -rm -f ./$(DEPDIR)/file.Po - -rm -f ./$(DEPDIR)/format-draw.Po - -rm -f ./$(DEPDIR)/format.Po - -rm -f ./$(DEPDIR)/grid-reader.Po - -rm -f ./$(DEPDIR)/grid-view.Po - -rm -f ./$(DEPDIR)/grid.Po - -rm -f ./$(DEPDIR)/hyperlinks.Po - -rm -f ./$(DEPDIR)/image-sixel.Po - -rm -f ./$(DEPDIR)/image.Po - -rm -f ./$(DEPDIR)/input-keys.Po - -rm -f ./$(DEPDIR)/input.Po - -rm -f ./$(DEPDIR)/job.Po - -rm -f ./$(DEPDIR)/key-bindings.Po - -rm -f ./$(DEPDIR)/key-string.Po - -rm -f ./$(DEPDIR)/layout-custom.Po - -rm -f ./$(DEPDIR)/layout-set.Po - -rm -f ./$(DEPDIR)/layout.Po - -rm -f ./$(DEPDIR)/log.Po - -rm -f ./$(DEPDIR)/menu.Po - -rm -f ./$(DEPDIR)/mode-tree.Po - -rm -f ./$(DEPDIR)/names.Po - -rm -f ./$(DEPDIR)/notify.Po - -rm -f ./$(DEPDIR)/options-table.Po - -rm -f ./$(DEPDIR)/options.Po - -rm -f ./$(DEPDIR)/osdep-freebsd.Po - -rm -f ./$(DEPDIR)/paste.Po - -rm -f ./$(DEPDIR)/popup.Po - -rm -f ./$(DEPDIR)/proc.Po - -rm -f ./$(DEPDIR)/regsub.Po - -rm -f ./$(DEPDIR)/resize.Po - -rm -f ./$(DEPDIR)/screen-redraw.Po - -rm -f ./$(DEPDIR)/screen-write.Po - -rm -f ./$(DEPDIR)/screen.Po - -rm -f ./$(DEPDIR)/server-acl.Po - -rm -f ./$(DEPDIR)/server-client.Po - -rm -f ./$(DEPDIR)/server-fn.Po - -rm -f ./$(DEPDIR)/server.Po - -rm -f ./$(DEPDIR)/session.Po - -rm -f ./$(DEPDIR)/sort.Po - -rm -f ./$(DEPDIR)/spawn.Po - -rm -f ./$(DEPDIR)/status.Po - -rm -f ./$(DEPDIR)/style.Po - -rm -f ./$(DEPDIR)/tmux.Po - -rm -f ./$(DEPDIR)/tty-acs.Po - -rm -f ./$(DEPDIR)/tty-draw.Po - -rm -f ./$(DEPDIR)/tty-features.Po - -rm -f ./$(DEPDIR)/tty-keys.Po - -rm -f ./$(DEPDIR)/tty-term.Po - -rm -f ./$(DEPDIR)/tty.Po - -rm -f ./$(DEPDIR)/utf8-combined.Po - -rm -f ./$(DEPDIR)/utf8.Po - -rm -f ./$(DEPDIR)/window-border.Po - -rm -f ./$(DEPDIR)/window-buffer.Po - -rm -f ./$(DEPDIR)/window-client.Po - -rm -f ./$(DEPDIR)/window-clock.Po - -rm -f ./$(DEPDIR)/window-copy.Po - -rm -f ./$(DEPDIR)/window-customize.Po - -rm -f ./$(DEPDIR)/window-tree.Po - -rm -f ./$(DEPDIR)/window-visible.Po - -rm -f ./$(DEPDIR)/window.Po - -rm -f ./$(DEPDIR)/xmalloc.Po - -rm -f compat/$(DEPDIR)/asprintf.Po - -rm -f compat/$(DEPDIR)/base64.Po - -rm -f compat/$(DEPDIR)/cfmakeraw.Po - -rm -f compat/$(DEPDIR)/clock_gettime.Po - -rm -f compat/$(DEPDIR)/closefrom.Po - -rm -f compat/$(DEPDIR)/daemon-darwin.Po - -rm -f compat/$(DEPDIR)/daemon.Po - -rm -f compat/$(DEPDIR)/err.Po - -rm -f compat/$(DEPDIR)/explicit_bzero.Po - -rm -f compat/$(DEPDIR)/fdforkpty.Po - -rm -f compat/$(DEPDIR)/fgetln.Po - -rm -f compat/$(DEPDIR)/forkpty-freebsd.Po - -rm -f compat/$(DEPDIR)/freezero.Po - -rm -f compat/$(DEPDIR)/getdtablecount.Po - -rm -f compat/$(DEPDIR)/getdtablesize.Po - -rm -f compat/$(DEPDIR)/getline.Po - -rm -f compat/$(DEPDIR)/getopt_long.Po - -rm -f compat/$(DEPDIR)/getpeereid.Po - -rm -f compat/$(DEPDIR)/getprogname.Po - -rm -f compat/$(DEPDIR)/htonll.Po - -rm -f compat/$(DEPDIR)/imsg-buffer.Po - -rm -f compat/$(DEPDIR)/imsg.Po - -rm -f compat/$(DEPDIR)/memmem.Po - -rm -f compat/$(DEPDIR)/ntohll.Po - -rm -f compat/$(DEPDIR)/reallocarray.Po - -rm -f compat/$(DEPDIR)/recallocarray.Po - -rm -f compat/$(DEPDIR)/setenv.Po - -rm -f compat/$(DEPDIR)/setproctitle.Po - -rm -f compat/$(DEPDIR)/strcasestr.Po - -rm -f compat/$(DEPDIR)/strlcat.Po - -rm -f compat/$(DEPDIR)/strlcpy.Po - -rm -f compat/$(DEPDIR)/strndup.Po - -rm -f compat/$(DEPDIR)/strnlen.Po - -rm -f compat/$(DEPDIR)/strsep.Po - -rm -f compat/$(DEPDIR)/strtonum.Po - -rm -f compat/$(DEPDIR)/systemd.Po - -rm -f compat/$(DEPDIR)/unvis.Po - -rm -f compat/$(DEPDIR)/utf8proc.Po - -rm -f compat/$(DEPDIR)/vis.Po - -rm -f fuzz/$(DEPDIR)/cmd-parse-fuzzer.Po - -rm -f fuzz/$(DEPDIR)/format-fuzzer.Po - -rm -f fuzz/$(DEPDIR)/input-fuzzer.Po - -rm -f fuzz/$(DEPDIR)/style-fuzzer.Po - -rm -f Makefile -maintainer-clean-am: distclean-am maintainer-clean-generic - -mostlyclean: mostlyclean-am - -mostlyclean-am: mostlyclean-compile mostlyclean-generic - -pdf: pdf-am - -pdf-am: - -ps: ps-am - -ps-am: - -uninstall-am: uninstall-binPROGRAMS - @$(NORMAL_INSTALL) - $(MAKE) $(AM_MAKEFLAGS) uninstall-hook -.MAKE: check-am install-am install-exec-am install-strip uninstall-am - -.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles am--refresh check \ - check-am clean clean-binPROGRAMS clean-checkPROGRAMS \ - clean-cscope clean-generic cscope cscopelist-am ctags ctags-am \ - dist dist-all dist-bzip2 dist-bzip3 dist-gzip dist-lzip \ - dist-shar dist-tarZ dist-xz dist-zip dist-zstd distcheck \ - distclean distclean-compile distclean-generic distclean-tags \ - distcleancheck distdir distuninstallcheck dvi dvi-am html \ - html-am info info-am install install-am install-binPROGRAMS \ - install-data install-data-am install-dvi install-dvi-am \ - install-exec install-exec-am install-exec-hook install-html \ - install-html-am install-info install-info-am install-man \ - install-pdf install-pdf-am install-ps install-ps-am \ - install-strip installcheck installcheck-am installdirs \ - maintainer-clean maintainer-clean-generic mostlyclean \ - mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \ - tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \ - uninstall-hook - -.PRECIOUS: Makefile - - -# Install tmux.1 in the right format. -install-exec-hook: - if test xmdoc = xmdoc; then \ - sed -e "s|@SYSCONFDIR@|$(sysconfdir)|g" $(srcdir)/tmux.1 \ - >$(srcdir)/tmux.1.mdoc; \ - else \ - sed -e "s|@SYSCONFDIR@|$(sysconfdir)|g" $(srcdir)/tmux.1| \ - $(AWK) -f $(srcdir)/mdoc2man.awk >$(srcdir)/tmux.1.man; \ - fi - $(mkdir_p) $(DESTDIR)$(mandir)/man1 - $(INSTALL_DATA) $(srcdir)/tmux.1.mdoc \ - $(DESTDIR)$(mandir)/man1/tmux.1 - -# And uninstall it. -uninstall-hook: - rm -f $(DESTDIR)$(mandir)/man1/tmux.1 - -# Tell versions [3.59,3.63) of GNU make to not export all variables. -# Otherwise a system limit (for SysV at least) may be exceeded. -.NOEXPORT: - -# Tell GNU make to disable its built-in pattern rules. -%:: %,v -%:: RCS/%,v -%:: RCS/% -%:: s.% -%:: SCCS/s.% diff --git a/screen-redraw.c b/screen-redraw.c index bc6b9a8a9..e09605cad 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1139,10 +1139,6 @@ redraw_draw_border_arrow(struct redraw_draw_ctx *dctx, else return; - -#ifdef ENABLE_SIXEL - tty_draw_images(c, wp, s); -#endif utf8_set(&gc->data, ch); gc->attr |= GRID_ATTR_CHARSET; } @@ -1622,8 +1618,16 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) tty_reset(tty); tty_sync_end(tty); - log_debug("%s: finished @%u redraw", c->name, scene->w->id); +#ifdef ENABLE_SIXEL + if (wp != NULL) + tty_draw_images(c, wp); + else { + TAILQ_FOREACH(loop, &scene->w->panes, entry) + tty_draw_images(c, wp); + } +#endif + log_debug("%s: finished @%u redraw", c->name, scene->w->id); } /* Get border cell type beneath status cell at offset x in pane status line. */ diff --git a/tmux.h b/tmux.h index 58885495e..2dfe25792 100644 --- a/tmux.h +++ b/tmux.h @@ -2753,7 +2753,7 @@ void tty_cmd_setselection(struct tty *, const struct tty_ctx *); void tty_cmd_rawstring(struct tty *, const struct tty_ctx *); #ifdef ENABLE_SIXEL void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *); -void tty_draw_images(struct client *, struct window_pane *, struct screen *); +void tty_draw_images(struct client *, struct window_pane *); #endif void tty_cmd_syncstart(struct tty *, const struct tty_ctx *); void tty_default_colours(struct grid_cell *, struct window_pane *, u_int *); diff --git a/tty.c b/tty.c index ed3028c41..470d7dc89 100644 --- a/tty.c +++ b/tty.c @@ -1545,20 +1545,20 @@ tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c) } void -tty_draw_images(struct client *c, struct window_pane *wp, struct screen *s) +tty_draw_images(struct client *c, struct window_pane *wp) { struct image *im; struct tty_ctx ttyctx; - TAILQ_FOREACH(im, &s->images, entry) { + TAILQ_FOREACH(im, &wp->screen->images, entry) { memset(&ttyctx, 0, sizeof ttyctx); /* Set the client independent properties. */ ttyctx.ocx = im->px; ttyctx.ocy = im->py; - ttyctx.orlower = s->rlower; - ttyctx.orupper = s->rupper; + ttyctx.orlower = wp->screen->rlower; + ttyctx.orupper = wp->screen->rupper; ttyctx.xoff = ttyctx.rxoff = wp->xoff; ttyctx.sx = wp->sx; From 9d58ff97fe708c4b349b0bc5a4c0da7c777b4e70 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 23 Jun 2026 09:14:13 +0100 Subject: [PATCH 09/71] Update format test. --- regress/format-strings.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/regress/format-strings.sh b/regress/format-strings.sh index bae66ab00..a7bcd5814 100644 --- a/regress/format-strings.sh +++ b/regress/format-strings.sh @@ -234,4 +234,14 @@ test_format "#{l:#{#}}}" "#{#}}" #test_format "#{l:#{}" "" #test_format "#{l:#{#}}" "" +# Fuzzy (subsequence) match operator m/z +test_format "#{m/z:wtr,window-tree}" "1" # subsequence in order +test_format "#{m/z:tree,window-tree}" "1" # contiguous is a subsequence +test_format "#{m/z:xyz,window-tree}" "0" # not a subsequence +test_format "#{m/z:wrt,window-tree}" "0" # right letters, wrong order +test_format "#{m/z:,window-tree}" "1" # empty pattern matches +test_format "#{m/z:window-tree,wtr}" "0" # pattern longer than text +test_format "#{m/z:WTR,window-tree}" "0" # case-sensitive by default +test_format "#{m/zi:WTR,window-tree}" "1" # /zi is case-insensitive + exit 0 From b82048eb18e666156546b4a3811b9fad4b46d5ed Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 23 Jun 2026 08:20:15 +0000 Subject: [PATCH 10/71] Add c key to clear filter. --- mode-tree.c | 15 +++++++++++++++ tmux.1 | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/mode-tree.c b/mode-tree.c index ff31edb47..88552a7d0 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -1113,6 +1113,17 @@ mode_tree_filter_free(void *data) mode_tree_remove_ref(data); } +static void +mode_tree_clear_filter(struct mode_tree_data *mtd) +{ + free(mtd->filter); + mtd->filter = NULL; + + mode_tree_build(mtd); + mode_tree_draw(mtd); + mtd->wp->flags |= PANE_REDRAW; +} + static void mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx, key_code key, void *data) @@ -1476,11 +1487,15 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mode_tree_search_set(mtd); break; case 'f': + case 'F': mtd->references++; status_prompt_set(c, NULL, "(filter) ", mtd->filter, mode_tree_filter_callback, mode_tree_filter_free, mtd, PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); break; + case 'c': + mode_tree_clear_filter(mtd); + break; case 'v': switch (mtd->preview) { case MODE_TREE_PREVIEW_OFF: diff --git a/tmux.1 b/tmux.1 index da5d126e9..7f1403e73 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2831,6 +2831,7 @@ The following keys may be used in client mode: .It Li "z" Ta "Suspend selected client" .It Li "Z" Ta "Suspend tagged clients" .It Li "f" Ta "Enter a format to filter items" +.It Li "c" Ta "Clear the current filter" .It Li "O" Ta "Change sort order" .It Li "r" Ta "Reverse sort order" .It Li "v" Ta "Toggle preview" @@ -2926,6 +2927,7 @@ The following keys may be used in tree mode: .It Li "C\-t" Ta "Tag all items" .It Li "\&:" Ta "Run a command for each tagged item" .It Li "f" Ta "Enter a format to filter items" +.It Li "c" Ta "Clear the current filter" .It Li "H" Ta "Jump to the starting pane" .It Li "O" Ta "Change sort order" .It Li "r" Ta "Reverse sort order" @@ -3024,6 +3026,7 @@ The following keys may be used in customize mode: .It Li "T" Ta "Tag no items" .It Li "C\-t" Ta "Tag all items" .It Li "f" Ta "Enter a format to filter items" +.It Li "c" Ta "Clear the current filter" .It Li "v" Ta "Toggle option information" .It Li "F1 or C\-h" Ta "Display help" .It Li "q" Ta "Exit mode" @@ -7900,6 +7903,7 @@ The following keys may be used in buffer mode: .It Li "D" Ta "Delete tagged buffers" .It Li "e" Ta "Open the buffer in an editor" .It Li "f" Ta "Enter a format to filter items" +.It Li "c" Ta "Clear the current filter" .It Li "O" Ta "Change sort order" .It Li "r" Ta "Reverse sort order" .It Li "v" Ta "Toggle preview" From 748ef8b2ae314e2e5b99704cae0770632b32ed26 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 23 Jun 2026 08:35:28 +0000 Subject: [PATCH 11/71] Extend match to do multiple terms. --- format.c | 34 +++++++++++++++++++++++++++++++--- tmux.1 | 3 ++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/format.c b/format.c index 797f64659..9aa6fc813 100644 --- a/format.c +++ b/format.c @@ -4507,11 +4507,14 @@ format_build_modifiers(struct format_expand_state *es, const char **s, return (list); } -/* Fuzzy match strings. */ +/* Fuzzy match a single token (no spaces). */ static int -format_fuzzy_match(const char *pattern, const char *text, int icase) +format_fuzzy_match_token(const char *pattern, size_t patternlen, + const char *text, int icase) { - while (*pattern != '\0') { + const char *end = pattern + patternlen; + + while (pattern != end) { if (*text == '\0') return (0); if (icase) { @@ -4526,6 +4529,31 @@ format_fuzzy_match(const char *pattern, const char *text, int icase) return (1); } +/* + * Fuzzy match strings. The pattern is split on spaces into tokens and every + * token must match as a sequence. + */ +static int +format_fuzzy_match(const char *pattern, const char *text, int icase) +{ + const char *start; + size_t len; + + while (*pattern != '\0') { + while (*pattern == ' ') + pattern++; + if (*pattern == '\0') + break; + start = pattern; + while (*pattern != '\0' && *pattern != ' ') + pattern++; + len = pattern - start; + if (!format_fuzzy_match_token(start, len, text, icase)) + return (0); + } + return (1); +} + /* Match against an fnmatch(3) pattern or regular expression. */ static char * format_match(struct format_modifier *fm, const char *pattern, const char *text) diff --git a/tmux.1 b/tmux.1 index 7f1403e73..dcb27b2fb 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6385,7 +6385,8 @@ means the pattern is a regular expression instead of the default pattern, .Ql z means the pattern is a fuzzy match, that is, it matches if its characters -appear in the string in order but not necessarily consecutively, +appear in the string in order but not necessarily consecutively; +the pattern is split on spaces into separate terms which must all match, and .Ql i means to ignore case. From de1940065aa37f67869a67b09feac97f0b042c35 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 23 Jun 2026 10:01:01 +0100 Subject: [PATCH 12/71] Update test. --- regress/format-strings.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/regress/format-strings.sh b/regress/format-strings.sh index a7bcd5814..82156d7b8 100644 --- a/regress/format-strings.sh +++ b/regress/format-strings.sh @@ -243,5 +243,10 @@ test_format "#{m/z:,window-tree}" "1" # empty pattern matches test_format "#{m/z:window-tree,wtr}" "0" # pattern longer than text test_format "#{m/z:WTR,window-tree}" "0" # case-sensitive by default test_format "#{m/zi:WTR,window-tree}" "1" # /zi is case-insensitive +test_format "#{m/z:win tree,window-tree}" "1" # space-separated tokens are ANDed +test_format "#{m/z:wn tr,window-tree}" "1" # each token matched as subsequence +test_format "#{m/z:win xyz,window-tree}" "0" # all tokens must match +test_format "#{m/z: tree ,window-tree}" "1" # leading/trailing spaces ignored +test_format "#{m/z:wt tr,window-tree}" "1" # repeated spaces ignored exit 0 From 3ec115115d9d0967852c2877b2755d7b1a8dd912 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 23 Jun 2026 09:13:30 +0000 Subject: [PATCH 13/71] Do not leak error message, use it instead. From Dane Jensen. --- layout.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/layout.c b/layout.c index 6fe75a9ed..1aaaff799 100644 --- a/layout.c +++ b/layout.c @@ -1507,6 +1507,7 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, enum layout_type type; u_int curval; int size = -1; + char *error = NULL; if (window_pane_is_floating(wp)) { *cause = xstrdup("can't split a floating pane"); @@ -1533,15 +1534,16 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, if (args_has(args, 'l')) { size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, - item, cause); + item, &error); } else if (args_has(args, 'p')) { size = args_strtonum_and_expand(args, 'p', 0, 100, item, - cause); - if (*cause == NULL) + &error); + if (error == NULL) size = curval * size / 100; } - if (*cause != NULL) { - *cause = xstrdup("invalid tiled geometry"); + if (error != NULL) { + xasprintf(cause, "invalid tiled geometry %s", error); + free(error); return (NULL); } From 9dba08ac8bd1d8f6eb508960172ca851ddfc06ec Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 23 Jun 2026 09:29:26 +0000 Subject: [PATCH 14/71] Allow -p more than 100%, and account for borders when sizing new panes. From Dane Jensen. --- arguments.c | 4 ++-- cmd-resize-pane.c | 4 ++-- cmd-split-window.c | 56 +++++++++++++++++++++++++--------------------- layout.c | 24 ++++++++++++++------ options.c | 12 ++++++++++ tmux.h | 5 ++++- window.c | 9 ++++++++ 7 files changed, 76 insertions(+), 38 deletions(-) diff --git a/arguments.c b/arguments.c index f4da6458a..fdf43752f 100644 --- a/arguments.c +++ b/arguments.c @@ -998,7 +998,7 @@ args_string_percentage(const char *value, long long minval, long long maxval, copy = xstrdup(value); copy[valuelen - 1] = '\0'; - ll = strtonum(copy, 0, 100, &errstr); + ll = strtonum(copy, 0, 1000, &errstr); free(copy); if (errstr != NULL) { *cause = xstrdup(errstr); @@ -1066,7 +1066,7 @@ args_string_percentage_and_expand(const char *value, long long minval, copy[valuelen - 1] = '\0'; f = format_single_from_target(item, copy); - ll = strtonum(f, 0, 100, &errstr); + ll = strtonum(f, 0, 1000, &errstr); free(f); free(copy); if (errstr != NULL) { diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index bc91ab2bc..8e548dc0e 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -94,7 +94,7 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) server_unzoom_window(w); if (args_has(args, 'x')) { - x = args_percentage(args, 'x', 0, INT_MAX, w->sx, &cause); + x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause); if (cause != NULL) { cmdq_error(item, "width %s", cause); free(cause); @@ -112,7 +112,7 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x); } if (args_has(args, 'y')) { - y = args_percentage(args, 'y', 0, INT_MAX, w->sy, &cause); + y = args_percentage(args, 'y', 0, PANE_MAXIMUM, w->sy, &cause); if (cause != NULL) { cmdq_error(item, "height %s", cause); free(cause); diff --git a/cmd-split-window.c b/cmd-split-window.c index bbce4dbf8..cdc42cefe 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -88,7 +88,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) int input, empty, is_floating, flags = 0; const char *template, *style, *value; char *cause = NULL, *cp, *title; - struct options_entry *oe; + const struct options_table_entry *oe; struct args_value *av; enum pane_lines lines; u_int count = args_count(args); @@ -118,8 +118,20 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) if (empty) flags |= SPAWN_EMPTY; + if ((value = args_get(args, 'B')) == NULL) + lines = window_get_pane_lines(w); + else { + oe = options_search("pane-border-lines"); + lines = options_find_choice(oe, value, &cause); + if (cause != NULL) { + cmdq_error(item, "pane-border-lines %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + if (is_floating) - lc = layout_get_floating_cell(item, args, w, wp, &cause); + lc = layout_get_floating_cell(item, args, lines, w, wp, &cause); else lc = layout_get_tiled_cell(item, args, w, wp, flags, &cause); if (cause != NULL) { @@ -156,10 +168,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) 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); + goto fail; } style = args_get(args, 's'); @@ -167,7 +176,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) if (options_set_string(new_wp->options, "window-style", 0, "%s", style) == NULL) { cmdq_error(item, "bad style: %s", style); - return (CMD_RETURN_ERROR); + goto fail; } options_set_string(new_wp->options, "window-active-style", 0, "%s", style); @@ -179,7 +188,7 @@ cmd_split_window_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); + goto fail; } } style = args_get(args, 'R'); @@ -188,22 +197,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) "%s", style) == NULL) { cmdq_error(item, "bad inactive border style: %s", style); - return (CMD_RETURN_ERROR); + goto fail; } } - 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, 'B')) + 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')) { @@ -228,10 +226,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) 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); + goto fail; case 1: input = 0; break; @@ -273,4 +268,13 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_WAIT); } return (CMD_RETURN_NORMAL); + +fail: + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); + layout_destroy_cell(w, lc, &w->layout_root); + + return (CMD_RETURN_ERROR); + } diff --git a/layout.c b/layout.c index 1aaaff799..de6f10cde 100644 --- a/layout.c +++ b/layout.c @@ -20,6 +20,7 @@ #include #include +#include #include "tmux.h" @@ -1563,7 +1564,8 @@ 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, struct window_pane *wp, char **cause) + enum pane_lines lines, struct window *w, struct window_pane *wp, + char **cause) { struct layout_cell *lcnew; int sx = w->sx / 2, sy = w->sy / 4; @@ -1571,22 +1573,26 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, char *error; if (args_has(args, 'x')) { - sx = args_percentage_and_expand(args, 'x', 0, w->sx - 1, w->sx, - item, &error); + sx = args_percentage_and_expand(args, 'x', 0, PANE_MAXIMUM, + w->sx, item, &error); if (error != NULL) { xasprintf(cause, "position %s", error); free(error); return (NULL); } + if (lines != PANE_LINES_NONE) + sx -= 2; } if (args_has(args, 'y')) { - sy = args_percentage_and_expand(args, 'y', 0, w->sy - 1, w->sy, - item, &error); + sy = args_percentage_and_expand(args, 'y', 0, PANE_MAXIMUM, + w->sy, item, &error); if (error != NULL) { xasprintf(cause, "position %s", error); free(error); return (NULL); } + if (lines != PANE_LINES_NONE) + sy -= 2; } if (args_has(args, 'X')) { ox = args_percentage_and_expand(args, 'X', -sx, w->sx, @@ -1616,7 +1622,9 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, ox = 4; } w->last_new_pane_x = ox; - } + } else + if (lines != PANE_LINES_NONE) + ox += 1; if (oy == INT_MAX) { if (w->last_new_pane_y == 0) oy = 2; @@ -1626,7 +1634,9 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, oy = 2; } w->last_new_pane_y = oy; - } + } else + if (lines != PANE_LINES_NONE) + oy += 1; if (sx < PANE_MINIMUM || sx > PANE_MAXIMUM) { *cause = xstrdup("invalid width"); diff --git a/options.c b/options.c index a9ef2f945..a4081a8d2 100644 --- a/options.c +++ b/options.c @@ -662,6 +662,18 @@ options_parse_get(struct options *oo, const char *s, int *idx, int only) return (o); } +const struct options_table_entry * +options_search(const char *name) +{ + const struct options_table_entry *oe; + + for (oe = options_table; oe->name != NULL; oe++) { + if (strcmp(oe->name, name) == 0) + return (oe); + } + return (NULL); +} + char * options_match(const char *s, int *idx, int *ambiguous) { diff --git a/tmux.h b/tmux.h index a453aef76..8d9a1105e 100644 --- a/tmux.h +++ b/tmux.h @@ -2558,6 +2558,7 @@ char *options_to_string(struct options_entry *, int, int); char *options_parse(const char *, int *); struct options_entry *options_parse_get(struct options *, const char *, int *, int); +const struct options_table_entry *options_search(const char *); char *options_match(const char *, int *, int *); struct options_entry *options_match_get(struct options *, const char *, int *, int, int *); @@ -3472,6 +3473,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 *); enum pane_lines window_pane_get_pane_lines(struct window_pane *); +enum pane_lines window_get_pane_lines(struct window *); 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, @@ -3535,7 +3537,8 @@ void layout_spread_out(struct window_pane *); struct layout_cell *layout_get_tiled_cell(struct cmdq_item *, struct args *, struct window *, struct window_pane *, int, char **); struct layout_cell *layout_get_floating_cell(struct cmdq_item *, struct args *, - struct window *, struct window_pane *, char **); + enum pane_lines, struct window *, struct window_pane *, + char **); int layout_remove_tile(struct window *, struct layout_cell *); /* layout-custom.c */ diff --git a/window.c b/window.c index 8862e65da..fe4047e96 100644 --- a/window.c +++ b/window.c @@ -2177,6 +2177,15 @@ window_pane_get_pane_lines(struct window_pane *wp) return (options_get_number(oo, "pane-border-lines")); } +enum pane_lines +window_get_pane_lines(struct window *w) +{ + struct options *oo; + + oo = w->options; + return (options_get_number(oo, "pane-border-lines")); +} + int window_get_pane_status(struct window *w) { From de086f9848d1276849c2820152b4fc29dc40b859 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 23 Jun 2026 11:29:27 +0000 Subject: [PATCH 15/71] Fix an infinite loop in customize mode when a filter does not match, and tweak a tmux.1 example. --- tmux.1 | 2 +- window-customize.c | 1 + window.c | 18 +++++++++--------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tmux.1 b/tmux.1 index dcb27b2fb..0589830ca 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3603,7 +3603,7 @@ The flag 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 & +$ make 2>&1|tmux new\-pane \-dI & .Ed .Pp All other options have the same meaning as for the diff --git a/window-customize.c b/window-customize.c index a31acc8ba..3a74989e1 100644 --- a/window-customize.c +++ b/window-customize.c @@ -472,6 +472,7 @@ window_customize_build_keys(struct window_customize_modedata *data, expanded = format_expand(ft, filter); if (!format_true(expanded)) { free(expanded); + bd = key_bindings_next(kt, bd); continue; } free(expanded); diff --git a/window.c b/window.c index fe4047e96..9759d78a6 100644 --- a/window.c +++ b/window.c @@ -2165,6 +2165,15 @@ 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_get_pane_lines(struct window *w) +{ + struct options *oo; + + oo = w->options; + return (options_get_number(oo, "pane-border-lines")); +} + enum pane_lines window_pane_get_pane_lines(struct window_pane *wp) { @@ -2177,15 +2186,6 @@ window_pane_get_pane_lines(struct window_pane *wp) return (options_get_number(oo, "pane-border-lines")); } -enum pane_lines -window_get_pane_lines(struct window *w) -{ - struct options *oo; - - oo = w->options; - return (options_get_number(oo, "pane-border-lines")); -} - int window_get_pane_status(struct window *w) { From 505d557fbb8d8919387a311f074cab130e5c40b5 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 23 Jun 2026 15:47:11 +0100 Subject: [PATCH 16/71] Fix SIXEL, from Marc Haesen. --- screen-redraw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen-redraw.c b/screen-redraw.c index e09605cad..12423ce46 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1623,7 +1623,7 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) tty_draw_images(c, wp); else { TAILQ_FOREACH(loop, &scene->w->panes, entry) - tty_draw_images(c, wp); + tty_draw_images(c, loop); } #endif From 1c394b8606b6070399ee8d6858376f8d01f0aa87 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 23 Jun 2026 20:31:42 +0100 Subject: [PATCH 17/71] Tweak the test. --- regress/format-strings.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/regress/format-strings.sh b/regress/format-strings.sh index 82156d7b8..da3fc0edb 100644 --- a/regress/format-strings.sh +++ b/regress/format-strings.sh @@ -249,4 +249,10 @@ test_format "#{m/z:win xyz,window-tree}" "0" # all tokens must match test_format "#{m/z: tree ,window-tree}" "1" # leading/trailing spaces ignored test_format "#{m/z:wt tr,window-tree}" "1" # repeated spaces ignored +# Space splits the pattern into tokens which must all match (AND) +test_format "#{m/z:win tree,window-tree}" "1" # both tokens present +test_format "#{m/z:tree win,window-tree}" "1" # token order independent +test_format "#{m/z:win xyz,window-tree}" "0" # second token absent +test_format "#{m/z: ,window-tree}" "1" # only spaces is empty pattern + exit 0 From c0a5e10313835cee31aa0802ae444184b9ecd70c Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 23 Jun 2026 20:07:58 +0000 Subject: [PATCH 18/71] Use flags for input callback instead of a single int done so the callback can be told about cursor movement in an incremental prompt. --- cmd-command-prompt.c | 13 ++++++------ cmd-confirm-before.c | 4 ++-- mode-tree.c | 4 ++-- status.c | 50 +++++++++++++++++++++++++++----------------- tmux.h | 5 ++++- window-customize.c | 10 ++++----- window-tree.c | 6 +++--- 7 files changed, 53 insertions(+), 39 deletions(-) diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index c5f958cda..95368d41b 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -35,7 +35,7 @@ static enum cmd_retval cmd_command_prompt_exec(struct cmd *, struct cmdq_item *); static int cmd_command_prompt_callback(struct client *, void *, - const char *, int); + const char *, key_code, int); static void cmd_command_prompt_free(void *); const struct cmd_entry cmd_command_prompt_entry = { @@ -178,7 +178,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) static int cmd_command_prompt_callback(struct client *c, void *data, const char *s, - int done) + __unused key_code key, int flags) { struct cmd_command_prompt_cdata *cdata = data; char *error; @@ -188,10 +188,10 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, int argc = 0; char **argv = NULL; - if (s == NULL) + if (s == NULL || (flags & PROMPT_INPUT_MOVE)) goto out; - if (done) { + if (flags & PROMPT_INPUT_DONE) { if (cdata->flags & PROMPT_INCREMENTAL) goto out; cmd_append_argv(&cdata->argc, &cdata->argv, s); @@ -204,10 +204,9 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, argc = cdata->argc; argv = cmd_copy_argv(cdata->argc, cdata->argv); - if (!done) + if (~flags & PROMPT_INPUT_DONE) cmd_append_argv(&argc, &argv, s); - - if (done) { + else { cmd_free_argv(cdata->argc, cdata->argv); cdata->argc = argc; cdata->argv = cmd_copy_argv(argc, argv); diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c index 72e9d87d1..8511018fc 100644 --- a/cmd-confirm-before.c +++ b/cmd-confirm-before.c @@ -34,7 +34,7 @@ static enum cmd_retval cmd_confirm_before_exec(struct cmd *, struct cmdq_item *); static int cmd_confirm_before_callback(struct client *, void *, - const char *, int); + const char *, key_code, int); static void cmd_confirm_before_free(void *); const struct cmd_entry cmd_confirm_before_entry = { @@ -120,7 +120,7 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) static int cmd_confirm_before_callback(struct client *c, void *data, const char *s, - __unused int done) + __unused key_code key, __unused int flags) { struct cmd_confirm_before_data *cdata = data; struct cmdq_item *item = cdata->item, *new_item; diff --git a/mode-tree.c b/mode-tree.c index 88552a7d0..cae060ecd 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -1059,7 +1059,7 @@ mode_tree_search_set(struct mode_tree_data *mtd) static int mode_tree_search_callback(__unused struct client *c, void *data, const char *s, - __unused int done) + __unused key_code key, __unused int flags) { struct mode_tree_data *mtd = data; @@ -1086,7 +1086,7 @@ mode_tree_search_free(void *data) static int mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, - __unused int done) + __unused key_code key, __unused int flags) { struct mode_tree_data *mtd = data; diff --git a/status.c b/status.c index 0e01dbff3..0f7f4ba68 100644 --- a/status.c +++ b/status.c @@ -670,9 +670,10 @@ static enum cmd_retval status_prompt_accept(__unused struct cmdq_item *item, void *data) { struct client *c = data; + void *pd = c->prompt_data; if (c->prompt_string != NULL) { - c->prompt_inputcb(c, c->prompt_data, "y", 1); + c->prompt_inputcb(c, pd, "y", KEYC_NONE, PROMPT_INPUT_DONE); status_prompt_clear(c); } return (CMD_RETURN_NORMAL); @@ -735,7 +736,7 @@ status_prompt_set(struct client *c, struct cmd_find_state *fs, c->flags |= CLIENT_REDRAWSTATUS; if (flags & PROMPT_INCREMENTAL) - c->prompt_inputcb(c, c->prompt_data, "=", 0); + c->prompt_inputcb(c, data, "=", KEYC_NONE, 0); if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT)) cmdq_append(c, cmdq_get_callback(status_prompt_accept, c)); @@ -1440,20 +1441,34 @@ status_prompt_incremental_exit_key(key_code key) return (0); } +/* Fire input callback when done. */ +static int +status_prompt_done(struct client *c, const char *s) +{ + struct prompt_data *pd = c->prompt_data; + + if (c->prompt_inputcb(c, pd, s, KEYC_NONE, PROMPT_INPUT_DONE) == 0) { + status_prompt_clear(c); + return (0); + } + return (1); +} + /* Handle keys in prompt. */ int status_prompt_key(struct client *c, key_code key) { + struct prompt_data *pd = c->prompt_data; struct options *oo = c->session->options; char *s, *cp, prefix = '='; - const char *histstr, *separators = NULL, *keystring; + const char *histstr, *separators = NULL, *ks; size_t size, idx; struct utf8_data tmp; int keys, word_is_separators; if (c->prompt_flags & PROMPT_KEY) { - keystring = key_string_lookup_key(key, 0); - c->prompt_inputcb(c, c->prompt_data, keystring, 1); + ks = key_string_lookup_key(key, 0); + c->prompt_inputcb(c, pd, ks, KEYC_NONE, PROMPT_INPUT_DONE); status_prompt_clear(c); return (0); } @@ -1466,7 +1481,7 @@ status_prompt_key(struct client *c, key_code key) if (key >= '0' && key <= '9') goto append_key; s = utf8_tocstr(c->prompt_buffer); - c->prompt_inputcb(c, c->prompt_data, s, 1); + c->prompt_inputcb(c, pd, s, KEYC_NONE, PROMPT_INPUT_DONE); status_prompt_clear(c); free(s); return (1); @@ -1501,10 +1516,11 @@ process_key: if ((c->prompt_flags & PROMPT_INCREMENTAL) && status_prompt_incremental_exit_key(key)) { s = utf8_tocstr(c->prompt_buffer); - c->prompt_inputcb(c, c->prompt_data, s, 1); - status_prompt_clear(c); - free(s); - return (1); + if (status_prompt_done(c, s) == 0) { + free(s); + return (1); + } + return (0); } switch (key) { @@ -1543,8 +1559,7 @@ process_key: case KEYC_BSPACE: case 'h'|KEYC_CTRL: if (c->prompt_flags & PROMPT_BSPACE_EXIT && size == 0) { - if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) - status_prompt_clear(c); + status_prompt_done(c, NULL); break; } if (c->prompt_index != 0) { @@ -1689,16 +1704,14 @@ process_key: s = utf8_tocstr(c->prompt_buffer); if (*s != '\0') status_prompt_add_history(s, c->prompt_type); - if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) - status_prompt_clear(c); + status_prompt_done(c, s); free(s); break; case '\033': /* Escape */ case '['|KEYC_CTRL: case 'c'|KEYC_CTRL: case 'g'|KEYC_CTRL: - if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) - status_prompt_clear(c); + status_prompt_done(c, NULL); break; case 'r'|KEYC_CTRL: if (~c->prompt_flags & PROMPT_INCREMENTAL) @@ -1763,8 +1776,7 @@ append_key: status_prompt_clear(c); else { s = utf8_tocstr(c->prompt_buffer); - if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) - status_prompt_clear(c); + status_prompt_done(c, s); free(s); } } @@ -1774,7 +1786,7 @@ changed: if (c->prompt_flags & PROMPT_INCREMENTAL) { s = utf8_tocstr(c->prompt_buffer); xasprintf(&cp, "%c%s", prefix, s); - c->prompt_inputcb(c, c->prompt_data, cp, 0); + c->prompt_inputcb(c, pd, cp, KEYC_NONE, 0); free(cp); free(s); } diff --git a/tmux.h b/tmux.h index 8d9a1105e..3f4d7823b 100644 --- a/tmux.h +++ b/tmux.h @@ -1991,7 +1991,10 @@ RB_HEAD(client_windows, client_window); #define CLIENT_PASTE_TIME_LIMIT 5 /* Client connection. */ -typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); +#define PROMPT_INPUT_DONE 0x1 +#define PROMPT_INPUT_MOVE 0x2 +typedef int (*prompt_input_cb)(struct client *, void *, const char *, key_code, + int); typedef void (*prompt_free_cb)(void *); typedef struct visible_ranges *(*overlay_check_cb)(struct client *, void *, u_int, u_int, u_int); diff --git a/window-customize.c b/window-customize.c index 3a74989e1..8f9784265 100644 --- a/window-customize.c +++ b/window-customize.c @@ -981,7 +981,7 @@ window_customize_free_item_callback(void *itemdata) static int window_customize_set_option_callback(struct client *c, void *itemdata, - const char *s, __unused int done) + const char *s, __unused key_code key, __unused int flags) { struct window_customize_itemdata *item = itemdata; struct window_customize_modedata *data = item->data; @@ -1195,7 +1195,7 @@ window_customize_reset_option(struct window_customize_modedata *data, static int window_customize_set_command_callback(struct client *c, void *itemdata, - const char *s, __unused int done) + const char *s, __unused key_code key, __unused int flags) { struct window_customize_itemdata *item = itemdata; struct window_customize_modedata *data = item->data; @@ -1234,7 +1234,7 @@ fail: static int window_customize_set_note_callback(__unused struct client *c, void *itemdata, - const char *s, __unused int done) + const char *s, __unused key_code key, __unused int flags) { struct window_customize_itemdata *item = itemdata; struct window_customize_modedata *data = item->data; @@ -1372,7 +1372,7 @@ window_customize_change_each(void *modedata, void *itemdata, static int window_customize_change_current_callback(__unused struct client *c, - void *modedata, const char *s, __unused int done) + void *modedata, const char *s, __unused key_code key, __unused int flags) { struct window_customize_modedata *data = modedata; struct window_customize_itemdata *item; @@ -1408,7 +1408,7 @@ window_customize_change_current_callback(__unused struct client *c, static int window_customize_change_tagged_callback(struct client *c, void *modedata, - const char *s, __unused int done) + const char *s, __unused key_code key, __unused int flags) { struct window_customize_modedata *data = modedata; diff --git a/window-tree.c b/window-tree.c index 28158b2b8..c0c7ae0ea 100644 --- a/window-tree.c +++ b/window-tree.c @@ -1082,7 +1082,7 @@ window_tree_command_done(__unused struct cmdq_item *item, void *modedata) static int window_tree_command_callback(struct client *c, void *modedata, const char *s, - __unused int done) + __unused key_code key, __unused int flags) { struct window_tree_modedata *data = modedata; @@ -1141,7 +1141,7 @@ window_tree_kill_each(__unused void *modedata, void *itemdata, static int window_tree_kill_current_callback(struct client *c, void *modedata, - const char *s, __unused int done) + const char *s, __unused key_code key, __unused int flags) { struct window_tree_modedata *data = modedata; struct mode_tree_data *mtd = data->data; @@ -1162,7 +1162,7 @@ window_tree_kill_current_callback(struct client *c, void *modedata, static int window_tree_kill_tagged_callback(struct client *c, void *modedata, - const char *s, __unused int done) + const char *s, __unused key_code key, __unused int flags) { struct window_tree_modedata *data = modedata; struct mode_tree_data *mtd = data->data; From 0c5e6770df8a987e39af96a9cf2f9c3cabefd145 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 23 Jun 2026 20:30:10 +0000 Subject: [PATCH 19/71] Turn off preview if no draw callback and add a function to move to top. --- mode-tree.c | 12 ++++++++++-- tmux.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/mode-tree.c b/mode-tree.c index cae060ecd..f7b47b02b 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -382,6 +382,13 @@ mode_tree_get_current_name(struct mode_tree_data *mtd) return (mtd->line_list[mtd->current].item->name); } +void +mode_tree_select_top(struct mode_tree_data *mtd) +{ + mtd->current = 0; + mtd->offset = 0; +} + void mode_tree_expand_current(struct mode_tree_data *mtd) { @@ -509,7 +516,9 @@ mode_tree_start(struct window_pane *wp, struct args *args, mtd->modedata = modedata; mtd->menu = menu; - if (args_has(args, 'N') > 1) + if (drawcb == NULL) + mtd->preview = MODE_TREE_PREVIEW_OFF; + else if (args_has(args, 'N') > 1) mtd->preview = MODE_TREE_PREVIEW_BIG; else if (args_has(args, 'N')) mtd->preview = MODE_TREE_PREVIEW_OFF; @@ -1487,7 +1496,6 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mode_tree_search_set(mtd); break; case 'f': - case 'F': mtd->references++; status_prompt_set(c, NULL, "(filter) ", mtd->filter, mode_tree_filter_callback, mode_tree_filter_free, mtd, diff --git a/tmux.h b/tmux.h index 3f4d7823b..bba11b79d 100644 --- a/tmux.h +++ b/tmux.h @@ -3570,6 +3570,7 @@ typedef const char** (*mode_tree_help_cb)(u_int *, const char**); u_int mode_tree_count_tagged(struct mode_tree_data *); void *mode_tree_get_current(struct mode_tree_data *); const char *mode_tree_get_current_name(struct mode_tree_data *); +void mode_tree_select_top(struct mode_tree_data *); void mode_tree_expand_current(struct mode_tree_data *); void mode_tree_collapse_current(struct mode_tree_data *); void mode_tree_expand(struct mode_tree_data *, uint64_t); From 89027bd22cee1ead0803f3a4539188ce7730d60c Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 23 Jun 2026 21:00:20 +0000 Subject: [PATCH 20/71] Fix MOVE flag handling and remove key code (not actually needed). --- cmd-command-prompt.c | 4 +-- cmd-confirm-before.c | 4 +-- mode-tree.c | 4 +-- status.c | 71 ++++++++++++++++++++++++-------------------- tmux.h | 3 +- window-customize.c | 10 +++---- window-tree.c | 6 ++-- 7 files changed, 53 insertions(+), 49 deletions(-) diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index 95368d41b..24affdfeb 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -35,7 +35,7 @@ static enum cmd_retval cmd_command_prompt_exec(struct cmd *, struct cmdq_item *); static int cmd_command_prompt_callback(struct client *, void *, - const char *, key_code, int); + const char *, int); static void cmd_command_prompt_free(void *); const struct cmd_entry cmd_command_prompt_entry = { @@ -178,7 +178,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) static int cmd_command_prompt_callback(struct client *c, void *data, const char *s, - __unused key_code key, int flags) + int flags) { struct cmd_command_prompt_cdata *cdata = data; char *error; diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c index 8511018fc..bd9489c94 100644 --- a/cmd-confirm-before.c +++ b/cmd-confirm-before.c @@ -34,7 +34,7 @@ static enum cmd_retval cmd_confirm_before_exec(struct cmd *, struct cmdq_item *); static int cmd_confirm_before_callback(struct client *, void *, - const char *, key_code, int); + const char *, int); static void cmd_confirm_before_free(void *); const struct cmd_entry cmd_confirm_before_entry = { @@ -120,7 +120,7 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) static int cmd_confirm_before_callback(struct client *c, void *data, const char *s, - __unused key_code key, __unused int flags) + __unused int flags) { struct cmd_confirm_before_data *cdata = data; struct cmdq_item *item = cdata->item, *new_item; diff --git a/mode-tree.c b/mode-tree.c index f7b47b02b..d91653805 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -1068,7 +1068,7 @@ mode_tree_search_set(struct mode_tree_data *mtd) static int mode_tree_search_callback(__unused struct client *c, void *data, const char *s, - __unused key_code key, __unused int flags) + __unused int flags) { struct mode_tree_data *mtd = data; @@ -1095,7 +1095,7 @@ mode_tree_search_free(void *data) static int mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, - __unused key_code key, __unused int flags) + __unused int flags) { struct mode_tree_data *mtd = data; diff --git a/status.c b/status.c index 0f7f4ba68..78c61e202 100644 --- a/status.c +++ b/status.c @@ -673,7 +673,7 @@ status_prompt_accept(__unused struct cmdq_item *item, void *data) void *pd = c->prompt_data; if (c->prompt_string != NULL) { - c->prompt_inputcb(c, pd, "y", KEYC_NONE, PROMPT_INPUT_DONE); + c->prompt_inputcb(c, pd, "y", PROMPT_INPUT_DONE); status_prompt_clear(c); } return (CMD_RETURN_NORMAL); @@ -686,7 +686,7 @@ status_prompt_set(struct client *c, struct cmd_find_state *fs, prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type) { struct format_tree *ft; - char *tmp; + char *tmp, *cp; server_client_clear_overlay(c); @@ -735,8 +735,13 @@ status_prompt_set(struct client *c, struct cmd_find_state *fs, c->tty.flags |= TTY_FREEZE; c->flags |= CLIENT_REDRAWSTATUS; - if (flags & PROMPT_INCREMENTAL) - c->prompt_inputcb(c, data, "=", KEYC_NONE, 0); + if (flags & PROMPT_INCREMENTAL) { + tmp = utf8_tocstr(c->prompt_buffer); + xasprintf(&cp, "=%s", tmp); + c->prompt_inputcb(c, c->prompt_data, cp, 0); + free(cp); + free(tmp); + } if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT)) cmdq_append(c, cmdq_get_callback(status_prompt_accept, c)); @@ -1425,10 +1430,25 @@ status_prompt_backward_word(struct client *c, const char *separators) c->prompt_index = idx; } -/* Should this key exit incremental prompt? */ -static int -status_prompt_incremental_exit_key(key_code key) +/* Fire input callback when done. */ +static void +status_prompt_done(struct client *c, const char *s) { + struct prompt_data *pd = c->prompt_data; + + if (c->prompt_inputcb(c, pd, s, PROMPT_INPUT_DONE) == 0) + status_prompt_clear(c); +} + +/* Check for a movement key. */ +static int +status_prompt_check_move(struct client *c, key_code key) +{ + struct prompt_data *pd = c->prompt_data; + char *s; + + if (~c->prompt_flags & PROMPT_INCREMENTAL) + return (0); switch (key) { case KEYC_UP: case KEYC_DOWN: @@ -1436,21 +1456,14 @@ status_prompt_incremental_exit_key(key_code key) case KEYC_RIGHT: case KEYC_PPAGE: case KEYC_NPAGE: - return (1); - } - return (0); -} - -/* Fire input callback when done. */ -static int -status_prompt_done(struct client *c, const char *s) -{ - struct prompt_data *pd = c->prompt_data; - - if (c->prompt_inputcb(c, pd, s, KEYC_NONE, PROMPT_INPUT_DONE) == 0) { - status_prompt_clear(c); + break; + default: return (0); } + s = utf8_tocstr(c->prompt_buffer); + if (c->prompt_inputcb(c, pd, s, PROMPT_INPUT_MOVE) == 0) + status_prompt_clear(c); + free(s); return (1); } @@ -1468,7 +1481,7 @@ status_prompt_key(struct client *c, key_code key) if (c->prompt_flags & PROMPT_KEY) { ks = key_string_lookup_key(key, 0); - c->prompt_inputcb(c, pd, ks, KEYC_NONE, PROMPT_INPUT_DONE); + c->prompt_inputcb(c, pd, ks, PROMPT_INPUT_DONE); status_prompt_clear(c); return (0); } @@ -1481,7 +1494,7 @@ status_prompt_key(struct client *c, key_code key) if (key >= '0' && key <= '9') goto append_key; s = utf8_tocstr(c->prompt_buffer); - c->prompt_inputcb(c, pd, s, KEYC_NONE, PROMPT_INPUT_DONE); + c->prompt_inputcb(c, pd, s, PROMPT_INPUT_DONE); status_prompt_clear(c); free(s); return (1); @@ -1513,16 +1526,8 @@ status_prompt_key(struct client *c, key_code key) } process_key: - if ((c->prompt_flags & PROMPT_INCREMENTAL) && - status_prompt_incremental_exit_key(key)) { - s = utf8_tocstr(c->prompt_buffer); - if (status_prompt_done(c, s) == 0) { - free(s); - return (1); - } - return (0); - } - + if (status_prompt_check_move(c, key)) + return (1); switch (key) { case KEYC_LEFT: case 'b'|KEYC_CTRL: @@ -1786,7 +1791,7 @@ changed: if (c->prompt_flags & PROMPT_INCREMENTAL) { s = utf8_tocstr(c->prompt_buffer); xasprintf(&cp, "%c%s", prefix, s); - c->prompt_inputcb(c, pd, cp, KEYC_NONE, 0); + c->prompt_inputcb(c, pd, cp, 0); free(cp); free(s); } diff --git a/tmux.h b/tmux.h index bba11b79d..ed39eec9f 100644 --- a/tmux.h +++ b/tmux.h @@ -1993,8 +1993,7 @@ RB_HEAD(client_windows, client_window); /* Client connection. */ #define PROMPT_INPUT_DONE 0x1 #define PROMPT_INPUT_MOVE 0x2 -typedef int (*prompt_input_cb)(struct client *, void *, const char *, key_code, - int); +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); diff --git a/window-customize.c b/window-customize.c index 8f9784265..da676bd34 100644 --- a/window-customize.c +++ b/window-customize.c @@ -981,7 +981,7 @@ window_customize_free_item_callback(void *itemdata) static int window_customize_set_option_callback(struct client *c, void *itemdata, - const char *s, __unused key_code key, __unused int flags) + const char *s, __unused int flags) { struct window_customize_itemdata *item = itemdata; struct window_customize_modedata *data = item->data; @@ -1195,7 +1195,7 @@ window_customize_reset_option(struct window_customize_modedata *data, static int window_customize_set_command_callback(struct client *c, void *itemdata, - const char *s, __unused key_code key, __unused int flags) + const char *s, __unused int flags) { struct window_customize_itemdata *item = itemdata; struct window_customize_modedata *data = item->data; @@ -1234,7 +1234,7 @@ fail: static int window_customize_set_note_callback(__unused struct client *c, void *itemdata, - const char *s, __unused key_code key, __unused int flags) + const char *s, __unused int flags) { struct window_customize_itemdata *item = itemdata; struct window_customize_modedata *data = item->data; @@ -1372,7 +1372,7 @@ window_customize_change_each(void *modedata, void *itemdata, static int window_customize_change_current_callback(__unused struct client *c, - void *modedata, const char *s, __unused key_code key, __unused int flags) + void *modedata, const char *s, __unused int flags) { struct window_customize_modedata *data = modedata; struct window_customize_itemdata *item; @@ -1408,7 +1408,7 @@ window_customize_change_current_callback(__unused struct client *c, static int window_customize_change_tagged_callback(struct client *c, void *modedata, - const char *s, __unused key_code key, __unused int flags) + const char *s, __unused int flags) { struct window_customize_modedata *data = modedata; diff --git a/window-tree.c b/window-tree.c index c0c7ae0ea..6e4b626c6 100644 --- a/window-tree.c +++ b/window-tree.c @@ -1082,7 +1082,7 @@ window_tree_command_done(__unused struct cmdq_item *item, void *modedata) static int window_tree_command_callback(struct client *c, void *modedata, const char *s, - __unused key_code key, __unused int flags) + __unused int flags) { struct window_tree_modedata *data = modedata; @@ -1141,7 +1141,7 @@ window_tree_kill_each(__unused void *modedata, void *itemdata, static int window_tree_kill_current_callback(struct client *c, void *modedata, - const char *s, __unused key_code key, __unused int flags) + const char *s, __unused int flags) { struct window_tree_modedata *data = modedata; struct mode_tree_data *mtd = data->data; @@ -1162,7 +1162,7 @@ window_tree_kill_current_callback(struct client *c, void *modedata, static int window_tree_kill_tagged_callback(struct client *c, void *modedata, - const char *s, __unused key_code key, __unused int flags) + const char *s, __unused int flags) { struct window_tree_modedata *data = modedata; struct mode_tree_data *mtd = data->data; From 1b5a07a740e0134c44a54d26966cc8aaad12350b Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 23 Jun 2026 23:52:39 +0000 Subject: [PATCH 21/71] Rename prompt-input to match the usual format names. --- status.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/status.c b/status.c index 78c61e202..44b70b7c1 100644 --- a/status.c +++ b/status.c @@ -904,7 +904,7 @@ status_prompt_redraw(struct client *c) status_prompt_area(c, &ax, &aw); tmp = utf8_tocstr(c->prompt_buffer); - format_add(ft, "prompt-input", "%s", tmp); + format_add(ft, "prompt_input", "%s", tmp); prompt = format_expand_time(ft, c->prompt_string); free(tmp); From 55cfdbaa3fc5e33a1eba96403acf1defca8e1ba2 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 23 Jun 2026 23:53:06 +0000 Subject: [PATCH 22/71] Remove fuzzy match operator again for now, this will be done differently. --- format.c | 52 +--------------------------------------------------- tmux.1 | 11 ++--------- 2 files changed, 3 insertions(+), 60 deletions(-) diff --git a/format.c b/format.c index 9aa6fc813..dbb8bce80 100644 --- a/format.c +++ b/format.c @@ -4507,53 +4507,6 @@ format_build_modifiers(struct format_expand_state *es, const char **s, return (list); } -/* Fuzzy match a single token (no spaces). */ -static int -format_fuzzy_match_token(const char *pattern, size_t patternlen, - const char *text, int icase) -{ - const char *end = pattern + patternlen; - - while (pattern != end) { - if (*text == '\0') - return (0); - if (icase) { - if (tolower((u_char)*pattern) == tolower((u_char)*text)) - pattern++; - } else { - if (*pattern == *text) - pattern++; - } - text++; - } - return (1); -} - -/* - * Fuzzy match strings. The pattern is split on spaces into tokens and every - * token must match as a sequence. - */ -static int -format_fuzzy_match(const char *pattern, const char *text, int icase) -{ - const char *start; - size_t len; - - while (*pattern != '\0') { - while (*pattern == ' ') - pattern++; - if (*pattern == '\0') - break; - start = pattern; - while (*pattern != '\0' && *pattern != ' ') - pattern++; - len = pattern - start; - if (!format_fuzzy_match_token(start, len, text, icase)) - return (0); - } - return (1); -} - /* Match against an fnmatch(3) pattern or regular expression. */ static char * format_match(struct format_modifier *fm, const char *pattern, const char *text) @@ -4564,10 +4517,7 @@ format_match(struct format_modifier *fm, const char *pattern, const char *text) if (fm->argc >= 1) s = fm->argv[0]; - if (strchr(s, 'z') != NULL) { - if (!format_fuzzy_match(pattern, text, strchr(s, 'i') != NULL)) - return (xstrdup("0")); - } else if (strchr(s, 'r') == NULL) { + if (strchr(s, 'r') == NULL) { if (strchr(s, 'i') != NULL) flags |= FNM_CASEFOLD; if (fnmatch(pattern, text, flags) != 0) diff --git a/tmux.1 b/tmux.1 index 0589830ca..c9457cc47 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6382,20 +6382,13 @@ An optional argument specifies flags: .Ql r means the pattern is a regular expression instead of the default .Xr glob 7 -pattern, -.Ql z -means the pattern is a fuzzy match, that is, it matches if its characters -appear in the string in order but not necessarily consecutively; -the pattern is split on spaces into separate terms which must all match, -and +pattern and .Ql i means to ignore case. For example: .Ql #{m:*foo*,#{host}} or -.Ql #{m/ri:\[ha]A,MYVAR} -or -.Ql #{m/z:abc,aXbXc} . +.Ql #{m/ri:\[ha]A,MYVAR} . A .Ql C performs a search for a From 6e6a434a2d64a29855e071511d04f2d26ecf5ca5 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 24 Jun 2026 09:24:18 +0100 Subject: [PATCH 23/71] Remove no longer needed test bits. --- regress/format-strings.sh | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/regress/format-strings.sh b/regress/format-strings.sh index da3fc0edb..bae66ab00 100644 --- a/regress/format-strings.sh +++ b/regress/format-strings.sh @@ -234,25 +234,4 @@ test_format "#{l:#{#}}}" "#{#}}" #test_format "#{l:#{}" "" #test_format "#{l:#{#}}" "" -# Fuzzy (subsequence) match operator m/z -test_format "#{m/z:wtr,window-tree}" "1" # subsequence in order -test_format "#{m/z:tree,window-tree}" "1" # contiguous is a subsequence -test_format "#{m/z:xyz,window-tree}" "0" # not a subsequence -test_format "#{m/z:wrt,window-tree}" "0" # right letters, wrong order -test_format "#{m/z:,window-tree}" "1" # empty pattern matches -test_format "#{m/z:window-tree,wtr}" "0" # pattern longer than text -test_format "#{m/z:WTR,window-tree}" "0" # case-sensitive by default -test_format "#{m/zi:WTR,window-tree}" "1" # /zi is case-insensitive -test_format "#{m/z:win tree,window-tree}" "1" # space-separated tokens are ANDed -test_format "#{m/z:wn tr,window-tree}" "1" # each token matched as subsequence -test_format "#{m/z:win xyz,window-tree}" "0" # all tokens must match -test_format "#{m/z: tree ,window-tree}" "1" # leading/trailing spaces ignored -test_format "#{m/z:wt tr,window-tree}" "1" # repeated spaces ignored - -# Space splits the pattern into tokens which must all match (AND) -test_format "#{m/z:win tree,window-tree}" "1" # both tokens present -test_format "#{m/z:tree win,window-tree}" "1" # token order independent -test_format "#{m/z:win xyz,window-tree}" "0" # second token absent -test_format "#{m/z: ,window-tree}" "1" # only spaces is empty pattern - exit 0 From 8fb484da3703a98765929e5c9ef68f44b33e535e Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 24 Jun 2026 08:35:46 +0000 Subject: [PATCH 24/71] Remove target completion which never worked very well, leave only command completion. --- status.c | 259 +++++-------------------------------------------------- 1 file changed, 21 insertions(+), 238 deletions(-) diff --git a/status.c b/status.c index 44b70b7c1..d5b10ac7b 100644 --- a/status.c +++ b/status.c @@ -38,15 +38,12 @@ static const char *status_prompt_down_history(u_int *, u_int); static void status_prompt_add_history(const char *, u_int); static char *status_prompt_complete(struct client *, const char *, u_int); -static char *status_prompt_complete_window_menu(struct client *, - struct session *, const char *, u_int, char); struct status_prompt_menu { struct client *c; u_int start; u_int size; char **list; - char flag; }; static const char *prompt_type_strings[] = { @@ -1890,20 +1887,14 @@ status_prompt_add_list(char ***list, u_int *size, const char *s) /* Build completion list. */ static char ** -status_prompt_complete_list(u_int *size, const char *s, int at_start) +status_prompt_complete_list(u_int *size, const char *s) { - char **list = NULL, *tmp; - const char **layout, *value, *cp; - const struct cmd_entry **cmdent; - const struct options_table_entry *oe; - size_t slen = strlen(s), valuelen; - struct options_entry *o; - struct options_array_item *a; - const char *layouts[] = { - "even-horizontal", "even-vertical", - "main-horizontal", "main-horizontal-mirrored", - "main-vertical", "main-vertical-mirrored", "tiled", NULL - }; + char **list = NULL, *tmp; + const char *value, *cp; + const struct cmd_entry **cmdent; + size_t slen = strlen(s), valuelen; + struct options_entry *o; + struct options_array_item *a; *size = 0; for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { @@ -1932,16 +1923,6 @@ status_prompt_complete_list(u_int *size, const char *s, int at_start) a = options_array_next(a); } } - if (at_start) - return (list); - for (oe = options_table; oe->name != NULL; oe++) { - if (strncmp(oe->name, s, slen) == 0) - status_prompt_add_list(&list, size, oe->name); - } - for (layout = layouts; *layout != NULL; layout++) { - if (strncmp(*layout, s, slen) == 0) - status_prompt_add_list(&list, size, *layout); - } return (list); } @@ -1976,22 +1957,11 @@ status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key, struct status_prompt_menu *spm = data; struct client *c = spm->c; u_int i; - char *s; if (key != KEYC_NONE) { idx += spm->start; - if (spm->flag == '\0') - s = xstrdup(spm->list[idx]); - else - xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]); - if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { - free(c->prompt_buffer); - c->prompt_buffer = utf8_fromcstr(s); - c->prompt_index = utf8_strlen(c->prompt_buffer); + if (status_prompt_replace_complete(c, spm->list[idx])) c->flags |= CLIENT_REDRAWSTATUS; - } else if (status_prompt_replace_complete(c, s)) - c->flags |= CLIENT_REDRAWSTATUS; - free(s); } for (i = 0; i < spm->size; i++) @@ -2002,7 +1972,7 @@ status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key, /* Show complete word menu. */ static int status_prompt_complete_list_menu(struct client *c, char **list, u_int size, - u_int offset, char flag) + u_int offset) { struct menu *menu; struct menu_item item; @@ -2019,7 +1989,6 @@ status_prompt_complete_list_menu(struct client *c, char **list, u_int size, spm->c = c; spm->size = size; spm->list = list; - spm->flag = flag; height = c->tty.sy - lines - 2; if (height > 10) @@ -2058,107 +2027,6 @@ status_prompt_complete_list_menu(struct client *c, char **list, u_int size, return (1); } -/* Show complete word menu. */ -static char * -status_prompt_complete_window_menu(struct client *c, struct session *s, - const char *word, u_int offset, char flag) -{ - struct menu *menu; - struct menu_item item; - struct status_prompt_menu *spm; - struct winlink *wl; - char **list = NULL, *tmp; - u_int lines = status_line_size(c), height; - u_int py, size = 0, i, ax, aw; - - if (c->tty.sy - lines < 3) - return (NULL); - - spm = xmalloc(sizeof *spm); - spm->c = c; - spm->flag = flag; - - height = c->tty.sy - lines - 2; - if (height > 10) - height = 10; - spm->start = 0; - - menu = menu_create(""); - RB_FOREACH(wl, winlinks, &s->windows) { - if (word != NULL && *word != '\0') { - xasprintf(&tmp, "%d", wl->idx); - if (strncmp(tmp, word, strlen(word)) != 0) { - free(tmp); - continue; - } - free(tmp); - } - - list = xreallocarray(list, size + 1, sizeof *list); - if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { - xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name); - xasprintf(&list[size++], "%d", wl->idx); - } else { - xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx, - wl->window->name); - xasprintf(&list[size++], "%s:%d", s->name, wl->idx); - } - item.name = tmp; - item.key = '0' + size - 1; - item.command = NULL; - menu_add_item(menu, &item, NULL, c, NULL); - free(tmp); - - if (size == height) - break; - } - if (size == 0) { - menu_free(menu); - free(spm); - return (NULL); - } - if (size == 1) { - menu_free(menu); - if (flag != '\0') { - xasprintf(&tmp, "-%c%s", flag, list[0]); - free(list[0]); - } else - tmp = list[0]; - free(list); - free(spm); - return (tmp); - } - if (height > size) - height = size; - - spm->size = size; - spm->list = list; - - status_prompt_area(c, &ax, &aw); - if (options_get_number(c->session->options, "status-position") == 0) - py = lines; - else - py = c->tty.sy - 3 - height; - offset += utf8_cstrwidth(c->prompt_string); - offset += ax; - if (offset > 2) - offset -= 2; - else - offset = 0; - - if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c, - BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, - status_prompt_menu_callback, spm) != 0) { - menu_free(menu); - for (i = 0; i < size; i++) - free(list[i]); - free(list); - free(spm); - return (NULL); - } - return (NULL); -} - /* Sort complete list. */ static int status_prompt_complete_sort(const void *a, const void *b) @@ -2168,110 +2036,25 @@ status_prompt_complete_sort(const void *a, const void *b) return (strcmp(*aa, *bb)); } -/* Complete a session. */ -static char * -status_prompt_complete_session(char ***list, u_int *size, const char *s, - char flag) -{ - struct session *loop; - char *out, *tmp, n[11]; - - RB_FOREACH(loop, sessions, &sessions) { - if (*s == '\0' || strncmp(loop->name, s, strlen(s)) == 0) { - *list = xreallocarray(*list, (*size) + 2, - sizeof **list); - xasprintf(&(*list)[(*size)++], "%s:", loop->name); - } else if (*s == '$') { - xsnprintf(n, sizeof n, "%u", loop->id); - if (s[1] == '\0' || - strncmp(n, s + 1, strlen(s) - 1) == 0) { - *list = xreallocarray(*list, (*size) + 2, - sizeof **list); - xasprintf(&(*list)[(*size)++], "$%s:", n); - } - } - } - out = status_prompt_complete_prefix(*list, *size); - if (out != NULL && flag != '\0') { - xasprintf(&tmp, "-%c%s", flag, out); - free(out); - out = tmp; - } - return (out); -} - /* Complete word. */ static char * status_prompt_complete(struct client *c, const char *word, u_int offset) { - struct session *session; - const char *s, *colon; - char **list = NULL, *copy = NULL, *out = NULL; - char flag = '\0'; - u_int size = 0, i; + char **list = NULL, *out = NULL; + u_int size = 0, i; - if (*word == '\0' && - c->prompt_type != PROMPT_TYPE_TARGET && - c->prompt_type != PROMPT_TYPE_WINDOW_TARGET) + if (c->prompt_type != PROMPT_TYPE_COMMAND || offset != 0 || + *word == '\0') return (NULL); - if (c->prompt_type != PROMPT_TYPE_TARGET && - c->prompt_type != PROMPT_TYPE_WINDOW_TARGET && - strncmp(word, "-t", 2) != 0 && - strncmp(word, "-s", 2) != 0) { - list = status_prompt_complete_list(&size, word, offset == 0); - if (size == 0) - out = NULL; - else if (size == 1) - xasprintf(&out, "%s ", list[0]); - else - out = status_prompt_complete_prefix(list, size); - goto found; - } + list = status_prompt_complete_list(&size, word); + if (size == 0) + out = NULL; + else if (size == 1) + xasprintf(&out, "%s ", list[0]); + else + out = status_prompt_complete_prefix(list, size); - if (c->prompt_type == PROMPT_TYPE_TARGET || - c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { - s = word; - flag = '\0'; - } else { - s = word + 2; - flag = word[1]; - offset += 2; - } - - /* If this is a window completion, open the window menu. */ - if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { - out = status_prompt_complete_window_menu(c, c->session, s, - offset, '\0'); - goto found; - } - colon = strchr(s, ':'); - - /* If there is no colon, complete as a session. */ - if (colon == NULL) { - out = status_prompt_complete_session(&list, &size, s, flag); - goto found; - } - - /* If there is a colon but no period, find session and show a menu. */ - if (strchr(colon + 1, '.') == NULL) { - if (*s == ':') - session = c->session; - else { - copy = xstrdup(s); - *strchr(copy, ':') = '\0'; - session = session_find(copy); - free(copy); - if (session == NULL) - goto found; - } - out = status_prompt_complete_window_menu(c, session, colon + 1, - offset, flag); - if (out == NULL) - return (NULL); - } - -found: if (size != 0) { qsort(list, size, sizeof *list, status_prompt_complete_sort); for (i = 0; i < size; i++) @@ -2283,7 +2066,7 @@ found: out = NULL; } if (out != NULL || - !status_prompt_complete_list_menu(c, list, size, offset, flag)) { + !status_prompt_complete_list_menu(c, list, size, offset)) { for (i = 0; i < size; i++) free(list[i]); free(list); From 0a71af41beb50b964ddb52ed6a2d5eff1d710e18 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 24 Jun 2026 08:52:15 +0000 Subject: [PATCH 25/71] Handle different borders in move-pane, from Dane Jensen. --- cmd-join-pane.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 23ee30aa0..54b565b0f 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -203,24 +203,29 @@ cmd_join_pane_move(struct cmdq_item *item, struct args *args, char *cause = NULL, flag; int xoff = lc->xoff, yoff = lc->yoff, adjust; u_int i; + enum pane_lines lines = window_pane_get_pane_lines(wp); if (args_has(args, 'X')) { - xoff = args_percentage_and_expand(args, 'X', -(int)lc->sx, + xoff = args_percentage_and_expand(args, 'X', -(int)w->sx, w->sx, w->sx, item, &cause); if (cause != NULL) { cmdq_error(item, "position %s", cause); free(cause); return (CMD_RETURN_ERROR); } + if (lines != PANE_LINES_NONE) + xoff += 1; } if (args_has(args, 'Y')) { - yoff = args_percentage_and_expand(args, 'Y', -(int)lc->sy, + yoff = args_percentage_and_expand(args, 'Y', -(int)w->sy, w->sy, w->sy, item, &cause); if (cause != NULL) { cmdq_error(item, "position %s", cause); free(cause); return (CMD_RETURN_ERROR); } + if (lines != PANE_LINES_NONE) + yoff += 1; } for (i = 0; i < nitems(flags); i++) { From 37633ff95768cc180568f3fd1bdcee15216f3ee8 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 24 Jun 2026 10:55:39 +0000 Subject: [PATCH 26/71] Add some enums for prompt return values to make them a bit less confusing. --- cmd-command-prompt.c | 20 ++++++------ cmd-confirm-before.c | 10 +++--- mode-tree.c | 30 ++++++++++-------- server-client.c | 8 ++++- status.c | 73 ++++++++++++++++++++++++-------------------- tmux.h | 27 +++++++++++++--- window-customize.c | 56 ++++++++++++++++----------------- window-tree.c | 28 ++++++++--------- 8 files changed, 143 insertions(+), 109 deletions(-) diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index 24affdfeb..5d5e122fd 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -34,8 +34,8 @@ static enum args_parse_type cmd_command_prompt_args_parse(struct args *, static enum cmd_retval cmd_command_prompt_exec(struct cmd *, struct cmdq_item *); -static int cmd_command_prompt_callback(struct client *, void *, - const char *, int); +static enum prompt_result cmd_command_prompt_callback(struct client *, void *, + const char *, enum prompt_key_result); static void cmd_command_prompt_free(void *); const struct cmd_entry cmd_command_prompt_entry = { @@ -176,9 +176,9 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_WAIT); } -static int +static enum prompt_result cmd_command_prompt_callback(struct client *c, void *data, const char *s, - int flags) + enum prompt_key_result key) { struct cmd_command_prompt_cdata *cdata = data; char *error; @@ -188,23 +188,23 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, int argc = 0; char **argv = NULL; - if (s == NULL || (flags & PROMPT_INPUT_MOVE)) + if (s == NULL || key == PROMPT_KEY_MOVE) goto out; - if (flags & PROMPT_INPUT_DONE) { + if (key == PROMPT_KEY_CLOSE) { if (cdata->flags & PROMPT_INCREMENTAL) goto out; cmd_append_argv(&cdata->argc, &cdata->argv, s); if (++cdata->current != cdata->count) { prompt = &cdata->prompts[cdata->current]; status_prompt_update(c, prompt->prompt, prompt->input); - return (1); + return (PROMPT_CONTINUE); } } argc = cdata->argc; argv = cmd_copy_argv(cdata->argc, cdata->argv); - if (~flags & PROMPT_INPUT_DONE) + if (key != PROMPT_KEY_CLOSE) cmd_append_argv(&argc, &argv, s); else { cmd_free_argv(cdata->argc, cdata->argv); @@ -226,12 +226,12 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, cmd_free_argv(argc, argv); if (c->prompt_inputcb != cmd_command_prompt_callback) - return (1); + return (PROMPT_CONTINUE); out: if (item != NULL) cmdq_continue(item); - return (0); + return (PROMPT_CLOSE); } static void diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c index bd9489c94..54bc9a2a1 100644 --- a/cmd-confirm-before.c +++ b/cmd-confirm-before.c @@ -33,8 +33,8 @@ static enum args_parse_type cmd_confirm_before_args_parse(struct args *, static enum cmd_retval cmd_confirm_before_exec(struct cmd *, struct cmdq_item *); -static int cmd_confirm_before_callback(struct client *, void *, - const char *, int); +static enum prompt_result cmd_confirm_before_callback(struct client *, void *, + const char *, enum prompt_key_result); static void cmd_confirm_before_free(void *); const struct cmd_entry cmd_confirm_before_entry = { @@ -118,9 +118,9 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_WAIT); } -static int +static enum prompt_result cmd_confirm_before_callback(struct client *c, void *data, const char *s, - __unused int flags) + __unused enum prompt_key_result key) { struct cmd_confirm_before_data *cdata = data; struct cmdq_item *item = cdata->item, *new_item; @@ -151,7 +151,7 @@ out: cmdq_get_client(item)->retval = retcode; cmdq_continue(item); } - return (0); + return (PROMPT_CLOSE); } static void diff --git a/mode-tree.c b/mode-tree.c index d91653805..917f0bbe5 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -1066,25 +1066,27 @@ mode_tree_search_set(struct mode_tree_data *mtd) mtd->wp->flags |= PANE_REDRAW; } -static int +static enum prompt_result mode_tree_search_callback(__unused struct client *c, void *data, const char *s, - __unused int flags) + enum prompt_key_result key) { struct mode_tree_data *mtd = data; if (mtd->dead) - return (0); + return (PROMPT_CLOSE); free(mtd->search); - if (s == NULL || *s == '\0') { + if (s == NULL || *s == '\0') mtd->search = NULL; - return (0); + else { + mtd->search = xstrdup(s); + mtd->search_icase = mode_tree_is_lowercase(s); + mode_tree_search_set(mtd); } - mtd->search = xstrdup(s); - mtd->search_icase = mode_tree_is_lowercase(s); - mode_tree_search_set(mtd); - return (0); + if (key == PROMPT_KEY_HANDLED) + return (PROMPT_CONTINUE); + return (PROMPT_CLOSE); } static void @@ -1093,14 +1095,14 @@ mode_tree_search_free(void *data) mode_tree_remove_ref(data); } -static int +static enum prompt_result mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, - __unused int flags) + enum prompt_key_result key) { struct mode_tree_data *mtd = data; if (mtd->dead) - return (0); + return (PROMPT_CLOSE); if (mtd->filter != NULL) free(mtd->filter); @@ -1113,7 +1115,9 @@ mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, mode_tree_draw(mtd); mtd->wp->flags |= PANE_REDRAW; - return (0); + if (key == PROMPT_KEY_HANDLED) + return (PROMPT_CONTINUE); + return (PROMPT_CLOSE); } static void diff --git a/server-client.c b/server-client.c index ecd94e3db..a0ffcc215 100644 --- a/server-client.c +++ b/server-client.c @@ -1504,8 +1504,14 @@ server_client_handle_key0(struct client *c, struct key_event *event, } server_client_clear_overlay(c); if (c->prompt_string != NULL) { - if (status_prompt_key(c, event->key) == 0) + switch (status_prompt_key(c, event->key)) { + case PROMPT_KEY_HANDLED: + case PROMPT_KEY_CLOSE: return (0); + case PROMPT_KEY_NOT_HANDLED: + case PROMPT_KEY_MOVE: + break; + } } } diff --git a/status.c b/status.c index d5b10ac7b..28f2de7f3 100644 --- a/status.c +++ b/status.c @@ -670,7 +670,7 @@ status_prompt_accept(__unused struct cmdq_item *item, void *data) void *pd = c->prompt_data; if (c->prompt_string != NULL) { - c->prompt_inputcb(c, pd, "y", PROMPT_INPUT_DONE); + c->prompt_inputcb(c, pd, "y", PROMPT_KEY_CLOSE); status_prompt_clear(c); } return (CMD_RETURN_NORMAL); @@ -735,7 +735,7 @@ status_prompt_set(struct client *c, struct cmd_find_state *fs, if (flags & PROMPT_INCREMENTAL) { tmp = utf8_tocstr(c->prompt_buffer); xasprintf(&cp, "=%s", tmp); - c->prompt_inputcb(c, c->prompt_data, cp, 0); + c->prompt_inputcb(c, c->prompt_data, cp, PROMPT_KEY_HANDLED); free(cp); free(tmp); } @@ -1428,24 +1428,28 @@ status_prompt_backward_word(struct client *c, const char *separators) } /* Fire input callback when done. */ -static void +static enum prompt_key_result status_prompt_done(struct client *c, const char *s) { struct prompt_data *pd = c->prompt_data; - if (c->prompt_inputcb(c, pd, s, PROMPT_INPUT_DONE) == 0) + if (c->prompt_inputcb(c, pd, s, PROMPT_KEY_CLOSE) == PROMPT_CLOSE) { status_prompt_clear(c); + return (PROMPT_KEY_CLOSE); + } + c->flags |= CLIENT_REDRAWSTATUS; + return (PROMPT_KEY_HANDLED); } /* Check for a movement key. */ -static int +static enum prompt_key_result status_prompt_check_move(struct client *c, key_code key) { struct prompt_data *pd = c->prompt_data; char *s; if (~c->prompt_flags & PROMPT_INCREMENTAL) - return (0); + return (PROMPT_KEY_NOT_HANDLED); switch (key) { case KEYC_UP: case KEYC_DOWN: @@ -1455,17 +1459,20 @@ status_prompt_check_move(struct client *c, key_code key) case KEYC_NPAGE: break; default: - return (0); + return (PROMPT_KEY_NOT_HANDLED); } s = utf8_tocstr(c->prompt_buffer); - if (c->prompt_inputcb(c, pd, s, PROMPT_INPUT_MOVE) == 0) + if (c->prompt_inputcb(c, pd, s, PROMPT_KEY_MOVE) == PROMPT_CLOSE) { status_prompt_clear(c); + free(s); + return (PROMPT_KEY_CLOSE); + } free(s); - return (1); + return (PROMPT_KEY_MOVE); } /* Handle keys in prompt. */ -int +enum prompt_key_result status_prompt_key(struct client *c, key_code key) { struct prompt_data *pd = c->prompt_data; @@ -1474,13 +1481,14 @@ status_prompt_key(struct client *c, key_code key) const char *histstr, *separators = NULL, *ks; size_t size, idx; struct utf8_data tmp; + enum prompt_key_result result = PROMPT_KEY_HANDLED; int keys, word_is_separators; if (c->prompt_flags & PROMPT_KEY) { ks = key_string_lookup_key(key, 0); - c->prompt_inputcb(c, pd, ks, PROMPT_INPUT_DONE); + c->prompt_inputcb(c, pd, ks, PROMPT_KEY_CLOSE); status_prompt_clear(c); - return (0); + return (PROMPT_KEY_CLOSE); } size = utf8_strlen(c->prompt_buffer); @@ -1491,10 +1499,10 @@ status_prompt_key(struct client *c, key_code key) if (key >= '0' && key <= '9') goto append_key; s = utf8_tocstr(c->prompt_buffer); - c->prompt_inputcb(c, pd, s, PROMPT_INPUT_DONE); + c->prompt_inputcb(c, pd, s, PROMPT_KEY_CLOSE); status_prompt_clear(c); free(s); - return (1); + return (PROMPT_KEY_NOT_HANDLED); } if (c->prompt_flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) { @@ -1502,7 +1510,7 @@ status_prompt_key(struct client *c, key_code key) key = 0x7f; else if ((key & KEYC_MASK_KEY) > 0x7f) { if (!KEYC_IS_UNICODE(key)) - return (0); + return (PROMPT_KEY_HANDLED); key &= KEYC_MASK_KEY; } else key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY; @@ -1518,13 +1526,14 @@ status_prompt_key(struct client *c, key_code key) case 2: goto append_key; default: - return (0); + return (PROMPT_KEY_HANDLED); } } process_key: - if (status_prompt_check_move(c, key)) - return (1); + result = status_prompt_check_move(c, key); + if (result != PROMPT_KEY_NOT_HANDLED) + return (result); switch (key) { case KEYC_LEFT: case 'b'|KEYC_CTRL: @@ -1560,10 +1569,8 @@ process_key: break; case KEYC_BSPACE: case 'h'|KEYC_CTRL: - if (c->prompt_flags & PROMPT_BSPACE_EXIT && size == 0) { - status_prompt_done(c, NULL); - break; - } + if (c->prompt_flags & PROMPT_BSPACE_EXIT && size == 0) + return (status_prompt_done(c, NULL)); if (c->prompt_index != 0) { if (c->prompt_index == size) c->prompt_buffer[--c->prompt_index].size = 0; @@ -1706,15 +1713,14 @@ process_key: s = utf8_tocstr(c->prompt_buffer); if (*s != '\0') status_prompt_add_history(s, c->prompt_type); - status_prompt_done(c, s); + result = status_prompt_done(c, s); free(s); - break; + return (result); case '\033': /* Escape */ case '['|KEYC_CTRL: case 'c'|KEYC_CTRL: case 'g'|KEYC_CTRL: - status_prompt_done(c, NULL); - break; + return (status_prompt_done(c, NULL)); case 'r'|KEYC_CTRL: if (~c->prompt_flags & PROMPT_INCREMENTAL) break; @@ -1745,7 +1751,7 @@ process_key: } c->flags |= CLIENT_REDRAWSTATUS; - return (0); + return (PROMPT_KEY_HANDLED); append_key: if (key <= 0x7f) { @@ -1755,7 +1761,7 @@ append_key: } else if (KEYC_IS_UNICODE(key)) utf8_to_data(key, &tmp); else - return (0); + return (PROMPT_KEY_HANDLED); c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, sizeof *c->prompt_buffer); @@ -1774,11 +1780,12 @@ append_key: } if (c->prompt_flags & PROMPT_SINGLE) { - if (utf8_strlen(c->prompt_buffer) != 1) + if (utf8_strlen(c->prompt_buffer) != 1) { status_prompt_clear(c); - else { + result = PROMPT_KEY_CLOSE; + } else { s = utf8_tocstr(c->prompt_buffer); - status_prompt_done(c, s); + result = status_prompt_done(c, s); free(s); } } @@ -1788,11 +1795,11 @@ changed: if (c->prompt_flags & PROMPT_INCREMENTAL) { s = utf8_tocstr(c->prompt_buffer); xasprintf(&cp, "%c%s", prefix, s); - c->prompt_inputcb(c, pd, cp, 0); + c->prompt_inputcb(c, pd, cp, PROMPT_KEY_HANDLED); free(cp); free(s); } - return (0); + return (result); } /* Get previous line from the history. */ diff --git a/tmux.h b/tmux.h index ed39eec9f..035844e98 100644 --- a/tmux.h +++ b/tmux.h @@ -1990,11 +1990,26 @@ RB_HEAD(client_windows, client_window); /* Maximum time to be pasting. */ #define CLIENT_PASTE_TIME_LIMIT 5 -/* Client connection. */ -#define PROMPT_INPUT_DONE 0x1 -#define PROMPT_INPUT_MOVE 0x2 -typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); +/* Prompt result. */ +enum prompt_result { + PROMPT_CONTINUE, + PROMPT_CLOSE +}; + +/* Prompt key result. */ +enum prompt_key_result { + PROMPT_KEY_NOT_HANDLED, + PROMPT_KEY_HANDLED, + PROMPT_KEY_CLOSE, + PROMPT_KEY_MOVE +}; + +/* Prompt callbacks. */ +typedef enum prompt_result (*prompt_input_cb)(struct client *, void *, + const char *, enum prompt_key_result); typedef void (*prompt_free_cb)(void *); + +/* Overlay callbacks. */ 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 *, @@ -2003,6 +2018,8 @@ typedef void (*overlay_draw_cb)(struct client *, void *); 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 *); + +/* Client connection. */ struct client { const char *name; struct tmuxpeer *peer; @@ -3107,7 +3124,7 @@ void status_prompt_set(struct client *, struct cmd_find_state *, void *, int, enum prompt_type); void status_prompt_clear(struct client *); int status_prompt_redraw(struct client *); -int status_prompt_key(struct client *, key_code); +enum prompt_key_result status_prompt_key(struct client *, key_code); void status_prompt_update(struct client *, const char *, const char *); void status_prompt_load_history(void); void status_prompt_save_history(void); diff --git a/window-customize.c b/window-customize.c index da676bd34..1e954fca9 100644 --- a/window-customize.c +++ b/window-customize.c @@ -979,9 +979,9 @@ window_customize_free_item_callback(void *itemdata) window_customize_destroy(data); } -static int +static enum prompt_result window_customize_set_option_callback(struct client *c, void *itemdata, - const char *s, __unused int flags) + const char *s, __unused enum prompt_key_result key) { struct window_customize_itemdata *item = itemdata; struct window_customize_modedata *data = item->data; @@ -993,12 +993,12 @@ window_customize_set_option_callback(struct client *c, void *itemdata, int idx = item->idx; if (s == NULL || *s == '\0' || data->dead) - return (0); + return (PROMPT_CLOSE); if (item == NULL || !window_customize_check_item(data, item, NULL)) - return (0); + return (PROMPT_CLOSE); o = options_get(oo, name); if (o == NULL) - return (0); + return (PROMPT_CLOSE); oe = options_table_entry(o); if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { @@ -1020,13 +1020,13 @@ window_customize_set_option_callback(struct client *c, void *itemdata, mode_tree_draw(data->data); data->wp->flags |= PANE_REDRAW; - return (0); + return (PROMPT_CLOSE); fail: *cause = toupper((u_char)*cause); status_message_set(c, -1, 1, 0, 0, "%s", cause); free(cause); - return (0); + return (PROMPT_CLOSE); } static void @@ -1193,9 +1193,9 @@ window_customize_reset_option(struct window_customize_modedata *data, } } -static int +static enum prompt_result window_customize_set_command_callback(struct client *c, void *itemdata, - const char *s, __unused int flags) + const char *s, __unused enum prompt_key_result key) { struct window_customize_itemdata *item = itemdata; struct window_customize_modedata *data = item->data; @@ -1204,9 +1204,9 @@ window_customize_set_command_callback(struct client *c, void *itemdata, char *error; if (s == NULL || *s == '\0' || data->dead) - return (0); + return (PROMPT_CLOSE); if (item == NULL || !window_customize_get_key(item, NULL, &bd)) - return (0); + return (PROMPT_CLOSE); pr = cmd_parse_from_string(s, NULL); switch (pr->status) { @@ -1223,27 +1223,27 @@ window_customize_set_command_callback(struct client *c, void *itemdata, mode_tree_draw(data->data); data->wp->flags |= PANE_REDRAW; - return (0); + return (PROMPT_CLOSE); fail: *error = toupper((u_char)*error); status_message_set(c, -1, 1, 0, 0, "%s", error); free(error); - return (0); + return (PROMPT_CLOSE); } -static int +static enum prompt_result window_customize_set_note_callback(__unused struct client *c, void *itemdata, - const char *s, __unused int flags) + const char *s, __unused enum prompt_key_result key) { struct window_customize_itemdata *item = itemdata; struct window_customize_modedata *data = item->data; struct key_binding *bd; if (s == NULL || *s == '\0' || data->dead) - return (0); + return (PROMPT_CLOSE); if (item == NULL || !window_customize_get_key(item, NULL, &bd)) - return (0); + return (PROMPT_CLOSE); free((void *)bd->note); bd->note = xstrdup(s); @@ -1252,7 +1252,7 @@ window_customize_set_note_callback(__unused struct client *c, void *itemdata, mode_tree_draw(data->data); data->wp->flags |= PANE_REDRAW; - return (0); + return (PROMPT_CLOSE); } static void @@ -1370,17 +1370,17 @@ window_customize_change_each(void *modedata, void *itemdata, options_push_changes(item->name); } -static int +static enum prompt_result window_customize_change_current_callback(__unused struct client *c, - void *modedata, const char *s, __unused int flags) + void *modedata, const char *s, __unused enum prompt_key_result key) { struct window_customize_modedata *data = modedata; struct window_customize_itemdata *item; if (s == NULL || *s == '\0' || data->dead) - return (0); + return (PROMPT_CLOSE); if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') - return (0); + return (PROMPT_CLOSE); item = mode_tree_get_current(data->data); switch (data->change) { @@ -1403,19 +1403,19 @@ window_customize_change_current_callback(__unused struct client *c, mode_tree_draw(data->data); data->wp->flags |= PANE_REDRAW; - return (0); + return (PROMPT_CLOSE); } -static int +static enum prompt_result window_customize_change_tagged_callback(struct client *c, void *modedata, - const char *s, __unused int flags) + const char *s, __unused enum prompt_key_result key) { struct window_customize_modedata *data = modedata; if (s == NULL || *s == '\0' || data->dead) - return (0); + return (PROMPT_CLOSE); if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') - return (0); + return (PROMPT_CLOSE); mode_tree_each_tagged(data->data, window_customize_change_each, c, KEYC_NONE, 0); @@ -1423,7 +1423,7 @@ window_customize_change_tagged_callback(struct client *c, void *modedata, mode_tree_draw(data->data); data->wp->flags |= PANE_REDRAW; - return (0); + return (PROMPT_CLOSE); } static void diff --git a/window-tree.c b/window-tree.c index 6e4b626c6..30c564dfd 100644 --- a/window-tree.c +++ b/window-tree.c @@ -1080,14 +1080,14 @@ window_tree_command_done(__unused struct cmdq_item *item, void *modedata) return (CMD_RETURN_NORMAL); } -static int +static enum prompt_result window_tree_command_callback(struct client *c, void *modedata, const char *s, - __unused int flags) + __unused enum prompt_key_result key) { struct window_tree_modedata *data = modedata; if (s == NULL || *s == '\0' || data->dead) - return (0); + return (PROMPT_CLOSE); data->entered = s; mode_tree_each_tagged(data->data, window_tree_command_each, c, @@ -1097,7 +1097,7 @@ window_tree_command_callback(struct client *c, void *modedata, const char *s, data->references++; cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); - return (0); + return (PROMPT_CLOSE); } static void @@ -1139,17 +1139,17 @@ window_tree_kill_each(__unused void *modedata, void *itemdata, } } -static int +static enum prompt_result window_tree_kill_current_callback(struct client *c, void *modedata, - const char *s, __unused int flags) + const char *s, __unused enum prompt_key_result key) { struct window_tree_modedata *data = modedata; struct mode_tree_data *mtd = data->data; if (s == NULL || *s == '\0' || data->dead) - return (0); + return (PROMPT_CLOSE); if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') - return (0); + return (PROMPT_CLOSE); window_tree_kill_each(data, mode_tree_get_current(mtd), c, KEYC_NONE); server_renumber_all(); @@ -1157,20 +1157,20 @@ window_tree_kill_current_callback(struct client *c, void *modedata, data->references++; cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); - return (0); + return (PROMPT_CLOSE); } -static int +static enum prompt_result window_tree_kill_tagged_callback(struct client *c, void *modedata, - const char *s, __unused int flags) + const char *s, __unused enum prompt_key_result key) { struct window_tree_modedata *data = modedata; struct mode_tree_data *mtd = data->data; if (s == NULL || *s == '\0' || data->dead) - return (0); + return (PROMPT_CLOSE); if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') - return (0); + return (PROMPT_CLOSE); mode_tree_each_tagged(mtd, window_tree_kill_each, c, KEYC_NONE, 1); server_renumber_all(); @@ -1178,7 +1178,7 @@ window_tree_kill_tagged_callback(struct client *c, void *modedata, data->references++; cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); - return (0); + return (PROMPT_CLOSE); } static key_code From 280a96232827d7eeab25da08e4232356e1adde1a Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 24 Jun 2026 11:05:58 +0000 Subject: [PATCH 27/71] Replace two-value prompt_mode enum with a flag. --- status.c | 17 ++++++++--------- tmux.h | 5 +---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/status.c b/status.c index 28f2de7f3..64afc1455 100644 --- a/status.c +++ b/status.c @@ -726,7 +726,6 @@ status_prompt_set(struct client *c, struct cmd_find_state *fs, c->prompt_flags = flags; c->prompt_type = prompt_type; - c->prompt_mode = PROMPT_ENTRY; if ((~flags & PROMPT_INCREMENTAL) && (~flags & PROMPT_NOFREEZE)) c->tty.flags |= TTY_FREEZE; @@ -882,7 +881,7 @@ status_prompt_redraw(struct client *c) n = options_get_number(s->options, "prompt-cursor-colour"); sl->active->default_ccolour = n; - if (c->prompt_mode == PROMPT_COMMAND) + if (c->prompt_flags & PROMPT_COMMANDMODE) n = options_get_number(oo, "prompt-command-cursor-style"); else n = options_get_number(oo, "prompt-cursor-style"); @@ -893,7 +892,7 @@ status_prompt_redraw(struct client *c) if (promptline > lines - 1) promptline = lines - 1; - if (c->prompt_mode == PROMPT_COMMAND) + if (c->prompt_flags & PROMPT_COMMANDMODE) style_apply(&gc, oo, "message-command-style", NULL); else style_apply(&gc, oo, "message-style", NULL); @@ -911,7 +910,7 @@ status_prompt_redraw(struct client *c) */ format_add(ft, "message", "%s", prompt); format_add(ft, "command_prompt", "%d", - c->prompt_mode == PROMPT_COMMAND); + !!(c->prompt_flags & PROMPT_COMMANDMODE)); msgfmt = options_get_string(oo, "message-format"); expanded = format_expand_time(ft, msgfmt); free(prompt); @@ -1040,7 +1039,7 @@ status_prompt_keypad_key(key_code key) static int status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) { - if (c->prompt_mode == PROMPT_ENTRY) { + if (~c->prompt_flags & PROMPT_COMMANDMODE) { switch (key) { case 'a'|KEYC_CTRL: case 'c'|KEYC_CTRL: @@ -1072,7 +1071,7 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) return (1); case '\033': /* Escape */ case '['|KEYC_CTRL: - c->prompt_mode = PROMPT_COMMAND; + c->prompt_flags |= PROMPT_COMMANDMODE; if (c->prompt_index != 0) c->prompt_index--; c->flags |= CLIENT_REDRAWSTATUS; @@ -1091,16 +1090,16 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) case 'C': case 's': case 'a': - c->prompt_mode = PROMPT_ENTRY; + c->prompt_flags &= ~PROMPT_COMMANDMODE; c->flags |= CLIENT_REDRAWSTATUS; break; /* switch mode and... */ case 'S': - c->prompt_mode = PROMPT_ENTRY; + c->prompt_flags &= ~PROMPT_COMMANDMODE; c->flags |= CLIENT_REDRAWSTATUS; *new_key = 'u'|KEYC_CTRL; return (1); case 'i': - c->prompt_mode = PROMPT_ENTRY; + c->prompt_flags &= ~PROMPT_COMMANDMODE; c->flags |= CLIENT_REDRAWSTATUS; return (0); case '\033': /* Escape */ diff --git a/tmux.h b/tmux.h index 035844e98..cce45b652 100644 --- a/tmux.h +++ b/tmux.h @@ -2163,10 +2163,6 @@ struct client { prompt_free_cb prompt_freecb; void *prompt_data; u_int prompt_hindex[PROMPT_NTYPES]; - enum { - PROMPT_ENTRY, - PROMPT_COMMAND - } prompt_mode; struct utf8_data *prompt_saved; #define PROMPT_SINGLE 0x1 #define PROMPT_NUMERIC 0x2 @@ -2177,6 +2173,7 @@ struct client { #define PROMPT_QUOTENEXT 0x40 #define PROMPT_BSPACE_EXIT 0x80 #define PROMPT_NOFREEZE 0x100 +#define PROMPT_COMMANDMODE 0x200 int prompt_flags; enum prompt_type prompt_type; int prompt_cursor; From 6935dee976beefadf441e1387df31368c8563a8b Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 22 Jun 2026 22:29:09 -0700 Subject: [PATCH 28/71] Added regression test for floating pane geometry and how it interacts with pane borders. --- regress/floating-pane-geometry.sh | 203 ++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 regress/floating-pane-geometry.sh diff --git a/regress/floating-pane-geometry.sh b/regress/floating-pane-geometry.sh new file mode 100644 index 000000000..7bd033ff6 --- /dev/null +++ b/regress/floating-pane-geometry.sh @@ -0,0 +1,203 @@ +#!/bin/sh + +# Test new-pane, resize-pane, and move-pane -x/-y/-X/-Y with floating panes. +# Verifies that size and position are correctly applied with the default border, +# with -B none, and with the pane-border-lines window option set to none. Also +# verifies that zero sizes are rejected. +# +# With a border, -x/-y/-X/-Y specify the onscreen footprint including the +# border: new-pane subtracts 2 from width/height and adds 1 to x/y-position +# so that the border lands on the specified screen coordinates. resize-pane +# -x/-y similarly subtracts 2 from the requested size. move-pane -X/-Y adds 1 +# to the position. Without a border all values are used directly. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest -f/dev/null" +$TMUX kill-server 2>/dev/null + +fail() +{ + echo "$*" >&2 + $TMUX kill-server 2>/dev/null + exit 1 +} + +must_fail() +{ + "$@" >/dev/null 2>&1 && fail "unexpected success: $*" + return 0 +} + +must_equal() +{ + got=$1 + want=$2 + [ "$got" = "$want" ] || fail "got '$got', expected '$want'" +} + +$TMUX new-session -d -x 80 -y 24 || exit 1 + +# --- Default border (single-line) --- + +# Explicit values: border subtracts 2 from size and adds 1 to position. +# -x 20 -> pane_width 18; -y 6 -> pane_height 4; +# -X 8 -> pane_left 9; -Y 3 -> pane_top 4. +id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \ + || fail "new-pane with explicit geometry failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 18 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 4 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 9 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 4 + +# resize-pane with border: requested size decremented by 2. +$TMUX resize-pane -t "$id" -x 30 || fail "resize-pane -x 30 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 28 +$TMUX resize-pane -t "$id" -y 10 || fail "resize-pane -y 10 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 8 + +# resize-pane with percentage and border. +# 75% of 80 = 60, 60 - 2 = 58; 75% of 24 = 18, 18 - 2 = 16. +$TMUX resize-pane -t "$id" -x 75% || fail "resize-pane -x 75% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 58 +$TMUX resize-pane -t "$id" -y 75% || fail "resize-pane -y 75% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 16 +$TMUX kill-pane -t "$id" || exit 1 + +# Percentage width, height and position with border. +# 50% of 80 = 40, 40-2 = 38; 50% of 24 = 12, 12-2 = 10. +# 10% of 80 = 8, 8+1 = 9; 10% of 24 = 2, 2+1 = 3. +id=$($TMUX new-pane -dPF '#{pane_id}' -x 50% -y 50% -X 10% -Y 10% 'sleep 100') \ + || fail "new-pane with percentage geometry failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 38 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 10 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 9 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 3 + +# move-pane -X/-Y with border: border lands at the given column/row, content +# is one cell inside. -X 5 -> pane_left 6; -Y 1 -> pane_top 2. +$TMUX move-pane -t "$id" -X 5 || fail "move-pane -X 5 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 6 +$TMUX move-pane -t "$id" -Y 1 || fail "move-pane -Y 1 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 2 +# 25% of 80 = 20, 20+1 = 21; 25% of 24 = 6, 6+1 = 7. +$TMUX move-pane -t "$id" -X 25% || fail "move-pane -X 25% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 21 +$TMUX move-pane -t "$id" -Y 25% || fail "move-pane -Y 25% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 7 +$TMUX kill-pane -t "$id" || exit 1 + +# --- -B none --- + +# Without a border all values are used directly. +id=$($TMUX new-pane -dPF '#{pane_id}' -B none -x 20 -y 6 -X 8 -Y 3 'sleep 100') \ + || fail "new-pane -B none with explicit geometry failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 20 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 6 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 3 + +# resize-pane without border: size used directly. +$TMUX resize-pane -t "$id" -x 30 || fail "resize-pane -x 30 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 30 +$TMUX resize-pane -t "$id" -y 10 || fail "resize-pane -y 10 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 10 + +# resize-pane with percentage, no border: 75% of 80 = 60; 75% of 24 = 18. +$TMUX resize-pane -t "$id" -x 75% || fail "resize-pane -x 75% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 60 +$TMUX resize-pane -t "$id" -y 75% || fail "resize-pane -y 75% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 18 +$TMUX kill-pane -t "$id" || exit 1 + +# Percentage geometry without border: 50% of 80 = 40; 10% of 80 = 8 etc. +id=$($TMUX new-pane -dPF '#{pane_id}' -B none -x 50% -y 50% -X 10% -Y 10% 'sleep 100') \ + || fail "new-pane -B none with percentage geometry failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 40 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 12 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 2 + +# move-pane without border: -X/-Y set the content position directly. +# -X 5 -> pane_left 5; -Y 1 -> pane_top 1. +$TMUX move-pane -t "$id" -X 5 || fail "move-pane -X 5 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 5 +$TMUX move-pane -t "$id" -Y 1 || fail "move-pane -Y 1 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 1 +# 25% of 80 = 20; 25% of 24 = 6. +$TMUX move-pane -t "$id" -X 25% || fail "move-pane -X 25% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 20 +$TMUX move-pane -t "$id" -Y 25% || fail "move-pane -Y 25% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 6 +$TMUX kill-pane -t "$id" || exit 1 + +# --- Window option pane-border-lines none --- + +$TMUX set-option -w pane-border-lines none || exit 1 + +# Inherits none from window option: same behaviour as -B none. +id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \ + || fail "new-pane with window pane-border-lines none failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 20 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 6 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 3 + +$TMUX resize-pane -t "$id" -x 30 || fail "resize-pane -x 30 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 30 +$TMUX resize-pane -t "$id" -y 10 || fail "resize-pane -y 10 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 10 + +$TMUX resize-pane -t "$id" -x 75% || fail "resize-pane -x 75% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 60 +$TMUX resize-pane -t "$id" -y 75% || fail "resize-pane -y 75% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 18 +$TMUX kill-pane -t "$id" || exit 1 + +id=$($TMUX new-pane -dPF '#{pane_id}' -x 50% -y 50% -X 10% -Y 10% 'sleep 100') \ + || fail "new-pane with window pane-border-lines none and percentages failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 40 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 12 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 2 + +# move-pane with window pane-border-lines none: same behaviour as -B none. +$TMUX move-pane -t "$id" -X 5 || fail "move-pane -X 5 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 5 +$TMUX move-pane -t "$id" -Y 1 || fail "move-pane -Y 1 failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 1 +$TMUX move-pane -t "$id" -X 25% || fail "move-pane -X 25% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 20 +$TMUX move-pane -t "$id" -Y 25% || fail "move-pane -Y 25% failed" +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 6 +$TMUX kill-pane -t "$id" || exit 1 + +# --- Invalid sizes --- + +# Zero width and height must be rejected by new-pane with the default border. +$TMUX set-option -w -u pane-border-lines || exit 1 +must_fail $TMUX new-pane -d -x 0 -y 6 -X 8 -Y 3 'sleep 100' +must_fail $TMUX new-pane -d -x 20 -y 0 -X 8 -Y 3 'sleep 100' + +# Zero width and height must be rejected by resize-pane. +id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \ + || fail "new-pane for resize-pane invalid size tests failed" +must_fail $TMUX resize-pane -t "$id" -x 0 +must_fail $TMUX resize-pane -t "$id" -y 0 +$TMUX kill-pane -t "$id" || exit 1 + +# Same rejections apply with no border. +$TMUX set-option -w pane-border-lines none || exit 1 +must_fail $TMUX new-pane -d -x 0 -y 6 -X 8 -Y 3 'sleep 100' +must_fail $TMUX new-pane -d -x 20 -y 0 -X 8 -Y 3 'sleep 100' + +id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \ + || fail "new-pane for resize-pane invalid size tests (no border) failed" +must_fail $TMUX resize-pane -t "$id" -x 0 +must_fail $TMUX resize-pane -t "$id" -y 0 +$TMUX kill-pane -t "$id" || exit 1 + +$TMUX kill-server 2>/dev/null +exit 0 From ba9faae8b62d80f9da62696800814cf6591957d1 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 24 Jun 2026 21:10:05 +0000 Subject: [PATCH 29/71] Free modes rather than going through reset when pane is destroyed as the reset path may try to destroy the pane again. --- window.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/window.c b/window.c index 9759d78a6..8a388001a 100644 --- a/window.c +++ b/window.c @@ -1127,13 +1127,28 @@ window_pane_wait_finish(struct window_pane *wp) cmdq_continue(item); } +static void +window_pane_free_modes(struct window_pane *wp) +{ + struct window_mode_entry *wme; + + while (!TAILQ_EMPTY(&wp->modes)) { + wme = TAILQ_FIRST(&wp->modes); + TAILQ_REMOVE(&wp->modes, wme, entry); + wme->mode->free(wme); + free(wme); + } + + wp->screen = &wp->base; +} + static void window_pane_destroy(struct window_pane *wp) { window_pane_wait_finish(wp); spawn_editor_finish(wp); - window_pane_reset_mode_all(wp); + window_pane_free_modes(wp); free(wp->searchstr); if (wp->fd != -1) { From 6b1219b370a12115b519be8757651da6d2369a3b Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 24 Jun 2026 21:47:06 +0000 Subject: [PATCH 30/71] Ignore mouse when help is open. --- mode-tree.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mode-tree.c b/mode-tree.c index 917f0bbe5..204a5e3ff 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -1284,6 +1284,10 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, } if (mtd->help) { + if (KEYC_IS_MOUSE(*key)) { + *key = KEYC_NONE; + return (0); + } mtd->help = 0; mode_tree_draw(mtd); *key = KEYC_NONE; From 51d037e881a4b795ef782186ed070a61e1c46595 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 25 Jun 2026 11:39:11 +0000 Subject: [PATCH 31/71] Major rework of prompts. The basic prompt mechanics (draw, editing, etc) are now wrapped up in prompt*.c and do not depend on a client. These functions are used to provide the original client prompt but also to allow panes to have their own prompts, which works much much better for floating panes. The mode prompts for both the tree modes and copy mode are switched over to be per pane. There are some visible changes (some of these may be changed if they don't seem to be working well): - Prompts in modes now appear in the bottom line, covering whatever content was there. - command-prompt has a -P flag to open a pane prompt. - Because they cover the content, the default style for prompts in modes now does not fill the entire line; the main command prompt stays the same. - The old completion menu has gone, and completions are now shown after the text. Builtin aliases are no longer completed. - Clicking the mouse on the prompt now moves the cursor or selects a completion. --- Makefile | 2 + cfg.c | 2 +- cmd-command-prompt.c | 55 +- cmd-display-panes.c | 2 +- cmd-show-prompt-history.c | 46 +- key-bindings.c | 64 +- mode-tree.c | 261 +++++- options-table.c | 4 +- prompt-history.c | 264 ++++++ prompt.c | 1595 +++++++++++++++++++++++++++++++++++ screen-redraw.c | 71 +- server-client.c | 131 ++- server.c | 2 +- status.c | 1645 +++---------------------------------- tmux.1 | 4 +- tmux.h | 157 +++- window-customize.c | 50 +- window-tree.c | 19 +- window.c | 170 ++++ 19 files changed, 2811 insertions(+), 1733 deletions(-) create mode 100644 prompt-history.c create mode 100644 prompt.c diff --git a/Makefile b/Makefile index de8cd2414..cfab9c7bb 100644 --- a/Makefile +++ b/Makefile @@ -102,6 +102,8 @@ SRCS= alerts.c \ popup.c \ proc.c \ procname.c \ + prompt.c \ + prompt-history.c \ regsub.c \ resize.c \ screen-redraw.c \ diff --git a/cfg.c b/cfg.c index 7d2bbe490..450ab88fb 100644 --- a/cfg.c +++ b/cfg.c @@ -57,7 +57,7 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data) if (cfg_item != NULL) cmdq_continue(cfg_item); - status_prompt_load_history(); + prompt_load_history(); return (CMD_RETURN_NORMAL); } diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index 5d5e122fd..045ef0b12 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -42,8 +42,8 @@ const struct cmd_entry cmd_command_prompt_entry = { .name = "command-prompt", .alias = NULL, - .args = { "1CbeFiklI:Np:t:T:", 0, 1, cmd_command_prompt_args_parse }, - .usage = "[-1CbeFiklN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE + .args = { "1CbeFiklI:NPp:t:T:", 0, 1, cmd_command_prompt_args_parse }, + .usage = "[-1CbeFiklNP] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " [-T prompt-type] [template]", .flags = CMD_CLIENT_TFLAG, @@ -62,6 +62,8 @@ struct cmd_command_prompt_cdata { int flags; enum prompt_type prompt_type; + struct window_pane *wp; + struct cmd_command_prompt_prompt *prompts; u_int count; u_int current; @@ -87,10 +89,15 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) struct cmd_command_prompt_cdata *cdata; char *tmp, *prompts, *prompt, *next_prompt; char *inputs = NULL, *next_input; + struct window_pane *wp = target->wp; u_int count = args_count(args); int wait = !args_has(args, 'b'), space = 1; + int pane = args_has(args, 'P'); - if (tc->prompt_string != NULL) + if (pane) { + if (wp == NULL || window_pane_has_prompt(wp)) + return (CMD_RETURN_NORMAL); + } else if (tc->prompt != NULL) return (CMD_RETURN_NORMAL); if (args_has(args, 'i')) wait = 0; @@ -98,6 +105,8 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) cdata = xcalloc(1, sizeof *cdata); if (wait) cdata->item = item; + if (pane) + cdata->wp = wp; cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait, args_has(args, 'F')); @@ -146,7 +155,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) } if ((type = args_get(args, 'T')) != NULL) { - cdata->prompt_type = status_prompt_type(type); + cdata->prompt_type = prompt_type(type); if (cdata->prompt_type == PROMPT_TYPE_INVALID) { cmdq_error(item, "unknown type: %s", type); cmd_command_prompt_free(cdata); @@ -167,9 +176,18 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) cdata->flags |= PROMPT_BSPACE_EXIT; if (args_has(args, 'C')) cdata->flags |= PROMPT_NOFREEZE; - status_prompt_set(tc, target, cdata->prompts[0].prompt, - cdata->prompts[0].input, cmd_command_prompt_callback, - cmd_command_prompt_free, cdata, cdata->flags, cdata->prompt_type); + if (pane) { + cdata->flags |= PROMPT_ISPANE; + window_pane_set_prompt(wp, tc, target, cdata->prompts[0].prompt, + cdata->prompts[0].input, cmd_command_prompt_callback, + cmd_command_prompt_free, cdata, cdata->flags, + cdata->prompt_type); + } else { + status_prompt_set(tc, target, cdata->prompts[0].prompt, + cdata->prompts[0].input, cmd_command_prompt_callback, + cmd_command_prompt_free, cdata, cdata->flags, + cdata->prompt_type); + } if (!wait) return (CMD_RETURN_NORMAL); @@ -197,7 +215,12 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, cmd_append_argv(&cdata->argc, &cdata->argv, s); if (++cdata->current != cdata->count) { prompt = &cdata->prompts[cdata->current]; - status_prompt_update(c, prompt->prompt, prompt->input); + if (cdata->wp != NULL) { + window_pane_update_prompt(cdata->wp, + prompt->prompt, prompt->input); + } else + status_prompt_update(c, prompt->prompt, + prompt->input); return (PROMPT_CONTINUE); } } @@ -225,12 +248,19 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, } cmd_free_argv(argc, argv); - if (c->prompt_inputcb != cmd_command_prompt_callback) + /* + * An incremental prompt fires its callback on every edit but must stay + * open for further typing; only an explicit close (handled above) ends + * it. + */ + if (cdata->flags & PROMPT_INCREMENTAL) return (PROMPT_CONTINUE); out: - if (item != NULL) + if (item != NULL) { + cdata->item = NULL; cmdq_continue(item); + } return (PROMPT_CLOSE); } @@ -240,6 +270,11 @@ cmd_command_prompt_free(void *data) struct cmd_command_prompt_cdata *cdata = data; u_int i; + if (cdata->item != NULL) { + cmdq_continue(cdata->item); + cdata->item = NULL; + } + for (i = 0; i < cdata->count; i++) { free(cdata->prompts[i].prompt); free(cdata->prompts[i].input); diff --git a/cmd-display-panes.c b/cmd-display-panes.c index 24195266e..c9f185d59 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -279,7 +279,7 @@ cmd_display_panes_draw(struct client *c, __unused void *data) tty_window_offset(&c->tty, &ctx.ox, &ctx.oy, &ctx.sx, &ctx.sy); if (options_get_number(s->options, "status-position") == 0) { lines = status_line_size(c); - if (c->message_string != NULL || c->prompt_string != NULL) + if (c->message_string != NULL || c->prompt != NULL) lines = (lines == 0 ? 1 : lines); ctx.statuslines = lines; ctx.statustop = 1; diff --git a/cmd-show-prompt-history.c b/cmd-show-prompt-history.c index 5b88499c0..741d22588 100644 --- a/cmd-show-prompt-history.c +++ b/cmd-show-prompt-history.c @@ -55,56 +55,44 @@ cmd_show_prompt_history_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); const char *typestr = args_get(args, 'T'); enum prompt_type type; - u_int tidx, hidx; + u_int t, h; + const char *v; if (cmd_get_entry(self) == &cmd_clear_prompt_history_entry) { if (typestr == NULL) { - for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { - for (hidx = 0; hidx < status_prompt_hsize[tidx]; - hidx++) - free(status_prompt_hlist[tidx][hidx]); - free(status_prompt_hlist[tidx]); - status_prompt_hlist[tidx] = NULL; - status_prompt_hsize[tidx] = 0; - } + for (t = 0; t < PROMPT_NTYPES; t++) + prompt_history_clear(t); } else { - type = status_prompt_type(typestr); + type = prompt_type(typestr); if (type == PROMPT_TYPE_INVALID) { cmdq_error(item, "invalid type: %s", typestr); return (CMD_RETURN_ERROR); } - for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) - free(status_prompt_hlist[type][hidx]); - free(status_prompt_hlist[type]); - status_prompt_hlist[type] = NULL; - status_prompt_hsize[type] = 0; + prompt_history_clear(type); } - return (CMD_RETURN_NORMAL); } if (typestr == NULL) { - for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { - cmdq_print(item, "History for %s:\n", - status_prompt_type_string(tidx)); - for (hidx = 0; hidx < status_prompt_hsize[tidx]; - hidx++) { - cmdq_print(item, "%d: %s", hidx + 1, - status_prompt_hlist[tidx][hidx]); + for (t = 0; t < PROMPT_NTYPES; t++) { + typestr = prompt_type_string(t); + cmdq_print(item, "History for %s:\n", typestr); + for (h = 0; h < prompt_history_size(t); h++) { + v = prompt_history_get(t, h); + cmdq_print(item, "%d: %s", h + 1, v); } cmdq_print(item, "%s", ""); } } else { - type = status_prompt_type(typestr); + type = prompt_type(typestr); if (type == PROMPT_TYPE_INVALID) { cmdq_error(item, "invalid type: %s", typestr); return (CMD_RETURN_ERROR); } - cmdq_print(item, "History for %s:\n", - status_prompt_type_string(type)); - for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) { - cmdq_print(item, "%d: %s", hidx + 1, - status_prompt_hlist[type][hidx]); + cmdq_print(item, "History for %s:\n", prompt_type_string(type)); + for (h = 0; h < prompt_history_size(type); h++) { + v = prompt_history_get(type, h); + cmdq_print(item, "%d: %s", h + 1, v); } cmdq_print(item, "%s", ""); } diff --git a/key-bindings.c b/key-bindings.c index 5a2c3d96d..02ec14dfc 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -514,8 +514,8 @@ key_bindings_init(void) "bind -Tcopy-mode M-l { send -X cursor-centre-horizontal }", "bind -Tcopy-mode C-n { send -X cursor-down }", "bind -Tcopy-mode C-p { send -X cursor-up }", - "bind -Tcopy-mode C-r { command-prompt -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }", - "bind -Tcopy-mode C-s { command-prompt -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }", + "bind -Tcopy-mode C-r { command-prompt -P -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }", + "bind -Tcopy-mode C-s { command-prompt -P -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }", "bind -Tcopy-mode C-v { send -X page-down }", "bind -Tcopy-mode C-w { send -X copy-pipe-and-cancel }", "bind -Tcopy-mode Escape { send -X cancel }", @@ -523,18 +523,18 @@ key_bindings_init(void) "bind -Tcopy-mode Space { send -X page-down }", "bind -Tcopy-mode , { send -X jump-reverse }", "bind -Tcopy-mode \\; { send -X jump-again }", - "bind -Tcopy-mode F { command-prompt -1p'(jump backward)' { send -X jump-backward -- '%%' } }", + "bind -Tcopy-mode F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }", "bind -Tcopy-mode N { send -X search-reverse }", "bind -Tcopy-mode P { send -X toggle-position }", "bind -Tcopy-mode R { send -X rectangle-toggle }", - "bind -Tcopy-mode T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", + "bind -Tcopy-mode T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", "bind -Tcopy-mode X { send -X set-mark }", - "bind -Tcopy-mode f { command-prompt -1p'(jump forward)' { send -X jump-forward -- '%%' } }", - "bind -Tcopy-mode g { command-prompt -p'(goto line)' { send -X goto-line -- '%%' } }", + "bind -Tcopy-mode f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }", + "bind -Tcopy-mode g { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }", "bind -Tcopy-mode n { send -X search-again }", "bind -Tcopy-mode q { send -X cancel }", "bind -Tcopy-mode r { send -X refresh-toggle }", - "bind -Tcopy-mode t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", + "bind -Tcopy-mode t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", "bind -Tcopy-mode Home { send -X start-of-line }", "bind -Tcopy-mode End { send -X end-of-line }", "bind -Tcopy-mode MouseDown1Pane select-pane", @@ -550,15 +550,15 @@ key_bindings_init(void) "bind -Tcopy-mode Down { send -X cursor-down }", "bind -Tcopy-mode Left { send -X cursor-left }", "bind -Tcopy-mode Right { send -X cursor-right }", - "bind -Tcopy-mode M-1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }", - "bind -Tcopy-mode M-2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }", - "bind -Tcopy-mode M-3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }", - "bind -Tcopy-mode M-4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }", - "bind -Tcopy-mode M-5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }", - "bind -Tcopy-mode M-6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }", - "bind -Tcopy-mode M-7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }", - "bind -Tcopy-mode M-8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }", - "bind -Tcopy-mode M-9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }", + "bind -Tcopy-mode M-1 { command-prompt -P -Np'(repeat)' -I1 { send -N '%%' } }", + "bind -Tcopy-mode M-2 { command-prompt -P -Np'(repeat)' -I2 { send -N '%%' } }", + "bind -Tcopy-mode M-3 { command-prompt -P -Np'(repeat)' -I3 { send -N '%%' } }", + "bind -Tcopy-mode M-4 { command-prompt -P -Np'(repeat)' -I4 { send -N '%%' } }", + "bind -Tcopy-mode M-5 { command-prompt -P -Np'(repeat)' -I5 { send -N '%%' } }", + "bind -Tcopy-mode M-6 { command-prompt -P -Np'(repeat)' -I6 { send -N '%%' } }", + "bind -Tcopy-mode M-7 { command-prompt -P -Np'(repeat)' -I7 { send -N '%%' } }", + "bind -Tcopy-mode M-8 { command-prompt -P -Np'(repeat)' -I8 { send -N '%%' } }", + "bind -Tcopy-mode M-9 { command-prompt -P -Np'(repeat)' -I9 { send -N '%%' } }", "bind -Tcopy-mode M-< { send -X history-top }", "bind -Tcopy-mode M-> { send -X history-bottom }", "bind -Tcopy-mode M-R { send -X top-line }", @@ -599,25 +599,25 @@ key_bindings_init(void) "bind -Tcopy-mode-vi Space { send -X begin-selection }", "bind -Tcopy-mode-vi '$' { send -X end-of-line }", "bind -Tcopy-mode-vi , { send -X jump-reverse }", - "bind -Tcopy-mode-vi / { command-prompt -T search -p'(search down)' { send -X search-forward -- '%%' } }", + "bind -Tcopy-mode-vi / { command-prompt -P -T search -p'(search down)' { send -X search-forward -- '%%' } }", "bind -Tcopy-mode-vi 0 { send -X start-of-line }", - "bind -Tcopy-mode-vi 1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }", - "bind -Tcopy-mode-vi 2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }", - "bind -Tcopy-mode-vi 3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }", - "bind -Tcopy-mode-vi 4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }", - "bind -Tcopy-mode-vi 5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }", - "bind -Tcopy-mode-vi 6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }", - "bind -Tcopy-mode-vi 7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }", - "bind -Tcopy-mode-vi 8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }", - "bind -Tcopy-mode-vi 9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }", - "bind -Tcopy-mode-vi : { command-prompt -p'(goto line)' { send -X goto-line -- '%%' } }", + "bind -Tcopy-mode-vi 1 { command-prompt -P -Np'(repeat)' -I1 { send -N '%%' } }", + "bind -Tcopy-mode-vi 2 { command-prompt -P -Np'(repeat)' -I2 { send -N '%%' } }", + "bind -Tcopy-mode-vi 3 { command-prompt -P -Np'(repeat)' -I3 { send -N '%%' } }", + "bind -Tcopy-mode-vi 4 { command-prompt -P -Np'(repeat)' -I4 { send -N '%%' } }", + "bind -Tcopy-mode-vi 5 { command-prompt -P -Np'(repeat)' -I5 { send -N '%%' } }", + "bind -Tcopy-mode-vi 6 { command-prompt -P -Np'(repeat)' -I6 { send -N '%%' } }", + "bind -Tcopy-mode-vi 7 { command-prompt -P -Np'(repeat)' -I7 { send -N '%%' } }", + "bind -Tcopy-mode-vi 8 { command-prompt -P -Np'(repeat)' -I8 { send -N '%%' } }", + "bind -Tcopy-mode-vi 9 { command-prompt -P -Np'(repeat)' -I9 { send -N '%%' } }", + "bind -Tcopy-mode-vi : { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }", "bind -Tcopy-mode-vi \\; { send -X jump-again }", - "bind -Tcopy-mode-vi ? { command-prompt -T search -p'(search up)' { send -X search-backward -- '%%' } }", + "bind -Tcopy-mode-vi ? { command-prompt -P -T search -p'(search up)' { send -X search-backward -- '%%' } }", "bind -Tcopy-mode-vi A { send -X append-selection-and-cancel }", "bind -Tcopy-mode-vi B { send -X previous-space }", "bind -Tcopy-mode-vi D { send -X copy-pipe-end-of-line-and-cancel }", "bind -Tcopy-mode-vi E { send -X next-space-end }", - "bind -Tcopy-mode-vi F { command-prompt -1p'(jump backward)' { send -X jump-backward -- '%%' } }", + "bind -Tcopy-mode-vi F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }", "bind -Tcopy-mode-vi G { send -X history-bottom }", "bind -Tcopy-mode-vi H { send -X top-line }", "bind -Tcopy-mode-vi J { send -X scroll-down }", @@ -626,14 +626,14 @@ key_bindings_init(void) "bind -Tcopy-mode-vi M { send -X middle-line }", "bind -Tcopy-mode-vi N { send -X search-reverse }", "bind -Tcopy-mode-vi P { send -X toggle-position }", - "bind -Tcopy-mode-vi T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", + "bind -Tcopy-mode-vi T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", "bind -Tcopy-mode-vi V { send -X select-line }", "bind -Tcopy-mode-vi W { send -X next-space }", "bind -Tcopy-mode-vi X { send -X set-mark }", "bind -Tcopy-mode-vi ^ { send -X back-to-indentation }", "bind -Tcopy-mode-vi b { send -X previous-word }", "bind -Tcopy-mode-vi e { send -X next-word-end }", - "bind -Tcopy-mode-vi f { command-prompt -1p'(jump forward)' { send -X jump-forward -- '%%' } }", + "bind -Tcopy-mode-vi f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }", "bind -Tcopy-mode-vi g { send -X history-top }", "bind -Tcopy-mode-vi h { send -X cursor-left }", "bind -Tcopy-mode-vi j { send -X cursor-down }", @@ -644,7 +644,7 @@ key_bindings_init(void) "bind -Tcopy-mode-vi o { send -X other-end }", "bind -Tcopy-mode-vi q { send -X cancel }", "bind -Tcopy-mode-vi r { send -X refresh-toggle }", - "bind -Tcopy-mode-vi t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", + "bind -Tcopy-mode-vi t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", "bind -Tcopy-mode-vi v { send -X rectangle-toggle }", "bind -Tcopy-mode-vi w { send -X next-word }", "bind -Tcopy-mode-vi '{' { send -X previous-paragraph }", diff --git a/mode-tree.c b/mode-tree.c index 204a5e3ff..149f9412b 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -37,6 +37,7 @@ enum mode_tree_preview { }; struct mode_tree_item; +struct mode_tree_prompt; TAILQ_HEAD(mode_tree_list, mode_tree_item); struct mode_tree_data { @@ -51,11 +52,11 @@ struct mode_tree_data { struct sort_criteria sort_crit; const char *view_name; - mode_tree_build_cb buildcb; - mode_tree_draw_cb drawcb; - mode_tree_search_cb searchcb; - mode_tree_menu_cb menucb; - mode_tree_height_cb heightcb; + mode_tree_build_cb buildcb; + mode_tree_draw_cb drawcb; + mode_tree_search_cb searchcb; + mode_tree_menu_cb menucb; + mode_tree_height_cb heightcb; mode_tree_key_cb keycb; mode_tree_swap_cb swapcb; mode_tree_sort_cb sortcb; @@ -77,6 +78,10 @@ struct mode_tree_data { u_int current; struct screen screen; + struct prompt *prompt; + struct mode_tree_prompt *prompt_data; + u_int prompt_cx; + int prompt_top; int preview; char *search; @@ -124,9 +129,25 @@ struct mode_tree_menu { u_int line; }; -static void mode_tree_free_items(struct mode_tree_list *); -static void mode_tree_draw_help(struct mode_tree_data *, - struct screen_write_ctx *); +/* + * Wrapper around a prompt owned by a mode tree. The mode tree holds a reference + * while the prompt is alive; the wrapper callbacks forward to the caller's + * callbacks and drop that reference when the prompt is freed. + */ +struct mode_tree_prompt { + struct mode_tree_data *mtd; + struct client *c; + mode_tree_prompt_input_cb inputcb; + prompt_free_cb freecb; + void *data; +}; + +static void mode_tree_free_items(struct mode_tree_list *); +static void mode_tree_draw_help(struct mode_tree_data *, + struct screen_write_ctx *); +static void mode_tree_draw_prompt(struct mode_tree_data *, + struct screen_write_ctx *); +static enum cmd_retval mode_tree_prompt_accept(struct cmdq_item *, void *); static const struct menu_item mode_tree_menu_items[] = { { "Scroll Left", '<', NULL }, @@ -650,6 +671,7 @@ mode_tree_free(struct mode_tree_data *mtd) if (mtd->zoomed == 0) server_unzoom_window(wp->window); + mode_tree_clear_prompt(mtd); mode_tree_free_items(&mtd->children); mode_tree_clear_lines(mtd); screen_free(&mtd->screen); @@ -942,10 +964,148 @@ mode_tree_draw(struct mode_tree_data *mtd) done: if (mtd->help) mode_tree_draw_help(mtd, &ctx); - screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0); + if (mtd->prompt != NULL) + mode_tree_draw_prompt(mtd, &ctx); + else { + s->mode &= ~MODE_CURSOR; + screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0); + } screen_write_stop(&ctx); } +static void +mode_tree_draw_prompt(struct mode_tree_data *mtd, struct screen_write_ctx *ctx) +{ + struct screen *s = &mtd->screen; + struct prompt_draw_data pdd; + u_int sx = screen_size_x(s), sy = screen_size_y(s); + u_int py; + + if (sx == 0 || sy == 0) + return; + + if (mtd->prompt_top) + py = 0; + else + py = sy - 1; + + pdd.ctx = ctx; + pdd.cursor_x = &mtd->prompt_cx; + pdd.area_x = 0; + pdd.area_width = sx; + pdd.prompt_line = py; + + s->mode |= MODE_CURSOR; + prompt_draw(mtd->prompt, &pdd); + screen_write_cursormove(ctx, mtd->prompt_cx, py, 0); +} + +void +mode_tree_clear_prompt(struct mode_tree_data *mtd) +{ + struct prompt *prompt = mtd->prompt; + + if (mtd->prompt != NULL) { + mtd->prompt = NULL; + prompt_free(prompt); + mtd->screen.mode &= ~MODE_CURSOR; + } +} + +int +mode_tree_has_prompt(struct mode_tree_data *mtd) +{ + return (mtd->prompt != NULL); +} + +static enum cmd_retval +mode_tree_prompt_accept(struct cmdq_item *item, void *data) +{ + struct mode_tree_data *mtd = data; + struct client *c = cmdq_get_client(item); + key_code key = 'y'; + + if (mtd->prompt != NULL && c != NULL) + mode_tree_key(mtd, c, &key, NULL, NULL, NULL); + + mode_tree_remove_ref(mtd); + return (CMD_RETURN_NORMAL); +} + +static enum prompt_result +mode_tree_prompt_input_callback(void *data, const char *s, + enum prompt_key_result key) +{ + struct mode_tree_prompt *mtp = data; + + if (mtp->inputcb != NULL) + return (mtp->inputcb(mtp->c, mtp->data, s, key)); + return (PROMPT_CLOSE); +} + +static void +mode_tree_prompt_free_callback(void *data) +{ + struct mode_tree_prompt *mtp = data; + + if (mtp->mtd->prompt_data == mtp) + mtp->mtd->prompt_data = NULL; + if (mtp->freecb != NULL) + mtp->freecb(mtp->data); + mode_tree_remove_ref(mtp->mtd); + free(mtp); +} + +void +mode_tree_set_prompt(struct mode_tree_data *mtd, struct client *c, + const char *prompt, const char *input, enum prompt_type type, int flags, + mode_tree_prompt_input_cb inputcb, prompt_free_cb freecb, void *data) +{ + struct session *s; + struct options *oo; + struct prompt_create_data pd; + struct mode_tree_prompt *mtp; + + if (c != NULL && c->session != NULL) { + s = c->session; + oo = s->options; + } else { + s = NULL; + oo = global_s_options; + } + + mode_tree_clear_prompt(mtd); + + mtp = xcalloc(1, sizeof *mtp); + mtp->mtd = mtd; + mtp->inputcb = inputcb; + mtp->freecb = freecb; + mtp->data = data; + + mtd->references++; + mtd->prompt_top = options_get_number(oo, "status-position") == 0; + + memset(&pd, 0, sizeof pd); + prompt_set_options(&pd, s); + pd.prompt = prompt; + pd.input = input; + pd.type = type; + pd.flags = flags|PROMPT_ISMODE; + pd.inputcb = mode_tree_prompt_input_callback; + pd.freecb = mode_tree_prompt_free_callback; + pd.data = mtp; + mtd->prompt = prompt_create(&pd); + mtd->prompt_data = mtp; + + mode_tree_draw(mtd); + mtd->wp->flags |= PANE_REDRAW; + + if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT) && c != NULL) { + mtd->references++; + cmdq_append(c, cmdq_get_callback(mode_tree_prompt_accept, mtd)); + } +} + static struct mode_tree_item * mode_tree_search_backward(struct mode_tree_data *mtd) { @@ -1089,12 +1249,6 @@ mode_tree_search_callback(__unused struct client *c, void *data, const char *s, return (PROMPT_CLOSE); } -static void -mode_tree_search_free(void *data) -{ - mode_tree_remove_ref(data); -} - static enum prompt_result mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, enum prompt_key_result key) @@ -1120,12 +1274,6 @@ mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, return (PROMPT_CLOSE); } -static void -mode_tree_filter_free(void *data) -{ - mode_tree_remove_ref(data); -} - static void mode_tree_clear_filter(struct mode_tree_data *mtd) { @@ -1225,7 +1373,7 @@ mode_tree_draw_help(struct mode_tree_data *mtd, struct screen_write_ctx *ctx) struct grid_cell gc; const char **line, **lines = NULL, *item = "item"; u_int sx = screen_size_x(s), sy = screen_size_y(s); - u_int x, y, w, h = 0, box_w, box_h; + u_int x, y, w, h = 0, box_w, box_h; if (mtd->helpcb == NULL) w = MODE_TREE_HELP_DEFAULT_WIDTH; @@ -1275,14 +1423,66 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, { struct mode_tree_line *line; struct mode_tree_item *current, *parent, *mti; - u_int i, x, y; + u_int i, x, y, py, sx; int choice, preview; + enum prompt_key_result result; + int redraw; + struct prompt *prompt; + struct mode_tree_prompt *mtp; if (mtd->line_size == 0) { *key = KEYC_NONE; return (1); } + if (mtd->prompt != NULL) { + redraw = 0; + prompt = mtd->prompt; + + mtp = mtd->prompt_data; + if (mtp != NULL) + mtp->c = c; + if (KEYC_IS_MOUSE(*key)) { + if (m == NULL || + MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1 || + MOUSE_DRAG(m->b) || MOUSE_RELEASE(m->b) || + cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) + result = PROMPT_KEY_NOT_HANDLED; + else { + sx = screen_size_x(&mtd->screen); + if (mtd->prompt_top) + py = 0; + else + py = screen_size_y(&mtd->screen) - 1; + if (y == py) { + result = prompt_mouse(prompt, x, 0, sx, + &redraw); + } else + result = PROMPT_KEY_NOT_HANDLED; + } + } else + result = prompt_key(prompt, *key, &redraw); + if (mtd->prompt_data == mtp && mtp != NULL) + mtp->c = NULL; + + /* + * Only an explicit close or the prompt marking itself closed + * ends it; cursor movement and editing keep it open. + */ + if (mtd->prompt == prompt && + (result == PROMPT_KEY_CLOSE || prompt_closed(prompt))) + mode_tree_clear_prompt(mtd); + + if (redraw || mtd->prompt != prompt) { + mode_tree_draw(mtd); + mtd->wp->flags |= PANE_REDRAW; + } + if (result != PROMPT_KEY_NOT_HANDLED) { + *key = KEYC_NONE; + return (0); + } + } + if (mtd->help) { if (KEYC_IS_MOUSE(*key)) { *key = KEYC_NONE; @@ -1489,11 +1689,10 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, case '?': case '/': case 's'|KEYC_CTRL: - mtd->references++; mtd->search_dir = MODE_TREE_SEARCH_FORWARD; - status_prompt_set(c, NULL, "(search) ", "", - mode_tree_search_callback, mode_tree_search_free, mtd, - PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); + mode_tree_set_prompt(mtd, c, "(search) ", "", + PROMPT_TYPE_SEARCH, PROMPT_NOFORMAT, + mode_tree_search_callback, NULL, mtd); break; case 'n': mtd->search_dir = MODE_TREE_SEARCH_FORWARD; @@ -1504,12 +1703,12 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mode_tree_search_set(mtd); break; case 'f': - mtd->references++; - status_prompt_set(c, NULL, "(filter) ", mtd->filter, - mode_tree_filter_callback, mode_tree_filter_free, mtd, - PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); + mode_tree_set_prompt(mtd, c, "(filter) ", mtd->filter, + PROMPT_TYPE_SEARCH, PROMPT_NOFORMAT, + mode_tree_filter_callback, NULL, mtd); break; case 'c': + mode_tree_clear_prompt(mtd); mode_tree_clear_filter(mtd); break; case 'v': diff --git a/options-table.c b/options-table.c index ed90bd923..0d7b20c53 100644 --- a/options-table.c +++ b/options-table.c @@ -764,7 +764,9 @@ const struct options_table_entry options_table[] = { { .name = "message-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=yellow,fg=black,fill=yellow", + .default_str = "bg=yellow,fg=black," + "#{?#{m/r:(^|#,)IS(PANE|MODE)($|#,),#{prompt_flags}},," + "fill=yellow}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of messages and the command prompt. " diff --git a/prompt-history.c b/prompt-history.c new file mode 100644 index 000000000..ac99bdb95 --- /dev/null +++ b/prompt-history.c @@ -0,0 +1,264 @@ +/* $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 +#include + +#include "tmux.h" + +static char *prompt_find_history_file(void); +static void prompt_add_typed_history(char *); + +/* Prompt history. */ +static char **prompt_hlist[PROMPT_NTYPES]; +static u_int prompt_hsize[PROMPT_NTYPES]; + +/* Find the history file to load/save from/to. */ +static char * +prompt_find_history_file(void) +{ + const char *home, *history_file; + char *path; + + history_file = options_get_string(global_options, "history-file"); + if (*history_file == '\0') + return (NULL); + if (*history_file == '/') + return (xstrdup(history_file)); + + if (history_file[0] != '~' || history_file[1] != '/') + return (NULL); + if ((home = find_home()) == NULL) + return (NULL); + xasprintf(&path, "%s%s", home, history_file + 1); + return (path); +} + +/* Add loaded history item to the appropriate list. */ +static void +prompt_add_typed_history(char *line) +{ + char *typestr; + enum prompt_type type = PROMPT_TYPE_INVALID; + + typestr = strsep(&line, ":"); + if (line != NULL) + type = prompt_type(typestr); + if (type == PROMPT_TYPE_INVALID) { + /* + * Invalid types are not expected, but this provides backward + * compatibility with old history files. + */ + if (line != NULL) + *(--line) = ':'; + prompt_add_history(typestr, PROMPT_TYPE_COMMAND); + } else + prompt_add_history(line, type); +} + +/* Load prompt history from file. */ +void +prompt_load_history(void) +{ + FILE *f; + char *history_file, *line, *tmp; + size_t length; + + if ((history_file = prompt_find_history_file()) == NULL) + return; + log_debug("loading history from %s", history_file); + + f = fopen(history_file, "r"); + if (f == NULL) { + log_debug("%s: %s", history_file, strerror(errno)); + free(history_file); + return; + } + free(history_file); + + for (;;) { + if ((line = fgetln(f, &length)) == NULL) + break; + + if (length > 0) { + if (line[length - 1] == '\n') { + line[length - 1] = '\0'; + prompt_add_typed_history(line); + } else { + tmp = xmalloc(length + 1); + memcpy(tmp, line, length); + tmp[length] = '\0'; + prompt_add_typed_history(tmp); + free(tmp); + } + } + } + fclose(f); +} + +/* Save prompt history to file. */ +void +prompt_save_history(void) +{ + FILE *f; + u_int i, type; + char *history_file; + + if ((history_file = prompt_find_history_file()) == NULL) + return; + log_debug("saving history to %s", history_file); + + f = fopen(history_file, "w"); + if (f == NULL) { + log_debug("%s: %s", history_file, strerror(errno)); + free(history_file); + return; + } + free(history_file); + + for (type = 0; type < PROMPT_NTYPES; type++) { + for (i = 0; i < prompt_hsize[type]; i++) { + fputs(prompt_type_string(type), f); + fputc(':', f); + fputs(prompt_hlist[type][i], f); + fputc('\n', f); + } + } + fclose(f); + +} + +/* Get previous line from the history. */ +const char * +prompt_up_history(u_int *idx, u_int type) +{ + /* + * History runs from 0 to size - 1. Index is from 0 to size. Zero is + * empty. + */ + + if (type >= PROMPT_NTYPES) + return (NULL); + if (prompt_hsize[type] == 0 || idx[type] == prompt_hsize[type]) + return (NULL); + idx[type]++; + return (prompt_hlist[type][prompt_hsize[type] - idx[type]]); +} + +/* Get next line from the history. */ +const char * +prompt_down_history(u_int *idx, u_int type) +{ + if (type >= PROMPT_NTYPES) + return (""); + if (prompt_hsize[type] == 0 || idx[type] == 0) + return (""); + idx[type]--; + if (idx[type] == 0) + return (""); + return (prompt_hlist[type][prompt_hsize[type] - idx[type]]); +} + +/* Add line to the history. */ +void +prompt_add_history(const char *line, u_int type) +{ + u_int i, oldsize, newsize, freecount, hlimit, new = 1; + size_t movesize; + + if (type >= PROMPT_NTYPES) + return; + + oldsize = prompt_hsize[type]; + if (oldsize > 0 && + strcmp(prompt_hlist[type][oldsize - 1], line) == 0) + new = 0; + + hlimit = options_get_number(global_options, "prompt-history-limit"); + if (hlimit > oldsize) { + if (new == 0) + return; + newsize = oldsize + new; + } else { + newsize = hlimit; + freecount = oldsize + new - newsize; + if (freecount > oldsize) + freecount = oldsize; + if (freecount == 0) + return; + for (i = 0; i < freecount; i++) + free(prompt_hlist[type][i]); + movesize = (oldsize - freecount) * + sizeof *prompt_hlist[type]; + if (movesize > 0) { + memmove(&prompt_hlist[type][0], + &prompt_hlist[type][freecount], movesize); + } + } + + if (newsize == 0) { + free(prompt_hlist[type]); + prompt_hlist[type] = NULL; + } else if (newsize != oldsize) { + prompt_hlist[type] = + xreallocarray(prompt_hlist[type], newsize, + sizeof *prompt_hlist[type]); + } + + if (new == 1 && newsize > 0) + prompt_hlist[type][newsize - 1] = xstrdup(line); + prompt_hsize[type] = newsize; +} + +/* Get history size. */ +u_int +prompt_history_size(enum prompt_type type) +{ + if (type >= PROMPT_NTYPES) + return (0); + return (prompt_hsize[type]); +} + +/* Get history entry. */ +const char * +prompt_history_get(enum prompt_type type, u_int idx) +{ + if (type >= PROMPT_NTYPES) + return (NULL); + if (idx >= prompt_hsize[type]) + return (NULL); + return (prompt_hlist[type][idx]); +} + +/* Clear prompt history. */ +void +prompt_history_clear(enum prompt_type type) +{ + u_int idx; + + if (type >= PROMPT_NTYPES) + return; + for (idx = 0; idx < prompt_hsize[type]; idx++) + free(prompt_hlist[type][idx]); + free(prompt_hlist[type]); + prompt_hlist[type] = NULL; + prompt_hsize[type] = 0; +} diff --git a/prompt.c b/prompt.c new file mode 100644 index 000000000..54cbeb13b --- /dev/null +++ b/prompt.c @@ -0,0 +1,1595 @@ +/* $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 +#include +#include +#include + +#include "tmux.h" + +struct prompt { + char *string; + struct utf8_data *buffer; + struct cmd_find_state state; + char *last; + size_t index; + + prompt_input_cb inputcb; + prompt_free_cb freecb; + void *data; + + char *message_format; + int keys; + char *word_separators; + struct grid_cell style; + struct grid_cell command_style; + enum screen_cursor_style cstyle; + enum screen_cursor_style command_cstyle; + int ccolour; + int cmode; + int command_cmode; + + enum prompt_type type; + int flags; + int closed; + + u_int hindex[PROMPT_NTYPES]; + struct utf8_data *copied; + + char **complete_list; + u_int complete_size; + char *complete_display; +}; + +static char *prompt_complete(struct prompt *, const char *, u_int); +static void prompt_clear_complete(struct prompt *); +static char *prompt_expand(struct prompt *); +static int prompt_replace_complete(struct prompt *, const char *); +static u_int prompt_width(struct prompt *, u_int); + +/* Get prompt flags as a string. */ +static const char * +prompt_flags_to_string(int flags) +{ + static char tmp[256]; + + *tmp = '\0'; + if (flags & PROMPT_SINGLE) + strlcat(tmp, "SINGLE,", sizeof tmp); + if (flags & PROMPT_NUMERIC) + strlcat(tmp, "NUMERIC,", sizeof tmp); + if (flags & PROMPT_INCREMENTAL) + strlcat(tmp, "INCREMENTAL,", sizeof tmp); + if (flags & PROMPT_NOFORMAT) + strlcat(tmp, "NOFORMAT,", sizeof tmp); + if (flags & PROMPT_KEY) + strlcat(tmp, "KEY,", sizeof tmp); + if (flags & PROMPT_ACCEPT) + strlcat(tmp, "ACCEPT,", sizeof tmp); + if (flags & PROMPT_QUOTENEXT) + strlcat(tmp, "QUOTENEXT,", sizeof tmp); + if (flags & PROMPT_BSPACE_EXIT) + strlcat(tmp, "BSPACE_EXIT,", sizeof tmp); + if (flags & PROMPT_NOFREEZE) + strlcat(tmp, "NOFREEZE,", sizeof tmp); + if (flags & PROMPT_COMMANDMODE) + strlcat(tmp, "COMMANDMODE,", sizeof tmp); + if (flags & PROMPT_ISPANE) + strlcat(tmp, "ISPANE,", sizeof tmp); + if (flags & PROMPT_ISMODE) + strlcat(tmp, "ISMODE,", sizeof tmp); + if (*tmp != '\0') + tmp[strlen(tmp) - 1] = '\0'; + return (tmp); +} + +/* Set prompt options from session options. */ +void +prompt_set_options(struct prompt_create_data *pd, struct session *s) +{ + struct options *oo; + u_int n; + + if (s != NULL) + oo = s->options; + else + oo = global_s_options; + + style_apply(&pd->style, oo, "message-style", NULL); + style_apply(&pd->command_style, oo, "message-command-style", NULL); + n = options_get_number(oo, "prompt-cursor-style"); + screen_set_cursor_style(n, &pd->cstyle, &pd->cmode); + n = options_get_number(oo, "prompt-command-cursor-style"); + screen_set_cursor_style(n, &pd->command_cstyle, &pd->command_cmode); + pd->ccolour = options_get_number(oo, "prompt-cursor-colour"); + pd->message_format = options_get_string(oo, "message-format"); + pd->keys = options_get_number(oo, "status-keys"); + pd->word_separators = options_get_string(oo, "word-separators"); +} + +/* Create prompt. */ +struct prompt * +prompt_create(const struct prompt_create_data *pd) +{ + struct prompt *pr; + struct format_tree *ft; + const char *input = pd->input; + char *tmp; + + pr = xcalloc(1, sizeof *pr); + + if (pd->fs != NULL) { + ft = format_create_from_state(NULL, NULL, pd->fs); + cmd_find_copy_state(&pr->state, pd->fs); + } else { + ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL); + cmd_find_clear_state(&pr->state, 0); + } + + if (input == NULL) + input = ""; + pr->string = xstrdup(pd->prompt); + if (pd->flags & PROMPT_NOFORMAT) + tmp = xstrdup(input); + else + tmp = format_expand_time(ft, input); + if (pd->flags & PROMPT_INCREMENTAL) { + pr->last = xstrdup(tmp); + pr->buffer = utf8_fromcstr(""); + } else { + pr->last = NULL; + pr->buffer = utf8_fromcstr(tmp); + } + pr->index = utf8_strlen(pr->buffer); + free(tmp); + + pr->inputcb = pd->inputcb; + pr->freecb = pd->freecb; + pr->data = pd->data; + + pr->flags = pd->flags; + pr->type = pd->type; + + memcpy(&pr->style, &pd->style, sizeof pr->style); + memcpy(&pr->command_style, &pd->command_style, + sizeof pr->command_style); + pr->cstyle = pd->cstyle; + pr->command_cstyle = pd->command_cstyle; + pr->ccolour = pd->ccolour; + pr->cmode = pd->cmode; + pr->command_cmode = pd->command_cmode; + pr->message_format = xstrdup(pd->message_format); + pr->keys = pd->keys; + pr->word_separators = xstrdup(pd->word_separators); + + format_free(ft); + return (pr); +} + +/* Free prompt. */ +void +prompt_free(struct prompt *pr) +{ + if (pr != NULL) { + if (pr->freecb != NULL && pr->data != NULL) + pr->freecb(pr->data); + free(pr->message_format); + free(pr->word_separators); + free(pr->last); + free(pr->string); + free(pr->buffer); + free(pr->copied); + prompt_clear_complete(pr); + free(pr); + } +} + +/* + * Fire the input callback. Returns one if the prompt is finished or zero if + * still open. + */ +static int +prompt_fire_callback(struct prompt *pr, const char *s, + enum prompt_key_result type, int *redraw) +{ + enum prompt_result result; + + result = pr->inputcb(pr->data, s, type); + if (result == PROMPT_CLOSE) { + pr->closed = 1; + return (1); + } + if (redraw != NULL) + *redraw = 1; + return (0); +} + +/* Start incremental prompt. */ +void +prompt_incremental_start(struct prompt *pr) +{ + char *tmp, *cp; + + if (pr->flags & PROMPT_INCREMENTAL) { + tmp = utf8_tocstr(pr->buffer); + xasprintf(&cp, "=%s", tmp); + prompt_fire_callback(pr, cp, PROMPT_KEY_HANDLED, NULL); + free(cp); + free(tmp); + } +} + +/* Update prompt. */ +void +prompt_update(struct prompt *pr, const char *msg, const char *input) +{ + struct format_tree *ft; + char *tmp; + + if (cmd_find_valid_state(&pr->state)) + ft = format_create_from_state(NULL, NULL, &pr->state); + else + ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL); + + free(pr->string); + pr->string = xstrdup(msg); + + if (input == NULL) + input = ""; + free(pr->buffer); + if (pr->flags & PROMPT_NOFORMAT) + tmp = xstrdup(input); + else + tmp = format_expand_time(ft, input); + pr->buffer = utf8_fromcstr(tmp); + pr->index = utf8_strlen(pr->buffer); + free(tmp); + + memset(pr->hindex, 0, sizeof pr->hindex); + pr->closed = 0; + prompt_clear_complete(pr); + + format_free(ft); +} + +/* Is this prompt closed? */ +int +prompt_closed(struct prompt *pr) +{ + return (pr->closed); +} + +/* Redraw character. Return 1 if can continue redrawing, 0 otherwise. */ +static int +prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset, + u_int pwidth, u_int *width, struct grid_cell *gc, + const struct utf8_data *ud) +{ + u_char ch; + + if (*width < offset) { + *width += ud->width; + return (1); + } + if (*width >= offset + pwidth) + return (0); + *width += ud->width; + if (*width > offset + pwidth) + return (0); + + ch = *ud->data; + if (ud->size == 1 && (ch <= 0x1f || ch == 0x7f)) { + gc->data.data[0] = '^'; + gc->data.data[1] = (ch == 0x7f) ? '?' : ch|0x40; + gc->data.size = gc->data.have = 2; + gc->data.width = 2; + } else + utf8_copy(&gc->data, ud); + screen_write_cell(ctx, gc); + return (1); +} + +/* + * Redraw quote indicator '^' if necessary. Return 1 if can continue redrawing, + * 0 otherwise. + */ +static int +prompt_redraw_quote(const struct prompt *pr, u_int pcursor, + struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width, + struct grid_cell *gc) +{ + struct utf8_data ud; + + if (pr->flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) { + utf8_set(&ud, '^'); + return (prompt_redraw_character(ctx, offset, pwidth, + width, gc, &ud)); + } + return (1); +} + +/* Draw the stored completion matches. */ +static void +prompt_draw_complete(struct prompt *pr, struct screen_write_ctx *ctx, u_int ax, + u_int aw, u_int cx, u_int py, const struct grid_cell *base) +{ + struct grid_cell gc; + struct utf8_data *ud; + u_int avail, width, i; + + if (pr->complete_display == NULL) + return; + if (pr->index != utf8_strlen(pr->buffer)) + return; + if (cx < ax || cx - ax >= aw) + return; + avail = aw - (cx - ax); + + memcpy(&gc, base, sizeof gc); + gc.attr |= GRID_ATTR_UNDERSCORE; + screen_write_cursormove(ctx, cx, py, 0); + + width = 0; + ud = utf8_fromcstr(pr->complete_display); + for (i = 0; ud[i].size != 0; i++) { + if (width + ud[i].width > avail) + break; + utf8_copy(&gc.data, &ud[i]); + screen_write_cell(ctx, &gc); + width += ud[i].width; + } + free(ud); +} + +/* Expand prompt string using the current input. */ +static char * +prompt_expand(struct prompt *pr) +{ + struct format_tree *ft; + char *expanded, *prompt, *tmp; + + if (cmd_find_valid_state(&pr->state)) + ft = format_create_from_state(NULL, NULL, &pr->state); + else + ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL); + tmp = utf8_tocstr(pr->buffer); + format_add(ft, "prompt_input", "%s", tmp); + free(tmp); + + format_add(ft, "prompt_flags", "%s", prompt_flags_to_string(pr->flags)); + format_add(ft, "prompt_type", "%s", prompt_type_string(pr->type)); + prompt = format_expand_time(ft, pr->string); + format_add(ft, "message", "%s", prompt); + if (pr->flags & PROMPT_COMMANDMODE) + format_add(ft, "command_prompt", "1"); + else + format_add(ft, "command_prompt", "0"); + expanded = format_expand_time(ft, pr->message_format); + free(prompt); + format_free(ft); + return (expanded); +} + +/* Work out the width used by the prompt string. */ +static u_int +prompt_width(struct prompt *pr, u_int aw) +{ + char *expanded; + u_int start; + + expanded = prompt_expand(pr); + start = format_width(expanded); + if (start > aw) + start = aw; + free(expanded); + return (start); +} + +/* Choose a completion from a mouse position. */ +static enum prompt_key_result +prompt_mouse_complete(struct prompt *pr, u_int x, u_int cx, u_int ax, u_int aw, + int *redraw) +{ + char *replace; + u_int avail, clicked, end, i, start, width; + + if (pr->complete_display == NULL || pr->complete_size == 0) + return (PROMPT_KEY_NOT_HANDLED); + if (pr->index != utf8_strlen(pr->buffer)) + return (PROMPT_KEY_NOT_HANDLED); + if (cx < ax || cx - ax >= aw || x < cx) + return (PROMPT_KEY_NOT_HANDLED); + + avail = aw - (cx - ax); + clicked = x - cx; + width = utf8_cstrwidth(pr->complete_display); + if (width > avail) + width = avail; + if (clicked >= width) + return (PROMPT_KEY_NOT_HANDLED); + + end = 0; + for (i = 0; i < pr->complete_size; i++) { + start = end + 1; + end = start + utf8_cstrwidth(pr->complete_list[i]); + if (clicked < start || clicked >= end) + continue; + + xasprintf(&replace, "%s ", pr->complete_list[i]); + if (prompt_replace_complete(pr, replace)) { + prompt_clear_complete(pr); + if (redraw != NULL) + *redraw = 1; + } + free(replace); + return (PROMPT_KEY_HANDLED); + } + return (PROMPT_KEY_HANDLED); +} + +/* Draw prompt. */ +void +prompt_draw(struct prompt *pr, struct prompt_draw_data *pd) +{ + struct screen_write_ctx *ctx = pd->ctx; + struct screen *s = ctx->s; + u_int ax = pd->area_x, py = pd->prompt_line; + u_int aw = pd->area_width, *cx = pd->cursor_x; + struct grid_cell gc; + u_int i, offset, left, start, width; + u_int pcursor, pwidth; + char *expanded; + + /* Choose the cursor colour and style for this prompt. */ + if (pr->flags & PROMPT_COMMANDMODE) { + memcpy(&gc, &pr->command_style, sizeof gc); + s->default_cstyle = pr->command_cstyle; + s->default_mode = pr->command_cmode; + } else { + memcpy(&gc, &pr->style, sizeof gc); + s->default_cstyle = pr->cstyle; + s->default_mode = pr->cmode; + } + s->default_ccolour = pr->ccolour; + + expanded = prompt_expand(pr); + start = format_width(expanded); + if (start > aw) + start = aw; + *cx = ax + start; + + screen_write_cursormove(ctx, ax, py, 0); + format_draw(ctx, &gc, aw, expanded, NULL, 0); + screen_write_cursormove(ctx, ax + start, py, 0); + free(expanded); + + left = aw - start; + if (left == 0) + return; + + pcursor = utf8_strwidth(pr->buffer, pr->index); + pwidth = utf8_strwidth(pr->buffer, -1); + if (pr->flags & PROMPT_QUOTENEXT) + pwidth++; + if (pcursor >= left) { + /* + * The cursor would be outside the screen so start drawing + * with it on the right. + */ + offset = (pcursor - left) + 1; + pwidth = left; + } else + offset = 0; + if (pwidth > left) + pwidth = left; + *cx = ax + start + pcursor - offset; + + width = 0; + for (i = 0; pr->buffer[i].size != 0; i++) { + if (!prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth, + &width, &gc)) + break; + if (!prompt_redraw_character(ctx, offset, pwidth, &width, &gc, + &pr->buffer[i])) + break; + } + prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth, &width, &gc); + + prompt_draw_complete(pr, ctx, ax, aw, *cx, py, &gc); +} + +/* Move cursor in prompt from a mouse position. */ +enum prompt_key_result +prompt_mouse(struct prompt *pr, u_int x, u_int ax, u_int aw, int *redraw) +{ + struct utf8_data *ud; + enum prompt_key_result result; + u_int cx, start, left, pcursor, pwidth, offset, width; + u_int target; + size_t idx; + + if (x < ax || x >= ax + aw) + return (PROMPT_KEY_NOT_HANDLED); + if (pr->flags & PROMPT_INCREMENTAL) + return (PROMPT_KEY_HANDLED); + + start = prompt_width(pr, aw); + left = aw - start; + if (left == 0) + return (PROMPT_KEY_HANDLED); + + pcursor = utf8_strwidth(pr->buffer, pr->index); + pwidth = utf8_strwidth(pr->buffer, -1); + if (pr->flags & PROMPT_QUOTENEXT) + pwidth++; + if (pcursor >= left) + offset = (pcursor - left) + 1; + else + offset = 0; + + cx = ax + start + pcursor - offset; + result = prompt_mouse_complete(pr, x, cx, ax, aw, redraw); + if (result != PROMPT_KEY_NOT_HANDLED) + return (result); + + if (x <= ax + start) + target = offset; + else + target = offset + x - (ax + start); + if (target > pwidth) + target = pwidth; + + width = 0; + for (idx = 0; pr->buffer[idx].size != 0; idx++) { + ud = &pr->buffer[idx]; + if (width >= target) + break; + width += ud->width; + } + if (idx == pr->index) + return (PROMPT_KEY_HANDLED); + + pr->index = idx; + prompt_clear_complete(pr); + if (redraw != NULL) + *redraw = 1; + + return (PROMPT_KEY_HANDLED); +} + +/* Is this a separator? */ +static int +prompt_in_list(const char *ws, const struct utf8_data *ud) +{ + if (ud->size != 1 || ud->width != 1) + return (0); + return (strchr(ws, *ud->data) != NULL); +} + +/* Is this a space? */ +static int +prompt_space(const struct utf8_data *ud) +{ + if (ud->size != 1 || ud->width != 1) + return (0); + return (*ud->data == ' '); +} + +/* Is this a keypad key? */ +static key_code +prompt_keypad_key(key_code key) +{ + if (key & KEYC_MASK_MODIFIERS) + return (key); + + switch (key) { + case KEYC_KP_SLASH: + return ('/'); + case KEYC_KP_STAR: + return ('*'); + case KEYC_KP_MINUS: + return ('-'); + case KEYC_KP_SEVEN: + return ('7'); + case KEYC_KP_EIGHT: + return ('8'); + case KEYC_KP_NINE: + return ('9'); + case KEYC_KP_PLUS: + return ('+'); + case KEYC_KP_FOUR: + return ('4'); + case KEYC_KP_FIVE: + return ('5'); + case KEYC_KP_SIX: + return ('6'); + case KEYC_KP_ONE: + return ('1'); + case KEYC_KP_TWO: + return ('2'); + case KEYC_KP_THREE: + return ('3'); + case KEYC_KP_ENTER: + return ('\r'); + case KEYC_KP_ZERO: + return ('0'); + case KEYC_KP_PERIOD: + return ('.'); + } + return (key); +} + +/* + * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key + * as an emacs key; return 2 to append to the buffer. Set *redraw if the + * translation changed something the host needs to redraw (such as switching + * between insert and command mode). + */ +static int +prompt_translate_key(struct prompt *pr, key_code key, key_code *new_key, + int *redraw) +{ + if (~pr->flags & PROMPT_COMMANDMODE) { + switch (key) { + case 'a'|KEYC_CTRL: + case 'c'|KEYC_CTRL: + case 'e'|KEYC_CTRL: + case 'g'|KEYC_CTRL: + case 'h'|KEYC_CTRL: + case '\011': /* Tab */ + case 'k'|KEYC_CTRL: + case 'n'|KEYC_CTRL: + case 'p'|KEYC_CTRL: + case 't'|KEYC_CTRL: + case 'u'|KEYC_CTRL: + case 'v'|KEYC_CTRL: + case 'w'|KEYC_CTRL: + case 'y'|KEYC_CTRL: + case '\n': + case '\r': + case KEYC_LEFT|KEYC_CTRL: + case KEYC_RIGHT|KEYC_CTRL: + case KEYC_BSPACE: + case KEYC_DC: + case KEYC_DOWN: + case KEYC_END: + case KEYC_HOME: + case KEYC_LEFT: + case KEYC_RIGHT: + case KEYC_UP: + *new_key = key; + return (1); + case '\033': /* Escape */ + case '['|KEYC_CTRL: + pr->flags |= PROMPT_COMMANDMODE; + if (pr->index != 0) + pr->index--; + *redraw = 1; + return (0); + } + *new_key = key; + return (2); + } + + switch (key) { + case KEYC_BSPACE: + *new_key = KEYC_LEFT; + return (1); + case 'A': + case 'I': + case 'C': + case 's': + case 'a': + pr->flags &= ~PROMPT_COMMANDMODE; + *redraw = 1; + break; /* switch mode and... */ + case 'S': + pr->flags &= ~PROMPT_COMMANDMODE; + *redraw = 1; + *new_key = 'u'|KEYC_CTRL; + return (1); + case 'i': + pr->flags &= ~PROMPT_COMMANDMODE; + *redraw = 1; + return (0); + case '\033': /* Escape */ + case '['|KEYC_CTRL: + return (0); + } + + switch (key) { + case 'A': + case '$': + *new_key = KEYC_END; + return (1); + case 'I': + case '0': + case '^': + *new_key = KEYC_HOME; + return (1); + case 'C': + case 'D': + *new_key = 'k'|KEYC_CTRL; + return (1); + case KEYC_BSPACE: + case 'X': + *new_key = KEYC_BSPACE; + return (1); + case 'b': + *new_key = 'b'|KEYC_META; + return (1); + case 'B': + *new_key = 'B'|KEYC_VI; + return (1); + case 'd': + *new_key = 'u'|KEYC_CTRL; + return (1); + case 'e': + *new_key = 'e'|KEYC_VI; + return (1); + case 'E': + *new_key = 'E'|KEYC_VI; + return (1); + case 'w': + *new_key = 'w'|KEYC_VI; + return (1); + case 'W': + *new_key = 'W'|KEYC_VI; + return (1); + case 'p': + *new_key = 'y'|KEYC_CTRL; + return (1); + case 'q': + *new_key = 'c'|KEYC_CTRL; + return (1); + case 's': + case KEYC_DC: + case 'x': + *new_key = KEYC_DC; + return (1); + case KEYC_DOWN: + case 'j': + *new_key = KEYC_DOWN; + return (1); + case KEYC_LEFT: + case 'h': + *new_key = KEYC_LEFT; + return (1); + case 'a': + case KEYC_RIGHT: + case 'l': + *new_key = KEYC_RIGHT; + return (1); + case KEYC_UP: + case 'k': + *new_key = KEYC_UP; + return (1); + case 'h'|KEYC_CTRL: + case 'c'|KEYC_CTRL: + case '\n': + case '\r': + return (1); + } + return (0); +} + +/* Paste into prompt. */ +static int +prompt_paste(struct prompt *pr) +{ + struct paste_buffer *pb; + const char *bufdata; + size_t size, n, bufsize; + u_int i; + struct utf8_data *ud, *udp; + enum utf8_state more; + + size = utf8_strlen(pr->buffer); + if (pr->copied != NULL) { + ud = pr->copied; + n = utf8_strlen(pr->copied); + } else { + if ((pb = paste_get_top(NULL)) == NULL) + return (0); + bufdata = paste_buffer_data(pb, &bufsize); + ud = udp = xreallocarray(NULL, bufsize + 1, sizeof *ud); + for (i = 0; i != bufsize; /* nothing */) { + more = utf8_open(udp, bufdata[i]); + if (more == UTF8_MORE) { + while (++i != bufsize && more == UTF8_MORE) + more = utf8_append(udp, bufdata[i]); + if (more == UTF8_DONE) { + udp++; + continue; + } + i -= udp->have; + } + if (bufdata[i] <= 31 || bufdata[i] >= 127) + break; + utf8_set(udp, bufdata[i]); + udp++; + i++; + } + udp->size = 0; + n = udp - ud; + } + if (n != 0) { + pr->buffer = xreallocarray(pr->buffer, size + n + 1, + sizeof *pr->buffer); + if (pr->index == size) { + memcpy(pr->buffer + pr->index, ud, + n * sizeof *pr->buffer); + pr->index += n; + pr->buffer[pr->index].size = 0; + } else { + memmove(pr->buffer + pr->index + n, + pr->buffer + pr->index, + (size + 1 - pr->index) * + sizeof *pr->buffer); + memcpy(pr->buffer + pr->index, ud, + n * sizeof *pr->buffer); + pr->index += n; + } + } + if (ud != pr->copied) + free(ud); + return (1); +} + +/* Finish completion. */ +static int +prompt_replace_complete(struct prompt *pr, const char *s) +{ + char word[64], *allocated = NULL; + size_t size, n, off, idx, used; + struct utf8_data *first, *last, *ud; + + /* Work out where the cursor currently is. */ + idx = pr->index; + if (idx != 0) + idx--; + size = utf8_strlen(pr->buffer); + + /* Find the word we are in. */ + first = &pr->buffer[idx]; + while (first > pr->buffer && !prompt_space(first)) + first--; + while (first->size != 0 && prompt_space(first)) + first++; + last = &pr->buffer[idx]; + while (last->size != 0 && !prompt_space(last)) + last++; + while (last > pr->buffer && prompt_space(last)) + last--; + if (last->size != 0) + last++; + if (last < first) + return (0); + if (s == NULL) { + used = 0; + for (ud = first; ud < last; ud++) { + if (used + ud->size >= sizeof word) + break; + memcpy(word + used, ud->data, ud->size); + used += ud->size; + } + if (ud != last) + return (0); + word[used] = '\0'; + } + + /* Try to complete it. */ + if (s == NULL) { + allocated = prompt_complete(pr, word, first - pr->buffer); + if (allocated == NULL) + return (0); + s = allocated; + } + + /* Trim out word. */ + n = size - (last - pr->buffer) + 1; /* with \0 */ + memmove(first, last, n * sizeof *pr->buffer); + size -= last - first; + + /* Insert the new word. */ + size += strlen(s); + off = first - pr->buffer; + pr->buffer = xreallocarray(pr->buffer, size + 1, + sizeof *pr->buffer); + first = pr->buffer + off; + memmove(first + strlen(s), first, n * sizeof *pr->buffer); + for (idx = 0; idx < strlen(s); idx++) + utf8_set(&first[idx], s[idx]); + pr->index = (first - pr->buffer) + strlen(s); + + free(allocated); + return (1); +} + +/* Prompt forward to the next beginning of a word. */ +static void +prompt_forward_word(struct prompt *pr, size_t size, int vi, + const char *separators) +{ + size_t idx = pr->index; + int word_is_separators; + + /* In emacs mode, skip until the first non-whitespace character. */ + if (!vi) { + while (idx != size && prompt_space(&pr->buffer[idx])) + idx++; + } + + /* Can't move forward if we're already at the end. */ + if (idx == size) { + pr->index = idx; + return; + } + + /* Determine the current character class (separators or not). */ + word_is_separators = prompt_in_list(separators, &pr->buffer[idx]) && + !prompt_space(&pr->buffer[idx]); + + /* Skip ahead until the first space or opposite character class. */ + do { + idx++; + if (prompt_space(&pr->buffer[idx])) { + /* In vi mode, go to the start of the next word. */ + if (vi) { + while (idx != size && + prompt_space(&pr->buffer[idx])) + idx++; + } + break; + } + } while (idx != size && word_is_separators == prompt_in_list( + separators, &pr->buffer[idx])); + + pr->index = idx; +} + +/* Prompt forward to the next end of a word. */ +static void +prompt_end_word(struct prompt *pr, size_t size, const char *separators) +{ + size_t idx = pr->index; + int word_is_separators; + + /* Can't move forward if we're already at the end. */ + if (idx == size) + return; + + /* Find the next word. */ + do { + idx++; + if (idx == size) { + pr->index = idx; + return; + } + } while (prompt_space(&pr->buffer[idx])); + + /* Determine the character class (separators or not). */ + word_is_separators = prompt_in_list(separators, + &pr->buffer[idx]); + + /* Skip ahead until the next space or opposite character class. */ + do { + idx++; + if (idx == size) + break; + } while (!prompt_space(&pr->buffer[idx]) && + word_is_separators == prompt_in_list(separators, &pr->buffer[idx])); + + /* Back up to the previous character to stop at the end of the word. */ + pr->index = idx - 1; +} + +/* Prompt backward to the previous beginning of a word. */ +static void +prompt_backward_word(struct prompt *pr, const char *separators) +{ + size_t idx = pr->index; + int word_is_separators; + + /* Find non-whitespace. */ + while (idx != 0) { + --idx; + if (!prompt_space(&pr->buffer[idx])) + break; + } + word_is_separators = prompt_in_list(separators, + &pr->buffer[idx]); + + /* Find the character before the beginning of the word. */ + while (idx != 0) { + --idx; + if (prompt_space(&pr->buffer[idx]) || + word_is_separators != prompt_in_list(separators, + &pr->buffer[idx])) { + /* Go back to the word. */ + idx++; + break; + } + } + pr->index = idx; +} + +/* Fire input callback when done. */ +static enum prompt_key_result +prompt_done(struct prompt *pr, const char *s, int *redraw) +{ + if (prompt_fire_callback(pr, s, PROMPT_KEY_CLOSE, redraw)) + return (PROMPT_KEY_CLOSE); + return (PROMPT_KEY_HANDLED); +} + +/* Check for a movement key. */ +static enum prompt_key_result +prompt_check_move(struct prompt *pr, key_code key) +{ + char *s; + + if (~pr->flags & PROMPT_INCREMENTAL) + return (PROMPT_KEY_NOT_HANDLED); + switch (key) { + case KEYC_UP: + case KEYC_DOWN: + case KEYC_LEFT: + case KEYC_RIGHT: + case KEYC_PPAGE: + case KEYC_NPAGE: + break; + default: + return (PROMPT_KEY_NOT_HANDLED); + } + s = utf8_tocstr(pr->buffer); + if (prompt_fire_callback(pr, s, PROMPT_KEY_MOVE, NULL)) { + free(s); + return (PROMPT_KEY_CLOSE); + } + free(s); + return (PROMPT_KEY_MOVE); +} + +/* Handle keys in prompt. */ +enum prompt_key_result +prompt_key(struct prompt *pr, key_code key, int *redraw) +{ + char *s, *cp, prefix = '='; + const char *histstr, *ks; + size_t size, idx; + struct utf8_data tmp; + enum prompt_key_result result = PROMPT_KEY_HANDLED; + int word_is_separators; + + pr->closed = 0; + + /* + * Drop any inline completion matches; the Tab handler rebuilds them if + * completion is still applicable. + */ + prompt_clear_complete(pr); + + if (pr->flags & PROMPT_KEY) { + ks = key_string_lookup_key(key, 0); + if (!prompt_fire_callback(pr, ks, PROMPT_KEY_CLOSE, NULL)) + pr->closed = 1; + return (PROMPT_KEY_CLOSE); + } + size = utf8_strlen(pr->buffer); + + key &= ~KEYC_MASK_FLAGS; + key = prompt_keypad_key(key); + + if (pr->flags & PROMPT_NUMERIC) { + if (key >= '0' && key <= '9') + goto append_key; + s = utf8_tocstr(pr->buffer); + if (!prompt_fire_callback(pr, s, PROMPT_KEY_CLOSE, NULL)) + pr->closed = 1; + free(s); + return (PROMPT_KEY_NOT_HANDLED); + } + + if (pr->flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) { + if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) + key = 0x7f; + else if ((key & KEYC_MASK_KEY) > 0x7f) { + if (!KEYC_IS_UNICODE(key)) + return (PROMPT_KEY_HANDLED); + key &= KEYC_MASK_KEY; + } else + key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY; + pr->flags &= ~PROMPT_QUOTENEXT; + goto append_key; + } + + if (pr->keys == MODEKEY_VI) { + switch (prompt_translate_key(pr, key, &key, redraw)) { + case 1: + goto process_key; + case 2: + goto append_key; + default: + return (PROMPT_KEY_HANDLED); + } + } + +process_key: + result = prompt_check_move(pr, key); + if (result != PROMPT_KEY_NOT_HANDLED) + return (result); + result = PROMPT_KEY_HANDLED; + + switch (key) { + case KEYC_LEFT: + case 'b'|KEYC_CTRL: + if (pr->index > 0) { + pr->index--; + break; + } + break; + case KEYC_RIGHT: + case 'f'|KEYC_CTRL: + if (pr->index < size) { + pr->index++; + break; + } + break; + case KEYC_HOME: + case 'a'|KEYC_CTRL: + if (pr->index != 0) { + pr->index = 0; + break; + } + break; + case KEYC_END: + case 'e'|KEYC_CTRL: + if (pr->index != size) { + pr->index = size; + break; + } + break; + case '\011': /* Tab */ + if (prompt_replace_complete(pr, NULL)) + goto changed; + break; + case KEYC_BSPACE: + case 'h'|KEYC_CTRL: + if (pr->flags & PROMPT_BSPACE_EXIT && size == 0) + return (prompt_done(pr, NULL, redraw)); + if (pr->index != 0) { + if (pr->index == size) + pr->buffer[--pr->index].size = 0; + else { + memmove(pr->buffer + pr->index - 1, + pr->buffer + pr->index, + (size + 1 - pr->index) * + sizeof *pr->buffer); + pr->index--; + } + goto changed; + } + break; + case KEYC_DC: + case 'd'|KEYC_CTRL: + if (pr->index != size) { + memmove(pr->buffer + pr->index, + pr->buffer + pr->index + 1, + (size + 1 - pr->index) * + sizeof *pr->buffer); + goto changed; + } + break; + case 'u'|KEYC_CTRL: + pr->buffer[0].size = 0; + pr->index = 0; + goto changed; + case 'k'|KEYC_CTRL: + if (pr->index < size) { + pr->buffer[pr->index].size = 0; + goto changed; + } + break; + case 'w'|KEYC_CTRL: + /* Find non-whitespace. */ + idx = pr->index; + while (idx != 0) { + idx--; + if (!prompt_space(&pr->buffer[idx])) + break; + } + word_is_separators = prompt_in_list(pr->word_separators, + &pr->buffer[idx]); + + /* Find the character before the beginning of the word. */ + while (idx != 0) { + idx--; + if (prompt_space(&pr->buffer[idx]) || + word_is_separators != prompt_in_list( + pr->word_separators, &pr->buffer[idx])) { + /* Go back to the word. */ + idx++; + break; + } + } + + free(pr->copied); + pr->copied = xcalloc(sizeof *pr->buffer, + (pr->index - idx) + 1); + memcpy(pr->copied, pr->buffer + idx, + (pr->index - idx) * sizeof *pr->buffer); + + memmove(pr->buffer + idx, pr->buffer + pr->index, + (size + 1 - pr->index) * sizeof *pr->buffer); + memset(pr->buffer + size - (pr->index - idx), '\0', + (pr->index - idx) * sizeof *pr->buffer); + pr->index = idx; + + goto changed; + case KEYC_RIGHT|KEYC_CTRL: + case 'f'|KEYC_META: + prompt_forward_word(pr, size, 0, pr->word_separators); + goto changed; + case 'E'|KEYC_VI: + prompt_end_word(pr, size, ""); + goto changed; + case 'e'|KEYC_VI: + prompt_end_word(pr, size, pr->word_separators); + goto changed; + case 'W'|KEYC_VI: + prompt_forward_word(pr, size, 1, ""); + goto changed; + case 'w'|KEYC_VI: + prompt_forward_word(pr, size, 1, pr->word_separators); + goto changed; + case 'B'|KEYC_VI: + prompt_backward_word(pr, ""); + goto changed; + case KEYC_LEFT|KEYC_CTRL: + case 'b'|KEYC_META: + prompt_backward_word(pr, pr->word_separators); + goto changed; + case KEYC_UP: + case 'p'|KEYC_CTRL: + histstr = prompt_up_history(pr->hindex, + pr->type); + if (histstr == NULL) + break; + free(pr->buffer); + pr->buffer = utf8_fromcstr(histstr); + pr->index = utf8_strlen(pr->buffer); + goto changed; + case KEYC_DOWN: + case 'n'|KEYC_CTRL: + histstr = prompt_down_history(pr->hindex, pr->type); + if (histstr == NULL) + break; + free(pr->buffer); + pr->buffer = utf8_fromcstr(histstr); + pr->index = utf8_strlen(pr->buffer); + goto changed; + case 'y'|KEYC_CTRL: + if (prompt_paste(pr)) + goto changed; + break; + case 't'|KEYC_CTRL: + idx = pr->index; + if (idx < size) + idx++; + if (idx >= 2) { + utf8_copy(&tmp, &pr->buffer[idx - 2]); + utf8_copy(&pr->buffer[idx - 2], &pr->buffer[idx - 1]); + utf8_copy(&pr->buffer[idx - 1], &tmp); + pr->index = idx; + goto changed; + } + break; + case '\r': + case '\n': + s = utf8_tocstr(pr->buffer); + if (*s != '\0') + prompt_add_history(s, pr->type); + result = prompt_done(pr, s, redraw); + free(s); + return (result); + case '\033': /* Escape */ + case '['|KEYC_CTRL: + case 'c'|KEYC_CTRL: + case 'g'|KEYC_CTRL: + return (prompt_done(pr, NULL, redraw)); + case 'r'|KEYC_CTRL: + if (~pr->flags & PROMPT_INCREMENTAL) + break; + if (pr->buffer[0].size == 0) { + prefix = '='; + free(pr->buffer); + pr->buffer = utf8_fromcstr(pr->last); + pr->index = utf8_strlen(pr->buffer); + } else + prefix = '-'; + goto changed; + case 's'|KEYC_CTRL: + if (~pr->flags & PROMPT_INCREMENTAL) + break; + if (pr->buffer[0].size == 0) { + prefix = '='; + free(pr->buffer); + pr->buffer = utf8_fromcstr(pr->last); + pr->index = utf8_strlen(pr->buffer); + } else + prefix = '+'; + goto changed; + case 'v'|KEYC_CTRL: + pr->flags |= PROMPT_QUOTENEXT; + break; + default: + goto append_key; + } + + *redraw = 1; + return (PROMPT_KEY_HANDLED); + +append_key: + if (key <= 0x7f) { + utf8_set(&tmp, key); + if (key <= 0x1f || key == 0x7f) + tmp.width = 2; + } else if (KEYC_IS_UNICODE(key)) + utf8_to_data(key, &tmp); + else + return (PROMPT_KEY_HANDLED); + + pr->buffer = xreallocarray(pr->buffer, size + 2, + sizeof *pr->buffer); + + if (pr->index == size) { + utf8_copy(&pr->buffer[pr->index], &tmp); + pr->index++; + pr->buffer[pr->index].size = 0; + } else { + memmove(pr->buffer + pr->index + 1, + pr->buffer + pr->index, + (size + 1 - pr->index) * + sizeof *pr->buffer); + utf8_copy(&pr->buffer[pr->index], &tmp); + pr->index++; + } + + if (pr->flags & PROMPT_SINGLE) { + if (utf8_strlen(pr->buffer) != 1) { + pr->closed = 1; + result = PROMPT_KEY_CLOSE; + } else { + s = utf8_tocstr(pr->buffer); + result = prompt_done(pr, s, redraw); + free(s); + } + } + +changed: + *redraw = 1; + if (pr->flags & PROMPT_INCREMENTAL) { + s = utf8_tocstr(pr->buffer); + xasprintf(&cp, "%c%s", prefix, s); + prompt_fire_callback(pr, cp, PROMPT_KEY_HANDLED, NULL); + free(cp); + free(s); + } + return (result); +} + +/* Add to completion list. */ +static void +prompt_complete_add(char ***list, u_int *size, const char *s) +{ + u_int i; + + for (i = 0; i < *size; i++) { + if (strcmp((*list)[i], s) == 0) + return; + } + *list = xreallocarray(*list, (*size) + 1, sizeof **list); + (*list)[(*size)++] = xstrdup(s); +} + +/* Build completion list. */ +static char ** +prompt_complete_commands(u_int *size, const char *s) +{ + char **list = NULL, *tmp; + const char *value, *cp; + const struct cmd_entry **cmdent; + size_t slen = strlen(s), valuelen; + struct options_entry *o; + struct options_array_item *a; + + *size = 0; + for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { + if (strncmp((*cmdent)->name, s, slen) == 0) + prompt_complete_add(&list, size, (*cmdent)->name); + } + o = options_get_only(global_options, "command-alias"); + if (o != NULL) { + a = options_array_first(o); + while (a != NULL) { + value = options_array_item_value(a)->string; + if ((cp = strchr(value, '=')) == NULL) + goto next; + valuelen = cp - value; + if (slen > valuelen || strncmp(value, s, slen) != 0) + goto next; + + xasprintf(&tmp, "%.*s", (int)valuelen, value); + prompt_complete_add(&list, size, tmp); + free(tmp); + + next: + a = options_array_next(a); + } + } + return (list); +} + +/* Find longest prefix. */ +static char * +prompt_complete_prefix(char **list, u_int size) +{ + char *out; + u_int i; + size_t j; + + if (list == NULL || size == 0) + return (NULL); + out = xstrdup(list[0]); + for (i = 1; i < size; i++) { + for (j = 0; out[j] != '\0' && list[i][j] != '\0'; j++) { + if (out[j] != list[i][j]) + break; + } + out[j] = '\0'; + } + return (out); +} + +/* Sort complete list. */ +static int +prompt_complete_sort(const void *a, const void *b) +{ + const char **aa = (const char **)a, **bb = (const char **)b; + + return (strcmp(*aa, *bb)); +} + +/* Free the stored inline completion matches. */ +static void +prompt_clear_complete(struct prompt *pr) +{ + u_int i; + + for (i = 0; i < pr->complete_size; i++) + free(pr->complete_list[i]); + free(pr->complete_list); + pr->complete_list = NULL; + pr->complete_size = 0; + + free(pr->complete_display); + pr->complete_display = NULL; +} + +/* + * Store the match list for inline display and build the dim suffix string: a + * leading space then the matches separated by spaces. + */ +static void +prompt_store_complete(struct prompt *pr, char **list, u_int size) +{ + char *display, *cp; + u_int i; + + prompt_clear_complete(pr); + pr->complete_list = list; + pr->complete_size = size; + + display = xstrdup(""); + for (i = 0; i < size; i++) { + xasprintf(&cp, "%s %s", display, list[i]); + free(display); + display = cp; + } + pr->complete_display = display; +} + +/* + * Complete word. Returns the text to insert when a unique match or a longer + * common prefix is available; otherwise stores the match list for inline + * display (and returns NULL) or returns NULL if there is nothing to do. + */ +static char * +prompt_complete(struct prompt *pr, const char *word, u_int offset) +{ + char **list = NULL, *out = NULL; + u_int size = 0, i; + + if (pr->type != PROMPT_TYPE_COMMAND || offset != 0 || + *word == '\0') + return (NULL); + + list = prompt_complete_commands(&size, word); + if (size == 0) { + free(list); + return (NULL); + } + qsort(list, size, sizeof *list, prompt_complete_sort); + for (i = 0; i < size; i++) + log_debug("complete %u: %s", i, list[i]); + + if (size == 1) + xasprintf(&out, "%s ", list[0]); + else + out = prompt_complete_prefix(list, size); + if (out != NULL && strcmp(word, out) == 0) { + free(out); + out = NULL; + } + + if (out != NULL || size <= 1) { + /* Inserting (or nothing to show): drop the list. */ + for (i = 0; i < size; i++) + free(list[i]); + free(list); + return (out); + } + + /* Multiple matches but nothing to insert: keep them for redraw. */ + prompt_store_complete(pr, list, size); + return (NULL); +} + + +/* Return the type of the prompt as an enum. */ +enum prompt_type +prompt_type(const char *type) +{ + u_int i; + + for (i = 0; i < PROMPT_NTYPES; i++) { + if (strcmp(type, prompt_type_string(i)) == 0) + return (i); + } + return (PROMPT_TYPE_INVALID); +} + +/* Get prompt type as a string. */ +const char * +prompt_type_string(enum prompt_type type) +{ + switch (type) { + case PROMPT_TYPE_COMMAND: + return ("command"); + case PROMPT_TYPE_SEARCH: + return ("search"); + case PROMPT_TYPE_INVALID: + return ("invalid"); + } + return ("unknown"); +} diff --git a/screen-redraw.c b/screen-redraw.c index 24072ea8c..e8dd8ec0f 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1505,6 +1505,62 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx, dctx->flags |= REDRAW_ISOLATES; } +/* Draw a pane's prompt over its content. */ +static void +redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp) +{ + struct redraw_scene *scene = dctx->scene; + struct client *c = scene->c; + struct tty *tty = &c->tty; + struct screen screen; + struct screen_write_ctx ctx; + struct prompt_draw_data pdd; + int ox = scene->ox, oy = scene->oy; + int sx = scene->sx, sy = scene->sy; + int line, cy, px, offset, width, wy; + + if (wp->prompt == NULL || wp->sx == 0 || wp->sy == 0) + return; + + if (~dctx->flags & REDRAW_STATUS_TOP) + wy = wp->yoff + (int)wp->sy - 1; + else + wy = wp->yoff; + if (wy < oy || wy >= oy + sy) + return; + line = wy - oy; + if (dctx->flags & REDRAW_STATUS_TOP) + cy = dctx->status_lines + line; + else + cy = line; + + if (wp->xoff + (int)wp->sx <= ox || wp->xoff >= ox + sx) + return; + if (wp->xoff < ox) { + offset = ox - wp->xoff; + px = 0; + } else { + offset = 0; + px = wp->xoff - ox; + } + width = wp->sx - offset; + if (px + width > sx) + width = sx - px; + + screen_init(&screen, wp->sx, 1, 0); + screen_write_start(&ctx, &screen); + pdd.ctx = &ctx; + pdd.cursor_x = &wp->prompt_cx; + pdd.area_x = 0; + pdd.area_width = wp->sx; + pdd.prompt_line = 0; + prompt_draw(wp->prompt, &pdd); + screen_write_stop(&ctx); + + tty_draw_line(tty, &screen, 0, offset, width, px, cy, NULL); + screen_free(&screen); +} + /* Draw scene to client. */ static void redraw_draw(struct client *c, struct window_pane *wp, int flags) @@ -1526,7 +1582,7 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) if (flags & REDRAW_STATUS) { if (c->message_string != NULL) redraw = status_message_redraw(c); - else if (c->prompt_string != NULL) + else if (c->prompt != NULL) redraw = status_prompt_redraw(c); else redraw = status_redraw(c); @@ -1600,9 +1656,20 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) else redraw_draw_lines(&dctx, flags); + if (flags & REDRAW_PANE) { + if (wp != NULL) + redraw_draw_pane_prompt(&dctx, wp); + else { + TAILQ_FOREACH(loop, &scene->w->panes, entry) { + if (window_pane_is_visible(loop)) + redraw_draw_pane_prompt(&dctx, loop); + } + } + } + if (flags & REDRAW_STATUS) { lines = dctx.status_lines; - if (c->message_string != NULL || c->prompt_string != NULL) + if (c->message_string != NULL || c->prompt != NULL) lines = (lines == 0 ? 1 : lines); if (dctx.flags & REDRAW_STATUS_TOP) y = 0; diff --git a/server-client.c b/server-client.c index a0ffcc215..60fdab2a6 100644 --- a/server-client.c +++ b/server-client.c @@ -489,10 +489,7 @@ server_client_lost(struct client *c) free(c->message_string); if (event_initialized(&c->message_timer)) evtimer_del(&c->message_timer); - - free(c->prompt_saved); - free(c->prompt_string); - free(c->prompt_buffer); + prompt_free(c->prompt); format_lost_client(c); environ_free(c->environ); @@ -1464,6 +1461,7 @@ server_client_handle_key0(struct client *c, struct key_event *event, { struct session *s = c->session; struct cmdq_item *item; + struct window_pane *wp; /* Check the client is good to accept input. */ if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) @@ -1493,6 +1491,7 @@ server_client_handle_key0(struct client *c, struct key_event *event, return (0); status_message_clear(c); } + if (c->overlay_key != NULL) { switch (c->overlay_key(c, c->overlay_data, event)) { case 0: @@ -1502,9 +1501,10 @@ server_client_handle_key0(struct client *c, struct key_event *event, return (0); } } + server_client_clear_overlay(c); - if (c->prompt_string != NULL) { - switch (status_prompt_key(c, event->key)) { + if (c->prompt != NULL) { + switch (status_prompt_key(c, event->key, &event->m)) { case PROMPT_KEY_HANDLED: case PROMPT_KEY_CLOSE: return (0); @@ -1513,6 +1513,29 @@ server_client_handle_key0(struct client *c, struct key_event *event, break; } } + + wp = s->curw->window->active; + if (wp == NULL || !window_pane_has_prompt(wp)) { + TAILQ_FOREACH(wp, &s->curw->window->panes, entry) { + if (window_pane_has_prompt(wp) && + window_pane_is_visible(wp)) + break; + } + } + if (wp != NULL && + window_pane_has_prompt(wp) && + window_pane_is_visible(wp)) { + switch (window_pane_prompt_key(wp, c, event->key, &event->m)) { + case PROMPT_KEY_HANDLED: + case PROMPT_KEY_CLOSE: + case PROMPT_KEY_MOVE: + return (0); + case PROMPT_KEY_NOT_HANDLED: + if (KEYC_IS_MOUSE(event->key)) + return (0); + break; + } + } } /* @@ -1784,6 +1807,42 @@ out: bufferevent_enable(wp->event, EV_READ); } +/* Move cursor for pane prompt. */ +static int +server_client_prompt_cursor(struct client *c, struct window_pane *wp, int *mode, + u_int *cx, u_int *cy) +{ + struct tty *tty = &c->tty; + struct visible_ranges *r; + u_int ox, oy, sx, sy; + int px, py; + + if (!window_pane_has_prompt(wp)) + return (0); + *mode &= ~MODE_CURSOR; + + tty_window_offset(tty, &ox, &oy, &sx, &sy); + if (status_at_line(c) == 0) + py = wp->yoff; + else + py = wp->yoff + wp->sy - 1; + px = wp->xoff + wp->prompt_cx; + if (px < (int)ox || px > (int)(ox + sx) || + py < (int)oy || py > (int)(oy + sy)) + return (1); + + *cx = px - ox; + *cy = py - oy; + + r = window_visible_ranges(wp, *cx, *cy, 1, NULL); + if (window_position_is_visible(r, *cx)) { + if (status_at_line(c) == 0) + *cy += status_line_size(c); + *mode |= MODE_CURSOR; + } + return (1); +} + /* * Update cursor position and mode settings. The scroll region and attributes * are cleared when idle (waiting for an event) as this is the most likely time @@ -1802,7 +1861,7 @@ server_client_reset_state(struct client *c) struct screen *s = NULL; struct options *oo = c->session->options; int mode = 0, cursor, flags, pane_mode = 0; - u_int cx = 0, cy = 0, ox, oy, sx, sy, n; + u_int cx = 0, cy = 0, ox, oy, sx, sy, prompt = 0; struct visible_ranges *r; if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) @@ -1816,7 +1875,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 (wp != NULL && c->prompt_string == NULL) + } else if (wp != NULL && c->prompt == NULL) s = wp->screen; else s = c->status.active; @@ -1832,42 +1891,36 @@ server_client_reset_state(struct client *c) tty_margin_off(tty); /* Move cursor to pane cursor and offset. */ - if (c->prompt_string != NULL) { - n = options_get_number(oo, "status-position"); - if (n == 0) - cy = status_prompt_line_at(c); - else { - n = status_line_size(c) - status_prompt_line_at(c); - if (n <= tty->sy) - cy = tty->sy - n; - else - cy = tty->sy - 1; - } - cx = c->prompt_cursor; + if (c->prompt != NULL) { + prompt = 1; + status_prompt_cursor(c, &cx, &cy); } else if (wp != NULL && c->overlay_draw == NULL) { - cursor = 0; - pane_mode = wp->base.mode; + prompt = server_client_prompt_cursor(c, wp, &mode, &cx, &cy); + if (!prompt) { + cursor = 0; + pane_mode = wp->base.mode; - tty_window_offset(tty, &ox, &oy, &sx, &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; + tty_window_offset(tty, &ox, &oy, &sx, &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 + (int)s->cx - (int)ox; - cy = wp->yoff + (int)s->cy - (int)oy; + cx = wp->xoff + (int)s->cx - (int)ox; + cy = wp->yoff + (int)s->cy - (int)oy; - r = window_visible_ranges(wp, cx, cy, 1, NULL); - if (!window_position_is_visible(r, cx)) - cursor = 0; + r = window_visible_ranges(wp, cx, cy, 1, NULL); + if (!window_position_is_visible(r, cx)) + cursor = 0; - if (status_at_line(c) == 0) - cy += status_line_size(c); + if (status_at_line(c) == 0) + cy += status_line_size(c); + } + + if ((pane_mode & MODE_SYNC) || !cursor) + mode &= ~MODE_CURSOR; } - - if ((pane_mode & MODE_SYNC) || !cursor) - mode &= ~MODE_CURSOR; } else if (c->overlay_mode == NULL || s == NULL) mode &= ~MODE_CURSOR; if (~pane_mode & MODE_SYNC) { @@ -1895,7 +1948,7 @@ server_client_reset_state(struct client *c) } /* Clear bracketed paste mode if at the prompt. */ - if (c->overlay_draw == NULL && c->prompt_string != NULL) + if (c->overlay_draw == NULL && prompt) mode &= ~MODE_BRACKETPASTE; /* Set the terminal mode and reset attributes. */ diff --git a/server.c b/server.c index e3f923d8a..edabc8a79 100644 --- a/server.c +++ b/server.c @@ -248,7 +248,7 @@ server_start(struct tmuxproc *client, uint64_t flags, struct event_base *base, proc_loop(server_proc, server_loop); job_kill_all(); - status_prompt_save_history(); + prompt_save_history(); exit(0); } diff --git a/status.c b/status.c index 64afc1455..bb0c3bef9 100644 --- a/status.c +++ b/status.c @@ -29,149 +29,10 @@ #include "tmux.h" +static void status_message_area(struct client *, u_int *, u_int *); static void status_message_callback(int, short, void *); static void status_timer_callback(int, short, void *); -static char *status_prompt_find_history_file(void); -static const char *status_prompt_up_history(u_int *, u_int); -static const char *status_prompt_down_history(u_int *, u_int); -static void status_prompt_add_history(const char *, u_int); - -static char *status_prompt_complete(struct client *, const char *, u_int); - -struct status_prompt_menu { - struct client *c; - u_int start; - u_int size; - char **list; -}; - -static const char *prompt_type_strings[] = { - "command", - "search", - "target", - "window-target" -}; - -/* Status prompt history. */ -char **status_prompt_hlist[PROMPT_NTYPES]; -u_int status_prompt_hsize[PROMPT_NTYPES]; - -/* Find the history file to load/save from/to. */ -static char * -status_prompt_find_history_file(void) -{ - const char *home, *history_file; - char *path; - - history_file = options_get_string(global_options, "history-file"); - if (*history_file == '\0') - return (NULL); - if (*history_file == '/') - return (xstrdup(history_file)); - - if (history_file[0] != '~' || history_file[1] != '/') - return (NULL); - if ((home = find_home()) == NULL) - return (NULL); - xasprintf(&path, "%s%s", home, history_file + 1); - return (path); -} - -/* Add loaded history item to the appropriate list. */ -static void -status_prompt_add_typed_history(char *line) -{ - char *typestr; - enum prompt_type type = PROMPT_TYPE_INVALID; - - typestr = strsep(&line, ":"); - if (line != NULL) - type = status_prompt_type(typestr); - if (type == PROMPT_TYPE_INVALID) { - /* - * Invalid types are not expected, but this provides backward - * compatibility with old history files. - */ - if (line != NULL) - *(--line) = ':'; - status_prompt_add_history(typestr, PROMPT_TYPE_COMMAND); - } else - status_prompt_add_history(line, type); -} - -/* Load status prompt history from file. */ -void -status_prompt_load_history(void) -{ - FILE *f; - char *history_file, *line, *tmp; - size_t length; - - if ((history_file = status_prompt_find_history_file()) == NULL) - return; - log_debug("loading history from %s", history_file); - - f = fopen(history_file, "r"); - if (f == NULL) { - log_debug("%s: %s", history_file, strerror(errno)); - free(history_file); - return; - } - free(history_file); - - for (;;) { - if ((line = fgetln(f, &length)) == NULL) - break; - - if (length > 0) { - if (line[length - 1] == '\n') { - line[length - 1] = '\0'; - status_prompt_add_typed_history(line); - } else { - tmp = xmalloc(length + 1); - memcpy(tmp, line, length); - tmp[length] = '\0'; - status_prompt_add_typed_history(tmp); - free(tmp); - } - } - } - fclose(f); -} - -/* Save status prompt history to file. */ -void -status_prompt_save_history(void) -{ - FILE *f; - u_int i, type; - char *history_file; - - if ((history_file = status_prompt_find_history_file()) == NULL) - return; - log_debug("saving history to %s", history_file); - - f = fopen(history_file, "w"); - if (f == NULL) { - log_debug("%s: %s", history_file, strerror(errno)); - free(history_file); - return; - } - free(history_file); - - for (type = 0; type < PROMPT_NTYPES; type++) { - for (i = 0; i < status_prompt_hsize[type]; i++) { - fputs(prompt_type_strings[type], f); - fputc(':', f); - fputs(status_prompt_hlist[type][i], f); - fputc('\n', f); - } - } - fclose(f); - -} - /* Status timer callback. */ static void status_timer_callback(__unused int fd, __unused short events, void *arg) @@ -185,7 +46,7 @@ status_timer_callback(__unused int fd, __unused short events, void *arg) if (s == NULL) return; - if (c->message_string == NULL && c->prompt_string == NULL) + if (c->message_string == NULL && c->prompt == NULL) c->flags |= CLIENT_REDRAWSTATUS; timerclear(&tv); @@ -452,6 +313,28 @@ status_redraw(struct client *c) return (force || changed); } +/* Escape # characters in a string so format_draw treats them as literal. */ +static char * +status_message_escape(const char *s) +{ + const char *cp; + char *out, *p; + size_t n = 0; + + for (cp = s; *cp != '\0'; cp++) { + if (*cp == '#') + n++; + } + p = out = xmalloc(strlen(s) + n + 1); + for (cp = s; *cp != '\0'; cp++) { + if (*cp == '#') + *p++ = '#'; + *p++ = *cp; + } + *p = '\0'; + return (out); +} + /* Set a status line message. */ void status_message_set(struct client *c, int delay, int ignore_styles, @@ -515,28 +398,19 @@ status_message_clear(struct client *c) free(c->message_string); c->message_string = NULL; - if (c->prompt_string == NULL) + if (c->prompt == NULL) c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ status_pop_screen(c); } -/* Clear status line message after timer expires. */ -static void -status_message_callback(__unused int fd, __unused short event, void *data) -{ - struct client *c = data; - - status_message_clear(c); -} - /* * Calculate prompt/message area geometry from the style's width and align * directives: x offset and available width within the status line. */ static void -status_prompt_area(struct client *c, u_int *area_x, u_int *area_w) +status_message_area(struct client *c, u_int *area_x, u_int *area_w) { struct session *s = c->session; struct style *sy; @@ -574,26 +448,13 @@ status_prompt_area(struct client *c, u_int *area_x, u_int *area_w) *area_w = w; } -/* Escape # characters in a string so format_draw treats them as literal. */ -static char * -status_prompt_escape(const char *s) +/* Clear status line message after timer expires. */ +static void +status_message_callback(__unused int fd, __unused short event, void *data) { - const char *cp; - char *out, *p; - size_t n = 0; + struct client *c = data; - for (cp = s; *cp != '\0'; cp++) { - if (*cp == '#') - n++; - } - p = out = xmalloc(strlen(s) + n + 1); - for (cp = s; *cp != '\0'; cp++) { - if (*cp == '#') - *p++ = '#'; - *p++ = *cp; - } - *p = '\0'; - return (out); + status_message_clear(c); } /* Draw client message on status line of present else on last line. */ @@ -624,7 +485,7 @@ status_message_redraw(struct client *c) if (messageline > lines - 1) messageline = lines - 1; - status_prompt_area(c, &ax, &aw); + status_message_area(c, &ax, &aw); ft = format_create_defaults(NULL, c, NULL, NULL, NULL); memcpy(&gc, &grid_default_cell, sizeof gc); @@ -635,7 +496,7 @@ status_message_redraw(struct client *c) * as literal text. */ if (c->message_ignore_styles) { - msg = status_prompt_escape(c->message_string); + msg = status_message_escape(c->message_string); format_add(ft, "message", "%s", msg); free(msg); } else @@ -662,109 +523,103 @@ status_message_redraw(struct client *c) return (1); } + +struct status_prompt_data { + struct client *c; + status_prompt_input_cb inputcb; + prompt_free_cb freecb; + void *data; +}; + +static enum prompt_result +status_prompt_input_callback(void *data, const char *s, + enum prompt_key_result key) +{ + struct status_prompt_data *spd = data; + struct client *c = spd->c; + status_prompt_input_cb inputcb = spd->inputcb; + void *arg = spd->data; + + if (inputcb != NULL) + return (inputcb(c, arg, s, key)); + return (PROMPT_CLOSE); +} + +static void +status_prompt_free_callback(void *data) +{ + struct status_prompt_data *spd = data; + prompt_free_cb freecb = spd->freecb; + void *arg = spd->data; + + if (freecb != NULL) + freecb(arg); + free(spd); +} + /* Accept prompt immediately. */ static enum cmd_retval status_prompt_accept(__unused struct cmdq_item *item, void *data) { struct client *c = data; - void *pd = c->prompt_data; - if (c->prompt_string != NULL) { - c->prompt_inputcb(c, pd, "y", PROMPT_KEY_CLOSE); - status_prompt_clear(c); - } + if (c->prompt != NULL) + status_prompt_key(c, 'y', NULL); return (CMD_RETURN_NORMAL); } /* Enable status line prompt. */ void status_prompt_set(struct client *c, struct cmd_find_state *fs, - const char *msg, const char *input, prompt_input_cb inputcb, + const char *msg, const char *input, status_prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type) { - struct format_tree *ft; - char *tmp, *cp; + struct prompt_create_data pd; + struct status_prompt_data *spd; server_client_clear_overlay(c); - if (fs != NULL) { - ft = format_create_from_state(NULL, c, fs); - cmd_find_copy_state(&c->prompt_state, fs); - } else { - ft = format_create_defaults(NULL, c, NULL, NULL, NULL); - cmd_find_clear_state(&c->prompt_state, 0); - } - - if (input == NULL) - input = ""; - status_message_clear(c); status_prompt_clear(c); status_push_screen(c); - c->prompt_string = xstrdup (msg); + spd = xcalloc(1, sizeof *spd); + spd->c = c; + spd->inputcb = inputcb; + spd->freecb = freecb; + spd->data = data; - if (flags & PROMPT_NOFORMAT) - tmp = xstrdup(input); - else - tmp = format_expand_time(ft, input); - if (flags & PROMPT_INCREMENTAL) { - c->prompt_last = xstrdup(tmp); - c->prompt_buffer = utf8_fromcstr(""); - } else { - c->prompt_last = NULL; - c->prompt_buffer = utf8_fromcstr(tmp); - } - c->prompt_index = utf8_strlen(c->prompt_buffer); - free(tmp); - - c->prompt_inputcb = inputcb; - c->prompt_freecb = freecb; - c->prompt_data = data; - - memset(c->prompt_hindex, 0, sizeof c->prompt_hindex); - - c->prompt_flags = flags; - c->prompt_type = prompt_type; + memset(&pd, 0, sizeof pd); + prompt_set_options(&pd, c->session); + pd.fs = fs; + pd.prompt = msg; + pd.input = input; + pd.type = prompt_type; + pd.flags = flags; + pd.inputcb = status_prompt_input_callback; + pd.freecb = status_prompt_free_callback; + pd.data = spd; + c->prompt = prompt_create(&pd); if ((~flags & PROMPT_INCREMENTAL) && (~flags & PROMPT_NOFREEZE)) c->tty.flags |= TTY_FREEZE; c->flags |= CLIENT_REDRAWSTATUS; - if (flags & PROMPT_INCREMENTAL) { - tmp = utf8_tocstr(c->prompt_buffer); - xasprintf(&cp, "=%s", tmp); - c->prompt_inputcb(c, c->prompt_data, cp, PROMPT_KEY_HANDLED); - free(cp); - free(tmp); - } + prompt_incremental_start(c->prompt); if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT)) cmdq_append(c, cmdq_get_callback(status_prompt_accept, c)); - format_free(ft); } /* Remove status line prompt. */ void status_prompt_clear(struct client *c) { - if (c->prompt_string == NULL) + if (c->prompt == NULL) return; - if (c->prompt_freecb != NULL && c->prompt_data != NULL) - c->prompt_freecb(c->prompt_data); - - free(c->prompt_last); - c->prompt_last = NULL; - - free(c->prompt_string); - c->prompt_string = NULL; - - free(c->prompt_buffer); - c->prompt_buffer = NULL; - - free(c->prompt_saved); - c->prompt_saved = NULL; + prompt_free(c->prompt); + c->prompt = NULL; c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ @@ -776,76 +631,25 @@ status_prompt_clear(struct client *c) void status_prompt_update(struct client *c, const char *msg, const char *input) { - struct format_tree *ft; - char *tmp; - - if (cmd_find_valid_state(&c->prompt_state)) - ft = format_create_from_state(NULL, c, &c->prompt_state); - else - ft = format_create_defaults(NULL, c, NULL, NULL, NULL); - - free(c->prompt_string); - c->prompt_string = xstrdup(msg); - - free(c->prompt_buffer); - tmp = format_expand_time(ft, input); - c->prompt_buffer = utf8_fromcstr(tmp); - c->prompt_index = utf8_strlen(c->prompt_buffer); - free(tmp); - - memset(c->prompt_hindex, 0, sizeof c->prompt_hindex); - + if (c->prompt == NULL) + return; + prompt_update(c->prompt, msg, input); c->flags |= CLIENT_REDRAWSTATUS; - format_free(ft); } -/* Redraw character. Return 1 if can continue redrawing, 0 otherwise. */ -static int -status_prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset, - u_int pwidth, u_int *width, struct grid_cell *gc, - const struct utf8_data *ud) +/* Get the screen line on which the prompt is drawn. */ +static u_int +status_prompt_screen_line(struct client *c) { - u_char ch; + struct tty *tty = &c->tty; + u_int n; - if (*width < offset) { - *width += ud->width; - return (1); - } - if (*width >= offset + pwidth) - return (0); - *width += ud->width; - if (*width > offset + pwidth) - return (0); - - ch = *ud->data; - if (ud->size == 1 && (ch <= 0x1f || ch == 0x7f)) { - gc->data.data[0] = '^'; - gc->data.data[1] = (ch == 0x7f) ? '?' : ch|0x40; - gc->data.size = gc->data.have = 2; - gc->data.width = 2; - } else - utf8_copy(&gc->data, ud); - screen_write_cell(ctx, gc); - return (1); -} - -/* - * Redraw quote indicator '^' if necessary. Return 1 if can continue redrawing, - * 0 otherwise. - */ -static int -status_prompt_redraw_quote(const struct client *c, u_int pcursor, - struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width, - struct grid_cell *gc) -{ - struct utf8_data ud; - - if (c->prompt_flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) { - utf8_set(&ud, '^'); - return (status_prompt_redraw_character(ctx, offset, pwidth, - width, gc, &ud)); - } - return (1); + if (options_get_number(c->session->options, "status-position") == 0) + return (status_prompt_line_at(c)); + n = status_line_size(c) - status_prompt_line_at(c); + if (n <= tty->sy) + return (tty->sy - n); + return (tty->sy - 1); } /* Draw client prompt on status line of present else on last line. */ @@ -854,16 +658,9 @@ status_prompt_redraw(struct client *c) { struct status_line *sl = &c->status; struct screen_write_ctx ctx; - struct session *s = c->session; - struct options *oo = s->options; struct screen old_screen; - u_int i, lines, offset, left, start, width, n; - u_int pcursor, pwidth, promptline; - u_int ax, aw; - struct grid_cell gc; - struct format_tree *ft; - const char *msgfmt; - char *expanded, *prompt, *tmp; + struct prompt_draw_data pdd; + u_int lines, ax, aw, promptline; if (c->tty.sx == 0 || c->tty.sy == 0) return (0); @@ -874,93 +671,22 @@ status_prompt_redraw(struct client *c) lines = 1; screen_init(sl->active, c->tty.sx, lines, 0); - if (cmd_find_valid_state(&c->prompt_state)) - ft = format_create_from_state(NULL, c, &c->prompt_state); - else - ft = format_create_defaults(NULL, c, NULL, NULL, NULL); - - n = options_get_number(s->options, "prompt-cursor-colour"); - sl->active->default_ccolour = n; - if (c->prompt_flags & PROMPT_COMMANDMODE) - n = options_get_number(oo, "prompt-command-cursor-style"); - else - n = options_get_number(oo, "prompt-cursor-style"); - screen_set_cursor_style(n, &sl->active->default_cstyle, - &sl->active->default_mode); - promptline = status_prompt_line_at(c); if (promptline > lines - 1) promptline = lines - 1; - if (c->prompt_flags & PROMPT_COMMANDMODE) - style_apply(&gc, oo, "message-command-style", NULL); - else - style_apply(&gc, oo, "message-style", NULL); - - status_prompt_area(c, &ax, &aw); - - tmp = utf8_tocstr(c->prompt_buffer); - format_add(ft, "prompt_input", "%s", tmp); - prompt = format_expand_time(ft, c->prompt_string); - free(tmp); - - /* - * Set #{message} to the prompt string and expand message-format. - * format_draw handles fill, alignment, and decorations in one call. - */ - format_add(ft, "message", "%s", prompt); - format_add(ft, "command_prompt", "%d", - !!(c->prompt_flags & PROMPT_COMMANDMODE)); - msgfmt = options_get_string(oo, "message-format"); - expanded = format_expand_time(ft, msgfmt); - free(prompt); - - start = format_width(expanded); - if (start > aw) - start = aw; + status_message_area(c, &ax, &aw); screen_write_start(&ctx, sl->active); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines); - screen_write_cursormove(&ctx, ax, promptline, 0); - format_draw(&ctx, &gc, aw, expanded, NULL, 0); - screen_write_cursormove(&ctx, ax + start, promptline, 0); - free(expanded); + pdd.ctx = &ctx; + pdd.area_x = ax; + pdd.area_width = aw; + pdd.prompt_line = promptline; + pdd.cursor_x = &sl->prompt_cx; + prompt_draw(c->prompt, &pdd); - left = aw - start; - if (left == 0) - goto finished; - - pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index); - pwidth = utf8_strwidth(c->prompt_buffer, -1); - if (c->prompt_flags & PROMPT_QUOTENEXT) - pwidth++; - if (pcursor >= left) { - /* - * The cursor would be outside the screen so start drawing - * with it on the right. - */ - offset = (pcursor - left) + 1; - pwidth = left; - } else - offset = 0; - if (pwidth > left) - pwidth = left; - c->prompt_cursor = ax + start + pcursor - offset; - - width = 0; - for (i = 0; c->prompt_buffer[i].size != 0; i++) { - if (!status_prompt_redraw_quote(c, pcursor, &ctx, offset, - pwidth, &width, &gc)) - break; - if (!status_prompt_redraw_character(&ctx, offset, pwidth, - &width, &gc, &c->prompt_buffer[i])) - break; - } - status_prompt_redraw_quote(c, pcursor, &ctx, offset, pwidth, &width, - &gc); - -finished: screen_write_stop(&ctx); if (grid_compare(sl->active->grid, old_screen.grid) == 0) { @@ -971,1133 +697,34 @@ finished: return (1); } -/* Is this a separator? */ -static int -status_prompt_in_list(const char *ws, const struct utf8_data *ud) +/* Work out the tty cursor position for the prompt. */ +void +status_prompt_cursor(struct client *c, u_int *cx, u_int *cy) { - if (ud->size != 1 || ud->width != 1) - return (0); - return (strchr(ws, *ud->data) != NULL); -} - -/* Is this a space? */ -static int -status_prompt_space(const struct utf8_data *ud) -{ - if (ud->size != 1 || ud->width != 1) - return (0); - return (*ud->data == ' '); -} - -static key_code -status_prompt_keypad_key(key_code key) -{ - if (key & KEYC_MASK_MODIFIERS) - return (key); - - switch (key) { - case KEYC_KP_SLASH: - return ('/'); - case KEYC_KP_STAR: - return ('*'); - case KEYC_KP_MINUS: - return ('-'); - case KEYC_KP_SEVEN: - return ('7'); - case KEYC_KP_EIGHT: - return ('8'); - case KEYC_KP_NINE: - return ('9'); - case KEYC_KP_PLUS: - return ('+'); - case KEYC_KP_FOUR: - return ('4'); - case KEYC_KP_FIVE: - return ('5'); - case KEYC_KP_SIX: - return ('6'); - case KEYC_KP_ONE: - return ('1'); - case KEYC_KP_TWO: - return ('2'); - case KEYC_KP_THREE: - return ('3'); - case KEYC_KP_ENTER: - return ('\r'); - case KEYC_KP_ZERO: - return ('0'); - case KEYC_KP_PERIOD: - return ('.'); - } - return (key); -} - -/* - * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key - * as an emacs key; return 2 to append to the buffer. - */ -static int -status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) -{ - if (~c->prompt_flags & PROMPT_COMMANDMODE) { - switch (key) { - case 'a'|KEYC_CTRL: - case 'c'|KEYC_CTRL: - case 'e'|KEYC_CTRL: - case 'g'|KEYC_CTRL: - case 'h'|KEYC_CTRL: - case '\011': /* Tab */ - case 'k'|KEYC_CTRL: - case 'n'|KEYC_CTRL: - case 'p'|KEYC_CTRL: - case 't'|KEYC_CTRL: - case 'u'|KEYC_CTRL: - case 'v'|KEYC_CTRL: - case 'w'|KEYC_CTRL: - case 'y'|KEYC_CTRL: - case '\n': - case '\r': - case KEYC_LEFT|KEYC_CTRL: - case KEYC_RIGHT|KEYC_CTRL: - case KEYC_BSPACE: - case KEYC_DC: - case KEYC_DOWN: - case KEYC_END: - case KEYC_HOME: - case KEYC_LEFT: - case KEYC_RIGHT: - case KEYC_UP: - *new_key = key; - return (1); - case '\033': /* Escape */ - case '['|KEYC_CTRL: - c->prompt_flags |= PROMPT_COMMANDMODE; - if (c->prompt_index != 0) - c->prompt_index--; - c->flags |= CLIENT_REDRAWSTATUS; - return (0); - } - *new_key = key; - return (2); - } - - switch (key) { - case KEYC_BSPACE: - *new_key = KEYC_LEFT; - return (1); - case 'A': - case 'I': - case 'C': - case 's': - case 'a': - c->prompt_flags &= ~PROMPT_COMMANDMODE; - c->flags |= CLIENT_REDRAWSTATUS; - break; /* switch mode and... */ - case 'S': - c->prompt_flags &= ~PROMPT_COMMANDMODE; - c->flags |= CLIENT_REDRAWSTATUS; - *new_key = 'u'|KEYC_CTRL; - return (1); - case 'i': - c->prompt_flags &= ~PROMPT_COMMANDMODE; - c->flags |= CLIENT_REDRAWSTATUS; - return (0); - case '\033': /* Escape */ - case '['|KEYC_CTRL: - return (0); - } - - switch (key) { - case 'A': - case '$': - *new_key = KEYC_END; - return (1); - case 'I': - case '0': - case '^': - *new_key = KEYC_HOME; - return (1); - case 'C': - case 'D': - *new_key = 'k'|KEYC_CTRL; - return (1); - case KEYC_BSPACE: - case 'X': - *new_key = KEYC_BSPACE; - return (1); - case 'b': - *new_key = 'b'|KEYC_META; - return (1); - case 'B': - *new_key = 'B'|KEYC_VI; - return (1); - case 'd': - *new_key = 'u'|KEYC_CTRL; - return (1); - case 'e': - *new_key = 'e'|KEYC_VI; - return (1); - case 'E': - *new_key = 'E'|KEYC_VI; - return (1); - case 'w': - *new_key = 'w'|KEYC_VI; - return (1); - case 'W': - *new_key = 'W'|KEYC_VI; - return (1); - case 'p': - *new_key = 'y'|KEYC_CTRL; - return (1); - case 'q': - *new_key = 'c'|KEYC_CTRL; - return (1); - case 's': - case KEYC_DC: - case 'x': - *new_key = KEYC_DC; - return (1); - case KEYC_DOWN: - case 'j': - *new_key = KEYC_DOWN; - return (1); - case KEYC_LEFT: - case 'h': - *new_key = KEYC_LEFT; - return (1); - case 'a': - case KEYC_RIGHT: - case 'l': - *new_key = KEYC_RIGHT; - return (1); - case KEYC_UP: - case 'k': - *new_key = KEYC_UP; - return (1); - case 'h'|KEYC_CTRL: - case 'c'|KEYC_CTRL: - case '\n': - case '\r': - return (1); - } - return (0); -} - -/* Paste into prompt. */ -static int -status_prompt_paste(struct client *c) -{ - struct paste_buffer *pb; - const char *bufdata; - size_t size, n, bufsize; - u_int i; - struct utf8_data *ud, *udp; - enum utf8_state more; - - size = utf8_strlen(c->prompt_buffer); - if (c->prompt_saved != NULL) { - ud = c->prompt_saved; - n = utf8_strlen(c->prompt_saved); - } else { - if ((pb = paste_get_top(NULL)) == NULL) - return (0); - bufdata = paste_buffer_data(pb, &bufsize); - ud = udp = xreallocarray(NULL, bufsize + 1, sizeof *ud); - for (i = 0; i != bufsize; /* nothing */) { - more = utf8_open(udp, bufdata[i]); - if (more == UTF8_MORE) { - while (++i != bufsize && more == UTF8_MORE) - more = utf8_append(udp, bufdata[i]); - if (more == UTF8_DONE) { - udp++; - continue; - } - i -= udp->have; - } - if (bufdata[i] <= 31 || bufdata[i] >= 127) - break; - utf8_set(udp, bufdata[i]); - udp++; - i++; - } - udp->size = 0; - n = udp - ud; - } - if (n != 0) { - c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, - sizeof *c->prompt_buffer); - if (c->prompt_index == size) { - memcpy(c->prompt_buffer + c->prompt_index, ud, - n * sizeof *c->prompt_buffer); - c->prompt_index += n; - c->prompt_buffer[c->prompt_index].size = 0; - } else { - memmove(c->prompt_buffer + c->prompt_index + n, - c->prompt_buffer + c->prompt_index, - (size + 1 - c->prompt_index) * - sizeof *c->prompt_buffer); - memcpy(c->prompt_buffer + c->prompt_index, ud, - n * sizeof *c->prompt_buffer); - c->prompt_index += n; - } - } - if (ud != c->prompt_saved) - free(ud); - return (1); -} - -/* Finish completion. */ -static int -status_prompt_replace_complete(struct client *c, const char *s) -{ - char word[64], *allocated = NULL; - size_t size, n, off, idx, used; - struct utf8_data *first, *last, *ud; - - /* Work out where the cursor currently is. */ - idx = c->prompt_index; - if (idx != 0) - idx--; - size = utf8_strlen(c->prompt_buffer); - - /* Find the word we are in. */ - first = &c->prompt_buffer[idx]; - while (first > c->prompt_buffer && !status_prompt_space(first)) - first--; - while (first->size != 0 && status_prompt_space(first)) - first++; - last = &c->prompt_buffer[idx]; - while (last->size != 0 && !status_prompt_space(last)) - last++; - while (last > c->prompt_buffer && status_prompt_space(last)) - last--; - if (last->size != 0) - last++; - if (last < first) - return (0); - if (s == NULL) { - used = 0; - for (ud = first; ud < last; ud++) { - if (used + ud->size >= sizeof word) - break; - memcpy(word + used, ud->data, ud->size); - used += ud->size; - } - if (ud != last) - return (0); - word[used] = '\0'; - } - - /* Try to complete it. */ - if (s == NULL) { - allocated = status_prompt_complete(c, word, - first - c->prompt_buffer); - if (allocated == NULL) - return (0); - s = allocated; - } - - /* Trim out word. */ - n = size - (last - c->prompt_buffer) + 1; /* with \0 */ - memmove(first, last, n * sizeof *c->prompt_buffer); - size -= last - first; - - /* Insert the new word. */ - size += strlen(s); - off = first - c->prompt_buffer; - c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, - sizeof *c->prompt_buffer); - first = c->prompt_buffer + off; - memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); - for (idx = 0; idx < strlen(s); idx++) - utf8_set(&first[idx], s[idx]); - c->prompt_index = (first - c->prompt_buffer) + strlen(s); - - free(allocated); - return (1); -} - -/* Prompt forward to the next beginning of a word. */ -static void -status_prompt_forward_word(struct client *c, size_t size, int vi, - const char *separators) -{ - size_t idx = c->prompt_index; - int word_is_separators; - - /* In emacs mode, skip until the first non-whitespace character. */ - if (!vi) - while (idx != size && - status_prompt_space(&c->prompt_buffer[idx])) - idx++; - - /* Can't move forward if we're already at the end. */ - if (idx == size) { - c->prompt_index = idx; - return; - } - - /* Determine the current character class (separators or not). */ - word_is_separators = status_prompt_in_list(separators, - &c->prompt_buffer[idx]) && - !status_prompt_space(&c->prompt_buffer[idx]); - - /* Skip ahead until the first space or opposite character class. */ - do { - idx++; - if (status_prompt_space(&c->prompt_buffer[idx])) { - /* In vi mode, go to the start of the next word. */ - if (vi) - while (idx != size && - status_prompt_space(&c->prompt_buffer[idx])) - idx++; - break; - } - } while (idx != size && word_is_separators == status_prompt_in_list( - separators, &c->prompt_buffer[idx])); - - c->prompt_index = idx; -} - -/* Prompt forward to the next end of a word. */ -static void -status_prompt_end_word(struct client *c, size_t size, const char *separators) -{ - size_t idx = c->prompt_index; - int word_is_separators; - - /* Can't move forward if we're already at the end. */ - if (idx == size) - return; - - /* Find the next word. */ - do { - idx++; - if (idx == size) { - c->prompt_index = idx; - return; - } - } while (status_prompt_space(&c->prompt_buffer[idx])); - - /* Determine the character class (separators or not). */ - word_is_separators = status_prompt_in_list(separators, - &c->prompt_buffer[idx]); - - /* Skip ahead until the next space or opposite character class. */ - do { - idx++; - if (idx == size) - break; - } while (!status_prompt_space(&c->prompt_buffer[idx]) && - word_is_separators == status_prompt_in_list(separators, - &c->prompt_buffer[idx])); - - /* Back up to the previous character to stop at the end of the word. */ - c->prompt_index = idx - 1; -} - -/* Prompt backward to the previous beginning of a word. */ -static void -status_prompt_backward_word(struct client *c, const char *separators) -{ - size_t idx = c->prompt_index; - int word_is_separators; - - /* Find non-whitespace. */ - while (idx != 0) { - --idx; - if (!status_prompt_space(&c->prompt_buffer[idx])) - break; - } - word_is_separators = status_prompt_in_list(separators, - &c->prompt_buffer[idx]); - - /* Find the character before the beginning of the word. */ - while (idx != 0) { - --idx; - if (status_prompt_space(&c->prompt_buffer[idx]) || - word_is_separators != status_prompt_in_list(separators, - &c->prompt_buffer[idx])) { - /* Go back to the word. */ - idx++; - break; - } - } - c->prompt_index = idx; -} - -/* Fire input callback when done. */ -static enum prompt_key_result -status_prompt_done(struct client *c, const char *s) -{ - struct prompt_data *pd = c->prompt_data; - - if (c->prompt_inputcb(c, pd, s, PROMPT_KEY_CLOSE) == PROMPT_CLOSE) { - status_prompt_clear(c); - return (PROMPT_KEY_CLOSE); - } - c->flags |= CLIENT_REDRAWSTATUS; - return (PROMPT_KEY_HANDLED); -} - -/* Check for a movement key. */ -static enum prompt_key_result -status_prompt_check_move(struct client *c, key_code key) -{ - struct prompt_data *pd = c->prompt_data; - char *s; - - if (~c->prompt_flags & PROMPT_INCREMENTAL) - return (PROMPT_KEY_NOT_HANDLED); - switch (key) { - case KEYC_UP: - case KEYC_DOWN: - case KEYC_LEFT: - case KEYC_RIGHT: - case KEYC_PPAGE: - case KEYC_NPAGE: - break; - default: - return (PROMPT_KEY_NOT_HANDLED); - } - s = utf8_tocstr(c->prompt_buffer); - if (c->prompt_inputcb(c, pd, s, PROMPT_KEY_MOVE) == PROMPT_CLOSE) { - status_prompt_clear(c); - free(s); - return (PROMPT_KEY_CLOSE); - } - free(s); - return (PROMPT_KEY_MOVE); + *cy = status_prompt_screen_line(c); + *cx = c->status.prompt_cx; } /* Handle keys in prompt. */ enum prompt_key_result -status_prompt_key(struct client *c, key_code key) +status_prompt_key(struct client *c, key_code key, struct mouse_event *m) { - struct prompt_data *pd = c->prompt_data; - struct options *oo = c->session->options; - char *s, *cp, prefix = '='; - const char *histstr, *separators = NULL, *ks; - size_t size, idx; - struct utf8_data tmp; - enum prompt_key_result result = PROMPT_KEY_HANDLED; - int keys, word_is_separators; + enum prompt_key_result result; + u_int ax, aw; + int redraw = 0; - if (c->prompt_flags & PROMPT_KEY) { - ks = key_string_lookup_key(key, 0); - c->prompt_inputcb(c, pd, ks, PROMPT_KEY_CLOSE); + if (KEYC_IS_MOUSE(key)) { + if (m == NULL || MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1 || + MOUSE_DRAG(m->b) || MOUSE_RELEASE(m->b) || + m->y != status_prompt_screen_line(c)) + return (PROMPT_KEY_NOT_HANDLED); + status_message_area(c, &ax, &aw); + result = prompt_mouse(c->prompt, m->x, ax, aw, &redraw); + } else + result = prompt_key(c->prompt, key, &redraw); + if (redraw && c->prompt != NULL) + c->flags |= CLIENT_REDRAWSTATUS; + if (c->prompt != NULL && prompt_closed(c->prompt)) status_prompt_clear(c); - return (PROMPT_KEY_CLOSE); - } - size = utf8_strlen(c->prompt_buffer); - - key &= ~KEYC_MASK_FLAGS; - key = status_prompt_keypad_key(key); - - if (c->prompt_flags & PROMPT_NUMERIC) { - if (key >= '0' && key <= '9') - goto append_key; - s = utf8_tocstr(c->prompt_buffer); - c->prompt_inputcb(c, pd, s, PROMPT_KEY_CLOSE); - status_prompt_clear(c); - free(s); - return (PROMPT_KEY_NOT_HANDLED); - } - - if (c->prompt_flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) { - if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) - key = 0x7f; - else if ((key & KEYC_MASK_KEY) > 0x7f) { - if (!KEYC_IS_UNICODE(key)) - return (PROMPT_KEY_HANDLED); - key &= KEYC_MASK_KEY; - } else - key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY; - c->prompt_flags &= ~PROMPT_QUOTENEXT; - goto append_key; - } - - keys = options_get_number(c->session->options, "status-keys"); - if (keys == MODEKEY_VI) { - switch (status_prompt_translate_key(c, key, &key)) { - case 1: - goto process_key; - case 2: - goto append_key; - default: - return (PROMPT_KEY_HANDLED); - } - } - -process_key: - result = status_prompt_check_move(c, key); - if (result != PROMPT_KEY_NOT_HANDLED) - return (result); - switch (key) { - case KEYC_LEFT: - case 'b'|KEYC_CTRL: - if (c->prompt_index > 0) { - c->prompt_index--; - break; - } - break; - case KEYC_RIGHT: - case 'f'|KEYC_CTRL: - if (c->prompt_index < size) { - c->prompt_index++; - break; - } - break; - case KEYC_HOME: - case 'a'|KEYC_CTRL: - if (c->prompt_index != 0) { - c->prompt_index = 0; - break; - } - break; - case KEYC_END: - case 'e'|KEYC_CTRL: - if (c->prompt_index != size) { - c->prompt_index = size; - break; - } - break; - case '\011': /* Tab */ - if (status_prompt_replace_complete(c, NULL)) - goto changed; - break; - case KEYC_BSPACE: - case 'h'|KEYC_CTRL: - if (c->prompt_flags & PROMPT_BSPACE_EXIT && size == 0) - return (status_prompt_done(c, NULL)); - if (c->prompt_index != 0) { - if (c->prompt_index == size) - c->prompt_buffer[--c->prompt_index].size = 0; - else { - memmove(c->prompt_buffer + c->prompt_index - 1, - c->prompt_buffer + c->prompt_index, - (size + 1 - c->prompt_index) * - sizeof *c->prompt_buffer); - c->prompt_index--; - } - goto changed; - } - break; - case KEYC_DC: - case 'd'|KEYC_CTRL: - if (c->prompt_index != size) { - memmove(c->prompt_buffer + c->prompt_index, - c->prompt_buffer + c->prompt_index + 1, - (size + 1 - c->prompt_index) * - sizeof *c->prompt_buffer); - goto changed; - } - break; - case 'u'|KEYC_CTRL: - c->prompt_buffer[0].size = 0; - c->prompt_index = 0; - goto changed; - case 'k'|KEYC_CTRL: - if (c->prompt_index < size) { - c->prompt_buffer[c->prompt_index].size = 0; - goto changed; - } - break; - case 'w'|KEYC_CTRL: - separators = options_get_string(oo, "word-separators"); - idx = c->prompt_index; - - /* Find non-whitespace. */ - while (idx != 0) { - idx--; - if (!status_prompt_space(&c->prompt_buffer[idx])) - break; - } - word_is_separators = status_prompt_in_list(separators, - &c->prompt_buffer[idx]); - - /* Find the character before the beginning of the word. */ - while (idx != 0) { - idx--; - if (status_prompt_space(&c->prompt_buffer[idx]) || - word_is_separators != status_prompt_in_list( - separators, &c->prompt_buffer[idx])) { - /* Go back to the word. */ - idx++; - break; - } - } - - free(c->prompt_saved); - c->prompt_saved = xcalloc(sizeof *c->prompt_buffer, - (c->prompt_index - idx) + 1); - memcpy(c->prompt_saved, c->prompt_buffer + idx, - (c->prompt_index - idx) * sizeof *c->prompt_buffer); - - memmove(c->prompt_buffer + idx, - c->prompt_buffer + c->prompt_index, - (size + 1 - c->prompt_index) * - sizeof *c->prompt_buffer); - memset(c->prompt_buffer + size - (c->prompt_index - idx), - '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer); - c->prompt_index = idx; - - goto changed; - case KEYC_RIGHT|KEYC_CTRL: - case 'f'|KEYC_META: - separators = options_get_string(oo, "word-separators"); - status_prompt_forward_word(c, size, 0, separators); - goto changed; - case 'E'|KEYC_VI: - status_prompt_end_word(c, size, ""); - goto changed; - case 'e'|KEYC_VI: - separators = options_get_string(oo, "word-separators"); - status_prompt_end_word(c, size, separators); - goto changed; - case 'W'|KEYC_VI: - status_prompt_forward_word(c, size, 1, ""); - goto changed; - case 'w'|KEYC_VI: - separators = options_get_string(oo, "word-separators"); - status_prompt_forward_word(c, size, 1, separators); - goto changed; - case 'B'|KEYC_VI: - status_prompt_backward_word(c, ""); - goto changed; - case KEYC_LEFT|KEYC_CTRL: - case 'b'|KEYC_META: - separators = options_get_string(oo, "word-separators"); - status_prompt_backward_word(c, separators); - goto changed; - case KEYC_UP: - case 'p'|KEYC_CTRL: - histstr = status_prompt_up_history(c->prompt_hindex, - c->prompt_type); - if (histstr == NULL) - break; - free(c->prompt_buffer); - c->prompt_buffer = utf8_fromcstr(histstr); - c->prompt_index = utf8_strlen(c->prompt_buffer); - goto changed; - case KEYC_DOWN: - case 'n'|KEYC_CTRL: - histstr = status_prompt_down_history(c->prompt_hindex, - c->prompt_type); - if (histstr == NULL) - break; - free(c->prompt_buffer); - c->prompt_buffer = utf8_fromcstr(histstr); - c->prompt_index = utf8_strlen(c->prompt_buffer); - goto changed; - case 'y'|KEYC_CTRL: - if (status_prompt_paste(c)) - goto changed; - break; - case 't'|KEYC_CTRL: - idx = c->prompt_index; - if (idx < size) - idx++; - if (idx >= 2) { - utf8_copy(&tmp, &c->prompt_buffer[idx - 2]); - utf8_copy(&c->prompt_buffer[idx - 2], - &c->prompt_buffer[idx - 1]); - utf8_copy(&c->prompt_buffer[idx - 1], &tmp); - c->prompt_index = idx; - goto changed; - } - break; - case '\r': - case '\n': - s = utf8_tocstr(c->prompt_buffer); - if (*s != '\0') - status_prompt_add_history(s, c->prompt_type); - result = status_prompt_done(c, s); - free(s); - return (result); - case '\033': /* Escape */ - case '['|KEYC_CTRL: - case 'c'|KEYC_CTRL: - case 'g'|KEYC_CTRL: - return (status_prompt_done(c, NULL)); - case 'r'|KEYC_CTRL: - if (~c->prompt_flags & PROMPT_INCREMENTAL) - break; - if (c->prompt_buffer[0].size == 0) { - prefix = '='; - free(c->prompt_buffer); - c->prompt_buffer = utf8_fromcstr(c->prompt_last); - c->prompt_index = utf8_strlen(c->prompt_buffer); - } else - prefix = '-'; - goto changed; - case 's'|KEYC_CTRL: - if (~c->prompt_flags & PROMPT_INCREMENTAL) - break; - if (c->prompt_buffer[0].size == 0) { - prefix = '='; - free(c->prompt_buffer); - c->prompt_buffer = utf8_fromcstr(c->prompt_last); - c->prompt_index = utf8_strlen(c->prompt_buffer); - } else - prefix = '+'; - goto changed; - case 'v'|KEYC_CTRL: - c->prompt_flags |= PROMPT_QUOTENEXT; - break; - default: - goto append_key; - } - - c->flags |= CLIENT_REDRAWSTATUS; - return (PROMPT_KEY_HANDLED); - -append_key: - if (key <= 0x7f) { - utf8_set(&tmp, key); - if (key <= 0x1f || key == 0x7f) - tmp.width = 2; - } else if (KEYC_IS_UNICODE(key)) - utf8_to_data(key, &tmp); - else - return (PROMPT_KEY_HANDLED); - - c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, - sizeof *c->prompt_buffer); - - if (c->prompt_index == size) { - utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); - c->prompt_index++; - c->prompt_buffer[c->prompt_index].size = 0; - } else { - memmove(c->prompt_buffer + c->prompt_index + 1, - c->prompt_buffer + c->prompt_index, - (size + 1 - c->prompt_index) * - sizeof *c->prompt_buffer); - utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); - c->prompt_index++; - } - - if (c->prompt_flags & PROMPT_SINGLE) { - if (utf8_strlen(c->prompt_buffer) != 1) { - status_prompt_clear(c); - result = PROMPT_KEY_CLOSE; - } else { - s = utf8_tocstr(c->prompt_buffer); - result = status_prompt_done(c, s); - free(s); - } - } - -changed: - c->flags |= CLIENT_REDRAWSTATUS; - if (c->prompt_flags & PROMPT_INCREMENTAL) { - s = utf8_tocstr(c->prompt_buffer); - xasprintf(&cp, "%c%s", prefix, s); - c->prompt_inputcb(c, pd, cp, PROMPT_KEY_HANDLED); - free(cp); - free(s); - } return (result); } - -/* Get previous line from the history. */ -static const char * -status_prompt_up_history(u_int *idx, u_int type) -{ - /* - * History runs from 0 to size - 1. Index is from 0 to size. Zero is - * empty. - */ - - if (status_prompt_hsize[type] == 0 || - idx[type] == status_prompt_hsize[type]) - return (NULL); - idx[type]++; - return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]); -} - -/* Get next line from the history. */ -static const char * -status_prompt_down_history(u_int *idx, u_int type) -{ - if (status_prompt_hsize[type] == 0 || idx[type] == 0) - return (""); - idx[type]--; - if (idx[type] == 0) - return (""); - return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]); -} - -/* Add line to the history. */ -static void -status_prompt_add_history(const char *line, u_int type) -{ - u_int i, oldsize, newsize, freecount, hlimit, new = 1; - size_t movesize; - - oldsize = status_prompt_hsize[type]; - if (oldsize > 0 && - strcmp(status_prompt_hlist[type][oldsize - 1], line) == 0) - new = 0; - - hlimit = options_get_number(global_options, "prompt-history-limit"); - if (hlimit > oldsize) { - if (new == 0) - return; - newsize = oldsize + new; - } else { - newsize = hlimit; - freecount = oldsize + new - newsize; - if (freecount > oldsize) - freecount = oldsize; - if (freecount == 0) - return; - for (i = 0; i < freecount; i++) - free(status_prompt_hlist[type][i]); - movesize = (oldsize - freecount) * - sizeof *status_prompt_hlist[type]; - if (movesize > 0) { - memmove(&status_prompt_hlist[type][0], - &status_prompt_hlist[type][freecount], movesize); - } - } - - if (newsize == 0) { - free(status_prompt_hlist[type]); - status_prompt_hlist[type] = NULL; - } else if (newsize != oldsize) { - status_prompt_hlist[type] = - xreallocarray(status_prompt_hlist[type], newsize, - sizeof *status_prompt_hlist[type]); - } - - if (new == 1 && newsize > 0) - status_prompt_hlist[type][newsize - 1] = xstrdup(line); - status_prompt_hsize[type] = newsize; -} - -/* Add to completion list. */ -static void -status_prompt_add_list(char ***list, u_int *size, const char *s) -{ - u_int i; - - for (i = 0; i < *size; i++) { - if (strcmp((*list)[i], s) == 0) - return; - } - *list = xreallocarray(*list, (*size) + 1, sizeof **list); - (*list)[(*size)++] = xstrdup(s); -} - -/* Build completion list. */ -static char ** -status_prompt_complete_list(u_int *size, const char *s) -{ - char **list = NULL, *tmp; - const char *value, *cp; - const struct cmd_entry **cmdent; - size_t slen = strlen(s), valuelen; - struct options_entry *o; - struct options_array_item *a; - - *size = 0; - for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { - if (strncmp((*cmdent)->name, s, slen) == 0) - status_prompt_add_list(&list, size, (*cmdent)->name); - if ((*cmdent)->alias != NULL && - strncmp((*cmdent)->alias, s, slen) == 0) - status_prompt_add_list(&list, size, (*cmdent)->alias); - } - o = options_get_only(global_options, "command-alias"); - if (o != NULL) { - a = options_array_first(o); - while (a != NULL) { - value = options_array_item_value(a)->string; - if ((cp = strchr(value, '=')) == NULL) - goto next; - valuelen = cp - value; - if (slen > valuelen || strncmp(value, s, slen) != 0) - goto next; - - xasprintf(&tmp, "%.*s", (int)valuelen, value); - status_prompt_add_list(&list, size, tmp); - free(tmp); - - next: - a = options_array_next(a); - } - } - return (list); -} - -/* Find longest prefix. */ -static char * -status_prompt_complete_prefix(char **list, u_int size) -{ - char *out; - u_int i; - size_t j; - - if (list == NULL || size == 0) - return (NULL); - out = xstrdup(list[0]); - for (i = 1; i < size; i++) { - j = strlen(list[i]); - if (j > strlen(out)) - j = strlen(out); - for (; j > 0; j--) { - if (out[j - 1] != list[i][j - 1]) - out[j - 1] = '\0'; - } - } - return (out); -} - -/* Complete word menu callback. */ -static void -status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key, - void *data) -{ - struct status_prompt_menu *spm = data; - struct client *c = spm->c; - u_int i; - - if (key != KEYC_NONE) { - idx += spm->start; - if (status_prompt_replace_complete(c, spm->list[idx])) - c->flags |= CLIENT_REDRAWSTATUS; - } - - for (i = 0; i < spm->size; i++) - free(spm->list[i]); - free(spm->list); -} - -/* Show complete word menu. */ -static int -status_prompt_complete_list_menu(struct client *c, char **list, u_int size, - u_int offset) -{ - struct menu *menu; - struct menu_item item; - struct status_prompt_menu *spm; - u_int lines = status_line_size(c), height, i; - u_int py, ax, aw; - - if (size <= 1) - return (0); - if (c->tty.sy - lines < 3) - return (0); - - spm = xmalloc(sizeof *spm); - spm->c = c; - spm->size = size; - spm->list = list; - - height = c->tty.sy - lines - 2; - if (height > 10) - height = 10; - if (height > size) - height = size; - spm->start = size - height; - - menu = menu_create(""); - for (i = spm->start; i < size; i++) { - item.name = list[i]; - item.key = '0' + (i - spm->start); - item.command = NULL; - menu_add_item(menu, &item, NULL, c, NULL); - } - - status_prompt_area(c, &ax, &aw); - if (options_get_number(c->session->options, "status-position") == 0) - py = lines; - else - py = c->tty.sy - 3 - height; - offset += utf8_cstrwidth(c->prompt_string); - offset += ax; - if (offset > 2) - offset -= 2; - else - offset = 0; - - if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c, - BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, - status_prompt_menu_callback, spm) != 0) { - menu_free(menu); - free(spm); - return (0); - } - return (1); -} - -/* Sort complete list. */ -static int -status_prompt_complete_sort(const void *a, const void *b) -{ - const char **aa = (const char **)a, **bb = (const char **)b; - - return (strcmp(*aa, *bb)); -} - -/* Complete word. */ -static char * -status_prompt_complete(struct client *c, const char *word, u_int offset) -{ - char **list = NULL, *out = NULL; - u_int size = 0, i; - - if (c->prompt_type != PROMPT_TYPE_COMMAND || offset != 0 || - *word == '\0') - return (NULL); - - list = status_prompt_complete_list(&size, word); - if (size == 0) - out = NULL; - else if (size == 1) - xasprintf(&out, "%s ", list[0]); - else - out = status_prompt_complete_prefix(list, size); - - if (size != 0) { - qsort(list, size, sizeof *list, status_prompt_complete_sort); - for (i = 0; i < size; i++) - log_debug("complete %u: %s", i, list[i]); - } - - if (out != NULL && strcmp(word, out) == 0) { - free(out); - out = NULL; - } - if (out != NULL || - !status_prompt_complete_list_menu(c, list, size, offset)) { - for (i = 0; i < size; i++) - free(list[i]); - free(list); - } - return (out); -} - -/* Return the type of the prompt as an enum. */ -enum prompt_type -status_prompt_type(const char *type) -{ - u_int i; - - for (i = 0; i < PROMPT_NTYPES; i++) { - if (strcmp(type, status_prompt_type_string(i)) == 0) - return (i); - } - return (PROMPT_TYPE_INVALID); -} - -/* Accessor for prompt_type_strings. */ -const char * -status_prompt_type_string(u_int type) -{ - if (type >= PROMPT_NTYPES) - return ("invalid"); - return (prompt_type_strings[type]); -} diff --git a/tmux.1 b/tmux.1 index c9457cc47..feeb84393 100644 --- a/tmux.1 +++ b/tmux.1 @@ -7359,7 +7359,7 @@ See for possible values for .Ar prompt\-type . .It Xo Ic command\-prompt -.Op Fl 1bCeFiklN +.Op Fl 1bCeFiklNP .Op Fl I Ar inputs .Op Fl p Ar prompts .Op Fl t Ar target\-client @@ -7439,6 +7439,8 @@ user exits the command prompt. makes .Em BSpace cancel an empty prompt. +.Fl P +opens a prompt inside a pane instead of on the status line. .Pp .Fl T tells diff --git a/tmux.h b/tmux.h index cce45b652..07d0922dc 100644 --- a/tmux.h +++ b/tmux.h @@ -62,6 +62,8 @@ struct mouse_event; struct options; struct options_array_item; struct options_entry; +struct prompt; +struct window_pane_prompt; struct redraw_scene; struct redraw_span; struct screen_write_citem; @@ -1296,6 +1298,10 @@ struct window_pane { char *searchstr; int searchregex; + struct prompt *prompt; + struct window_pane_prompt *prompt_data; + u_int prompt_cx; + int border_gc_set; struct grid_cell border_gc; int active_border_gc_set; @@ -1936,20 +1942,12 @@ struct status_line { struct screen *active; int references; + u_int prompt_cx; + struct grid_cell style; struct style_line_entry entries[STATUS_LINES_LIMIT]; }; -/* Prompt type. */ -#define PROMPT_NTYPES 4 -enum prompt_type { - PROMPT_TYPE_COMMAND, - PROMPT_TYPE_SEARCH, - PROMPT_TYPE_TARGET, - PROMPT_TYPE_WINDOW_TARGET, - PROMPT_TYPE_INVALID = 0xff -}; - /* File in client. */ typedef void (*client_file_cb) (struct client *, const char *, int, int, struct evbuffer *, void *); @@ -1990,6 +1988,14 @@ RB_HEAD(client_windows, client_window); /* Maximum time to be pasting. */ #define CLIENT_PASTE_TIME_LIMIT 5 +/* Prompt type. */ +#define PROMPT_NTYPES 2 +enum prompt_type { + PROMPT_TYPE_COMMAND, + PROMPT_TYPE_SEARCH, + PROMPT_TYPE_INVALID = 0xff +}; + /* Prompt result. */ enum prompt_result { PROMPT_CONTINUE, @@ -2005,11 +2011,63 @@ enum prompt_key_result { }; /* Prompt callbacks. */ -typedef enum prompt_result (*prompt_input_cb)(struct client *, void *, +typedef enum prompt_result (*prompt_input_cb)(void *, const char *, + enum prompt_key_result); +typedef enum prompt_result (*status_prompt_input_cb)(struct client *, void *, + const char *, enum prompt_key_result); +typedef enum prompt_result (*mode_tree_prompt_input_cb)(struct client *, void *, const char *, enum prompt_key_result); typedef void (*prompt_free_cb)(void *); -/* Overlay callbacks. */ +/* Prompt flags. */ +#define PROMPT_SINGLE 0x1 +#define PROMPT_NUMERIC 0x2 +#define PROMPT_INCREMENTAL 0x4 +#define PROMPT_NOFORMAT 0x8 +#define PROMPT_KEY 0x10 +#define PROMPT_ACCEPT 0x20 +#define PROMPT_QUOTENEXT 0x40 +#define PROMPT_BSPACE_EXIT 0x80 +#define PROMPT_NOFREEZE 0x100 +#define PROMPT_COMMANDMODE 0x200 +#define PROMPT_ISPANE 0x400 +#define PROMPT_ISMODE 0x800 + +/* Prompt create data. */ +struct prompt_create_data { + struct cmd_find_state *fs; + const char *prompt; + const char *input; + enum prompt_type type; + int flags; + + struct grid_cell style; + struct grid_cell command_style; + enum screen_cursor_style cstyle; + enum screen_cursor_style command_cstyle; + int ccolour; + int cmode; + int command_cmode; + const char *message_format; + int keys; + const char *word_separators; + + prompt_input_cb inputcb; + prompt_free_cb freecb; + void *data; +}; + +/* Prompt draw data. */ +struct prompt_draw_data { + struct screen_write_ctx *ctx; + u_int *cursor_x; + + u_int area_x; + u_int area_width; + u_int prompt_line; +}; + +/* Overlay callbacks */ 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 *, @@ -2154,29 +2212,7 @@ struct client { char *message_string; struct event message_timer; - char *prompt_string; - struct utf8_data *prompt_buffer; - struct cmd_find_state prompt_state; - char *prompt_last; - size_t prompt_index; - prompt_input_cb prompt_inputcb; - prompt_free_cb prompt_freecb; - void *prompt_data; - u_int prompt_hindex[PROMPT_NTYPES]; - struct utf8_data *prompt_saved; -#define PROMPT_SINGLE 0x1 -#define PROMPT_NUMERIC 0x2 -#define PROMPT_INCREMENTAL 0x4 -#define PROMPT_NOFORMAT 0x8 -#define PROMPT_KEY 0x10 -#define PROMPT_ACCEPT 0x20 -#define PROMPT_QUOTENEXT 0x40 -#define PROMPT_BSPACE_EXIT 0x80 -#define PROMPT_NOFREEZE 0x100 -#define PROMPT_COMMANDMODE 0x200 - int prompt_flags; - enum prompt_type prompt_type; - int prompt_cursor; + struct prompt *prompt; struct session *session; struct session *last_session; @@ -3100,8 +3136,6 @@ void server_check_unattached(void); void server_unzoom_window(struct window *); /* status.c */ -extern char **status_prompt_hlist[]; -extern u_int status_prompt_hsize[]; void status_timer_start(struct client *); void status_timer_start_all(void); void status_update_cache(struct session *); @@ -3117,16 +3151,38 @@ void printflike(6, 7) status_message_set(struct client *, int, int, int, int, void status_message_clear(struct client *); int status_message_redraw(struct client *); void status_prompt_set(struct client *, struct cmd_find_state *, - const char *, const char *, prompt_input_cb, prompt_free_cb, + const char *, const char *, status_prompt_input_cb, prompt_free_cb, void *, int, enum prompt_type); void status_prompt_clear(struct client *); int status_prompt_redraw(struct client *); -enum prompt_key_result status_prompt_key(struct client *, key_code); +void status_prompt_cursor(struct client *, u_int *, u_int *); +enum prompt_key_result status_prompt_key(struct client *, key_code, + struct mouse_event *); void status_prompt_update(struct client *, const char *, const char *); -void status_prompt_load_history(void); -void status_prompt_save_history(void); -const char *status_prompt_type_string(u_int); -enum prompt_type status_prompt_type(const char *type); + +/* prompt.c */ +void prompt_set_options(struct prompt_create_data *, struct session *); +struct prompt *prompt_create(const struct prompt_create_data *); +void prompt_free(struct prompt *); +void prompt_incremental_start(struct prompt *); +void prompt_draw(struct prompt *, struct prompt_draw_data *); +enum prompt_key_result prompt_key(struct prompt *, key_code, int *); +enum prompt_key_result prompt_mouse(struct prompt *, u_int, u_int, u_int, + int *); +void prompt_update(struct prompt *, const char *, const char *); +int prompt_closed(struct prompt *); +enum prompt_type prompt_type(const char *); +const char *prompt_type_string(enum prompt_type); + +/* prompt-history.c */ +const char *prompt_up_history(u_int *, u_int); +const char *prompt_down_history(u_int *, u_int); +void prompt_add_history(const char *, u_int); +u_int prompt_history_size(enum prompt_type); +const char *prompt_history_get(enum prompt_type, u_int); +void prompt_history_clear(enum prompt_type); +void prompt_load_history(void); +void prompt_save_history(void); /* resize.c */ void resize_window(struct window *, u_int, u_int, int, int); @@ -3452,6 +3508,16 @@ int window_pane_key(struct window_pane *, struct client *, struct mouse_event *); void window_pane_paste(struct window_pane *, key_code, char *, size_t); +void window_pane_set_prompt(struct window_pane *, struct client *, + struct cmd_find_state *, const char *, const char *, + status_prompt_input_cb, prompt_free_cb, void *, int, + enum prompt_type); +void window_pane_clear_prompt(struct window_pane *); +int window_pane_has_prompt(struct window_pane *); +void window_pane_update_prompt(struct window_pane *, const char *, + const char *); +enum prompt_key_result window_pane_prompt_key(struct window_pane *, + struct client *, key_code, struct mouse_event *); int window_pane_is_visible(struct window_pane *); int window_pane_exited(struct window_pane *); u_int window_pane_search(struct window_pane *, const char *, int, @@ -3612,6 +3678,11 @@ void mode_tree_remove(struct mode_tree_data *, struct mode_tree_item *); void mode_tree_draw(struct mode_tree_data *); int mode_tree_key(struct mode_tree_data *, struct client *, key_code *, struct mouse_event *, u_int *, u_int *); +void mode_tree_set_prompt(struct mode_tree_data *, struct client *, + const char *, const char *, enum prompt_type, int, + mode_tree_prompt_input_cb, prompt_free_cb, void *); +void mode_tree_clear_prompt(struct mode_tree_data *); +int mode_tree_has_prompt(struct mode_tree_data *); void mode_tree_run_command(struct client *, struct cmd_find_state *, const char *, const char *); diff --git a/window-customize.c b/window-customize.c index 1e954fca9..559593873 100644 --- a/window-customize.c +++ b/window-customize.c @@ -1145,10 +1145,10 @@ window_customize_set_option(struct client *c, new_item->idx = idx; data->references++; - status_prompt_set(c, NULL, prompt, value, + mode_tree_set_prompt(data->data, c, prompt, value, + PROMPT_TYPE_COMMAND, PROMPT_NOFORMAT, window_customize_set_option_callback, - window_customize_free_item_callback, new_item, - PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + window_customize_free_item_callback, new_item); free(prompt); free(value); @@ -1283,10 +1283,10 @@ window_customize_set_key(struct client *c, new_item->key = key; data->references++; - status_prompt_set(c, NULL, prompt, value, + mode_tree_set_prompt(data->data, c, prompt, value, + PROMPT_TYPE_COMMAND, PROMPT_NOFORMAT, window_customize_set_command_callback, - window_customize_free_item_callback, new_item, - PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + window_customize_free_item_callback, new_item); free(prompt); free(value); } else if (strcmp(s, "Note") == 0) { @@ -1299,11 +1299,11 @@ window_customize_set_key(struct client *c, new_item->key = key; data->references++; - status_prompt_set(c, NULL, prompt, + mode_tree_set_prompt(data->data, c, prompt, (bd->note == NULL ? "" : bd->note), + PROMPT_TYPE_COMMAND, PROMPT_NOFORMAT, window_customize_set_note_callback, - window_customize_free_item_callback, new_item, - PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + window_customize_free_item_callback, new_item); free(prompt); } } @@ -1477,11 +1477,11 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, xasprintf(&prompt, "Reset %s to default? ", item->name); data->references++; data->change = WINDOW_CUSTOMIZE_RESET; - status_prompt_set(c, NULL, prompt, "", - window_customize_change_current_callback, - window_customize_free_callback, data, + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + window_customize_change_current_callback, + window_customize_free_callback, data); free(prompt); break; case 'D': @@ -1491,11 +1491,11 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, xasprintf(&prompt, "Reset %u tagged to default? ", tagged); data->references++; data->change = WINDOW_CUSTOMIZE_RESET; - status_prompt_set(c, NULL, prompt, "", - window_customize_change_tagged_callback, - window_customize_free_callback, data, + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + window_customize_change_tagged_callback, + window_customize_free_callback, data); free(prompt); break; case 'u': @@ -1508,11 +1508,11 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, xasprintf(&prompt, "Unset %s? ", item->name); data->references++; data->change = WINDOW_CUSTOMIZE_UNSET; - status_prompt_set(c, NULL, prompt, "", - window_customize_change_current_callback, - window_customize_free_callback, data, + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + window_customize_change_current_callback, + window_customize_free_callback, data); free(prompt); break; case 'U': @@ -1522,11 +1522,11 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, xasprintf(&prompt, "Unset %u tagged? ", tagged); data->references++; data->change = WINDOW_CUSTOMIZE_UNSET; - status_prompt_set(c, NULL, prompt, "", - window_customize_change_tagged_callback, - window_customize_free_callback, data, + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + window_customize_change_tagged_callback, + window_customize_free_callback, data); free(prompt); break; case 'H': diff --git a/window-tree.c b/window-tree.c index 30c564dfd..58a8bbb9b 100644 --- a/window-tree.c +++ b/window-tree.c @@ -1317,10 +1317,11 @@ again: if (prompt == NULL) break; data->references++; - status_prompt_set(c, NULL, prompt, "", + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, + PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, window_tree_kill_current_callback, window_tree_command_free, - data, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + data); free(prompt); break; case 'X': @@ -1329,10 +1330,11 @@ again: break; xasprintf(&prompt, "Kill %u tagged? ", tagged); data->references++; - status_prompt_set(c, NULL, prompt, "", + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, + PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, window_tree_kill_tagged_callback, window_tree_command_free, - data, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + data); free(prompt); break; case ':': @@ -1342,9 +1344,10 @@ again: else xasprintf(&prompt, "(current) "); data->references++; - status_prompt_set(c, NULL, prompt, "", + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, PROMPT_NOFORMAT, window_tree_command_callback, window_tree_command_free, - data, PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + data); free(prompt); break; case '\r': diff --git a/window.c b/window.c index 8a388001a..9d3106a22 100644 --- a/window.c +++ b/window.c @@ -80,6 +80,14 @@ RB_GENERATE(windows, window, entry, window_cmp); RB_GENERATE(winlinks, winlink, entry, winlink_cmp); RB_GENERATE(window_pane_tree, window_pane, tree_entry, window_pane_cmp); +struct window_pane_prompt { + u_int wp_id; + struct client *c; + status_prompt_input_cb inputcb; + prompt_free_cb freecb; + void *data; +}; + int window_cmp(struct window *w1, struct window *w2) { @@ -1148,6 +1156,8 @@ window_pane_destroy(struct window_pane *wp) window_pane_wait_finish(wp); spawn_editor_finish(wp); + window_pane_clear_prompt(wp); + window_pane_free_modes(wp); free(wp->searchstr); @@ -1360,6 +1370,7 @@ window_pane_reset_mode(struct window_pane *wp) server_kill_pane(wp); } +/* Reset all modes. */ void window_pane_reset_mode_all(struct window_pane *wp) { @@ -1367,6 +1378,165 @@ window_pane_reset_mode_all(struct window_pane *wp) window_pane_reset_mode(wp); } +/* Prompt input callback. */ +static enum prompt_result +window_pane_prompt_input_callback(void *data, const char *s, + enum prompt_key_result key) +{ + struct window_pane_prompt *wpp = data; + + if (wpp->inputcb != NULL) + return (wpp->inputcb(wpp->c, wpp->data, s, key)); + return (PROMPT_CLOSE); +} + +/* Prompt free callback. */ +static void +window_pane_prompt_free_callback(void *data) +{ + struct window_pane_prompt *wpp = data; + struct window_pane *wp; + + wp = window_pane_find_by_id(wpp->wp_id); + if (wp != NULL && wp->prompt_data == wpp) + wp->prompt_data = NULL; + if (wpp->freecb != NULL) + wpp->freecb(wpp->data); + free(wpp); +} + +/* Open a prompt owned by a pane, drawn over the pane instead of the status. */ +void +window_pane_set_prompt(struct window_pane *wp, struct client *c, + struct cmd_find_state *fs, const char *msg, const char *input, + status_prompt_input_cb inputcb, prompt_free_cb freecb, void *data, + int flags, enum prompt_type type) +{ + struct session *s = NULL; + struct prompt_create_data pd; + struct window_pane_prompt *wpp; + + if (c != NULL) + s = c->session; + + window_pane_clear_prompt(wp); + + wpp = xcalloc(1, sizeof *wpp); + wpp->wp_id = wp->id; + wpp->c = c; + wpp->inputcb = inputcb; + wpp->freecb = freecb; + wpp->data = data; + + memset(&pd, 0, sizeof pd); + prompt_set_options(&pd, s); + pd.fs = fs; + pd.prompt = msg; + pd.input = input; + pd.type = type; + pd.flags = flags; + pd.inputcb = window_pane_prompt_input_callback; + pd.freecb = window_pane_prompt_free_callback; + pd.data = wpp; + + wp->prompt = prompt_create(&pd); + wp->prompt_data = wpp; + wp->flags |= PANE_REDRAW; + + prompt_incremental_start(wp->prompt); +} + +/* Close a pane prompt. */ +void +window_pane_clear_prompt(struct window_pane *wp) +{ + struct prompt *prompt = wp->prompt; + + if (prompt == NULL) + return; + wp->prompt = NULL; + prompt_free(prompt); + wp->flags |= PANE_REDRAW; +} + +/* Does this pane have an open prompt? */ +int +window_pane_has_prompt(struct window_pane *wp) +{ + return (wp->prompt != NULL); +} + +/* Replace the message and input of an open pane prompt. */ +void +window_pane_update_prompt(struct window_pane *wp, const char *msg, + const char *input) +{ + if (wp->prompt != NULL) { + prompt_update(wp->prompt, msg, input); + wp->flags |= PANE_REDRAW; + } +} + +/* + * Pass a key to a pane prompt. The client is set transiently for the duration + * of the key in case the prompt or pane is destroyed by the callback. + */ +enum prompt_key_result +window_pane_prompt_key(struct window_pane *wp, struct client *c, key_code key, + struct mouse_event *m) +{ + struct prompt *prompt = wp->prompt; + struct window_pane_prompt *wpp = wp->prompt_data; + enum prompt_key_result result; + u_int wp_id = wp->id, x, y, py; + int redraw = 0; + + if (prompt == NULL) + return (PROMPT_KEY_NOT_HANDLED); + + if (wpp != NULL) + wpp->c = c; + if (KEYC_IS_MOUSE(key)) { + if (m == NULL || + MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1 || + MOUSE_DRAG(m->b) || + MOUSE_RELEASE(m->b) || + cmd_mouse_at(wp, m, &x, &y, 0) != 0) + result = PROMPT_KEY_NOT_HANDLED; + else { + if (c != NULL && status_at_line(c) == 0) + py = 0; + else + py = wp->sy - 1; + if (y == py) { + result = prompt_mouse(prompt, x, 0, wp->sx, + &redraw); + } else + result = PROMPT_KEY_NOT_HANDLED; + } + } else + result = prompt_key(prompt, key, &redraw); + + wp = window_pane_find_by_id(wp_id); + if (wp == NULL) + return (result); + if (wpp != NULL && wp->prompt_data == wpp) + wpp->c = NULL; + + /* + * Only an explicit close or the prompt marking itself closed ends it; + * cursor movement and editing keep it open. + */ + if (wp->prompt == prompt && + (result == PROMPT_KEY_CLOSE || prompt_closed(prompt))) + window_pane_clear_prompt(wp); + + if (redraw || wp->prompt != prompt) + wp->flags |= PANE_REDRAW; + + return (result); +} + static void window_pane_copy_paste(struct window_pane *wp, char *buf, size_t len) { From 3d26aa0dfb809854a6ca1c52ee419bf50ce3b57f Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 25 Jun 2026 12:40:49 +0100 Subject: [PATCH 32/71] Prompt regress (will not pass yet). --- regress/prompt-keys.sh | 300 ++++++++++++++++++++++++++++++++ regress/prompt-mechanics.sh | 336 ++++++++++++++++++++++++++++++++++++ 2 files changed, 636 insertions(+) create mode 100644 regress/prompt-keys.sh create mode 100644 regress/prompt-mechanics.sh diff --git a/regress/prompt-keys.sh b/regress/prompt-keys.sh new file mode 100644 index 000000000..7fa7a673b --- /dev/null +++ b/regress/prompt-keys.sh @@ -0,0 +1,300 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +LANG=C.UTF-8 +export TERM LC_ALL LANG + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT="$TEST_TMUX -Ltest -f/dev/null" # outer (host for the client) +IN="$TEST_TMUX -Ltest2 -f/dev/null" # inner (under test) + +$OUT kill-server 2>/dev/null +$IN kill-server 2>/dev/null +trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" EXIT + +fail() { + echo "[FAIL] $1" + exit 1 +} + +# Capture the outer pane: what the inner client rendered. No -e, so we match +# plain visible text, not styles or escape sequences. +capture() { + $OUT capture-pane -p +} + +# The "(search) ..." prompt row of a mode prompt. +search_row() { + capture | grep '(search)' | head -1 +} + +# The inner status line is the last row of the outer capture (status is at the +# bottom). Used to assert a prompt is in the pane and not on the status line. +status_line() { + capture | tail -1 +} + +# Inner mode must still be a tree mode (the prompt did not close or crash it). +in_tree_mode() { + [ "$($IN display-message -p '#{pane_mode}')" = "tree-mode" ] +} + +# Small settle for the key -> inner server -> inner client -> outer pane round +# trip to redraw before we capture. Matches the short waits other regress tests +# use; we do not depend on exact timing beyond the redraw completing. +settle() { + sleep 0.5 +} + +# Assert the mode search prompt currently shows exactly "(search) ". +search_is() { + want=$1; msg=$2 + search_row | grep -qF "(search) $want" || \ + fail "$msg (wanted '(search) $want', got '$(search_row)')" +} + +# --- Inner session under test. --------------------------------------------- +# +# Two windows so the tree has content; status on so we can distinguish the pane +# from the status line; fixed size and manual sizing so the layout is stable. +# A root-table key opens a status-line command prompt whose accept action writes +# the final buffer into @r, so we can recover it exactly. +$IN new -d -x80 -y24 "sh -c 'exec sleep 1000'" || exit 1 +$IN set -g status on || exit 1 +$IN set -g status-position bottom || exit 1 +$IN set -g status-keys emacs || exit 1 +$IN set -g window-size manual || exit 1 +$IN new-window -d "sh -c 'exec sleep 1000'" || exit 1 +$IN bind -n M-r command-prompt -p ">" "set -g @r '%%'" || exit 1 + +# --- Outer session: attach the inner one inside its pane. ------------------- +$OUT new -d -x80 -y24 || exit 1 +$OUT set -g status off || exit 1 +$OUT set -g window-size manual || exit 1 +$OUT send-keys -l "$IN attach" || exit 1 +$OUT send-keys Enter || exit 1 +sleep 1 + +# =========================================================================== +# Mode prompt (choose-tree search): the thorough engine vehicle. +# =========================================================================== + +$IN choose-tree || exit 1 +settle +in_tree_mode || fail "choose-tree did not enter tree-mode" + +# --- 1. Search prompt is drawn in the pane, not on the status line. --- +$IN send-keys C-s || exit 1 +settle +search_row | grep -q '(search)' || fail "search prompt not drawn in the pane" +status_line | grep -q '(search)' && \ + fail "search prompt drawn on the status line, not in the pane" + +# --- 2. emacs editing: insert and delete at middle, start and end. --- +# Cursor position is checked behaviourally: move, insert a marker, read the row. +# This needs no cursor coordinates and fails if a movement/edit key is wrong. + +# Middle insert: "abcd", Left Left (cursor between b and c), insert X -> abXcd. +$IN send-keys -l "abcd" || exit 1 +settle +search_is "abcd" "literal input not shown in search prompt" +$IN send-keys Left Left || exit 1 +$IN send-keys -l "X" || exit 1 +settle +search_is "abXcd" "middle insert wrong (Left/insert)" + +# Middle delete: BSpace removes X (before cursor), DC removes c (at cursor). +$IN send-keys BSpace || exit 1 +$IN send-keys DC || exit 1 +settle +search_is "abd" "middle delete wrong (BSpace/Delete)" + +# Clear, then start/end insert with C-a and C-e. +$IN send-keys C-u || exit 1 +$IN send-keys -l "mno" || exit 1 +$IN send-keys C-a || exit 1 +$IN send-keys -l "S" || exit 1 +$IN send-keys C-e || exit 1 +$IN send-keys -l "E" || exit 1 +settle +search_is "SmnoE" "C-a/C-e start/end insert wrong" + +# Word kill: "hello world", C-w removes the last word leaving "hello " (with the +# separating space). capture-pane trims trailing spaces, so make the space +# visible by inserting a marker after it: the buffer becomes "hello Z". +$IN send-keys C-u || exit 1 +$IN send-keys -l "hello world" || exit 1 +$IN send-keys C-w || exit 1 +$IN send-keys -l "Z" || exit 1 +settle +search_is "hello Z" "C-w did not kill a word" + +# C-a then C-k kills the whole line. +$IN send-keys C-a || exit 1 +$IN send-keys C-k || exit 1 +settle +search_row | grep -q '(search) [^ ]' && fail "C-a C-k did not clear the line" + +# --- 3. Editing kept the prompt open the whole time. --- +in_tree_mode || fail "editing keys closed the mode" +search_row | grep -q '(search)' || fail "editing keys closed the prompt" + +# --- 4. Unicode wide character: insert, render, delete as one unit. --- +$IN send-keys C-u || exit 1 +$IN send-keys -l "a中b" || exit 1 +settle +search_is "a中b" "wide character not shown" +# Left moves over "b" (one column); BSpace deletes the wide "中" as a single +# width-2 unit, leaving "ab". +$IN send-keys Left || exit 1 +$IN send-keys BSpace || exit 1 +settle +search_is "ab" "wide character not deleted as one unit" + +# --- 5. Control character: quote-next inserts it literally, shown as ^G. --- +$IN send-keys C-u || exit 1 +$IN send-keys -l "a" || exit 1 +$IN send-keys C-v || exit 1 # quote next key +$IN send-keys C-g || exit 1 # literal BEL -> displayed as ^G +$IN send-keys -l "b" || exit 1 +settle +search_is "a^Gb" "control character not shown as ^G" +# Deleted as a single unit too. +$IN send-keys Left || exit 1 +$IN send-keys BSpace || exit 1 +settle +search_is "ab" "control character not deleted as one unit" + +# --- 6. Kill and yank: C-w fills the yank buffer, C-y pastes it at the cursor. --- +# (prompt_key only fills the yank buffer from C-w; C-y then yanks that text, or +# the top paste buffer if nothing has been killed. Establish our own kill here +# so the result is deterministic.) +$IN send-keys C-u || exit 1 +$IN send-keys -l "one two" || exit 1 +$IN send-keys C-w || exit 1 # kill "two", buffer "one " +$IN send-keys C-y || exit 1 # yank it back -> "one two" +settle +search_is "one two" "C-y did not yank the killed text" +$IN send-keys C-y || exit 1 # yank again at cursor -> "one twotwo" +settle +search_is "one twotwo" "second C-y did not yank again" + +# --- 7. History: accept a string, reopen, Up recalls it. --- +$IN send-keys C-u || exit 1 +$IN send-keys -l "alpha" || exit 1 +$IN send-keys Enter || exit 1 +settle +$IN send-keys C-s || exit 1 +settle +$IN send-keys Up || exit 1 +settle +search_is "alpha" "history (Up) did not recall the previous entry" + +# --- 8. Escape closes the prompt but leaves the mode open. --- +$IN send-keys Escape || exit 1 +settle +search_row | grep -q '(search)' && fail "Escape left a dangling search prompt" +in_tree_mode || fail "Escape closed the mode as well as the prompt" + +# Leave the mode. +$IN send-keys q || exit 1 +settle + +# =========================================================================== +# Status-line prompt (command-prompt): the same engine on the status line. +# Keys go through the inner client's terminal (outer send-keys); the accepted +# buffer is recovered exactly via %% -> @r. +# =========================================================================== + +# --- 10. Prompt is drawn on the status line (the last row). --- +$IN set -g @r "" || exit 1 +$OUT send-keys M-r || exit 1 +settle +status_line | grep -q '>' || fail "status-line prompt not drawn on the status line" + +# --- 11. emacs cursor-marker edit, accept recovers the exact buffer. --- +$OUT send-keys -l "abc" || exit 1 +$OUT send-keys Home || exit 1 +$OUT send-keys -l "X" || exit 1 +settle +status_line | grep -qF "> Xabc" || \ + fail "status-line edit wrong (got '$(status_line)')" +$OUT send-keys Enter || exit 1 +settle +[ "$($IN show -gv @r)" = "Xabc" ] || \ + fail "status-line accept recovered '$($IN show -gv @r)', wanted 'Xabc'" + +# --- 12. Unicode on the status line: insert, move, delete wide char. --- +$IN set -g @r "" || exit 1 +$OUT send-keys M-r || exit 1 +settle +$OUT send-keys -l "a㋡b" || exit 1 +settle +status_line | grep -qF "a㋡b" || \ + fail "status-line wide character not shown (got '$(status_line)')" +# Home, insert Z (start); End, BSpace (delete b); BSpace (delete wide char). +$OUT send-keys Home || exit 1 +$OUT send-keys -l "Z" || exit 1 +$OUT send-keys End || exit 1 +$OUT send-keys BSpace || exit 1 +$OUT send-keys BSpace || exit 1 +settle +$OUT send-keys Enter || exit 1 +settle +[ "$($IN show -gv @r)" = "Za" ] || \ + fail "status-line wide edit recovered '$($IN show -gv @r)', wanted 'Za'" + +# --- 13. Overflow: more text than fits stays within the line and is kept. --- +big="0123456789012345678901234567890123456789012345678901234567890123456789ABCDEFGHIJ" +$IN set -g @r "" || exit 1 +$OUT send-keys M-r || exit 1 +settle +$OUT send-keys -l "$big" || exit 1 +settle +# The drawn status line must not exceed the client width (80): no wrap, no crash. +width=$(status_line | awk '{print length($0)}') +[ "$width" -le 80 ] || fail "overflowing prompt drew $width columns, wider than 80" +$OUT send-keys Enter || exit 1 +settle +# The whole buffer was kept despite only part being visible. +[ "$($IN show -gv @r)" = "$big" ] || fail "overflowing prompt lost buffer content" + +# --- 14. Escape closes the status-line prompt cleanly. --- +$IN set -g @r "SENTINEL" || exit 1 +$OUT send-keys M-r || exit 1 +settle +$OUT send-keys -l "discard" || exit 1 +$OUT send-keys Escape || exit 1 +settle +status_line | grep -q '> discard' && fail "Escape left a dangling status-line prompt" +[ "$($IN show -gv @r)" = "SENTINEL" ] || fail "Escape ran the prompt's accept action" + +# =========================================================================== +# Two clients attached to the same window: a mode prompt must render on both. +# =========================================================================== + +$OUT new-window || exit 1 +$OUT set -g status off || exit 1 +$OUT send-keys -l "$IN attach" || exit 1 +$OUT send-keys Enter || exit 1 +sleep 1 + +$IN choose-tree || exit 1 +settle +$IN send-keys C-s || exit 1 +settle +$IN send-keys -l "dual" || exit 1 +settle +for w in $($OUT list-windows -F '#{window_index}'); do + $OUT capture-pane -t ":$w" -p | grep -qF "(search) dual" || \ + fail "mode prompt not shown on client in outer window $w" +done +$IN send-keys Escape || exit 1 +settle + +# --- Inner tmux is still alive and responsive. --- +$IN display-message -p '#{version}' >/dev/null 2>&1 || fail "inner tmux died" + +exit 0 diff --git a/regress/prompt-mechanics.sh b/regress/prompt-mechanics.sh new file mode 100644 index 000000000..6f3e5294e --- /dev/null +++ b/regress/prompt-mechanics.sh @@ -0,0 +1,336 @@ +#!/bin/sh + +# Exercise the prompt mechanics shared by all three host paths of the prompt +# engine in prompt.c: +# +# status.c status_prompt_set - the status-line command prompt. +# window.c window_pane_set_prompt - a prompt drawn over a pane (-P). +# mode-tree.c mode_tree_set_prompt - search/filter prompts in tree modes. +# +# prompt-keys.sh covers the editing keys; this test covers that each path OPENS +# and DRAWS in the right place and that the prompt flags select the right engine +# behaviour: -1 (single), -N (numeric), -i (incremental), -k (key), -e +# (backspace exit), -I (prefill), multi prompts, type-scoped history and command +# completion. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +LANG=C.UTF-8 +export TERM LC_ALL LANG + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT="$TEST_TMUX -Ltest -f/dev/null" # outer (host for the client) +IN="$TEST_TMUX -Ltest2 -f/dev/null" # inner (under test) + +$OUT kill-server 2>/dev/null +$IN kill-server 2>/dev/null +trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" EXIT + +fail() { + echo "[FAIL] $1" + exit 1 +} + +# Capture the outer pane: what the inner client rendered. Pane-area prompts +# (over a pane or in a tree mode) and the status-line prompt all end up here. +capture() { + $OUT capture-pane -p +} + +# The inner status line is the last row of the outer capture. +status_line() { + capture | tail -1 +} + +# The recovered buffer. Every prompt below accepts into the @r option so the +# exact final string can be checked; reset it to a sentinel first so we can tell +# "accept ran" from "prompt cancelled". +got() { + $IN show -gv @r +} +reset() { + $IN set -g @r "SENTINEL" || exit 1 +} + +# Settle for the key -> inner server -> inner client -> outer pane redraw round +# trip, as in prompt-keys.sh. +settle() { + sleep 0.5 +} + +# --- Inner session under test. --------------------------------------------- +# +# The window is created at -y23 so that, with a one-row bottom status, the pane +# area (23) exactly fills the 24-row client: a pane prompt drawn on the pane's +# bottom row lands on visible row 23, just above the status line on row 24. A +# second, distinctively named window gives the tree something to filter. +$IN new -d -x80 -y23 -n YAK "sh -c 'exec sleep 1000'" || exit 1 +$IN set -g status on || exit 1 +$IN set -g status-position bottom || exit 1 +$IN set -g status-keys emacs || exit 1 +$IN set -g window-size manual || exit 1 +$IN new-window -d -n ZEBRA "sh -c 'exec sleep 1000'" || exit 1 + +# One root-table key per path/flag. The accept template records the final +# buffer in @r (or both buffers, for the multi-prompt case). +$IN bind -n M-s command-prompt -p '(stat)' "set -g @r '%%'" || exit 1 +$IN bind -n M-p command-prompt -P -p '(pane)' "set -g @r '%%'" || exit 1 +$IN bind -n M-o command-prompt -P -1 -p '(one)' "set -g @r '%%'" || exit 1 +$IN bind -n M-n command-prompt -P -N -I 5 -p '(num)' "set -g @r '%%'" || exit 1 +$IN bind -n M-i command-prompt -P -i -p '(inc)' "set -g @r '%%'" || exit 1 +$IN bind -n M-k command-prompt -P -k -p '(key)' "set -g @r '%%'" || exit 1 +$IN bind -n M-e command-prompt -P -e -p '(bs)' "set -g @r '%%'" || exit 1 +$IN bind -n M-j command-prompt -P -I hello -p '(pre)' "set -g @r '%%'" || exit 1 +$IN bind -n M-m command-prompt -p 'first,second' "set -g @r '%1/%2'" || exit 1 +$IN bind -n M-c command-prompt -p '(cmd)' "set -g @r '%%'" || exit 1 +$IN bind -n M-h command-prompt -T search -p '(srch)' "set -g @r '%%'" || exit 1 + +# --- Outer session: attach the inner one inside its pane. ------------------- +$OUT new -d -x80 -y24 || exit 1 +$OUT set -g status off || exit 1 +$OUT set -g window-size manual || exit 1 +$OUT send-keys -l "$IN attach" || exit 1 +$OUT send-keys Enter || exit 1 +sleep 1 + +# =========================================================================== +# 1. Each path opens and draws in the right place. +# =========================================================================== + +# --- 1a. status.c: drawn on the status line (the last row). --- +reset +$OUT send-keys M-s || exit 1 +settle +status_line | grep -qF '(stat)' || \ + fail "status-line prompt not on the status line (got '$(status_line)')" +$OUT send-keys -l "go" || exit 1 +$OUT send-keys Enter || exit 1 +settle +[ "$(got)" = "go" ] || fail "status-line accept recovered '$(got)', wanted 'go'" + +# --- 1b. window.c: drawn over the pane, not on the status line. --- +reset +$OUT send-keys M-p || exit 1 +settle +capture | grep -qF '(pane)' || fail "pane prompt not drawn in the pane" +status_line | grep -qF '(pane)' && \ + fail "pane prompt drawn on the status line, not over the pane" +$OUT send-keys -l "deep" || exit 1 +$OUT send-keys Enter || exit 1 +settle +[ "$(got)" = "deep" ] || fail "pane prompt accept recovered '$(got)', wanted 'deep'" + +# --- 1c. mode-tree.c: search prompt drawn in the pane. --- +$IN choose-tree || exit 1 +settle +[ "$($IN display-message -p '#{pane_mode}')" = "tree-mode" ] || \ + fail "choose-tree did not enter tree-mode" +$IN send-keys C-s || exit 1 +settle +capture | grep -qF '(search)' || fail "mode-tree search prompt not drawn in the pane" +status_line | grep -qF '(search)' && \ + fail "mode-tree search prompt drawn on the status line" +$IN send-keys Escape || exit 1 +settle + +# --- 1d. mode-tree.c: filter prompt opens, applies, prefills, and clears. --- +$IN send-keys f || exit 1 +settle +capture | grep -qF '(filter)' || fail "mode-tree filter prompt not drawn" +$IN send-keys -l "ZEBRA" || exit 1 +$IN send-keys Enter || exit 1 +settle +capture | grep -q 'filter: active' || fail "accepting the filter did not apply it" +# Reopening the filter prompt prefills it with the current filter. +$IN send-keys f || exit 1 +settle +capture | grep -qF '(filter) ZEBRA' || \ + fail "filter prompt not prefilled with the current filter" +$IN send-keys Escape || exit 1 +settle +# 'c' clears the filter. +$IN send-keys c || exit 1 +settle +capture | grep -q 'filter: active' && fail "'c' did not clear the filter" +$IN send-keys q || exit 1 +settle + +# =========================================================================== +# 2. Flags select the right engine behaviour. +# =========================================================================== + +# --- 2a. -1 (PROMPT_SINGLE): one keystroke closes and accepts that char. --- +reset +$OUT send-keys M-o || exit 1 +settle +capture | grep -qF '(one)' || fail "single prompt did not open" +$OUT send-keys -l "q" || exit 1 +settle +[ "$(got)" = "q" ] || fail "single prompt recovered '$(got)', wanted 'q'" +capture | grep -qF '(one)' && fail "single prompt stayed open after one key" + +# --- 2b. -N (PROMPT_NUMERIC): prefilled, digits append, non-digit closes. --- +reset +$OUT send-keys M-n || exit 1 +settle +capture | grep -qF '(num) 5' || fail "numeric prompt not prefilled with 5" +$OUT send-keys -l "7" || exit 1 # 57 +$OUT send-keys Enter || exit 1 # Enter is a non-digit: close +settle +[ "$(got)" = "57" ] || fail "numeric accept recovered '$(got)', wanted '57'" +# A non-digit key closes the prompt with the existing buffer, dropping the key. +reset +$OUT send-keys M-n || exit 1 +settle +$OUT send-keys -l "x" || exit 1 +settle +[ "$(got)" = "5" ] || fail "numeric non-digit close recovered '$(got)', wanted '5'" +capture | grep -qF '(num)' && fail "numeric prompt stayed open after a non-digit" + +# --- 2c. -i (PROMPT_INCREMENTAL): callback fires on every edit, stays open. --- +# The incremental code path prefixes the buffer with '=' (or +/-), so the '=' +# proves the value came through the incremental callback, not a plain accept. +reset +$OUT send-keys M-i || exit 1 +settle +[ "$(got)" = "=" ] || fail "incremental prompt did not fire on open (got '$(got)')" +$OUT send-keys -l "a" || exit 1 +settle +[ "$(got)" = "=a" ] || fail "incremental did not fire after 'a' (got '$(got)')" +$OUT send-keys -l "b" || exit 1 +settle +[ "$(got)" = "=ab" ] || fail "incremental did not fire after 'b' (got '$(got)')" +capture | grep -qF '(inc)' || fail "incremental prompt closed during editing" +$OUT send-keys Escape || exit 1 +settle +capture | grep -qF '(inc)' && fail "Escape did not close the incremental prompt" + +# --- 2d. -k (PROMPT_KEY): the next key closes and delivers its name. --- +reset +$OUT send-keys M-k || exit 1 +settle +capture | grep -qF '(key)' || fail "key prompt did not open" +$OUT send-keys -l "z" || exit 1 +settle +[ "$(got)" = "z" ] || fail "key prompt recovered '$(got)', wanted 'z'" +capture | grep -qF '(key)' && fail "key prompt stayed open after a key" + +# --- 2e. -e (PROMPT_BSPACE_EXIT): backspace on empty cancels (no accept). --- +reset +$OUT send-keys M-e || exit 1 +settle +$OUT send-keys BSpace || exit 1 +settle +[ "$(got)" = "SENTINEL" ] || fail "backspace-exit ran the accept action (got '$(got)')" +capture | grep -qF '(bs)' && fail "backspace on empty did not close the prompt" + +# --- 2f. -I (prefill): prompt opens with the given buffer. --- +reset +$OUT send-keys M-j || exit 1 +settle +capture | grep -qF '(pre) hello' || fail "prefill not shown (got '$(capture | grep -F '(pre)')')" +$OUT send-keys Enter || exit 1 +settle +[ "$(got)" = "hello" ] || fail "prefill accept recovered '$(got)', wanted 'hello'" + +# --- 2g. Multi prompt: accept advances to the next, both are delivered. --- +reset +$OUT send-keys M-m || exit 1 +settle +capture | grep -qF 'first' || fail "first of multi prompt not shown" +$OUT send-keys -l "X" || exit 1 +$OUT send-keys Enter || exit 1 +settle +capture | grep -qF 'second' || fail "multi prompt did not advance to the second" +$OUT send-keys -l "Y" || exit 1 +$OUT send-keys Enter || exit 1 +settle +[ "$(got)" = "X/Y" ] || fail "multi prompt recovered '$(got)', wanted 'X/Y'" + +# =========================================================================== +# 3. Type-scoped history and command completion. +# =========================================================================== + +# --- 3a. Command history is recalled with Up; search history is separate. --- +reset +$OUT send-keys M-c || exit 1 +settle +$OUT send-keys -l "alpha" || exit 1 +$OUT send-keys Enter || exit 1 +settle +[ "$(got)" = "alpha" ] || fail "command prompt accept recovered '$(got)'" +# Reopen the command prompt: Up recalls the command-type entry. +$OUT send-keys M-c || exit 1 +settle +$OUT send-keys Up || exit 1 +settle +capture | grep -qF '(cmd) alpha' || fail "Up did not recall command history" +$OUT send-keys Escape || exit 1 +settle +# A search-type prompt must NOT recall the command-type entry (separate rings). +$OUT send-keys M-h || exit 1 +settle +$OUT send-keys Up || exit 1 +settle +capture | grep -qF 'alpha' && fail "search prompt recalled command-type history" +$OUT send-keys Escape || exit 1 +settle + +# --- 3b. Tab completion works in a command prompt (command type only). --- +$OUT send-keys M-c || exit 1 +settle +$OUT send-keys -l "new-w" || exit 1 +$OUT send-keys Tab || exit 1 +settle +status_line | grep -qF 'new-window' || \ + fail "Tab did not complete new-w to new-window (got '$(status_line)')" +$OUT send-keys Escape || exit 1 +settle +# A search-type prompt does not complete commands: Tab is literal / inert. +$OUT send-keys M-h || exit 1 +settle +$OUT send-keys -l "new-w" || exit 1 +$OUT send-keys Tab || exit 1 +settle +status_line | grep -qF 'new-window' && fail "search prompt completed a command" +$OUT send-keys Escape || exit 1 +settle + +# =========================================================================== +# 4. Robustness: re-entrancy guard and Escape, then liveness. +# =========================================================================== + +# --- 4a. A second prompt while one is open is refused (status path). --- +client=$($IN list-clients -F '#{client_name}' | head -1) +reset +$OUT send-keys M-s || exit 1 +settle +$OUT send-keys -l "AAA" || exit 1 +settle +$IN command-prompt -t"$client" -p '(re)' "set -g @r 'REENTERED'" 2>/dev/null +settle +capture | grep -qF '(re)' && fail "a second status prompt opened over the first" +status_line | grep -qF '(stat) AAA' || fail "first status prompt was disturbed" +$OUT send-keys Escape || exit 1 +settle +[ "$(got)" = "SENTINEL" ] || fail "Escape ran the status prompt accept action" + +# --- 4b. A second pane prompt while one is open is refused (pane path). --- +reset +$OUT send-keys M-p || exit 1 +settle +$OUT send-keys -l "BBB" || exit 1 +settle +$IN command-prompt -P -t"$client" -p '(re)' "set -g @r 'REENTERED'" 2>/dev/null +settle +capture | grep -qF '(re)' && fail "a second pane prompt opened over the first" +capture | grep -qF '(pane) BBB' || fail "first pane prompt was disturbed" +$OUT send-keys Escape || exit 1 +settle +[ "$(got)" = "SENTINEL" ] || fail "Escape ran the pane prompt accept action" + +# --- 4c. Inner tmux survived every path and flag. --- +$IN display-message -p '#{version}' >/dev/null 2>&1 || fail "inner tmux died" + +exit 0 From 7c1ef6a387d6041a9b2f6aa87a4ef9598f7cab86 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 25 Jun 2026 14:27:58 +0000 Subject: [PATCH 33/71] Format variable for client colours. --- format.c | 30 ++++++++++++++++++++++++++++++ tmux.1 | 1 + 2 files changed, 31 insertions(+) diff --git a/format.c b/format.c index dbb8bce80..75b00e2bf 100644 --- a/format.c +++ b/format.c @@ -1463,6 +1463,33 @@ format_cb_client_cell_width(struct format_tree *ft) return (NULL); } +/* Callback for client_colours. */ +static void * +format_cb_client_colours(struct format_tree *ft) +{ + struct tty_term *term; + u_int colours; + + if (ft->c == NULL || (~ft->c->tty.flags & TTY_STARTED)) + return (NULL); + term = ft->c->tty.term; + + if (term->flags & TERM_RGBCOLOURS) + colours = 16777216; + else if (term->flags & TERM_256COLOURS) + colours = 256; + else { + colours = tty_term_number(term, TTYC_COLORS); + if (colours < 8) + colours = 2; + else if (colours < 16) + colours = 8; + else + colours = 16; + } + return (format_printf("%u", colours)); +} + /* Callback for client_control_mode. */ static void * format_cb_client_control_mode(struct format_tree *ft) @@ -3196,6 +3223,9 @@ static const struct format_table_entry format_table[] = { { "client_cell_width", FORMAT_TABLE_STRING, format_cb_client_cell_width }, + { "client_colours", FORMAT_TABLE_STRING, + format_cb_client_colours + }, { "client_control_mode", FORMAT_TABLE_STRING, format_cb_client_control_mode }, diff --git a/tmux.1 b/tmux.1 index feeb84393..1f96045a2 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6713,6 +6713,7 @@ The following variables are available, where appropriate: .It Li "client_activity" Ta "" Ta "Time client last had activity" .It Li "client_cell_height" Ta "" Ta "Height of each client cell in pixels" .It Li "client_cell_width" Ta "" Ta "Width of each client cell in pixels" +.It Li "client_colours" Ta "" Ta "Number of colours client supports" .It Li "client_control_mode" Ta "" Ta "1 if client is in control mode" .It Li "client_created" Ta "" Ta "Time client created" .It Li "client_discarded" Ta "" Ta "Bytes discarded when client behind" From 0d016195ac43841c986ee4cc7cfdba15ee1d2a18 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 25 Jun 2026 16:32:42 +0000 Subject: [PATCH 34/71] Expand colours as formats like styles. --- cmd-display-panes.c | 21 ++++++++++----------- options-table.c | 25 +++++++++++++++---------- options.c | 34 +++++++++++++++++++++++++--------- prompt.c | 8 +++++--- screen.c | 7 ++++--- style.c | 23 +++++++++++++++++++++++ tmux.h | 3 +++ window-clock.c | 9 +++++++-- window-customize.c | 9 +++++++++ 9 files changed, 101 insertions(+), 38 deletions(-) diff --git a/cmd-display-panes.c b/cmd-display-panes.c index c9f185d59..f8991dfe1 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -133,7 +133,8 @@ cmd_display_panes_draw_pane(struct cmd_display_panes_ctx *ctx, struct grid_cell fgc, bgc; u_int pane, idx, px, py, i, j, xoff, yoff, sx, sy; u_int cx, cy; - int colour, active_colour; + const char *name; + struct format_tree *ft; char buf[16], lbuf[16], *ptr; size_t len, llen; @@ -191,18 +192,16 @@ cmd_display_panes_draw_pane(struct cmd_display_panes_ctx *ctx, if (sx < len) return; - colour = options_get_number(oo, "display-panes-colour"); - active_colour = options_get_number(oo, "display-panes-active-colour"); + if (w->active == wp) + name = "display-panes-active-colour"; + else + name = "display-panes-colour"; + ft = format_create_defaults(NULL, c, s, NULL, wp); + style_apply(&fgc, oo, name, ft); + format_free(ft); - memcpy(&fgc, &grid_default_cell, sizeof fgc); memcpy(&bgc, &grid_default_cell, sizeof bgc); - if (w->active == wp) { - fgc.fg = active_colour; - bgc.bg = active_colour; - } else { - fgc.fg = colour; - bgc.bg = colour; - } + bgc.bg = fgc.fg; if (pane > 9 && pane < 35) llen = xsnprintf(lbuf, sizeof lbuf, "%c", 'a' + (pane - 10)); diff --git a/options-table.c b/options-table.c index 0d7b20c53..6050c24a0 100644 --- a/options-table.c +++ b/options-table.c @@ -328,9 +328,10 @@ const struct options_table_entry options_table[] = { }, { .name = "cursor-colour", - .type = OPTIONS_TABLE_COLOUR, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_num = -1, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "", .text = "Colour of the cursor." }, @@ -635,16 +636,18 @@ const struct options_table_entry options_table[] = { }, { .name = "display-panes-active-colour", - .type = OPTIONS_TABLE_COLOUR, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_num = 1, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "red", .text = "Colour of the active pane for 'display-panes'." }, { .name = "display-panes-colour", - .type = OPTIONS_TABLE_COLOUR, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_num = 4, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "blue", .text = "Colour of not active panes for 'display-panes'." }, @@ -991,9 +994,10 @@ const struct options_table_entry options_table[] = { }, { .name = "prompt-cursor-colour", - .type = OPTIONS_TABLE_COLOUR, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_num = -1, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "", .text = "Colour of the cursor when in the command prompt." }, @@ -1145,9 +1149,10 @@ const struct options_table_entry options_table[] = { }, { .name = "clock-mode-colour", - .type = OPTIONS_TABLE_COLOUR, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 4, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "blue", .text = "Colour of the clock in clock mode." }, diff --git a/options.c b/options.c index a4081a8d2..b9e319acf 100644 --- a/options.c +++ b/options.c @@ -989,9 +989,12 @@ struct style * options_string_to_style(struct options *oo, const char *name, struct format_tree *ft) { - struct options_entry *o; - const char *s; - char *expanded; + struct options_entry *o; + const struct options_table_entry *oe; + const struct grid_cell *dgc = &grid_default_cell; + const char *s; + char *expanded; + int failed; o = options_get(oo, name); if (o == NULL || !OPTIONS_IS_STRING(o)) @@ -1000,20 +1003,27 @@ options_string_to_style(struct options *oo, const char *name, if (o->cached) return (&o->style); s = o->value.string; + oe = o->tableentry; log_debug("%s: %s is '%s'", __func__, name, s); - style_set(&o->style, &grid_default_cell); + style_set(&o->style, dgc); o->cached = (strstr(s, "#{") == NULL); if (ft != NULL && !o->cached) { expanded = format_expand(ft, s); - if (style_parse(&o->style, &grid_default_cell, expanded) != 0) { - free(expanded); - return (NULL); - } + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_COLOUR)) + failed = style_parse_colour(&o->style, dgc, expanded); + else + failed = style_parse(&o->style, dgc, expanded); free(expanded); + if (failed != 0) + return (NULL); } else { - if (style_parse(&o->style, &grid_default_cell, s) != 0) + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_COLOUR)) + failed = style_parse_colour(&o->style, dgc, s); + else + failed = style_parse(&o->style, dgc, s); + if (failed != 0) return (NULL); } return (&o->style); @@ -1041,6 +1051,12 @@ options_from_string_check(const struct options_table_entry *oe, xasprintf(cause, "invalid style: %s", value); return (-1); } + if ((oe->flags & OPTIONS_TABLE_IS_COLOUR) && + strstr(value, "#{") == NULL && + style_parse_colour(&sy, &grid_default_cell, value) != 0) { + xasprintf(cause, "invalid colour: %s", value); + return (-1); + } return (0); } diff --git a/prompt.c b/prompt.c index 54cbeb13b..57e267374 100644 --- a/prompt.c +++ b/prompt.c @@ -106,8 +106,9 @@ prompt_flags_to_string(int flags) void prompt_set_options(struct prompt_create_data *pd, struct session *s) { - struct options *oo; - u_int n; + struct options *oo; + struct grid_cell gc; + u_int n; if (s != NULL) oo = s->options; @@ -120,7 +121,8 @@ prompt_set_options(struct prompt_create_data *pd, struct session *s) screen_set_cursor_style(n, &pd->cstyle, &pd->cmode); n = options_get_number(oo, "prompt-command-cursor-style"); screen_set_cursor_style(n, &pd->command_cstyle, &pd->command_cmode); - pd->ccolour = options_get_number(oo, "prompt-cursor-colour"); + style_apply(&gc, oo, "prompt-cursor-colour", NULL); + pd->ccolour = gc.fg; pd->message_format = options_get_string(oo, "message-format"); pd->keys = options_get_number(oo, "status-keys"); pd->word_separators = options_get_string(oo, "word-separators"); diff --git a/screen.c b/screen.c index 7afaefec0..cea934fad 100644 --- a/screen.c +++ b/screen.c @@ -177,10 +177,11 @@ screen_reset_tabs(struct screen *s) void screen_set_default_cursor(struct screen *s, struct options *oo) { - int c; + struct grid_cell gc; + int c; - c = options_get_number(oo, "cursor-colour"); - s->default_ccolour = c; + style_apply(&gc, oo, "cursor-colour", NULL); + s->default_ccolour = gc.fg; c = options_get_number(oo, "cursor-style"); s->default_mode = 0; diff --git a/style.c b/style.c index 796600253..60ac4dd6e 100644 --- a/style.c +++ b/style.c @@ -434,6 +434,29 @@ style_apply(struct grid_cell *gc, struct options *oo, const char *name, style_add(gc, oo, name, ft); } +/* Parse a single colour into a style */ +int +style_parse_colour(struct style *sy, const struct grid_cell *base, + const char *s) +{ + int c; + + style_set(sy, base); + + if (*s == '\0') { + sy->gc.fg = -1; + return (0); + } + + if ((c = colour_fromstring(s)) == -1) + return (-1); + if (c == 8) + sy->gc.fg = base->fg; + else + sy->gc.fg = c; + return (0); +} + /* Initialize style from cell. */ void style_set(struct style *sy, const struct grid_cell *gc) diff --git a/tmux.h b/tmux.h index 07d0922dc..bbf25b9c8 100644 --- a/tmux.h +++ b/tmux.h @@ -2307,6 +2307,7 @@ enum options_table_type { #define OPTIONS_TABLE_IS_ARRAY 0x1 #define OPTIONS_TABLE_IS_HOOK 0x2 #define OPTIONS_TABLE_IS_STYLE 0x4 +#define OPTIONS_TABLE_IS_COLOUR 0x8 struct options_table_entry { const char *name; @@ -3896,6 +3897,8 @@ int popup_modify(struct client *, const char *, const char *, /* style.c */ int style_parse(struct style *,const struct grid_cell *, const char *); +int style_parse_colour(struct style *, + const struct grid_cell *, const char *); const char *style_tostring(struct style *); struct style *style_add(struct grid_cell *, struct options *, const char *, struct format_tree *); diff --git a/window-clock.c b/window-clock.c index 6a89df4b3..cf7040009 100644 --- a/window-clock.c +++ b/window-clock.c @@ -222,19 +222,24 @@ static void window_clock_draw_screen(struct window_mode_entry *wme) { struct window_pane *wp = wme->wp; + struct window *w = wp->window; struct window_clock_mode_data *data = wme->data; struct screen_write_ctx ctx; int colour, style; struct screen *s = &data->screen; struct grid_cell gc; + struct format_tree *ft; char tim[64], *ptr; const char *timeformat; time_t t; struct tm *tm; u_int i, j, x, y, idx; - colour = options_get_number(wp->window->options, "clock-mode-colour"); - style = options_get_number(wp->window->options, "clock-mode-style"); + ft = format_create_defaults(NULL, NULL, NULL, NULL, wp); + style_apply(&gc, w->options, "clock-mode-colour", ft); + format_free(ft); + colour = gc.fg; + style = options_get_number(w->options, "clock-mode-style"); screen_write_start(&ctx, s); diff --git a/window-customize.c b/window-customize.c index 559593873..9796cbf0b 100644 --- a/window-customize.c +++ b/window-customize.c @@ -769,6 +769,15 @@ window_customize_draw_option(struct window_customize_modedata *data, "EXAMPLE")) goto out; } + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_COLOUR)) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 1, + &grid_default_cell, "This is a colour option: ")) + goto out; + style_apply(&gc, item->oo, name, ft); + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &gc, + "EXAMPLE")) + goto out; + } if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_STYLE)) { if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 1, &grid_default_cell, "This is a style option: ")) From 2d0845860a7d083f63062ca4f3f54bd52e07d79d Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 25 Jun 2026 17:17:25 +0000 Subject: [PATCH 35/71] Fix scrollbar with a format colour. --- style.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/style.c b/style.c index 60ac4dd6e..f386b83eb 100644 --- a/style.c +++ b/style.c @@ -480,18 +480,25 @@ style_set_scrollbar_style_from_option(struct style *sb_style, const struct options_table_entry *oe; struct options_entry *o; const char *s; + char *style, *expanded; style_set(sb_style, &grid_default_cell); o = options_get(oo, "pane-scrollbars-style"); if (o == NULL) fatalx("missing pane-scrollbars-style"); oe = options_table_entry(o); - if (style_parse(sb_style, &grid_default_cell, oe->default_str) != 0) + style = format_single(NULL, oe->default_str, NULL, NULL, NULL, NULL); + if (style_parse(sb_style, &grid_default_cell, style) != 0) fatalx("bad pane-scrollbars-style default"); s = options_get_string(oo, "pane-scrollbars-style"); - if (s != NULL && style_parse(sb_style, &grid_default_cell, s) != 0) - style_parse(sb_style, &grid_default_cell, oe->default_str); + if (s != NULL) { + expanded = format_single(NULL, s, NULL, NULL, NULL, NULL); + if (style_parse(sb_style, &grid_default_cell, expanded) != 0) + style_parse(sb_style, &grid_default_cell, style); + free(expanded); + } + free(style); if (sb_style->width < 1) sb_style->width = PANE_SCROLLBARS_DEFAULT_WIDTH; if (sb_style->pad < 0) From 8c55a388cde9ef283a9b8dcaeb8abc6f81a79978 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 25 Jun 2026 23:17:25 +0000 Subject: [PATCH 36/71] Add nicer dark and light colour sets (themes) used on terminals with 256 or more colours. Currently based these on emacs but they could change. Terminals with fewer colours use the ANSI colours. A new "theme" option overrides the detected theme (set to "terminal" to go back to ANSI colours). --- colour.c | 94 ++++++++++++++++++++- grid.c | 17 ++-- options-table.c | 220 +++++++++++++++++++++++++++++++++++++++++++----- options.c | 11 +++ server-client.c | 64 ++++++++++++++ tmux.1 | 51 +++++++++++ tmux.h | 21 +++++ tty.c | 29 +++++++ 8 files changed, 478 insertions(+), 29 deletions(-) diff --git a/colour.c b/colour.c index 1cad01f91..0db080fc4 100644 --- a/colour.c +++ b/colour.c @@ -26,6 +26,85 @@ #include "tmux.h" +/* Theme colour slots and their server options. */ +static const struct { + const char *name; + const char *dark_option; + const char *light_option; + int terminal_colour; +} colour_theme_table[] = { + { "themeblack", + "dark-theme-black", + "light-theme-black", + 0 + }, + { "themewhite", + "dark-theme-white", + "light-theme-white", + 7 + }, + { "themelightgrey", + "dark-theme-light-grey", + "light-theme-light-grey", + 7 + }, + { "themedarkgrey", + "dark-theme-dark-grey", + "light-theme-dark-grey", + 0 + }, + { "themegreen", + "dark-theme-green", + "light-theme-green", + 2 + }, + { "themeyellow", + "dark-theme-yellow", + "light-theme-yellow", + 3 + }, + { "themered", + "dark-theme-red", + "light-theme-red", + 1 + }, + { "themeblue", + "dark-theme-blue", + "light-theme-blue", + 4 + }, + { "themecyan", + "dark-theme-cyan", + "light-theme-cyan", + 6 + }, + { "thememagenta", + "dark-theme-magenta", + "light-theme-magenta", + 5 + } +}; + +/* Get theme colour option. */ +const char * +colour_theme_option(u_int n, enum client_theme theme) +{ + if (n >= nitems(colour_theme_table)) + return (NULL); + if (theme == THEME_LIGHT) + return (colour_theme_table[n].light_option); + return (colour_theme_table[n].dark_option); +} + +/* Get theme terminal colour. */ +int +colour_theme_terminal_colour(u_int n) +{ + if (n >= nitems(colour_theme_table)) + return (8); + return (colour_theme_table[n].terminal_colour); +} + static int colour_dist_sq(int R, int G, int B, int r, int g, int b) { @@ -126,7 +205,7 @@ colour_dim(int c, u_int dim) { u_char r, g, b; - if (dim == 0 || COLOUR_DEFAULT(c)) + if (dim == 0 || COLOUR_DEFAULT(c) || (c & COLOUR_FLAG_THEME)) return (c); if (dim >= 100) return (colour_join_rgb(0, 0, 0)); @@ -152,6 +231,13 @@ colour_tostring(int c) if (c == -1) return ("none"); + if (c & COLOUR_FLAG_THEME) { + c &= 0xff; + if (c >= 0 && (u_int)c < nitems(colour_theme_table)) + return (colour_theme_table[c].name); + return ("invalid"); + } + if (c & COLOUR_FLAG_RGB) { colour_split_rgb(c, &r, &g, &b); xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b); @@ -252,6 +338,7 @@ colour_fromstring(const char *s) const char *cp; int n; u_char r, g, b; + u_int i; if (*s == '#' && strlen(s) == 7) { for (cp = s + 1; isxdigit((u_char) *cp); cp++) @@ -282,6 +369,11 @@ colour_fromstring(const char *s) if (strcasecmp(s, "terminal") == 0) return (9); + for (i = 0; i < nitems(colour_theme_table); i++) { + if (strcasecmp(s, colour_theme_table[i].name) == 0) + return (i|COLOUR_FLAG_THEME); + } + if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0) return (0); if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0) diff --git a/grid.c b/grid.c index 52da173a1..0796b875c 100644 --- a/grid.c +++ b/grid.c @@ -86,7 +86,8 @@ grid_need_extended_cell(const struct grid_cell_entry *gce, return (1); if (gc->data.size > 1 || gc->data.width > 1) return (1); - if ((gc->fg & COLOUR_FLAG_RGB) || (gc->bg & COLOUR_FLAG_RGB)) + if ((gc->fg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) || + (gc->bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME))) return (1); if (gc->us != 8) /* only supports 256 or RGB */ return (1); @@ -218,7 +219,7 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg, int moved) if (bg != 8) gee->bg = bg; } else if (bg != 8) { - if (bg & COLOUR_FLAG_RGB) { + if (bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) { grid_get_extended_cell(gl, gce, gce->flags); gee = grid_extended_cell(gl, gce, &grid_cleared_cell); gee->bg = bg; @@ -798,7 +799,9 @@ grid_string_cells_fg(const struct grid_cell *gc, int *values) u_char r, g, b; n = 0; - if (gc->fg & COLOUR_FLAG_256) { + if (gc->fg & COLOUR_FLAG_THEME) + values[n++] = 39; + else if (gc->fg & COLOUR_FLAG_256) { values[n++] = 38; values[n++] = 5; values[n++] = gc->fg & 0xff; @@ -847,7 +850,9 @@ grid_string_cells_bg(const struct grid_cell *gc, int *values) u_char r, g, b; n = 0; - if (gc->bg & COLOUR_FLAG_256) { + if (gc->bg & COLOUR_FLAG_THEME) + values[n++] = 49; + else if (gc->bg & COLOUR_FLAG_256) { values[n++] = 48; values[n++] = 5; values[n++] = gc->bg & 0xff; @@ -896,7 +901,9 @@ grid_string_cells_us(const struct grid_cell *gc, int *values) u_char r, g, b; n = 0; - if (gc->us & COLOUR_FLAG_256) { + if (gc->us & COLOUR_FLAG_THEME) { + values[n++] = 59; + } else if (gc->us & COLOUR_FLAG_256) { values[n++] = 58; values[n++] = 5; values[n++] = gc->us & 0xff; diff --git a/options-table.c b/options-table.c index 6050c24a0..17758d714 100644 --- a/options-table.c +++ b/options-table.c @@ -109,6 +109,9 @@ static const char *options_table_extended_keys_format_list[] = { static const char *options_table_allow_passthrough_list[] = { "off", "on", "all", NULL }; +static const char *options_table_theme_list[] = { + "detect", "terminal", "light", "dark", NULL +}; static const char *options_table_copy_mode_line_numbers_list[] = { "off", "default", "absolute", "relative", "hybrid", NULL }; @@ -446,7 +449,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .flags = OPTIONS_TABLE_IS_STYLE, - .default_str = "default", + .default_str = "bg=themedarkgrey,fg=themewhite", .separator = ",", .text = "Default style of menu." }, @@ -455,7 +458,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .flags = OPTIONS_TABLE_IS_STYLE, - .default_str = "bg=yellow,fg=black", + .default_str = "bg=themeyellow,fg=themeblack", .separator = ",", .text = "Default style of selected menu item." }, @@ -463,7 +466,7 @@ const struct options_table_entry options_table[] = { { .name = "menu-border-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default", + .default_str = "bg=themedarkgrey,fg=themelightgrey", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Default style of menu borders." @@ -538,6 +541,175 @@ const struct options_table_entry options_table[] = { "automatically detected." }, + { .name = "theme", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_SERVER, + .choices = options_table_theme_list, + .default_num = 0, + .text = "Whether tmux should detect the terminal theme, use terminal " + "ANSI colours, or force the light or dark theme." + }, + + { .name = "dark-theme-black", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray5,black}", + .text = "Dark theme colour for black." + }, + + { .name = "dark-theme-white", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray90,white}", + .text = "Dark theme colour for white." + }, + + { .name = "dark-theme-light-grey", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray70,white}", + .text = "Dark theme colour for light grey." + }, + + { .name = "dark-theme-dark-grey", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray15,black}", + .text = "Dark theme colour for dark grey." + }, + + { .name = "dark-theme-green", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},palegreen,green}", + .text = "Dark theme colour for green." + }, + + { .name = "dark-theme-yellow", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},lightgoldenrod,yellow}", + .text = "Dark theme colour for yellow." + }, + + { .name = "dark-theme-red", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},lightsalmon,red}", + .text = "Dark theme colour for red." + }, + + { .name = "dark-theme-blue", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},lightskyblue,blue}", + .text = "Dark theme colour for blue." + }, + + { .name = "dark-theme-cyan", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},aquamarine,cyan}", + .text = "Dark theme colour for cyan." + }, + + { .name = "dark-theme-magenta", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},plum,magenta}", + .text = "Dark theme colour for magenta." + }, + + { .name = "light-theme-black", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray10,black}", + .text = "Light theme colour for black." + }, + + { .name = "light-theme-white", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray95,white}", + .text = "Light theme colour for white." + }, + + { .name = "light-theme-light-grey", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray80,white}", + .text = "Light theme colour for light grey." + }, + + { .name = "light-theme-dark-grey", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},gray45,black}", + .text = "Light theme colour for dark grey." + }, + + { .name = "light-theme-green", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},forestgreen,green}", + .text = "Light theme colour for green." + }, + + { .name = "light-theme-yellow", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},darkgoldenrod,yellow}", + .text = "Light theme colour for yellow." + }, + + { .name = "light-theme-red", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},violetred4,red}", + .text = "Light theme colour for red." + }, + + { .name = "light-theme-blue", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},royalblue,blue}", + .text = "Light theme colour for blue." + }, + + { .name = "light-theme-cyan", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},darkcyan,cyan}", + .text = "Light theme colour for cyan." + }, + + { .name = "light-theme-magenta", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "#{?#{e|>=:#{client_colours},256},purple,magenta}", + .text = "Light theme colour for magenta." + }, + { .name = "user-keys", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, @@ -639,7 +811,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "red", + .default_str = "themered", .text = "Colour of the active pane for 'display-panes'." }, @@ -647,7 +819,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "blue", + .default_str = "themeblue", .text = "Colour of not active panes for 'display-panes'." }, @@ -738,7 +910,7 @@ const struct options_table_entry options_table[] = { { .name = "message-command-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=black,fg=yellow,fill=black", + .default_str = "bg=themegreen,fg=themeblack,fill=themegreen", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the command prompt when in command mode, if " @@ -767,9 +939,9 @@ const struct options_table_entry options_table[] = { { .name = "message-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=yellow,fg=black," + .default_str = "bg=themeyellow,fg=themeblack," "#{?#{m/r:(^|#,)IS(PANE|MODE)($|#,),#{prompt_flags}},," - "fill=yellow}", + "fill=themeyellow}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of messages and the command prompt. " @@ -968,7 +1140,7 @@ const struct options_table_entry options_table[] = { { .name = "status-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=green,fg=black", + .default_str = "bg=themegreen,fg=themeblack", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the status line." @@ -1152,7 +1324,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "blue", + .default_str = "themeblue", .text = "Colour of the clock in clock mode." }, @@ -1167,7 +1339,7 @@ const struct options_table_entry options_table[] = { { .name = "copy-mode-match-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "bg=cyan,fg=black", + .default_str = "bg=themecyan,fg=themeblack", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of search matches in copy mode." @@ -1176,7 +1348,7 @@ const struct options_table_entry options_table[] = { { .name = "copy-mode-current-match-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "bg=magenta,fg=black", + .default_str = "bg=thememagenta,fg=themeblack", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the current search match in copy mode." @@ -1185,7 +1357,7 @@ const struct options_table_entry options_table[] = { { .name = "copy-mode-mark-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "bg=red,fg=black", + .default_str = "bg=themeyellow,fg=themeblack", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the marked line in copy mode." @@ -1224,7 +1396,7 @@ const struct options_table_entry options_table[] = { { .name = "copy-mode-current-line-number-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "fg=yellow", + .default_str = "fg=themeyellow", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of current line number in copy mode." @@ -1233,7 +1405,7 @@ const struct options_table_entry options_table[] = { { .name = "copy-mode-line-number-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "fg=white,dim", + .default_str = "fg=themelightgrey,dim", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of line numbers in copy mode." @@ -1282,7 +1454,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .flags = OPTIONS_TABLE_IS_STYLE, - .default_str = "noattr,bg=yellow,fg=black", + .default_str = "noattr,bg=themeyellow,fg=themeblack", .separator = ",", .text = "Style of indicators and highlighting in modes." }, @@ -1331,7 +1503,9 @@ const struct options_table_entry options_table[] = { { .name = "pane-active-border-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "#{?pane_in_mode,fg=yellow,#{?synchronize-panes,fg=red,fg=green}}", + .default_str = "fg=#{?pane_marked,thememagenta," + "#{?synchronize-panes,themered," + "#{?pane_in_mode,themeyellow,themegreen}}}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the active pane border." @@ -1390,7 +1564,7 @@ const struct options_table_entry options_table[] = { { .name = "pane-border-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "default", + .default_str = "fg=themelightgrey", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the pane status lines." @@ -1415,7 +1589,7 @@ const struct options_table_entry options_table[] = { { .name = "pane-scrollbars-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "bg=black,fg=white,width=1,pad=0", + .default_str = "bg=themedarkgrey,fg=themelightgrey,width=1,pad=0", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the pane scrollbar." @@ -1432,7 +1606,7 @@ const struct options_table_entry options_table[] = { { .name = "popup-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default", + .default_str = "bg=themedarkgrey,fg=themewhite", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Default style of popups." @@ -1441,7 +1615,7 @@ const struct options_table_entry options_table[] = { { .name = "popup-border-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default", + .default_str = "fg=themelightgrey", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Default style of popup borders." @@ -1519,8 +1693,8 @@ const struct options_table_entry options_table[] = { .default_str = "fg=#{?#{||:" "#{&&:#{pane_format},#{pane_active}}," "#{&&:#{window_format},#{window_active}}}," - "#{display-panes-active-colour}," - "#{display-panes-colour}}", + "themered," + "themeblue}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of preview indicator in tree mode." diff --git a/options.c b/options.c index b9e319acf..db0f221a3 100644 --- a/options.c +++ b/options.c @@ -1214,6 +1214,17 @@ options_push_changes(const char *name) log_debug("%s: %s", __func__, name); + if (strcmp(name, "theme") == 0 || + strncmp(name, "dark-theme-", 11) == 0 || + strncmp(name, "light-theme-", 12) == 0) { + TAILQ_FOREACH(loop, &clients, entry) { + server_client_update_theme_colours(loop); + if (loop->tty.flags & TTY_OPENED) + tty_invalidate(&loop->tty); + server_redraw_client(loop); + } + } + if (strcmp(name, "automatic-rename") == 0) { RB_FOREACH(w, windows, &windows) { if (w->active == NULL) diff --git a/server-client.c b/server-client.c index 60fdab2a6..5b2651658 100644 --- a/server-client.c +++ b/server-client.c @@ -291,6 +291,7 @@ struct client * server_client_create(int fd) { struct client *c; + u_int i; setblocking(fd, 0); @@ -313,6 +314,9 @@ server_client_create(int fd) c->tty.sx = 80; c->tty.sy = 24; + + for (i = 0; i < COLOUR_THEME_COUNT; i++) + c->theme_colours[i] = 8; c->theme = THEME_UNKNOWN; status_init(c); @@ -364,6 +368,7 @@ server_client_open(struct client *c, char **cause) if (tty_open(&c->tty, cause) != 0) return (-1); + server_client_update_theme_colours(c); return (0); } @@ -1081,6 +1086,52 @@ have_event: return (key); } +/* Update client theme colours from server options. */ +void +server_client_update_theme_colours(struct client *c) +{ + struct format_tree *ft; + const char *name, *value; + enum client_theme theme; + char *expanded; + u_int i; + int colour, option; + + if (c == NULL) + return; + + option = options_get_number(global_options, "theme"); + if (option == 1) { + for (i = 0; i < COLOUR_THEME_COUNT; i++) + c->theme_colours[i] = colour_theme_terminal_colour(i); + return; + } + + ft = format_create(c, NULL, FORMAT_NONE, FORMAT_NOJOBS); + format_defaults(ft, c, NULL, NULL, NULL); + + theme = c->theme; + if (option == 2) + theme = THEME_LIGHT; + else if (option == 3) + theme = THEME_DARK; + for (i = 0; i < COLOUR_THEME_COUNT; i++) { + c->theme_colours[i] = 8; + name = colour_theme_option(i, theme); + if (name == NULL) + continue; + value = options_get_string(global_options, name); + expanded = format_expand(ft, value); + colour = colour_fromstring(expanded); + free(expanded); + if (colour == -1 || (colour & COLOUR_FLAG_THEME)) + continue; + c->theme_colours[i] = colour; + } + + format_free(ft); +} + /* Is this a bracket paste key? */ static int server_client_is_bracket_paste(struct client *c, key_code key) @@ -2928,6 +2979,8 @@ out: static void server_client_report_theme(struct client *c, enum client_theme theme) { + enum client_theme old = c->theme; + if (theme == THEME_LIGHT) { c->theme = THEME_LIGHT; notify_client("client-light-theme", c); @@ -2936,6 +2989,17 @@ server_client_report_theme(struct client *c, enum client_theme theme) notify_client("client-dark-theme", c); } + /* + * If the theme has changed, update the theme colours and redraw the + * client. + */ + if (c->theme != old) { + server_client_update_theme_colours(c); + if (c->tty.flags & TTY_OPENED) + tty_invalidate(&c->tty); + server_redraw_client(c); + } + /* * Request foreground and background colour again. Don't forward 2031 to * panes until a response is received. diff --git a/tmux.1 b/tmux.1 index 1f96045a2..fe5265e50 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4571,6 +4571,57 @@ where the number is a hexadecimal number, or a range of the form Give the command to pipe to if the .Ic copy\-pipe copy mode command is used without arguments. +.It Xo Ic theme +.Op Ic detect | terminal | light | dark +.Xc +Whether tmux should use the detected client theme +.Pq Ic detect , +use terminal ANSI colours +.Pq Ic terminal , +or force the light or dark theme. +.It Xo Ic dark\-theme\-black , +.Ic dark\-theme\-white , +.Ic dark\-theme\-light\-grey , +.Ic dark\-theme\-dark\-grey , +.Ic dark\-theme\-green , +.Ic dark\-theme\-yellow , +.Ic dark\-theme\-red , +.Ic dark\-theme\-blue , +.Ic dark\-theme\-cyan , +.Ic dark\-theme\-magenta , +.Ic light\-theme\-black , +.Ic light\-theme\-white , +.Ic light\-theme\-light\-grey , +.Ic light\-theme\-dark\-grey , +.Ic light\-theme\-green , +.Ic light\-theme\-yellow , +.Ic light\-theme\-red , +.Ic light\-theme\-blue , +.Ic light\-theme\-cyan +.No and Ic light\-theme\-magenta Ar colour +.Xc +Set the theme colours. +These are the named colours +.Ic themeblack , +.Ic themewhite , +.Ic themelightgrey , +.Ic themedarkgrey , +.Ic themegreen , +.Ic themeyellow , +.Ic themered , +.Ic themeblue , +.Ic themecyan +and +.Ic thememagenta +which may be used in styles and elsewhere a +.Ar colour +is accepted. +The +.Ic dark\-theme\-* +set is used when the client reports a dark background and the +.Ic light\-theme\-* +set when it reports a light background; +an unknown background is treated as dark. .It Ic default\-client\-command Ar command Set the default command to run when tmux is called without a command. The default is diff --git a/tmux.h b/tmux.h index bbf25b9c8..3ebf8ce70 100644 --- a/tmux.h +++ b/tmux.h @@ -728,10 +728,26 @@ enum hanguljamo_state { /* Colour flags. */ #define COLOUR_FLAG_256 0x01000000 #define COLOUR_FLAG_RGB 0x02000000 +#define COLOUR_FLAG_THEME 0x04000000 /* Special colours. */ #define COLOUR_DEFAULT(c) ((c) == 8 || (c) == 9) +/* Theme colours. */ +enum colour_theme { + COLOUR_THEME_BLACK, + COLOUR_THEME_WHITE, + COLOUR_THEME_LIGHT_GREY, + COLOUR_THEME_DARK_GREY, + COLOUR_THEME_GREEN, + COLOUR_THEME_YELLOW, + COLOUR_THEME_RED, + COLOUR_THEME_BLUE, + COLOUR_THEME_CYAN, + COLOUR_THEME_MAGENTA +}; +#define COLOUR_THEME_COUNT 10 + /* Replacement palette. */ struct colour_palette { int fg; @@ -2219,6 +2235,8 @@ struct client { int references; + int theme_colours[COLOUR_THEME_COUNT]; + void *pan_window; u_int pan_ox; u_int pan_oy; @@ -3113,6 +3131,7 @@ void server_client_print(struct client *, int, struct evbuffer *); /* server-fn.c */ void server_redraw_client(struct client *); +void server_client_update_theme_colours(struct client *); void server_status_client(struct client *); void server_redraw_session(struct session *); void server_redraw_session_group(struct session *); @@ -3226,6 +3245,8 @@ int colour_dim(int, u_int); const char *colour_tostring(int); enum client_theme colour_totheme(int); int colour_fromstring(const char *); +const char *colour_theme_option(u_int, enum client_theme); +int colour_theme_terminal_colour(u_int); int colour_256toRGB(int); int colour_256to16(int); int colour_byname(const char *); diff --git a/tty.c b/tty.c index 15cab4bfa..fa1b45076 100644 --- a/tty.c +++ b/tty.c @@ -51,6 +51,7 @@ static void tty_check_bg(struct tty *, struct colour_palette *, struct grid_cell *); static void tty_check_us(struct tty *, struct colour_palette *, struct grid_cell *); +static int tty_map_theme_colour(struct tty *, int); static void tty_colours_fg(struct tty *, const struct grid_cell *); static void tty_colours_bg(struct tty *, const struct grid_cell *); static void tty_colours_us(struct tty *, const struct grid_cell *); @@ -2517,6 +2518,9 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, gc2.bg = changed; } } + gc2.fg = tty_map_theme_colour(tty, gc2.fg); + gc2.bg = tty_map_theme_colour(tty, gc2.bg); + gc2.us = tty_map_theme_colour(tty, gc2.us); if (style_ctx->dim != 0) { gc2.fg = tty_dim_default_colour(tty, gc2.fg, 1); gc2.bg = tty_dim_default_colour(tty, gc2.bg, 0); @@ -2666,6 +2670,28 @@ tty_colours(struct tty *tty, const struct grid_cell *gc) tty_colours_us(tty, gc); } +static int +tty_map_theme_colour(struct tty *tty, int colour) +{ + struct client *c; + u_int n; + int m; + + if (~colour & COLOUR_FLAG_THEME) + return (colour); + + n = colour & 0xff; + if (n >= COLOUR_THEME_COUNT) + return (8); + if (tty == NULL || (c = tty->client) == NULL) + return (8); + + m = c->theme_colours[n]; + if (m == -1 || (m & COLOUR_FLAG_THEME)) + return (8); + return (m); +} + static void tty_check_fg(struct tty *tty, struct colour_palette *palette, struct grid_cell *gc) @@ -2688,6 +2714,7 @@ tty_check_fg(struct tty *tty, struct colour_palette *palette, if ((c = colour_palette_get(palette, c)) != -1) gc->fg = c; } + gc->fg = tty_map_theme_colour(tty, gc->fg); /* Is this a 24-bit colour? */ if (gc->fg & COLOUR_FLAG_RGB) { @@ -2748,6 +2775,7 @@ tty_check_bg(struct tty *tty, struct colour_palette *palette, if ((c = colour_palette_get(palette, gc->bg)) != -1) gc->bg = c; } + gc->bg = tty_map_theme_colour(tty, gc->bg); /* Is this a 24-bit colour? */ if (gc->bg & COLOUR_FLAG_RGB) { @@ -2798,6 +2826,7 @@ tty_check_us(__unused struct tty *tty, struct colour_palette *palette, if ((c = colour_palette_get(palette, gc->us)) != -1) gc->us = c; } + gc->us = tty_map_theme_colour(tty, gc->us); /* Convert underscore colour if only RGB can be supported. */ if (!tty_term_has(tty->term, TTYC_SETULC1)) { From 569e9d932f0f83046dfd1a0fd94ea1b47d60c1b5 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 25 Jun 2026 23:48:20 +0000 Subject: [PATCH 37/71] Make the default colours more conservative, its really the greys that matter. --- options-table.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/options-table.c b/options-table.c index 17758d714..40e3a7162 100644 --- a/options-table.c +++ b/options-table.c @@ -586,7 +586,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},palegreen,green}", + .default_str = "#{?#{e|>=:#{client_colours},256},darkseagreen,green}", .text = "Dark theme colour for green." }, @@ -594,7 +594,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},lightgoldenrod,yellow}", + .default_str = "#{?#{e|>=:#{client_colours},256},darkkhaki,yellow}", .text = "Dark theme colour for yellow." }, @@ -602,7 +602,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},lightsalmon,red}", + .default_str = "#{?#{e|>=:#{client_colours},256},indianred,red}", .text = "Dark theme colour for red." }, @@ -610,7 +610,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},lightskyblue,blue}", + .default_str = "#{?#{e|>=:#{client_colours},256},skyblue3,blue}", .text = "Dark theme colour for blue." }, @@ -618,7 +618,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},aquamarine,cyan}", + .default_str = "#{?#{e|>=:#{client_colours},256},cadetblue,cyan}", .text = "Dark theme colour for cyan." }, @@ -626,7 +626,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},plum,magenta}", + .default_str = "#{?#{e|>=:#{client_colours},256},mediumpurple,magenta}", .text = "Dark theme colour for magenta." }, @@ -666,7 +666,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},forestgreen,green}", + .default_str = "#{?#{e|>=:#{client_colours},256},seagreen,green}", .text = "Light theme colour for green." }, @@ -682,7 +682,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},violetred4,red}", + .default_str = "#{?#{e|>=:#{client_colours},256},indianred4,red}", .text = "Light theme colour for red." }, @@ -690,7 +690,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},royalblue,blue}", + .default_str = "#{?#{e|>=:#{client_colours},256},steelblue,blue}", .text = "Light theme colour for blue." }, @@ -706,7 +706,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},purple,magenta}", + .default_str = "#{?#{e|>=:#{client_colours},256},purple4,magenta}", .text = "Light theme colour for magenta." }, From 3631f8123654a610a547c3c1b361509b63b513ba Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 08:10:48 +0000 Subject: [PATCH 38/71] Apply better colours to various builtin bits in modes. --- mode-tree.c | 254 +++++++++++++++++++++++++++++---------------- options-table.c | 17 ++- screen-write.c | 8 +- tmux.1 | 8 ++ tmux.h | 3 +- window-buffer.c | 21 ++-- window-client.c | 125 +++++++++++++--------- window-customize.c | 31 ++++-- window-tree.c | 95 +++++++++++------ 9 files changed, 364 insertions(+), 198 deletions(-) diff --git a/mode-tree.c b/mode-tree.c index 149f9412b..f6e3eb75b 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -36,6 +36,24 @@ enum mode_tree_preview { MODE_TREE_PREVIEW_BIG }; +#define MODE_TREE_PREFIX_STYLE \ + "#{?mode_tree_selected,#[default]#[noacs]," \ + "#[fg=themelightgrey]#[bg=default]#[noacs]}" + +#define MODE_TREE_PREFIX_FORMAT \ + MODE_TREE_PREFIX_STYLE \ + "#{p/#{mode_tree_key_width}:" \ + "#{?#{!=:#{mode_tree_key},},(#{mode_tree_key}),}}" \ + "#{R:#{?mode_tree_parent_last, ," \ + "#[acs]x" MODE_TREE_PREFIX_STYLE " }," \ + "#{mode_tree_repeat}}" \ + "#{?mode_tree_branch," \ + "#[acs]#{?mode_tree_last,mq,tq}" MODE_TREE_PREFIX_STYLE "> ,}" \ + "#{?mode_tree_has_children," \ + "#{?mode_tree_expanded,#[fg=themered]-" MODE_TREE_PREFIX_STYLE " ," \ + "#[fg=themegreen]+" MODE_TREE_PREFIX_STYLE " }," \ + "#{?mode_tree_flat,, }}" + struct mode_tree_item; struct mode_tree_prompt; TAILQ_HEAD(mode_tree_list, mode_tree_item); @@ -159,31 +177,53 @@ static const struct menu_item mode_tree_menu_items[] = { }; static const char* mode_tree_help_start[] = { - "#[bold] Up, k #[default]#[acs]x#[default] Move cursor up", - "#[bold] Down, j #[default]#[acs]x#[default] Move cursor down", - "#[bold] g #[default]#[acs]x#[default] Go to top", - "#[bold] G #[default]#[acs]x#[default] Go to bottom", - "#[bold] PPage, C-b #[default]#[acs]x#[default] Page up", - "#[bold] NPage, C-f #[default]#[acs]x#[default] Page down", - "#[bold] Left, h #[default]#[acs]x#[default] Collapse %1", - "#[bold] Right, l #[default]#[acs]x#[default] Expand %1", - "#[bold] M-- #[default]#[acs]x#[default] Collapse all %1s", - "#[bold] M-+ #[default]#[acs]x#[default] Expand all %1s", - "#[bold] t #[default]#[acs]x#[default] Toggle %1 tag", - "#[bold] T #[default]#[acs]x#[default] Untag all %1s", - "#[bold] C-t #[default]#[acs]x#[default] Tag all %1s", - "#[bold] C-s #[default]#[acs]x#[default] Search forward", - "#[bold] C-r #[default]#[acs]x#[default] Search backward", - "#[bold] n #[default]#[acs]x#[default] Repeat search forward", - "#[bold] N #[default]#[acs]x#[default] Repeat search backward", - "#[bold] f #[default]#[acs]x#[default] Filter %1s", - "#[bold] O #[default]#[acs]x#[default] Change sort order", - "#[bold] r #[default]#[acs]x#[default] Reverse sort order", - "#[bold] v #[default]#[acs]x#[default] Toggle preview", + "#[fg=themelightgrey]" + " Up, k #[#{E:tree-mode-border-style},acs]x#[default] Move cursor up", + "#[fg=themelightgrey]" + " Down, j #[#{E:tree-mode-border-style},acs]x#[default] Move cursor down", + "#[fg=themelightgrey]" + " g #[#{E:tree-mode-border-style},acs]x#[default] Go to top", + "#[fg=themelightgrey]" + " G #[#{E:tree-mode-border-style},acs]x#[default] Go to bottom", + "#[fg=themelightgrey]" + " PPage, C-b #[#{E:tree-mode-border-style},acs]x#[default] Page up", + "#[fg=themelightgrey]" + " NPage, C-f #[#{E:tree-mode-border-style},acs]x#[default] Page down", + "#[fg=themelightgrey]" + " Left, h #[#{E:tree-mode-border-style},acs]x#[default] Collapse %1", + "#[fg=themelightgrey]" + " Right, l #[#{E:tree-mode-border-style},acs]x#[default] Expand %1", + "#[fg=themelightgrey]" + " M-- #[#{E:tree-mode-border-style},acs]x#[default] Collapse all %1s", + "#[fg=themelightgrey]" + " M-+ #[#{E:tree-mode-border-style},acs]x#[default] Expand all %1s", + "#[fg=themelightgrey]" + " t #[#{E:tree-mode-border-style},acs]x#[default] Toggle %1 tag", + "#[fg=themelightgrey]" + " T #[#{E:tree-mode-border-style},acs]x#[default] Untag all %1s", + "#[fg=themelightgrey]" + " C-t #[#{E:tree-mode-border-style},acs]x#[default] Tag all %1s", + "#[fg=themelightgrey]" + " C-s #[#{E:tree-mode-border-style},acs]x#[default] Search forward", + "#[fg=themelightgrey]" + " C-r #[#{E:tree-mode-border-style},acs]x#[default] Search backward", + "#[fg=themelightgrey]" + " n #[#{E:tree-mode-border-style},acs]x#[default] Repeat search forward", + "#[fg=themelightgrey]" + " N #[#{E:tree-mode-border-style},acs]x#[default] Repeat search backward", + "#[fg=themelightgrey]" + " f #[#{E:tree-mode-border-style},acs]x#[default] Filter %1s", + "#[fg=themelightgrey]" + " O #[#{E:tree-mode-border-style},acs]x#[default] Change sort order", + "#[fg=themelightgrey]" + " r #[#{E:tree-mode-border-style},acs]x#[default] Reverse sort order", + "#[fg=themelightgrey]" + " v #[#{E:tree-mode-border-style},acs]x#[default] Toggle preview", NULL }; static const char* mode_tree_help_end[] = { - "#[bold] q, Escape #[default]#[acs]x#[default] Exit mode", + "#[fg=themelightgrey]" + " q, Escape #[#{E:tree-mode-border-style},acs]x#[default] Exit mode", NULL }; #define MODE_TREE_HELP_DEFAULT_WIDTH 39 @@ -784,12 +824,14 @@ mode_tree_draw(struct mode_tree_data *mtd) struct mode_tree_item *mti; struct options *oo = wp->window->options; struct screen_write_ctx ctx; - struct grid_cell gc0, gc; - u_int w, h, i, j, sy, box_x, box_y, width; - char *text, *start, *key; - const char *tag, *symbol; - size_t size, n; - int keylen, pad, alignlen[mtd->maxdepth + 1]; + struct format_tree *ft; + struct grid_cell gc0, gc, box_gc; + u_int w, h, i, sy, box_x, box_y; + u_int width, text_width, prefix_width, left; + char *text, *prefix; + const char *tag, *separator; + size_t n; + int keylen, alignlen[mtd->maxdepth + 1]; if (mtd->line_size == 0) return; @@ -797,12 +839,15 @@ mode_tree_draw(struct mode_tree_data *mtd) memcpy(&gc0, &grid_default_cell, sizeof gc0); memcpy(&gc, &grid_default_cell, sizeof gc); style_apply(&gc, oo, "mode-style", NULL); + memcpy(&box_gc, &grid_default_cell, sizeof box_gc); + style_apply(&box_gc, oo, "tree-mode-border-style", NULL); w = mtd->width; h = mtd->height; screen_write_start(&ctx, s); screen_write_clearscreen(&ctx, 8); + ft = format_create_defaults(NULL, NULL, NULL, NULL, wp); keylen = 0; for (i = 0; i < mtd->line_size; i++) { @@ -833,52 +878,53 @@ mode_tree_draw(struct mode_tree_data *mtd) screen_write_cursormove(&ctx, 0, i - mtd->offset, 0); - pad = keylen - 2 - mti->keylen; if (mti->key != KEYC_NONE) - xasprintf(&key, "(%s)%*s", mti->keystr, pad, ""); + format_add(ft, "mode_tree_key", "%s", mti->keystr); else - key = xstrdup(""); - - if (line->flat) - symbol = ""; - else if (TAILQ_EMPTY(&mti->children)) - symbol = " "; - else if (mti->expanded) - symbol = "- "; - else - symbol = "+ "; - - if (line->depth == 0) - start = xstrdup(symbol); - else { - size = (4 * line->depth) + 32; - - start = xcalloc(1, size); - for (j = 1; j < line->depth; j++) { - if (mti->parent != NULL && - mtd->line_list[mti->parent->line].last) - strlcat(start, " ", size); - else - strlcat(start, "\001x\001 ", size); - } - if (line->last) - strlcat(start, "\001mq\001> ", size); + format_add(ft, "mode_tree_key", "%s", ""); + format_add(ft, "mode_tree_key_width", "%d", keylen); + format_add(ft, "mode_tree_selected", "%d", i == mtd->current); + if (line->depth == 0) { + format_add(ft, "mode_tree_repeat", "%u", 0); + format_add(ft, "mode_tree_branch", "0"); + format_add(ft, "mode_tree_parent_last", "0"); + } else { + format_add(ft, "mode_tree_repeat", "%u", + line->depth - 1); + format_add(ft, "mode_tree_branch", "1"); + if (mti->parent != NULL && + mtd->line_list[mti->parent->line].last) + format_add(ft, "mode_tree_parent_last", "1"); else - strlcat(start, "\001tq\001> ", size); - strlcat(start, symbol, size); + format_add(ft, "mode_tree_parent_last", "0"); } + if (TAILQ_EMPTY(&mti->children)) + format_add(ft, "mode_tree_has_children", "0"); + else + format_add(ft, "mode_tree_has_children", "1"); + format_add(ft, "mode_tree_last", "%d", line->last); + format_add(ft, "mode_tree_expanded", "%d", mti->expanded); + format_add(ft, "mode_tree_flat", "%d", line->flat); + prefix = format_expand(ft, MODE_TREE_PREFIX_FORMAT); + prefix_width = format_width(prefix); + if (prefix_width > w) + prefix_width = w; if (mti->tagged) tag = "*"; else tag = ""; - xasprintf(&text, "%-*s%s%*s%s%s", keylen, key, start, - mti->align * alignlen[line->depth], mti->name, tag, - (mti->text != NULL) ? ": " : "" ); - width = utf8_cstrwidth(text); - if (width > w) - width = w; - free(start); + if (mti->text != NULL) + separator = "#[fg=themelightgrey]: #[default]"; + else + separator = ""; + xasprintf(&text, "%*s%s%s", + mti->align * alignlen[line->depth], mti->name, tag, separator); + text_width = format_width(text); + left = (prefix_width < w) ? (w - prefix_width) : 0; + if (text_width > left) + text_width = left; + width = prefix_width + text_width; if (mti->tagged) { gc.attr ^= GRID_ATTR_BRIGHT; @@ -887,27 +933,43 @@ mode_tree_draw(struct mode_tree_data *mtd) if (i != mtd->current) { screen_write_clearendofline(&ctx, 8); - screen_write_nputs(&ctx, w, &gc0, "%s", text); - if (mti->text != NULL) { - format_draw(&ctx, &gc0, w - width, mti->text, - NULL, 0); + format_draw(&ctx, &grid_default_cell, prefix_width, + prefix, NULL, 0); + if (left != 0) { + screen_write_cursormove(&ctx, prefix_width, + i - mtd->offset, 0); + format_draw(&ctx, &gc0, left, text, NULL, 0); + if (mti->text != NULL && width < w) { + screen_write_cursormove(&ctx, width, + i - mtd->offset, 0); + format_draw(&ctx, &gc0, w - width, + mti->text, NULL, 0); + } } } else { screen_write_clearendofline(&ctx, gc.bg); - screen_write_nputs(&ctx, w, &gc, "%s", text); - if (mti->text != NULL) { - format_draw(&ctx, &gc, w - width, mti->text, - NULL, 0); + format_draw(&ctx, &gc, prefix_width, prefix, NULL, 0); + if (left != 0) { + screen_write_cursormove(&ctx, prefix_width, + i - mtd->offset, 0); + format_draw(&ctx, &gc, left, text, NULL, 0); + if (mti->text != NULL && width < w) { + screen_write_cursormove(&ctx, width, + i - mtd->offset, 0); + format_draw(&ctx, &gc, w - width, + mti->text, NULL, 0); + } } } free(text); - free(key); + free(prefix); if (mti->tagged) { gc.attr ^= GRID_ATTR_BRIGHT; gc0.attr ^= GRID_ATTR_BRIGHT; } } + format_free(ft); if (mtd->preview == MODE_TREE_PREVIEW_OFF) goto done; @@ -922,7 +984,7 @@ mode_tree_draw(struct mode_tree_data *mtd) mti = mti->parent; screen_write_cursormove(&ctx, 0, h, 0); - screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL); + screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, &box_gc, NULL); if (mtd->sort_crit.order_seq != NULL) { xasprintf(&text, " %s (sort: %s%s)%s%s%s", mti->name, @@ -935,21 +997,21 @@ mode_tree_draw(struct mode_tree_data *mtd) xasprintf(&text, " %s", mti->name); if (w - 2 >= strlen(text)) { screen_write_cursormove(&ctx, 1, h, 0); - screen_write_puts(&ctx, &gc0, "%s", text); + screen_write_puts(&ctx, &box_gc, "%s", text); if (mtd->no_matches) n = (sizeof "no matches") - 1; else n = (sizeof "active") - 1; if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) { - screen_write_puts(&ctx, &gc0, " (filter: "); + screen_write_puts(&ctx, &box_gc, " (filter: "); if (mtd->no_matches) - screen_write_puts(&ctx, &gc, "no matches"); + screen_write_puts(&ctx, &box_gc, "no matches"); else - screen_write_puts(&ctx, &gc0, "active"); - screen_write_puts(&ctx, &gc0, ") "); + screen_write_puts(&ctx, &box_gc, "active"); + screen_write_puts(&ctx, &box_gc, ") "); } else - screen_write_puts(&ctx, &gc0, " "); + screen_write_puts(&ctx, &box_gc, " "); } free(text); @@ -1353,12 +1415,14 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x, static void mode_tree_draw_help_line(struct screen_write_ctx *ctx, - const struct grid_cell *gc, const char *line, const char *item, u_int x, - u_int y, u_int w) + const struct grid_cell *gc, struct format_tree *ft, const char *line, + const char *item, u_int x, u_int y, u_int w) { - char *expanded; + char *expanded, *replaced; - expanded = cmd_template_replace(line, item, 1); + replaced = cmd_template_replace(line, item, 1); + expanded = format_expand(ft, replaced); + free(replaced); screen_write_cursormove(ctx, x, y, 0); screen_write_clearcharacter(ctx, w, gc->bg); screen_write_cursormove(ctx, x, y, 0); @@ -1370,7 +1434,9 @@ static void mode_tree_draw_help(struct mode_tree_data *mtd, struct screen_write_ctx *ctx) { struct screen *s = &mtd->screen; - struct grid_cell gc; + struct options *oo = mtd->wp->window->options; + struct grid_cell box_gc, gc; + struct format_tree *ft; const char **line, **lines = NULL, *item = "item"; u_int sx = screen_size_x(s), sy = screen_size_y(s); u_int x, y, w, h = 0, box_w, box_h; @@ -1396,18 +1462,22 @@ mode_tree_draw_help(struct mode_tree_data *mtd, struct screen_write_ctx *ctx) x = (sx - box_w) / 2; y = (sy - box_h) / 2; + memcpy(&box_gc, &grid_default_cell, sizeof box_gc); + style_apply(&box_gc, oo, "tree-mode-border-style", NULL); memcpy(&gc, &grid_default_cell, sizeof gc); + ft = format_create_defaults(NULL, NULL, NULL, NULL, mtd->wp); screen_write_cursormove(ctx, x, y, 0); - screen_write_box(ctx, box_w, box_h, BOX_LINES_DEFAULT, &gc, NULL); + screen_write_box(ctx, box_w, box_h, BOX_LINES_DEFAULT, &box_gc, NULL); y++; x++; for (line = mode_tree_help_start; *line != NULL; line++, y++) - mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w); + mode_tree_draw_help_line(ctx, &gc, ft, *line, item, x, y, w); for (line = lines; line != NULL && *line != NULL; line++, y++) - mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w); + mode_tree_draw_help_line(ctx, &gc, ft, *line, item, x, y, w); for (line = mode_tree_help_end; *line != NULL; line++, y++) - mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w); + mode_tree_draw_help_line(ctx, &gc, ft, *line, item, x, y, w); + format_free(ft); } static void @@ -1488,6 +1558,10 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, *key = KEYC_NONE; return (0); } + if (*key == KEYC_FOCUS_IN || *key == KEYC_FOCUS_OUT) { + *key = KEYC_NONE; + return (0); + } mtd->help = 0; mode_tree_draw(mtd); *key = KEYC_NONE; diff --git a/options-table.c b/options-table.c index 40e3a7162..d47bd03ca 100644 --- a/options-table.c +++ b/options-table.c @@ -594,7 +594,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},darkkhaki,yellow}", + .default_str = "#{?#{e|>=:#{client_colours},256},darkgoldenrod,yellow}", .text = "Dark theme colour for yellow." }, @@ -1504,8 +1504,8 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "fg=#{?pane_marked,thememagenta," - "#{?synchronize-panes,themered," - "#{?pane_in_mode,themeyellow,themegreen}}}", + "#{?synchronize-panes,themered," + "#{?pane_in_mode,themeyellow,themegreen}}}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the active pane border." @@ -1615,7 +1615,7 @@ const struct options_table_entry options_table[] = { { .name = "popup-border-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "fg=themelightgrey", + .default_str = "bg=themedarkgrey,fg=themelightgrey", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Default style of popup borders." @@ -1678,6 +1678,15 @@ const struct options_table_entry options_table[] = { "A value of 0 means no limit." }, + { .name = "tree-mode-border-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "bg=themedarkgrey,fg=themelightgrey", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of borders in tree mode." + }, + { .name = "tree-mode-preview-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, diff --git a/screen-write.c b/screen-write.c index f00619f3f..45d126a6f 100644 --- a/screen-write.c +++ b/screen-write.c @@ -749,7 +749,8 @@ screen_write_hline(struct screen_write_ctx *ctx, u_int nx, int left, int right, /* Draw a vertical line on screen. */ void -screen_write_vline(struct screen_write_ctx *ctx, u_int ny, int top, int bottom) +screen_write_vline(struct screen_write_ctx *ctx, u_int ny, int top, int bottom, + const struct grid_cell *gcp) { struct screen *s = ctx->s; struct grid_cell gc; @@ -758,7 +759,10 @@ screen_write_vline(struct screen_write_ctx *ctx, u_int ny, int top, int bottom) cx = s->cx; cy = s->cy; - memcpy(&gc, &grid_default_cell, sizeof gc); + if (gcp != NULL) + memcpy(&gc, gcp, sizeof gc); + else + memcpy(&gc, &grid_default_cell, sizeof gc); gc.attr |= GRID_ATTR_CHARSET; screen_write_putc(ctx, &gc, top ? 'w' : 'x'); diff --git a/tmux.1 b/tmux.1 index fe5265e50..27c5e6924 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5851,6 +5851,14 @@ A value of 0 (the default) means no limit. When a limit is set, panes are arranged to not exceed this number of columns, with additional panes stacked in extra rows. .Pp +.It Ic tree\-mode\-border\-style Ar style +Set the style of borders in tree mode. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp .It Ic tree\-mode\-preview\-format Ar format Format of the preview indicator in tree mode. .Pp diff --git a/tmux.h b/tmux.h index 3ebf8ce70..c6b4c6469 100644 --- a/tmux.h +++ b/tmux.h @@ -3367,7 +3367,8 @@ void screen_write_fast_copy(struct screen_write_ctx *, struct screen *, u_int, u_int, u_int, u_int); void screen_write_hline(struct screen_write_ctx *, u_int, int, int, enum box_lines, const struct grid_cell *); -void screen_write_vline(struct screen_write_ctx *, u_int, int, int); +void screen_write_vline(struct screen_write_ctx *, u_int, int, int, + const struct grid_cell *); void screen_write_menu(struct screen_write_ctx *, struct menu *, int, enum box_lines, const struct grid_cell *, const struct grid_cell *, const struct grid_cell *); diff --git a/window-buffer.c b/window-buffer.c index 5c0424c2e..6798e2027 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -336,13 +336,20 @@ window_buffer_sort(struct sort_criteria *sort_crit) } static const char* window_buffer_help_lines[] = { - "#[bold] Enter #[default]#[acs]x#[default] Paste selected %1", - "#[bold] p #[default]#[acs]x#[default] Paste selected %1", - "#[bold] P #[default]#[acs]x#[default] Paste tagged %1s", - "#[bold] d #[default]#[acs]x#[default] Delete selected %1", - "#[bold] D #[default]#[acs]x#[default] Delete tagged %1s", - "#[bold] e #[default]#[acs]x#[default] Open %1 in editor", - "#[bold] f #[default]#[acs]x#[default] Enter a filter", + "#[fg=themelightgrey]" + " Enter #[#{E:tree-mode-border-style},acs]x#[default] Paste selected %1", + "#[fg=themelightgrey]" + " p #[#{E:tree-mode-border-style},acs]x#[default] Paste selected %1", + "#[fg=themelightgrey]" + " P #[#{E:tree-mode-border-style},acs]x#[default] Paste tagged %1s", + "#[fg=themelightgrey]" + " d #[#{E:tree-mode-border-style},acs]x#[default] Delete selected %1", + "#[fg=themelightgrey]" + " D #[#{E:tree-mode-border-style},acs]x#[default] Delete tagged %1s", + "#[fg=themelightgrey]" + " e #[#{E:tree-mode-border-style},acs]x#[default] Open %1 in editor", + "#[fg=themelightgrey]" + " f #[#{E:tree-mode-border-style},acs]x#[default] Enter a filter", NULL }; diff --git a/window-client.c b/window-client.c index bf7bbf173..85d12d743 100644 --- a/window-client.c +++ b/window-client.c @@ -49,87 +49,91 @@ static void window_client_key(struct window_mode_entry *, #define WINDOW_CLIENT_FEATURE(f) \ "#{?#{I/f:" #f "}," \ - "#[fg=green],#[dim]}#{p/15:#{l:" #f "}}" \ + "#[fg=themegreen],#[fg=themelightgrey]}#{p/15:#{l:" #f "}}" \ "#[default]" static const char *window_client_info_lines[] = { - "Client Name #[acs]x#[default] " + "Client Name #[#{E:tree-mode-border-style},acs]x#[default] " "#{client_name} " - "#[dim](PID #{client_pid})#[default]", - "Session #[acs]x#[default] " + "#[fg=themelightgrey](PID #{client_pid})#[default]", + "Session #[#{E:tree-mode-border-style},acs]x#[default] " "#{session_name}", - "Attach Time #[acs]x#[default] " + "Attach Time #[#{E:tree-mode-border-style},acs]x#[default] " "#{t:client_created} " - "#[dim](#{t/r:client_created})#[default]", - "Activity Time #[acs]x#[default] " + "#[fg=themelightgrey](#{t/r:client_created})#[default]", + "Activity Time #[#{E:tree-mode-border-style},acs]x#[default] " "#{t:client_activity} " - "#[dim](#{t/r:client_activity})#[default]", - "Terminal Type #[acs]x#[default] " + "#[fg=themelightgrey](#{t/r:client_activity})#[default]", + "Terminal Type #[#{E:tree-mode-border-style},acs]x#[default] " "#{?client_termtype,#{client_termtype},Unknown}", - "TERM #[acs]x#[default] " + "TERM #[#{E:tree-mode-border-style},acs]x#[default] " "#{client_termname}", - "Size #[acs]x#[default] " + "Size #[#{E:tree-mode-border-style},acs]x#[default] " "#{client_width}x#{client_height} " - "#[dim](cell #{client_cell_width}x#{client_cell_height})#[default]", - "Bytes Written #[acs]x#[default] " + "#[fg=themelightgrey](cell #{client_cell_width}x" + "#{client_cell_height})#[default]", + "Bytes Written #[#{E:tree-mode-border-style},acs]x#[default] " "#{client_written} " - "#[dim](#{client_discarded} discarded)#[default]", + "#[fg=themelightgrey](#{client_discarded} discarded)#[default]", - "Features #[acs]x#[default] " + "Features #[#{E:tree-mode-border-style},acs]x#[default] " WINDOW_CLIENT_FEATURE(256) " " WINDOW_CLIENT_FEATURE(RGB) " " WINDOW_CLIENT_FEATURE(bpaste) " " WINDOW_CLIENT_FEATURE(ccolour), - " #[acs]x#[default] " + " #[#{E:tree-mode-border-style},acs]x#[default] " WINDOW_CLIENT_FEATURE(clipboard) " " WINDOW_CLIENT_FEATURE(cstyle) " " WINDOW_CLIENT_FEATURE(extkeys) " " WINDOW_CLIENT_FEATURE(focus), - " #[acs]x#[default] " + " #[#{E:tree-mode-border-style},acs]x#[default] " WINDOW_CLIENT_FEATURE(hyperlinks) " " WINDOW_CLIENT_FEATURE(ignorefkeys) " " WINDOW_CLIENT_FEATURE(margins) " " WINDOW_CLIENT_FEATURE(mouse), - " #[acs]x#[default] " + " #[#{E:tree-mode-border-style},acs]x#[default] " WINDOW_CLIENT_FEATURE(osc7) " " WINDOW_CLIENT_FEATURE(overline) " " WINDOW_CLIENT_FEATURE(progressbar) " " WINDOW_CLIENT_FEATURE(rectfill), - " #[acs]x#[default] " + " #[#{E:tree-mode-border-style},acs]x#[default] " WINDOW_CLIENT_FEATURE(sixel) " " WINDOW_CLIENT_FEATURE(strikethrough) " " WINDOW_CLIENT_FEATURE(sync) " " WINDOW_CLIENT_FEATURE(title), - " #[acs]x#[default] " + " #[#{E:tree-mode-border-style},acs]x#[default] " WINDOW_CLIENT_FEATURE(usstyle), - "#[acs]qqqqqqqqqqqqqqn#{R:q,#{window_width}}#[default]", + "#[#{E:tree-mode-border-style},acs]qqqqqqqqqqqqqqn#{R:q,#{window_width}}#[default]", - "prefix #[acs]x#[default] " + "prefix #[#{E:tree-mode-border-style},acs]x#[default] " "#{prefix}", - "mouse #[acs]x#[default] " - "#{?mouse,#{?#{I/c:kmous},,#[fg=red]}on,#[dim]off} " + "mouse #[#{E:tree-mode-border-style},acs]x#[default] " + "#{?mouse,#{?#{I/c:kmous},,#[fg=themered]}on,#[fg=themelightgrey]off} " "#{?#{I/c:kmous},,#[align=right]unavailable: [kmous] missing}", - "set-clipboard #[acs]x#[default] " - "#{?#{!=:#{set-clipboard},off},#{?#{I/f:clipboard},,#[fg=red]}#{set-clipboard},#[dim]off} " + "set-clipboard #[#{E:tree-mode-border-style},acs]x#[default] " + "#{?#{!=:#{set-clipboard},off},#{?#{I/f:clipboard},," + "#[fg=themered]}#{set-clipboard},#[fg=themelightgrey]off} " "#{?#{I/f:clipboard},,#[align=right]unavailable: [Ms] missing}", - "get-clipboard #[acs]x#[default] " - "#{?#{!=:#{get-clipboard},off},#{?#{I/f:clipboard},,#[fg=red]}#{get-clipboard},#[dim]off} " + "get-clipboard #[#{E:tree-mode-border-style},acs]x#[default] " + "#{?#{!=:#{get-clipboard},off},#{?#{I/f:clipboard},," + "#[fg=themered]}#{get-clipboard},#[fg=themelightgrey]off} " "#{?#{I/f:clipboard},,#[align=right]unavailable: [Ms] missing}", - "focus-events #[acs]x#[default] " - "#{?focus-events,#{?#{I/f:focus},,#[fg=red]}on,#[dim]off} " + "focus-events #[#{E:tree-mode-border-style},acs]x#[default] " + "#{?focus-events,#{?#{I/f:focus},,#[fg=themered]}on,#[fg=themelightgrey]off} " "#{?#{I/f:focus},,#[align=right]unavailable: [Enfcs] or [Dcfcs] missing}", - "extended-keys #[acs]x#[default] " - "#{?#{!=:#{extended-keys},off},#{?#{I/f:extkeys},,#[fg=red]}#{extended-keys},#[dim]off} " + "extended-keys #[#{E:tree-mode-border-style},acs]x#[default] " + "#{?#{!=:#{extended-keys},off},#{?#{I/f:extkeys},," + "#[fg=themered]}#{extended-keys},#[fg=themelightgrey]off} " "#{?#{I/f:extkeys},,#[align=right]unavailable: [Eneks] or [Dseks] missing}", - "set-titles #[acs]x#[default] " - "#{?set-titles,on,#[dim]off}", + "set-titles #[#{E:tree-mode-border-style},acs]x#[default] " + "#{?set-titles,on,#[fg=themelightgrey]off}", - "escape-time #[acs]x#[default] " + "escape-time #[#{E:tree-mode-border-style},acs]x#[default] " "#{escape-time} ms", }; @@ -258,6 +262,8 @@ window_client_draw_info(__unused void *modedata, void *itemdata, struct window_client_itemdata *item = itemdata; struct client *c = item->c; struct screen *s = ctx->s; + struct window *w = c->session->curw->window; + struct grid_cell gc; u_int cx = s->cx, cy = s->cy, i; struct format_tree *ft; char *expanded; @@ -273,6 +279,12 @@ window_client_draw_info(__unused void *modedata, void *itemdata, format_draw(ctx, &grid_default_cell, sx, expanded, NULL, 0); free(expanded); } + if (sx > 14 && i < sy) { + memcpy(&gc, &grid_default_cell, sizeof gc); + style_apply(&gc, w->options, "tree-mode-border-style", NULL); + screen_write_cursormove(ctx, cx + 14, cy + i, 0); + screen_write_vline(ctx, sy - i, 0, 0, &gc); + } format_free(ft); } @@ -284,20 +296,24 @@ window_client_draw(void *modedata, void *itemdata, struct window_client_modedata *data = modedata; struct window_client_itemdata *item = itemdata; struct client *c = item->c; + struct session *session = c->session; struct screen *s = ctx->s; + struct window *w; struct window_pane *wp; + struct grid_cell gc; u_int cx = s->cx, cy = s->cy, lines, at; - if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) + if (session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return; if (data->preview_is_info) { window_client_draw_info(modedata, itemdata, ctx, sx, sy); return; } - wp = c->session->curw->window->active; + w = session->curw->window; + wp = w->active; if (data->hide_preview_this_pane && wp == data->wp) { - if (!TAILQ_EMPTY(&c->session->curw->window->last_panes)) - wp = TAILQ_FIRST(&c->session->curw->window->last_panes); + if (!TAILQ_EMPTY(&w->last_panes)) + wp = TAILQ_FIRST(&w->last_panes); else wp = NULL; } @@ -318,7 +334,9 @@ window_client_draw(void *modedata, void *itemdata, screen_write_cursormove(ctx, cx, cy + 2, 0); else screen_write_cursormove(ctx, cx, cy + sy - 1 - lines, 0); - screen_write_hline(ctx, sx, 0, 0, BOX_LINES_DEFAULT, NULL); + memcpy(&gc, &grid_default_cell, sizeof gc); + style_apply(&gc, w->options, "tree-mode-border-style", NULL); + screen_write_hline(ctx, sx, 0, 0, BOX_LINES_DEFAULT, &gc); if (at != 0) screen_write_cursormove(ctx, cx, cy, 0); @@ -369,15 +387,24 @@ window_client_sort(struct sort_criteria *sort_crit) } static const char* window_client_help_lines[] = { - "#[bold] i #[default]#[acs]x#[default] Toggle info view", - "#[bold] Enter #[default]#[acs]x#[default] Choose selected %1", - "#[bold] d #[default]#[acs]x#[default] Detach selected %1", - "#[bold] D #[default]#[acs]x#[default] Detach tagged %1s", - "#[bold] x #[default]#[acs]x#[default] Detach selected %1", - "#[bold] X #[default]#[acs]x#[default] Detach tagged %1s", - "#[bold] z #[default]#[acs]x#[default] Suspend selected %1", - "#[bold] Z #[default]#[acs]x#[default] Suspend tagged %1s", - "#[bold] f #[default]#[acs]x#[default] Enter a filter", + "#[fg=themelightgrey]" + " i #[#{E:tree-mode-border-style},acs]x#[default] Toggle info view", + "#[fg=themelightgrey]" + " Enter #[#{E:tree-mode-border-style},acs]x#[default] Choose selected %1", + "#[fg=themelightgrey]" + " d #[#{E:tree-mode-border-style},acs]x#[default] Detach selected %1", + "#[fg=themelightgrey]" + " D #[#{E:tree-mode-border-style},acs]x#[default] Detach tagged %1s", + "#[fg=themelightgrey]" + " x #[#{E:tree-mode-border-style},acs]x#[default] Detach selected %1", + "#[fg=themelightgrey]" + " X #[#{E:tree-mode-border-style},acs]x#[default] Detach tagged %1s", + "#[fg=themelightgrey]" + " z #[#{E:tree-mode-border-style},acs]x#[default] Suspend selected %1", + "#[fg=themelightgrey]" + " Z #[#{E:tree-mode-border-style},acs]x#[default] Suspend tagged %1s", + "#[fg=themelightgrey]" + " f #[#{E:tree-mode-border-style},acs]x#[default] Enter a filter", NULL }; diff --git a/window-customize.c b/window-customize.c index 9796cbf0b..32caae9a3 100644 --- a/window-customize.c +++ b/window-customize.c @@ -36,8 +36,8 @@ static void window_customize_key(struct window_mode_entry *, #define WINDOW_CUSTOMIZE_DEFAULT_FORMAT \ "#{?is_option," \ "#{?option_is_global,,#[reverse](#{option_scope})#[default] }" \ - "#[ignore]" \ - "#{option_value}#{?option_unit, #{option_unit},}" \ + "#[fg=themelightgrey]#[ignore]#{option_value}" \ + "#{?option_unit, #{option_unit},}" \ "," \ "#{key}" \ "}" @@ -881,15 +881,24 @@ window_customize_height(__unused void *modedata, __unused u_int height) } static const char* window_customize_help_lines[] = { - "#[bold] Enter, s #[default]#[acs]x#[default] Set %1 value", - "#[bold] S #[default]#[acs]x#[default] Set global %1 value", - "#[bold] w #[default]#[acs]x#[default] Set window %1 value", - "#[bold] d #[default]#[acs]x#[default] Set to default value", - "#[bold] D #[default]#[acs]x#[default] Set tagged %1s to default value", - "#[bold] u #[default]#[acs]x#[default] Unset an %1", - "#[bold] U #[default]#[acs]x#[default] Unset tagged %1s", - "#[bold] f #[default]#[acs]x#[default] Enter a filter", - "#[bold] v #[default]#[acs]x#[default] Toggle information", + "#[fg=themelightgrey]" + " Enter, s #[#{E:tree-mode-border-style},acs]x#[default] Set %1 value", + "#[fg=themelightgrey]" + " S #[#{E:tree-mode-border-style},acs]x#[default] Set global %1 value", + "#[fg=themelightgrey]" + " w #[#{E:tree-mode-border-style},acs]x#[default] Set window %1 value", + "#[fg=themelightgrey]" + " d #[#{E:tree-mode-border-style},acs]x#[default] Set to default value", + "#[fg=themelightgrey]" + " D #[#{E:tree-mode-border-style},acs]x#[default] Set tagged %1s to default value", + "#[fg=themelightgrey]" + " u #[#{E:tree-mode-border-style},acs]x#[default] Unset an %1", + "#[fg=themelightgrey]" + " U #[#{E:tree-mode-border-style},acs]x#[default] Unset tagged %1s", + "#[fg=themelightgrey]" + " f #[#{E:tree-mode-border-style},acs]x#[default] Enter a filter", + "#[fg=themelightgrey]" + " v #[#{E:tree-mode-border-style},acs]x#[default] Toggle information", NULL }; diff --git a/window-tree.c b/window-tree.c index 58a8bbb9b..c808af99d 100644 --- a/window-tree.c +++ b/window-tree.c @@ -401,7 +401,8 @@ window_tree_build(void *modedata, struct sort_criteria *sort_crit, static void window_tree_draw_label(struct screen_write_ctx *ctx, u_int px, u_int py, - u_int sx, u_int sy, const struct grid_cell *gc, const char *label) + u_int sx, u_int sy, const struct grid_cell *border_gc, + const struct grid_cell *label_gc, const char *label) { u_int width, ox, oy; char *new_label = NULL; @@ -420,14 +421,22 @@ window_tree_draw_label(struct screen_write_ctx *ctx, u_int px, u_int py, screen_write_cursormove(ctx, px + ox - 2, py + oy - 1, 0); screen_write_box(ctx, width + 4, 3, BOX_LINES_DEFAULT, - NULL, NULL); + border_gc, NULL); screen_write_cursormove(ctx, px + ox - 1, py + oy, 0); - screen_write_clearcharacter(ctx, width + 2, 8); + screen_write_clearcharacter(ctx, width + 2, border_gc->bg); screen_write_cursormove(ctx, px + ox, py + oy, 0); - format_draw(ctx, gc, width, label, NULL, 0); + format_draw(ctx, label_gc, width, label, NULL, 0); free(new_label); } +static void +window_tree_border_cell(struct grid_cell *gc, struct options *oo, + struct format_tree *ft) +{ + memcpy(gc, &grid_default_cell, sizeof *gc); + style_apply(gc, oo, "tree-mode-border-style", ft); +} + static void window_tree_draw_session(struct window_tree_modedata *data, struct session *s, struct screen_write_ctx *ctx, u_int sx, u_int sy) @@ -437,7 +446,7 @@ window_tree_draw_session(struct window_tree_modedata *data, struct session *s, u_int cx = ctx->s->cx, cy = ctx->s->cy; u_int loop, total, visible, each, width, offset; u_int current, start, end, remaining, i; - struct grid_cell gc; + struct grid_cell gc, label_gc; int left, right; char *label; const char *format; @@ -495,20 +504,21 @@ window_tree_draw_session(struct window_tree_modedata *data, struct session *s, if (each == 0) return; + window_tree_border_cell(&gc, data->wp->window->options, NULL); if (left) { data->left = cx + 2; screen_write_cursormove(ctx, cx + 2, cy, 0); - screen_write_vline(ctx, sy, 0, 0); + screen_write_vline(ctx, sy, 0, 0, &gc); screen_write_cursormove(ctx, cx, cy + sy / 2, 0); - screen_write_puts(ctx, &grid_default_cell, "<"); + screen_write_puts(ctx, &gc, "<"); } else data->left = -1; if (right) { data->right = cx + sx - 3; screen_write_cursormove(ctx, cx + sx - 3, cy, 0); - screen_write_vline(ctx, sy, 0, 0); + screen_write_vline(ctx, sy, 0, 0, &gc); screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0); - screen_write_puts(ctx, &grid_default_cell, ">"); + screen_write_puts(ctx, &gc, ">"); } else data->right = -1; @@ -530,8 +540,10 @@ window_tree_draw_session(struct window_tree_modedata *data, struct session *s, ft = format_create(NULL, NULL, FORMAT_WINDOW|w->id, 0); format_defaults(ft, NULL, s, wl, NULL); - memcpy(&gc, &grid_default_cell, sizeof gc); - style_apply(&gc, oo, "tree-mode-preview-style", ft); + window_tree_border_cell(&gc, oo, ft); + memcpy(&label_gc, &grid_default_cell, sizeof label_gc); + style_apply(&label_gc, oo, "tree-mode-preview-style", ft); + label_gc.bg = gc.bg; if (left) offset = 3 + (i * each); @@ -550,7 +562,7 @@ window_tree_draw_session(struct window_tree_modedata *data, struct session *s, label = format_expand(ft, format); if (*label != '\0') { window_tree_draw_label(ctx, cx + offset, cy, - width, sy, &gc, label); + width, sy, &gc, &label_gc, label); } free(label); } @@ -558,7 +570,7 @@ window_tree_draw_session(struct window_tree_modedata *data, struct session *s, if (loop != end - 1) { screen_write_cursormove(ctx, cx + offset + width, cy, 0); - screen_write_vline(ctx, sy, 0, 0); + screen_write_vline(ctx, sy, 0, 0, &gc); } loop++; @@ -575,7 +587,7 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, u_int cx = ctx->s->cx, cy = ctx->s->cy; u_int loop, total, visible, each, width, offset; u_int current, start, end, remaining, i; - struct grid_cell gc; + struct grid_cell gc, label_gc; int left, right; char *label; const char *format; @@ -639,20 +651,21 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, if (each == 0) return; + window_tree_border_cell(&gc, data->wp->window->options, NULL); if (left) { data->left = cx + 2; screen_write_cursormove(ctx, cx + 2, cy, 0); - screen_write_vline(ctx, sy, 0, 0); + screen_write_vline(ctx, sy, 0, 0, &gc); screen_write_cursormove(ctx, cx, cy + sy / 2, 0); - screen_write_puts(ctx, &grid_default_cell, "<"); + screen_write_puts(ctx, &gc, "<"); } else data->left = -1; if (right) { data->right = cx + sx - 3; screen_write_cursormove(ctx, cx + sx - 3, cy, 0); - screen_write_vline(ctx, sy, 0, 0); + screen_write_vline(ctx, sy, 0, 0, &gc); screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0); - screen_write_puts(ctx, &grid_default_cell, ">"); + screen_write_puts(ctx, &gc, ">"); } else data->right = -1; @@ -675,8 +688,10 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, ft = format_create(NULL, NULL, FORMAT_PANE|wp->id, 0); format_defaults(ft, NULL, s, wl, wp); - memcpy(&gc, &grid_default_cell, sizeof gc); - style_apply(&gc, oo, "tree-mode-preview-style", ft); + window_tree_border_cell(&gc, oo, ft); + memcpy(&label_gc, &grid_default_cell, sizeof label_gc); + style_apply(&label_gc, oo, "tree-mode-preview-style", ft); + label_gc.bg = gc.bg; if (left) offset = 3 + (i * each); @@ -695,7 +710,7 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, label = format_expand(ft, format); if (*label != '\0') { window_tree_draw_label(ctx, cx + offset, cy, - width, sy, &gc, label); + width, sy, &gc, &label_gc, label); } free(label); } @@ -703,7 +718,7 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, if (loop != end - 1) { screen_write_cursormove(ctx, cx + offset + width, cy, 0); - screen_write_vline(ctx, sy, 0, 0); + screen_write_vline(ctx, sy, 0, 0, &gc); } loop++; @@ -889,18 +904,30 @@ window_tree_sort(struct sort_criteria *sort_crit) } static const char* window_tree_help_lines[] = { - "#[bold] Enter #[default]#[acs]x#[default] Choose selected item", - "#[bold] S-Up #[default]#[acs]x#[default] Swap current and previous window", - "#[bold] S-Down #[default]#[acs]x#[default] Swap current and next window", - "#[bold] x #[default]#[acs]x#[default] Kill selected item", - "#[bold] X #[default]#[acs]x#[default] Kill tagged items", - "#[bold] < #[default]#[acs]x#[default] Scroll previews left", - "#[bold] > #[default]#[acs]x#[default] Scroll previews right", - "#[bold] m #[default]#[acs]x#[default] Set the marked pane", - "#[bold] M #[default]#[acs]x#[default] Clear the marked pane", - "#[bold] : #[default]#[acs]x#[default] Run a command for each tagged item", - "#[bold] f #[default]#[acs]x#[default] Enter a format", - "#[bold] H #[default]#[acs]x#[default] Jump to the starting pane", + "#[fg=themelightgrey]" + " Enter #[#{E:tree-mode-border-style},acs]x#[default] Choose selected item", + "#[fg=themelightgrey]" + " S-Up #[#{E:tree-mode-border-style},acs]x#[default] Swap current and previous window", + "#[fg=themelightgrey]" + " S-Down #[#{E:tree-mode-border-style},acs]x#[default] Swap current and next window", + "#[fg=themelightgrey]" + " x #[#{E:tree-mode-border-style},acs]x#[default] Kill selected item", + "#[fg=themelightgrey]" + " X #[#{E:tree-mode-border-style},acs]x#[default] Kill tagged items", + "#[fg=themelightgrey]" + " < #[#{E:tree-mode-border-style},acs]x#[default] Scroll previews left", + "#[fg=themelightgrey]" + " > #[#{E:tree-mode-border-style},acs]x#[default] Scroll previews right", + "#[fg=themelightgrey]" + " m #[#{E:tree-mode-border-style},acs]x#[default] Set the marked pane", + "#[fg=themelightgrey]" + " M #[#{E:tree-mode-border-style},acs]x#[default] Clear the marked pane", + "#[fg=themelightgrey]" + " : #[#{E:tree-mode-border-style},acs]x#[default] Run a command for each tagged item", + "#[fg=themelightgrey]" + " f #[#{E:tree-mode-border-style},acs]x#[default] Enter a format", + "#[fg=themelightgrey]" + " H #[#{E:tree-mode-border-style},acs]x#[default] Jump to the starting pane", NULL }; From f5e9bcc1fc59f06014f911bab66fa0b0ebf99362 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 08:19:44 +0000 Subject: [PATCH 39/71] Another couple of bits look nicer in grey. --- window-client.c | 38 +++++++++++++++++++------------------- window-tree.c | 6 +++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/window-client.c b/window-client.c index 85d12d743..a780222e3 100644 --- a/window-client.c +++ b/window-client.c @@ -38,7 +38,7 @@ static void window_client_key(struct window_mode_entry *, #define WINDOW_CLIENT_DEFAULT_COMMAND "detach-client -t '%%'" #define WINDOW_CLIENT_DEFAULT_FORMAT \ - "#{t/p:client_activity}: session #{session_name}" + "#[fg=themelightgrey]#{t/p:client_activity}: session #[default]#{session_name}" #define WINDOW_CLIENT_DEFAULT_KEY_FORMAT \ "#{?#{e|<:#{line},10}," \ @@ -52,30 +52,30 @@ static void window_client_key(struct window_mode_entry *, "#[fg=themegreen],#[fg=themelightgrey]}#{p/15:#{l:" #f "}}" \ "#[default]" static const char *window_client_info_lines[] = { - "Client Name #[#{E:tree-mode-border-style},acs]x#[default] " - "#{client_name} " + "#[fg=themelightgrey]Client Name #[#{E:tree-mode-border-style},acs]x#[default] " + "#{client_name} #[fg=themelightgrey]" "#[fg=themelightgrey](PID #{client_pid})#[default]", - "Session #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]Session #[#{E:tree-mode-border-style},acs]x#[default] " "#{session_name}", - "Attach Time #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]Attach Time #[#{E:tree-mode-border-style},acs]x#[default] " "#{t:client_created} " "#[fg=themelightgrey](#{t/r:client_created})#[default]", - "Activity Time #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]Activity Time #[#{E:tree-mode-border-style},acs]x#[default] " "#{t:client_activity} " "#[fg=themelightgrey](#{t/r:client_activity})#[default]", - "Terminal Type #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]Terminal Type #[#{E:tree-mode-border-style},acs]x#[default] " "#{?client_termtype,#{client_termtype},Unknown}", - "TERM #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]TERM #[#{E:tree-mode-border-style},acs]x#[default] " "#{client_termname}", - "Size #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]Size #[#{E:tree-mode-border-style},acs]x#[default] " "#{client_width}x#{client_height} " "#[fg=themelightgrey](cell #{client_cell_width}x" "#{client_cell_height})#[default]", - "Bytes Written #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]Bytes Written #[#{E:tree-mode-border-style},acs]x#[default] " "#{client_written} " "#[fg=themelightgrey](#{client_discarded} discarded)#[default]", - "Features #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]Features #[#{E:tree-mode-border-style},acs]x#[default] " WINDOW_CLIENT_FEATURE(256) " " WINDOW_CLIENT_FEATURE(RGB) " " WINDOW_CLIENT_FEATURE(bpaste) " " @@ -104,36 +104,36 @@ static const char *window_client_info_lines[] = { WINDOW_CLIENT_FEATURE(usstyle), "#[#{E:tree-mode-border-style},acs]qqqqqqqqqqqqqqn#{R:q,#{window_width}}#[default]", - "prefix #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]prefix #[#{E:tree-mode-border-style},acs]x#[default] " "#{prefix}", - "mouse #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]mouse #[#{E:tree-mode-border-style},acs]x#[default] " "#{?mouse,#{?#{I/c:kmous},,#[fg=themered]}on,#[fg=themelightgrey]off} " "#{?#{I/c:kmous},,#[align=right]unavailable: [kmous] missing}", - "set-clipboard #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]set-clipboard #[#{E:tree-mode-border-style},acs]x#[default] " "#{?#{!=:#{set-clipboard},off},#{?#{I/f:clipboard},," "#[fg=themered]}#{set-clipboard},#[fg=themelightgrey]off} " "#{?#{I/f:clipboard},,#[align=right]unavailable: [Ms] missing}", - "get-clipboard #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]get-clipboard #[#{E:tree-mode-border-style},acs]x#[default] " "#{?#{!=:#{get-clipboard},off},#{?#{I/f:clipboard},," "#[fg=themered]}#{get-clipboard},#[fg=themelightgrey]off} " "#{?#{I/f:clipboard},,#[align=right]unavailable: [Ms] missing}", - "focus-events #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]focus-events #[#{E:tree-mode-border-style},acs]x#[default] " "#{?focus-events,#{?#{I/f:focus},,#[fg=themered]}on,#[fg=themelightgrey]off} " "#{?#{I/f:focus},,#[align=right]unavailable: [Enfcs] or [Dcfcs] missing}", - "extended-keys #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]extended-keys #[#{E:tree-mode-border-style},acs]x#[default] " "#{?#{!=:#{extended-keys},off},#{?#{I/f:extkeys},," "#[fg=themered]}#{extended-keys},#[fg=themelightgrey]off} " "#{?#{I/f:extkeys},,#[align=right]unavailable: [Eneks] or [Dseks] missing}", - "set-titles #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]set-titles #[#{E:tree-mode-border-style},acs]x#[default] " "#{?set-titles,on,#[fg=themelightgrey]off}", - "escape-time #[#{E:tree-mode-border-style},acs]x#[default] " + "#[fg=themelightgrey]escape-time #[#{E:tree-mode-border-style},acs]x#[default] " "#{escape-time} ms", }; diff --git a/window-tree.c b/window-tree.c index c808af99d..31cef5dca 100644 --- a/window-tree.c +++ b/window-tree.c @@ -39,14 +39,14 @@ static void window_tree_key(struct window_mode_entry *, #define WINDOW_TREE_DEFAULT_FORMAT \ "#{?pane_format," \ "#{?pane_marked,#[reverse],}#{?pane_floating_flag,#[italics],}" \ - "#{pane_current_command}#{pane_flags}" \ + "#{pane_current_command}#[fg=themelightgrey]#{pane_flags}" \ "#{?#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}},: \"#{pane_title}\",}" \ ",window_format," \ "#{?window_marked_flag,#[reverse],}" \ - "#{window_name}#{window_flags}" \ + "#{window_name}#[fg=themelightgrey]#{window_flags}" \ "#{?#{&&:#{==:#{window_panes},1},#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}}},: \"#{pane_title}\",}" \ "," \ - "#{session_windows} windows" \ + "#[fg=themelightgrey]#{session_windows} windows" \ "#{?session_grouped, " \ "(group #{session_group}: " \ "#{session_group_list})," \ From 8e79c4a03d9d83b38abdddbef33e6ec6b4a526bf Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 09:38:44 +0000 Subject: [PATCH 40/71] Bump style buffer to 1024, GitHub issue 5279 from Moritz Angermann. --- style.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style.c b/style.c index f386b83eb..25b8b0196 100644 --- a/style.c +++ b/style.c @@ -285,7 +285,7 @@ style_tostring(struct style *sy) struct grid_cell *gc = &sy->gc; int off = 0; const char *comma = "", *tmp = ""; - static char s[256]; + static char s[1024]; char b[21]; *s = '\0'; From dd2fef667325ad64f80e63ffd2dee6ee168066d0 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 09:54:56 +0000 Subject: [PATCH 41/71] Do not count arguments starting - as optional arguments, makes old resize-pane syntax work. GitHub issue 5275, based on a changed from Dane Jensen. --- arguments.c | 14 +++++++++++++- cmd-resize-pane.c | 12 ++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/arguments.c b/arguments.c index fdf43752f..cab40aa6e 100644 --- a/arguments.c +++ b/arguments.c @@ -149,7 +149,7 @@ args_parse_flag_argument(struct args_value *values, u_int count, char **cause, int optional_argument) { struct args_value *argument, *new; - const char *s; + const char *s, *as; new = xcalloc(1, sizeof *new); if (*string != '\0') { @@ -180,12 +180,24 @@ args_parse_flag_argument(struct args_value *values, u_int count, char **cause, xasprintf(cause, "-%c expects an argument", flag); return (-1); } + + if (optional_argument && argument->type == ARGS_STRING) { + as = argument->string; + if (as[0] == '-' && (as[1] == '-' || isalpha((u_char)as[1]))) { + args_free_value(new); + free(new); + log_debug("%s: -%c (optional)", __func__, flag); + args_set(args, flag, NULL, ARGS_ENTRY_OPTIONAL_VALUE); + return (0); + } + } args_copy_value(new, argument); (*i)++; out: s = args_value_as_string(new); log_debug("%s: -%c = %s", __func__, flag, s); + args_set(args, flag, new, 0); return (0); } diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 8e548dc0e..25dd5a217 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -18,6 +18,7 @@ #include +#include #include #include @@ -63,8 +64,7 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) const char *errstr, *argval; const char flags[4] = { 'U', 'D', 'L', 'R' }; char *cause = NULL, flag; - u_int opposite = 0; - int adjust, x, y, status; + int adjust, x, y, status, opposite = 0; long unsigned i; struct grid *gd = wp->base.grid; @@ -147,8 +147,12 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) continue; argval = args_get(args, flag); - if (argval == NULL) - argval = "1"; + if (argval == NULL) { + if (args_count(args) == 0) + argval = "1"; + else + argval = args_string(args, 0); + } adjust = strtonum(argval, INT_MIN, INT_MAX, &errstr); if (errstr != NULL) { From d0bf3bf06d4d007f62348671e6aab00bb15cfb7e Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 10:02:01 +0000 Subject: [PATCH 42/71] Add ability to float a tiled pane to break-pane, from Dane Jensen. --- cmd-break-pane.c | 52 ++++++++++++++++++++++++++++++-- cmd-resize-pane.c | 15 ++++------ layout.c | 76 +++++++++++++++++++++++++++++++++-------------- tmux.1 | 40 ++++++++++++++++++++++++- tmux.h | 15 ++++++++-- 5 files changed, 159 insertions(+), 39 deletions(-) diff --git a/cmd-break-pane.c b/cmd-break-pane.c index da15e8b5f..8453c7e86 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -34,9 +34,10 @@ const struct cmd_entry cmd_break_pane_entry = { .name = "break-pane", .alias = "breakp", - .args = { "abdPF:n:s:t:", 0, 0, NULL }, - .usage = "[-abdP] [-F format] [-n window-name] [-s src-pane] " - "[-t dst-window]", + .args = { "abdPF:n:s:t:Wx:X:y:Y:", 0, 0, NULL }, + .usage = "[-abdPW] [-F format] [-n window-name] [-s src-pane] " + "[-t dst-window] [-x width] [-y height] [-X x-position] " + "[-Y y-position]", .source = { 's', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX }, @@ -45,6 +46,48 @@ const struct cmd_entry cmd_break_pane_entry = { .exec = cmd_break_pane_exec }; +static enum cmd_retval +cmd_break_pane_float(struct cmdq_item *item, struct args *args, + struct window *w, struct window_pane *wp) +{ + struct layout_cell *lc = wp->layout_cell; + u_int sx = lc->saved_sx, sy = lc->saved_sy; + int ox = lc->saved_xoff, oy = lc->saved_yoff; + char *cause = NULL; + enum pane_lines lines = window_get_pane_lines(w); + + if (window_pane_is_floating(wp)) { + cmdq_error(item, "pane is already floating"); + 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 (layout_floating_args_parse(item, args, lines, w, &sx, &sy, &ox, &oy, + &cause) != 0) { + cmdq_error(item, "failed to float pane: %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + layout_remove_tile(w, lc); + layout_set_size(lc, sx, sy, ox, oy); + + lc->flags |= LAYOUT_CELL_FLOATING; + TAILQ_REMOVE(&w->z_index, wp, zentry); + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); + + if (!args_has(args, 'd')) + window_set_active_pane(w, wp, 1); + 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 cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) { @@ -62,6 +105,9 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) int idx = target->idx, before; const char *template, *name = args_get(args, 'n'); + if (args_has(args, 'W')) + return (cmd_break_pane_float(item, args, w, wp)); + if (name != NULL && !check_name(name, WINDOW_NAME_FORBID)) { cmdq_error(item, "invalid window name: %s", name); return (CMD_RETURN_ERROR); diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 25dd5a217..734280af1 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -101,9 +101,8 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } if (window_pane_is_floating(wp)) { - layout_resize_floating_pane_to(wp, LAYOUT_LEFTRIGHT, x, - &cause); - if (cause != NULL) { + if (layout_resize_floating_pane_to(wp, LAYOUT_LEFTRIGHT, + x, &cause) != 0) { cmdq_error(item, "size %s", cause); free(cause); return (CMD_RETURN_ERROR); @@ -130,9 +129,8 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) break; } if (window_pane_is_floating(wp)) { - layout_resize_floating_pane_to(wp, LAYOUT_TOPBOTTOM, y, - &cause); - if (cause != NULL) { + if (layout_resize_floating_pane_to(wp, LAYOUT_TOPBOTTOM, + y, &cause) != 0) { cmdq_error(item, "size %s", cause); free(cause); return (CMD_RETURN_ERROR); @@ -168,9 +166,8 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) if (flag == 'L' || flag == 'U') opposite = 1; - layout_resize_floating_pane(wp, type, adjust, opposite, - &cause); - if (cause != NULL) { + if (layout_resize_floating_pane(wp, type, adjust, + opposite, &cause) != 0) { cmdq_error(item, "adjustment %s", cause); free(cause); return (CMD_RETURN_ERROR); diff --git a/layout.c b/layout.c index de6f10cde..569616f1f 100644 --- a/layout.c +++ b/layout.c @@ -74,6 +74,12 @@ layout_create_cell(struct layout_cell *lcparent) lc->xoff = INT_MAX; lc->yoff = INT_MAX; + lc->saved_sx = UINT_MAX; + lc->saved_sy = UINT_MAX; + + lc->saved_xoff = INT_MAX; + lc->saved_yoff = INT_MAX; + lc->wp = NULL; return (lc); @@ -814,7 +820,7 @@ layout_resize_pane_to(struct window_pane *wp, enum layout_type type, } /* Resize a floating pane to an absolute size. */ -void +int layout_resize_floating_pane_to(struct window_pane *wp, enum layout_type type, u_int size, char **cause) { @@ -822,7 +828,7 @@ layout_resize_floating_pane_to(struct window_pane *wp, enum layout_type type, if (~lc->flags & LAYOUT_CELL_FLOATING) { *cause = xstrdup("pane is not floating"); - return; + return (-1); } if (window_pane_get_pane_lines(wp) != PANE_LINES_NONE && @@ -830,23 +836,24 @@ layout_resize_floating_pane_to(struct window_pane *wp, enum layout_type type, size -= 2; if (size < PANE_MINIMUM || size > PANE_MAXIMUM) { *cause = xstrdup("size is too big or too small"); - return; + return (-1); } if (type == LAYOUT_TOPBOTTOM) { if (lc->sy == size) - return; + return (0); lc->sy = size; } else { if (lc->sx == size) - return; + return (0); lc->sx = size; } redraw_invalidate_scene(wp->window); + return (0); } /* Resize a floating pane relative to its current size. */ -void +int layout_resize_floating_pane(struct window_pane *wp, enum layout_type type, int change, int opposite, char **cause) { @@ -855,16 +862,16 @@ layout_resize_floating_pane(struct window_pane *wp, enum layout_type type, if (~lc->flags & LAYOUT_CELL_FLOATING) { *cause = xstrdup("pane is not floating"); - return; + return (-1); } if (change == 0) - return; + return (0); if (type == LAYOUT_TOPBOTTOM) { size = lc->sy + change; if (size < PANE_MINIMUM || size > PANE_MAXIMUM) { *cause = xstrdup("change is too big or too small"); - return; + return (-1); } lc->sy = size; if (opposite) @@ -873,13 +880,14 @@ layout_resize_floating_pane(struct window_pane *wp, enum layout_type type, size = lc->sx + change; if (size < PANE_MINIMUM || size > PANE_MAXIMUM) { *cause = xstrdup("change is too big or too small"); - return; + return (-1); } lc->sx = size; if (opposite) lc->xoff -= change; } redraw_invalidate_scene(wp->window); + return (0); } /* Resize a layout cell. */ @@ -1561,16 +1569,35 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, return (lc); } -/* Get a new floating cell. */ struct layout_cell * layout_get_floating_cell(struct cmdq_item *item, struct args *args, enum pane_lines lines, 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 = UINT_MAX, sy = UINT_MAX; int ox = INT_MAX, oy = INT_MAX; - char *error; + + if (layout_floating_args_parse(item, args, lines, w, &sx, &sy, &ox, &oy, + cause) != 0) + return (NULL); + + lcnew = layout_floating_pane(w, wp, sx, sy, ox, oy); + return (lcnew); +} + +int +layout_floating_args_parse(struct cmdq_item *item, struct args *args, + enum pane_lines lines, struct window *w, u_int *sxp, u_int *syp, int *oxp, + int *oyp, char **cause) +{ + int sx, sy, ox, oy; + char *error = NULL; + + sx = *sxp == UINT_MAX ? w->sx / 2 : *sxp; + sy = *syp == UINT_MAX ? w->sy / 4 : *syp; + ox = *oxp == INT_MAX ? INT_MAX : *oxp; + oy = *oyp == INT_MAX ? INT_MAX : *oyp; if (args_has(args, 'x')) { sx = args_percentage_and_expand(args, 'x', 0, PANE_MAXIMUM, @@ -1578,7 +1605,7 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, if (error != NULL) { xasprintf(cause, "position %s", error); free(error); - return (NULL); + return (-1); } if (lines != PANE_LINES_NONE) sx -= 2; @@ -1589,7 +1616,7 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, if (error != NULL) { xasprintf(cause, "position %s", error); free(error); - return (NULL); + return (-1); } if (lines != PANE_LINES_NONE) sy -= 2; @@ -1598,18 +1625,18 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, ox = args_percentage_and_expand(args, 'X', -sx, w->sx, w->sx, item, &error); if (error != NULL) { - xasprintf(cause, "size %s", error); + xasprintf(cause, "position %s", error); free(error); - return (NULL); + return (-1); } } if (args_has(args, 'Y')) { oy = args_percentage_and_expand(args, 'Y', -sy, w->sy, w->sy, item, &error); if (error != NULL) { - xasprintf(cause, "size %s", error); + xasprintf(cause, "position %s", error); free(error); - return (NULL); + return (-1); } } @@ -1640,15 +1667,18 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, if (sx < PANE_MINIMUM || sx > PANE_MAXIMUM) { *cause = xstrdup("invalid width"); - return (NULL); + return (-1); } if (sy < PANE_MINIMUM || sy > PANE_MAXIMUM) { *cause = xstrdup("invalid height"); - return (NULL); + return (-1); } - lcnew = layout_floating_pane(w, wp, sx, sy, ox, oy); - return (lcnew); + *sxp = sx; + *syp = sy; + *oxp = ox; + *oyp = oy; + return (0); } /* diff --git a/tmux.1 b/tmux.1 index 27c5e6924..4cc1ca335 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2690,11 +2690,15 @@ Commands related to windows and panes are as follows: .Bl -tag -width Ds .Tg breakp .It Xo Ic break\-pane -.Op Fl abdP +.Op Fl abdPW .Op Fl F Ar format .Op Fl n Ar window\-name .Op Fl s Ar src\-pane .Op Fl t Ar dst\-window +.Op Fl x Ar width +.Op Fl y Ar height +.Op Fl X Ar x-position +.Op Fl Y Ar y-position .Xc .D1 Pq alias: Ic breakp Break @@ -2717,6 +2721,40 @@ By default, it uses the format .Ql #{session_name}:#{window_index}.#{pane_index} but a different format may be specified with .Fl F . +.Pp +If the +.Fl W +option is given, +.Ar src\-pane +is lifted out of the tiled layout and made floating. +The +.Fl x +and +.Fl y +options set the width and height of the floating pane in columns and lines +respectively. +The default is half the window width and a quarter the window 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. +.Pp +If the pane had previously been floating, the position and sizes are restored +from the saved values not specified by the +.Fl x , +.Fl y , +.Fl X , +and +.Fl Y +options. +.Pp +If +.Fl d +is given, the active pane is not changed. +The pane must not already be floating or hidden, and the window must not +be zoomed. .Tg capturep .It Xo Ic capture\-pane .Op Fl aeFHLpPqCJMN diff --git a/tmux.h b/tmux.h index c6b4c6469..266cf9c7a 100644 --- a/tmux.h +++ b/tmux.h @@ -1478,6 +1478,12 @@ struct layout_cell { int xoff; int yoff; + u_int saved_sx; + u_int saved_sy; + + int saved_xoff; + int saved_yoff; + struct window_pane *wp; struct layout_cells cells; @@ -3626,9 +3632,9 @@ void layout_resize_pane(struct window_pane *, enum layout_type, int, int); void layout_resize_pane_to(struct window_pane *, enum layout_type, u_int); -void layout_resize_floating_pane(struct window_pane *, +int layout_resize_floating_pane(struct window_pane *, enum layout_type, int, int, char **); -void layout_resize_floating_pane_to(struct window_pane *, +int layout_resize_floating_pane_to(struct window_pane *, enum layout_type, u_int, char **); void layout_assign_pane(struct layout_cell *, struct window_pane *, int); @@ -3643,7 +3649,10 @@ struct layout_cell *layout_get_tiled_cell(struct cmdq_item *, struct args *, struct window *, struct window_pane *, int, char **); struct layout_cell *layout_get_floating_cell(struct cmdq_item *, struct args *, enum pane_lines, struct window *, struct window_pane *, - char **); + char **cause); +int layout_floating_args_parse(struct cmdq_item *, struct args *, + enum pane_lines, struct window *, u_int *, u_int *, int *, + int *, char **); int layout_remove_tile(struct window *, struct layout_cell *); /* layout-custom.c */ From 666464d7a9268450640ed84bce31cf6ed95ac73f Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 26 Jun 2026 11:28:15 +0100 Subject: [PATCH 43/71] Add X11 colours script. --- tools/x11-colours.sh | 632 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 632 insertions(+) create mode 100644 tools/x11-colours.sh diff --git a/tools/x11-colours.sh b/tools/x11-colours.sh new file mode 100644 index 000000000..6b60cc38c --- /dev/null +++ b/tools/x11-colours.sh @@ -0,0 +1,632 @@ +#!/bin/sh +set -eu + +awk ' +{ + if ($0 !~ /\{[[:space:]]*".*",[[:space:]]*0x[0-9A-Fa-f]+/) + next + + name = $0 + sub(/^[^"]*"/, "", name) + sub(/".*$/, "", name) + + hex = $0 + sub(/^.*0x/, "", hex) + sub(/[^0-9A-Fa-f].*$/, "", hex) + + printf "%s|%s\n", name, tolower(hex) +} +' <<'COLOURS' | + { "AliceBlue", 0xf0f8ff }, + { "AntiqueWhite", 0xfaebd7 }, + { "AntiqueWhite1", 0xffefdb }, + { "AntiqueWhite2", 0xeedfcc }, + { "AntiqueWhite3", 0xcdc0b0 }, + { "AntiqueWhite4", 0x8b8378 }, + { "BlanchedAlmond", 0xffebcd }, + { "BlueViolet", 0x8a2be2 }, + { "CadetBlue", 0x5f9ea0 }, + { "CadetBlue1", 0x98f5ff }, + { "CadetBlue2", 0x8ee5ee }, + { "CadetBlue3", 0x7ac5cd }, + { "CadetBlue4", 0x53868b }, + { "CornflowerBlue", 0x6495ed }, + { "DarkBlue", 0x00008b }, + { "DarkCyan", 0x008b8b }, + { "DarkGoldenrod", 0xb8860b }, + { "DarkGoldenrod1", 0xffb90f }, + { "DarkGoldenrod2", 0xeead0e }, + { "DarkGoldenrod3", 0xcd950c }, + { "DarkGoldenrod4", 0x8b6508 }, + { "DarkGray", 0xa9a9a9 }, + { "DarkGreen", 0x006400 }, + { "DarkGrey", 0xa9a9a9 }, + { "DarkKhaki", 0xbdb76b }, + { "DarkMagenta", 0x8b008b }, + { "DarkOliveGreen", 0x556b2f }, + { "DarkOliveGreen1", 0xcaff70 }, + { "DarkOliveGreen2", 0xbcee68 }, + { "DarkOliveGreen3", 0xa2cd5a }, + { "DarkOliveGreen4", 0x6e8b3d }, + { "DarkOrange", 0xff8c00 }, + { "DarkOrange1", 0xff7f00 }, + { "DarkOrange2", 0xee7600 }, + { "DarkOrange3", 0xcd6600 }, + { "DarkOrange4", 0x8b4500 }, + { "DarkOrchid", 0x9932cc }, + { "DarkOrchid1", 0xbf3eff }, + { "DarkOrchid2", 0xb23aee }, + { "DarkOrchid3", 0x9a32cd }, + { "DarkOrchid4", 0x68228b }, + { "DarkRed", 0x8b0000 }, + { "DarkSalmon", 0xe9967a }, + { "DarkSeaGreen", 0x8fbc8f }, + { "DarkSeaGreen1", 0xc1ffc1 }, + { "DarkSeaGreen2", 0xb4eeb4 }, + { "DarkSeaGreen3", 0x9bcd9b }, + { "DarkSeaGreen4", 0x698b69 }, + { "DarkSlateBlue", 0x483d8b }, + { "DarkSlateGray", 0x2f4f4f }, + { "DarkSlateGray1", 0x97ffff }, + { "DarkSlateGray2", 0x8deeee }, + { "DarkSlateGray3", 0x79cdcd }, + { "DarkSlateGray4", 0x528b8b }, + { "DarkSlateGrey", 0x2f4f4f }, + { "DarkTurquoise", 0x00ced1 }, + { "DarkViolet", 0x9400d3 }, + { "DeepPink", 0xff1493 }, + { "DeepPink1", 0xff1493 }, + { "DeepPink2", 0xee1289 }, + { "DeepPink3", 0xcd1076 }, + { "DeepPink4", 0x8b0a50 }, + { "DeepSkyBlue", 0x00bfff }, + { "DeepSkyBlue1", 0x00bfff }, + { "DeepSkyBlue2", 0x00b2ee }, + { "DeepSkyBlue3", 0x009acd }, + { "DeepSkyBlue4", 0x00688b }, + { "DimGray", 0x696969 }, + { "DimGrey", 0x696969 }, + { "DodgerBlue", 0x1e90ff }, + { "DodgerBlue1", 0x1e90ff }, + { "DodgerBlue2", 0x1c86ee }, + { "DodgerBlue3", 0x1874cd }, + { "DodgerBlue4", 0x104e8b }, + { "FloralWhite", 0xfffaf0 }, + { "ForestGreen", 0x228b22 }, + { "GhostWhite", 0xf8f8ff }, + { "GreenYellow", 0xadff2f }, + { "HotPink", 0xff69b4 }, + { "HotPink1", 0xff6eb4 }, + { "HotPink2", 0xee6aa7 }, + { "HotPink3", 0xcd6090 }, + { "HotPink4", 0x8b3a62 }, + { "IndianRed", 0xcd5c5c }, + { "IndianRed1", 0xff6a6a }, + { "IndianRed2", 0xee6363 }, + { "IndianRed3", 0xcd5555 }, + { "IndianRed4", 0x8b3a3a }, + { "LavenderBlush", 0xfff0f5 }, + { "LavenderBlush1", 0xfff0f5 }, + { "LavenderBlush2", 0xeee0e5 }, + { "LavenderBlush3", 0xcdc1c5 }, + { "LavenderBlush4", 0x8b8386 }, + { "LawnGreen", 0x7cfc00 }, + { "LemonChiffon", 0xfffacd }, + { "LemonChiffon1", 0xfffacd }, + { "LemonChiffon2", 0xeee9bf }, + { "LemonChiffon3", 0xcdc9a5 }, + { "LemonChiffon4", 0x8b8970 }, + { "LightBlue", 0xadd8e6 }, + { "LightBlue1", 0xbfefff }, + { "LightBlue2", 0xb2dfee }, + { "LightBlue3", 0x9ac0cd }, + { "LightBlue4", 0x68838b }, + { "LightCoral", 0xf08080 }, + { "LightCyan", 0xe0ffff }, + { "LightCyan1", 0xe0ffff }, + { "LightCyan2", 0xd1eeee }, + { "LightCyan3", 0xb4cdcd }, + { "LightCyan4", 0x7a8b8b }, + { "LightGoldenrod", 0xeedd82 }, + { "LightGoldenrod1", 0xffec8b }, + { "LightGoldenrod2", 0xeedc82 }, + { "LightGoldenrod3", 0xcdbe70 }, + { "LightGoldenrod4", 0x8b814c }, + { "LightGoldenrodYellow", 0xfafad2 }, + { "LightGray", 0xd3d3d3 }, + { "LightGreen", 0x90ee90 }, + { "LightGrey", 0xd3d3d3 }, + { "LightPink", 0xffb6c1 }, + { "LightPink1", 0xffaeb9 }, + { "LightPink2", 0xeea2ad }, + { "LightPink3", 0xcd8c95 }, + { "LightPink4", 0x8b5f65 }, + { "LightSalmon", 0xffa07a }, + { "LightSalmon1", 0xffa07a }, + { "LightSalmon2", 0xee9572 }, + { "LightSalmon3", 0xcd8162 }, + { "LightSalmon4", 0x8b5742 }, + { "LightSeaGreen", 0x20b2aa }, + { "LightSkyBlue", 0x87cefa }, + { "LightSkyBlue1", 0xb0e2ff }, + { "LightSkyBlue2", 0xa4d3ee }, + { "LightSkyBlue3", 0x8db6cd }, + { "LightSkyBlue4", 0x607b8b }, + { "LightSlateBlue", 0x8470ff }, + { "LightSlateGray", 0x778899 }, + { "LightSlateGrey", 0x778899 }, + { "LightSteelBlue", 0xb0c4de }, + { "LightSteelBlue1", 0xcae1ff }, + { "LightSteelBlue2", 0xbcd2ee }, + { "LightSteelBlue3", 0xa2b5cd }, + { "LightSteelBlue4", 0x6e7b8b }, + { "LightYellow", 0xffffe0 }, + { "LightYellow1", 0xffffe0 }, + { "LightYellow2", 0xeeeed1 }, + { "LightYellow3", 0xcdcdb4 }, + { "LightYellow4", 0x8b8b7a }, + { "LimeGreen", 0x32cd32 }, + { "MediumAquamarine", 0x66cdaa }, + { "MediumBlue", 0x0000cd }, + { "MediumOrchid", 0xba55d3 }, + { "MediumOrchid1", 0xe066ff }, + { "MediumOrchid2", 0xd15fee }, + { "MediumOrchid3", 0xb452cd }, + { "MediumOrchid4", 0x7a378b }, + { "MediumPurple", 0x9370db }, + { "MediumPurple1", 0xab82ff }, + { "MediumPurple2", 0x9f79ee }, + { "MediumPurple3", 0x8968cd }, + { "MediumPurple4", 0x5d478b }, + { "MediumSeaGreen", 0x3cb371 }, + { "MediumSlateBlue", 0x7b68ee }, + { "MediumSpringGreen", 0x00fa9a }, + { "MediumTurquoise", 0x48d1cc }, + { "MediumVioletRed", 0xc71585 }, + { "MidnightBlue", 0x191970 }, + { "MintCream", 0xf5fffa }, + { "MistyRose", 0xffe4e1 }, + { "MistyRose1", 0xffe4e1 }, + { "MistyRose2", 0xeed5d2 }, + { "MistyRose3", 0xcdb7b5 }, + { "MistyRose4", 0x8b7d7b }, + { "NavajoWhite", 0xffdead }, + { "NavajoWhite1", 0xffdead }, + { "NavajoWhite2", 0xeecfa1 }, + { "NavajoWhite3", 0xcdb38b }, + { "NavajoWhite4", 0x8b795e }, + { "NavyBlue", 0x000080 }, + { "OldLace", 0xfdf5e6 }, + { "OliveDrab", 0x6b8e23 }, + { "OliveDrab1", 0xc0ff3e }, + { "OliveDrab2", 0xb3ee3a }, + { "OliveDrab3", 0x9acd32 }, + { "OliveDrab4", 0x698b22 }, + { "OrangeRed", 0xff4500 }, + { "OrangeRed1", 0xff4500 }, + { "OrangeRed2", 0xee4000 }, + { "OrangeRed3", 0xcd3700 }, + { "OrangeRed4", 0x8b2500 }, + { "PaleGoldenrod", 0xeee8aa }, + { "PaleGreen", 0x98fb98 }, + { "PaleGreen1", 0x9aff9a }, + { "PaleGreen2", 0x90ee90 }, + { "PaleGreen3", 0x7ccd7c }, + { "PaleGreen4", 0x548b54 }, + { "PaleTurquoise", 0xafeeee }, + { "PaleTurquoise1", 0xbbffff }, + { "PaleTurquoise2", 0xaeeeee }, + { "PaleTurquoise3", 0x96cdcd }, + { "PaleTurquoise4", 0x668b8b }, + { "PaleVioletRed", 0xdb7093 }, + { "PaleVioletRed1", 0xff82ab }, + { "PaleVioletRed2", 0xee799f }, + { "PaleVioletRed3", 0xcd6889 }, + { "PaleVioletRed4", 0x8b475d }, + { "PapayaWhip", 0xffefd5 }, + { "PeachPuff", 0xffdab9 }, + { "PeachPuff1", 0xffdab9 }, + { "PeachPuff2", 0xeecbad }, + { "PeachPuff3", 0xcdaf95 }, + { "PeachPuff4", 0x8b7765 }, + { "PowderBlue", 0xb0e0e6 }, + { "RebeccaPurple", 0x663399 }, + { "RosyBrown", 0xbc8f8f }, + { "RosyBrown1", 0xffc1c1 }, + { "RosyBrown2", 0xeeb4b4 }, + { "RosyBrown3", 0xcd9b9b }, + { "RosyBrown4", 0x8b6969 }, + { "RoyalBlue", 0x4169e1 }, + { "RoyalBlue1", 0x4876ff }, + { "RoyalBlue2", 0x436eee }, + { "RoyalBlue3", 0x3a5fcd }, + { "RoyalBlue4", 0x27408b }, + { "SaddleBrown", 0x8b4513 }, + { "SandyBrown", 0xf4a460 }, + { "SeaGreen", 0x2e8b57 }, + { "SeaGreen1", 0x54ff9f }, + { "SeaGreen2", 0x4eee94 }, + { "SeaGreen3", 0x43cd80 }, + { "SeaGreen4", 0x2e8b57 }, + { "SkyBlue", 0x87ceeb }, + { "SkyBlue1", 0x87ceff }, + { "SkyBlue2", 0x7ec0ee }, + { "SkyBlue3", 0x6ca6cd }, + { "SkyBlue4", 0x4a708b }, + { "SlateBlue", 0x6a5acd }, + { "SlateBlue1", 0x836fff }, + { "SlateBlue2", 0x7a67ee }, + { "SlateBlue3", 0x6959cd }, + { "SlateBlue4", 0x473c8b }, + { "SlateGray", 0x708090 }, + { "SlateGray1", 0xc6e2ff }, + { "SlateGray2", 0xb9d3ee }, + { "SlateGray3", 0x9fb6cd }, + { "SlateGray4", 0x6c7b8b }, + { "SlateGrey", 0x708090 }, + { "SpringGreen", 0x00ff7f }, + { "SpringGreen1", 0x00ff7f }, + { "SpringGreen2", 0x00ee76 }, + { "SpringGreen3", 0x00cd66 }, + { "SpringGreen4", 0x008b45 }, + { "SteelBlue", 0x4682b4 }, + { "SteelBlue1", 0x63b8ff }, + { "SteelBlue2", 0x5cacee }, + { "SteelBlue3", 0x4f94cd }, + { "SteelBlue4", 0x36648b }, + { "VioletRed", 0xd02090 }, + { "VioletRed1", 0xff3e96 }, + { "VioletRed2", 0xee3a8c }, + { "VioletRed3", 0xcd3278 }, + { "VioletRed4", 0x8b2252 }, + { "WebGray", 0x808080 }, + { "WebGreen", 0x008000 }, + { "WebGrey", 0x808080 }, + { "WebMaroon", 0x800000 }, + { "WebPurple", 0x800080 }, + { "WhiteSmoke", 0xf5f5f5 }, + { "X11Gray", 0xbebebe }, + { "X11Green", 0x00ff00 }, + { "X11Grey", 0xbebebe }, + { "X11Maroon", 0xb03060 }, + { "X11Purple", 0xa020f0 }, + { "YellowGreen", 0x9acd32 }, + { "alice blue", 0xf0f8ff }, + { "antique white", 0xfaebd7 }, + { "aqua", 0x00ffff }, + { "aquamarine", 0x7fffd4 }, + { "aquamarine1", 0x7fffd4 }, + { "aquamarine2", 0x76eec6 }, + { "aquamarine3", 0x66cdaa }, + { "aquamarine4", 0x458b74 }, + { "azure", 0xf0ffff }, + { "azure1", 0xf0ffff }, + { "azure2", 0xe0eeee }, + { "azure3", 0xc1cdcd }, + { "azure4", 0x838b8b }, + { "beige", 0xf5f5dc }, + { "bisque", 0xffe4c4 }, + { "bisque1", 0xffe4c4 }, + { "bisque2", 0xeed5b7 }, + { "bisque3", 0xcdb79e }, + { "bisque4", 0x8b7d6b }, + { "black", 0x000000 }, + { "blanched almond", 0xffebcd }, + { "blue violet", 0x8a2be2 }, + { "blue", 0x0000ff }, + { "blue1", 0x0000ff }, + { "blue2", 0x0000ee }, + { "blue3", 0x0000cd }, + { "blue4", 0x00008b }, + { "brown", 0xa52a2a }, + { "brown1", 0xff4040 }, + { "brown2", 0xee3b3b }, + { "brown3", 0xcd3333 }, + { "brown4", 0x8b2323 }, + { "burlywood", 0xdeb887 }, + { "burlywood1", 0xffd39b }, + { "burlywood2", 0xeec591 }, + { "burlywood3", 0xcdaa7d }, + { "burlywood4", 0x8b7355 }, + { "cadet blue", 0x5f9ea0 }, + { "chartreuse", 0x7fff00 }, + { "chartreuse1", 0x7fff00 }, + { "chartreuse2", 0x76ee00 }, + { "chartreuse3", 0x66cd00 }, + { "chartreuse4", 0x458b00 }, + { "chocolate", 0xd2691e }, + { "chocolate1", 0xff7f24 }, + { "chocolate2", 0xee7621 }, + { "chocolate3", 0xcd661d }, + { "chocolate4", 0x8b4513 }, + { "coral", 0xff7f50 }, + { "coral1", 0xff7256 }, + { "coral2", 0xee6a50 }, + { "coral3", 0xcd5b45 }, + { "coral4", 0x8b3e2f }, + { "cornflower blue", 0x6495ed }, + { "cornsilk", 0xfff8dc }, + { "cornsilk1", 0xfff8dc }, + { "cornsilk2", 0xeee8cd }, + { "cornsilk3", 0xcdc8b1 }, + { "cornsilk4", 0x8b8878 }, + { "crimson", 0xdc143c }, + { "cyan", 0x00ffff }, + { "cyan1", 0x00ffff }, + { "cyan2", 0x00eeee }, + { "cyan3", 0x00cdcd }, + { "cyan4", 0x008b8b }, + { "dark blue", 0x00008b }, + { "dark cyan", 0x008b8b }, + { "dark goldenrod", 0xb8860b }, + { "dark gray", 0xa9a9a9 }, + { "dark green", 0x006400 }, + { "dark grey", 0xa9a9a9 }, + { "dark khaki", 0xbdb76b }, + { "dark magenta", 0x8b008b }, + { "dark olive green", 0x556b2f }, + { "dark orange", 0xff8c00 }, + { "dark orchid", 0x9932cc }, + { "dark red", 0x8b0000 }, + { "dark salmon", 0xe9967a }, + { "dark sea green", 0x8fbc8f }, + { "dark slate blue", 0x483d8b }, + { "dark slate gray", 0x2f4f4f }, + { "dark slate grey", 0x2f4f4f }, + { "dark turquoise", 0x00ced1 }, + { "dark violet", 0x9400d3 }, + { "deep pink", 0xff1493 }, + { "deep sky blue", 0x00bfff }, + { "dim gray", 0x696969 }, + { "dim grey", 0x696969 }, + { "dodger blue", 0x1e90ff }, + { "firebrick", 0xb22222 }, + { "firebrick1", 0xff3030 }, + { "firebrick2", 0xee2c2c }, + { "firebrick3", 0xcd2626 }, + { "firebrick4", 0x8b1a1a }, + { "floral white", 0xfffaf0 }, + { "forest green", 0x228b22 }, + { "fuchsia", 0xff00ff }, + { "gainsboro", 0xdcdcdc }, + { "ghost white", 0xf8f8ff }, + { "gold", 0xffd700 }, + { "gold1", 0xffd700 }, + { "gold2", 0xeec900 }, + { "gold3", 0xcdad00 }, + { "gold4", 0x8b7500 }, + { "goldenrod", 0xdaa520 }, + { "goldenrod1", 0xffc125 }, + { "goldenrod2", 0xeeb422 }, + { "goldenrod3", 0xcd9b1d }, + { "goldenrod4", 0x8b6914 }, + { "green yellow", 0xadff2f }, + { "green", 0x00ff00 }, + { "green1", 0x00ff00 }, + { "green2", 0x00ee00 }, + { "green3", 0x00cd00 }, + { "green4", 0x008b00 }, + { "honeydew", 0xf0fff0 }, + { "honeydew1", 0xf0fff0 }, + { "honeydew2", 0xe0eee0 }, + { "honeydew3", 0xc1cdc1 }, + { "honeydew4", 0x838b83 }, + { "hot pink", 0xff69b4 }, + { "indian red", 0xcd5c5c }, + { "indigo", 0x4b0082 }, + { "ivory", 0xfffff0 }, + { "ivory1", 0xfffff0 }, + { "ivory2", 0xeeeee0 }, + { "ivory3", 0xcdcdc1 }, + { "ivory4", 0x8b8b83 }, + { "khaki", 0xf0e68c }, + { "khaki1", 0xfff68f }, + { "khaki2", 0xeee685 }, + { "khaki3", 0xcdc673 }, + { "khaki4", 0x8b864e }, + { "lavender blush", 0xfff0f5 }, + { "lavender", 0xe6e6fa }, + { "lawn green", 0x7cfc00 }, + { "lemon chiffon", 0xfffacd }, + { "light blue", 0xadd8e6 }, + { "light coral", 0xf08080 }, + { "light cyan", 0xe0ffff }, + { "light goldenrod yellow", 0xfafad2 }, + { "light goldenrod", 0xeedd82 }, + { "light gray", 0xd3d3d3 }, + { "light green", 0x90ee90 }, + { "light grey", 0xd3d3d3 }, + { "light pink", 0xffb6c1 }, + { "light salmon", 0xffa07a }, + { "light sea green", 0x20b2aa }, + { "light sky blue", 0x87cefa }, + { "light slate blue", 0x8470ff }, + { "light slate gray", 0x778899 }, + { "light slate grey", 0x778899 }, + { "light steel blue", 0xb0c4de }, + { "light yellow", 0xffffe0 }, + { "lime green", 0x32cd32 }, + { "lime", 0x00ff00 }, + { "linen", 0xfaf0e6 }, + { "magenta", 0xff00ff }, + { "magenta1", 0xff00ff }, + { "magenta2", 0xee00ee }, + { "magenta3", 0xcd00cd }, + { "magenta4", 0x8b008b }, + { "maroon", 0xb03060 }, + { "maroon1", 0xff34b3 }, + { "maroon2", 0xee30a7 }, + { "maroon3", 0xcd2990 }, + { "maroon4", 0x8b1c62 }, + { "medium aquamarine", 0x66cdaa }, + { "medium blue", 0x0000cd }, + { "medium orchid", 0xba55d3 }, + { "medium purple", 0x9370db }, + { "medium sea green", 0x3cb371 }, + { "medium slate blue", 0x7b68ee }, + { "medium spring green", 0x00fa9a }, + { "medium turquoise", 0x48d1cc }, + { "medium violet red", 0xc71585 }, + { "midnight blue", 0x191970 }, + { "mint cream", 0xf5fffa }, + { "misty rose", 0xffe4e1 }, + { "moccasin", 0xffe4b5 }, + { "navajo white", 0xffdead }, + { "navy blue", 0x000080 }, + { "navy", 0x000080 }, + { "old lace", 0xfdf5e6 }, + { "olive drab", 0x6b8e23 }, + { "olive", 0x808000 }, + { "orange red", 0xff4500 }, + { "orange", 0xffa500 }, + { "orange1", 0xffa500 }, + { "orange2", 0xee9a00 }, + { "orange3", 0xcd8500 }, + { "orange4", 0x8b5a00 }, + { "orchid", 0xda70d6 }, + { "orchid1", 0xff83fa }, + { "orchid2", 0xee7ae9 }, + { "orchid3", 0xcd69c9 }, + { "orchid4", 0x8b4789 }, + { "pale goldenrod", 0xeee8aa }, + { "pale green", 0x98fb98 }, + { "pale turquoise", 0xafeeee }, + { "pale violet red", 0xdb7093 }, + { "papaya whip", 0xffefd5 }, + { "peach puff", 0xffdab9 }, + { "peru", 0xcd853f }, + { "pink", 0xffc0cb }, + { "pink1", 0xffb5c5 }, + { "pink2", 0xeea9b8 }, + { "pink3", 0xcd919e }, + { "pink4", 0x8b636c }, + { "plum", 0xdda0dd }, + { "plum1", 0xffbbff }, + { "plum2", 0xeeaeee }, + { "plum3", 0xcd96cd }, + { "plum4", 0x8b668b }, + { "powder blue", 0xb0e0e6 }, + { "purple", 0xa020f0 }, + { "purple1", 0x9b30ff }, + { "purple2", 0x912cee }, + { "purple3", 0x7d26cd }, + { "purple4", 0x551a8b }, + { "rebecca purple", 0x663399 }, + { "red", 0xff0000 }, + { "red1", 0xff0000 }, + { "red2", 0xee0000 }, + { "red3", 0xcd0000 }, + { "red4", 0x8b0000 }, + { "rosy brown", 0xbc8f8f }, + { "royal blue", 0x4169e1 }, + { "saddle brown", 0x8b4513 }, + { "salmon", 0xfa8072 }, + { "salmon1", 0xff8c69 }, + { "salmon2", 0xee8262 }, + { "salmon3", 0xcd7054 }, + { "salmon4", 0x8b4c39 }, + { "sandy brown", 0xf4a460 }, + { "sea green", 0x2e8b57 }, + { "seashell", 0xfff5ee }, + { "seashell1", 0xfff5ee }, + { "seashell2", 0xeee5de }, + { "seashell3", 0xcdc5bf }, + { "seashell4", 0x8b8682 }, + { "sienna", 0xa0522d }, + { "sienna1", 0xff8247 }, + { "sienna2", 0xee7942 }, + { "sienna3", 0xcd6839 }, + { "sienna4", 0x8b4726 }, + { "silver", 0xc0c0c0 }, + { "sky blue", 0x87ceeb }, + { "slate blue", 0x6a5acd }, + { "slate gray", 0x708090 }, + { "slate grey", 0x708090 }, + { "snow", 0xfffafa }, + { "snow1", 0xfffafa }, + { "snow2", 0xeee9e9 }, + { "snow3", 0xcdc9c9 }, + { "snow4", 0x8b8989 }, + { "spring green", 0x00ff7f }, + { "steel blue", 0x4682b4 }, + { "tan", 0xd2b48c }, + { "tan1", 0xffa54f }, + { "tan2", 0xee9a49 }, + { "tan3", 0xcd853f }, + { "tan4", 0x8b5a2b }, + { "teal", 0x008080 }, + { "thistle", 0xd8bfd8 }, + { "thistle1", 0xffe1ff }, + { "thistle2", 0xeed2ee }, + { "thistle3", 0xcdb5cd }, + { "thistle4", 0x8b7b8b }, + { "tomato", 0xff6347 }, + { "tomato1", 0xff6347 }, + { "tomato2", 0xee5c42 }, + { "tomato3", 0xcd4f39 }, + { "tomato4", 0x8b3626 }, + { "turquoise", 0x40e0d0 }, + { "turquoise1", 0x00f5ff }, + { "turquoise2", 0x00e5ee }, + { "turquoise3", 0x00c5cd }, + { "turquoise4", 0x00868b }, + { "violet red", 0xd02090 }, + { "violet", 0xee82ee }, + { "web gray", 0x808080 }, + { "web green", 0x008000 }, + { "web grey", 0x808080 }, + { "web maroon", 0x800000 }, + { "web purple", 0x800080 }, + { "wheat", 0xf5deb3 }, + { "wheat1", 0xffe7ba }, + { "wheat2", 0xeed8ae }, + { "wheat3", 0xcdba96 }, + { "wheat4", 0x8b7e66 }, + { "white smoke", 0xf5f5f5 }, + { "white", 0xffffff }, + { "x11 gray", 0xbebebe }, + { "x11 green", 0x00ff00 }, + { "x11 grey", 0xbebebe }, + { "x11 maroon", 0xb03060 }, + { "x11 purple", 0xa020f0 }, + { "yellow green", 0x9acd32 }, + { "yellow", 0xffff00 }, + { "yellow1", 0xffff00 }, + { "yellow2", 0xeeee00 }, + { "yellow3", 0xcdcd00 }, + { "yellow4", 0x8b8b00 } +COLOURS +LC_ALL=C sort -f | +awk -F '|' ' +function h2d(s, i, n, c, p) { + n = 0 + for (i = 1; i <= length(s); i++) { + c = substr(s, i, 1) + p = index("0123456789abcdef", tolower(c)) + if (p == 0) + return -1 + n = n * 16 + p - 1 + } + return n +} + +{ + name[NR] = $1 + hex[NR] = $2 + if (length($1) > width) + width = length($1) +} + +END { + esc = sprintf("%c", 27) + + for (i = 1; i <= NR; i++) { + r = h2d(substr(hex[i], 1, 2)) + g = h2d(substr(hex[i], 3, 2)) + b = h2d(substr(hex[i], 5, 2)) + + printf "%-*s %s[38;2;%d;%d;%dmXXXXXXXX%s[0m %s[48;2;%d;%d;%dm %s[0m\n", \ + width, name[i], esc, r, g, b, esc, esc, r, g, b, esc + } +} +' From 19db019e0c51eebbd87892fe4ed913a6f3107071 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 10:29:38 +0000 Subject: [PATCH 44/71] Do not force theme colours to default when capturing. --- grid.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/grid.c b/grid.c index 0796b875c..44efa574d 100644 --- a/grid.c +++ b/grid.c @@ -797,11 +797,16 @@ grid_string_cells_fg(const struct grid_cell *gc, int *values) { size_t n; u_char r, g, b; + int c; n = 0; - if (gc->fg & COLOUR_FLAG_THEME) - values[n++] = 39; - else if (gc->fg & COLOUR_FLAG_256) { + if (gc->fg & COLOUR_FLAG_THEME) { + c = colour_theme_terminal_colour(gc->fg & 0xff); + if (c == 8) + values[n++] = 39; + else + values[n++] = c + 30; + } else if (gc->fg & COLOUR_FLAG_256) { values[n++] = 38; values[n++] = 5; values[n++] = gc->fg & 0xff; @@ -848,11 +853,16 @@ grid_string_cells_bg(const struct grid_cell *gc, int *values) { size_t n; u_char r, g, b; + int c; n = 0; - if (gc->bg & COLOUR_FLAG_THEME) - values[n++] = 49; - else if (gc->bg & COLOUR_FLAG_256) { + if (gc->bg & COLOUR_FLAG_THEME) { + c = colour_theme_terminal_colour(gc->bg & 0xff); + if (c == 8) + values[n++] = 49; + else + values[n++] = c + 40; + } else if (gc->bg & COLOUR_FLAG_256) { values[n++] = 48; values[n++] = 5; values[n++] = gc->bg & 0xff; @@ -899,10 +909,18 @@ grid_string_cells_us(const struct grid_cell *gc, int *values) { size_t n; u_char r, g, b; + int c; n = 0; if (gc->us & COLOUR_FLAG_THEME) { - values[n++] = 59; + c = colour_theme_terminal_colour(gc->us & 0xff); + if (c == 8) + values[n++] = 59; + else { + values[n++] = 58; + values[n++] = 5; + values[n++] = c; + } } else if (gc->us & COLOUR_FLAG_256) { values[n++] = 58; values[n++] = 5; From 1e5aef279a7fd0c5766f5e8d10c21725a24e4ee6 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 26 Jun 2026 11:32:32 +0100 Subject: [PATCH 45/71] Update tests for colour/position changes. --- .../colour-two-plus-float.result | 16 ++++++------- .../floating-basic.result | 16 ++++++------- .../floating-border-double.result | 20 ++++++++-------- .../floating-clip-bottom.result | 8 +++---- .../floating-clip-edge.result | 10 ++++---- .../floating-clip-left.result | 16 ++++++------- .../floating-clip-right.result | 16 ++++++------- .../floating-clip-top.result | 8 +++---- .../floating-clip-topleft.result | 10 ++++---- .../floating-empty.result | 16 ++++++------- .../floating-mixed-config.result | 22 ++++++++--------- .../floating-outside.result | 12 +++++----- .../floating-over-scrollbar.result | 16 ++++++------- .../floating-over-status.result | 10 ++++---- .../floating-over-tiled-double.result | 16 ++++++------- .../floating-over-tiled.result | 16 ++++++------- .../floating-overlap.result | 22 ++++++++--------- .../floating-title.result | 16 ++++++------- .../marked-pane-float.result | 16 ++++++------- .../marked-pane-lr.result | 24 +++++++++---------- .../marked-pane-status.result | 24 +++++++++---------- .../marked-pane-tb.result | 2 +- .../marked-pane-three.result | 24 +++++++++---------- .../scrollbar-floating.result | 16 ++++++------- .../scrollbar-split-left.result | 24 +++++++++---------- .../scrollbar-split-right.result | 24 +++++++++---------- .../window-style-active.result | 16 ++++++------- 27 files changed, 218 insertions(+), 218 deletions(-) diff --git a/regress/screen-redraw-results/colour-two-plus-float.result b/regress/screen-redraw-results/colour-two-plus-float.result index 74ea83e12..5167fc151 100644 --- a/regress/screen-redraw-results/colour-two-plus-float.result +++ b/regress/screen-redraw-results/colour-two-plus-float.result @@ -1,12 +1,12 @@ │ - ┌────────────────┐ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - └────────────────┘ + │ + ┌──────────────┐ + │ │ + │ │ + │ │ + │ │ + └──────────────┘ + │ │ │ │ diff --git a/regress/screen-redraw-results/floating-basic.result b/regress/screen-redraw-results/floating-basic.result index f51ccbe29..9ef48423a 100644 --- a/regress/screen-redraw-results/floating-basic.result +++ b/regress/screen-redraw-results/floating-basic.result @@ -1,12 +1,12 @@ base - ┌────────────────────┐ - │FLOAT │ - │ │ - │ │ - │ │ - │ │ - │ │ - └────────────────────┘ + + ┌──────────────────┐ + │FLOAT │ + │ │ + │ │ + │ │ + └──────────────────┘ + diff --git a/regress/screen-redraw-results/floating-border-double.result b/regress/screen-redraw-results/floating-border-double.result index a1b1cd7a9..deaad2261 100644 --- a/regress/screen-redraw-results/floating-border-double.result +++ b/regress/screen-redraw-results/floating-border-double.result @@ -1,12 +1,12 @@ -bas╔════════════════════════════╗ - ║FLOAT ║ - ║ ║ - ║ ║ - ║ ║ - ║ ║ - ║ ║ - ║ ║ - ║ ║ - ╚════════════════════════════╝ +base + ╔══════════════════════════╗ + ║FLOAT ║ + ║ ║ + ║ ║ + ║ ║ + ║ ║ + ║ ║ + ╚══════════════════════════╝ + diff --git a/regress/screen-redraw-results/floating-clip-bottom.result b/regress/screen-redraw-results/floating-clip-bottom.result index f8a11cdcf..0c309854b 100644 --- a/regress/screen-redraw-results/floating-clip-bottom.result +++ b/regress/screen-redraw-results/floating-clip-bottom.result @@ -6,7 +6,7 @@ 06:abcdefghijklmnopqrstuvwxyz0123456789 07:abcdefghijklmnopqrstuvwxyz0123456789 08:abcdefghijklmnopqrstuvwxyz0123456789 -09:abcdef┌────────────────────┐23456789 -10:abcdef│BOTTOM │23456789 -11:abcdef│ │23456789 - │ │ +09:abcdefghijklmnopqrstuvwxyz0123456789 +10:abcdefg┌──────────────────┐123456789 +11:abcdefg│BOTTOM │123456789 + │ │ diff --git a/regress/screen-redraw-results/floating-clip-edge.result b/regress/screen-redraw-results/floating-clip-edge.result index 9998eff49..baf6eb863 100644 --- a/regress/screen-redraw-results/floating-clip-edge.result +++ b/regress/screen-redraw-results/floating-clip-edge.result @@ -5,8 +5,8 @@ base - ┌────────── - │CLIP - │ - │ - │ + + ┌───────── + │CLIP + │ + │ diff --git a/regress/screen-redraw-results/floating-clip-left.result b/regress/screen-redraw-results/floating-clip-left.result index f4c37c5da..204256433 100644 --- a/regress/screen-redraw-results/floating-clip-left.result +++ b/regress/screen-redraw-results/floating-clip-left.result @@ -1,12 +1,12 @@ 01:abcdefghijklmnopqrstuvwxyz0123456789 02:abcdefghijklmnopqrstuvwxyz0123456789 -──────────┐ijklmnopqrstuvwxyz0123456789 - │ijklmnopqrstuvwxyz0123456789 - │ijklmnopqrstuvwxyz0123456789 - │ijklmnopqrstuvwxyz0123456789 - │ijklmnopqrstuvwxyz0123456789 - │ijklmnopqrstuvwxyz0123456789 - │ijklmnopqrstuvwxyz0123456789 -──────────┘ijklmnopqrstuvwxyz0123456789 +03:abcdefghijklmnopqrstuvwxyz0123456789 +─────────┐hijklmnopqrstuvwxyz0123456789 + │hijklmnopqrstuvwxyz0123456789 + │hijklmnopqrstuvwxyz0123456789 + │hijklmnopqrstuvwxyz0123456789 + │hijklmnopqrstuvwxyz0123456789 +─────────┘hijklmnopqrstuvwxyz0123456789 +10:abcdefghijklmnopqrstuvwxyz0123456789 11:abcdefghijklmnopqrstuvwxyz0123456789 diff --git a/regress/screen-redraw-results/floating-clip-right.result b/regress/screen-redraw-results/floating-clip-right.result index 24323b4ad..b42f44f06 100644 --- a/regress/screen-redraw-results/floating-clip-right.result +++ b/regress/screen-redraw-results/floating-clip-right.result @@ -1,12 +1,12 @@ 01:abcdefghijklmnopqrstuvwxyz0123456789 02:abcdefghijklmnopqrstuvwxyz0123456789 -03:abcdefghijklmnopqrstuvwxyz┌────────── -04:abcdefghijklmnopqrstuvwxyz│RIGHT -05:abcdefghijklmnopqrstuvwxyz│ -06:abcdefghijklmnopqrstuvwxyz│ -07:abcdefghijklmnopqrstuvwxyz│ -08:abcdefghijklmnopqrstuvwxyz│ -09:abcdefghijklmnopqrstuvwxyz│ -10:abcdefghijklmnopqrstuvwxyz└────────── +03:abcdefghijklmnopqrstuvwxyz0123456789 +04:abcdefghijklmnopqrstuvwxyz0┌───────── +05:abcdefghijklmnopqrstuvwxyz0│RIGHT +06:abcdefghijklmnopqrstuvwxyz0│ +07:abcdefghijklmnopqrstuvwxyz0│ +08:abcdefghijklmnopqrstuvwxyz0│ +09:abcdefghijklmnopqrstuvwxyz0└───────── +10:abcdefghijklmnopqrstuvwxyz0123456789 11:abcdefghijklmnopqrstuvwxyz0123456789 diff --git a/regress/screen-redraw-results/floating-clip-top.result b/regress/screen-redraw-results/floating-clip-top.result index 2a754ca65..53ea2704a 100644 --- a/regress/screen-redraw-results/floating-clip-top.result +++ b/regress/screen-redraw-results/floating-clip-top.result @@ -1,7 +1,7 @@ -01:abcdef│ │23456789 -02:abcdef│ │23456789 -03:abcdef│ │23456789 -04:abcdef└────────────────────┘23456789 +01:abcdefg│ │123456789 +02:abcdefg│ │123456789 +03:abcdefg└──────────────────┘123456789 +04:abcdefghijklmnopqrstuvwxyz0123456789 05:abcdefghijklmnopqrstuvwxyz0123456789 06:abcdefghijklmnopqrstuvwxyz0123456789 07:abcdefghijklmnopqrstuvwxyz0123456789 diff --git a/regress/screen-redraw-results/floating-clip-topleft.result b/regress/screen-redraw-results/floating-clip-topleft.result index dbffb559e..0aebf5518 100644 --- a/regress/screen-redraw-results/floating-clip-topleft.result +++ b/regress/screen-redraw-results/floating-clip-topleft.result @@ -1,8 +1,8 @@ - │ - │ - │ - │ -──────────────┘ + │ + │ + │ +─────────────┘ + diff --git a/regress/screen-redraw-results/floating-empty.result b/regress/screen-redraw-results/floating-empty.result index 31a71de51..4bb7948d2 100644 --- a/regress/screen-redraw-results/floating-empty.result +++ b/regress/screen-redraw-results/floating-empty.result @@ -1,12 +1,12 @@ ········································ ········································ -·······┌────────────────────┐··········· -·······│FLOAT │··········· -·······│ │··········· -·······│ │··········· -·······│ │··········· -·······│ │··········· -·······│ │··········· -·······└────────────────────┘··········· +········································ +········┌──────────────────┐············ +········│FLOAT │············ +········│ │············ +········│ │············ +········│ │············ +········└──────────────────┘············ +········································ ········································ diff --git a/regress/screen-redraw-results/floating-mixed-config.result b/regress/screen-redraw-results/floating-mixed-config.result index ae6f2efba..30424c086 100644 --- a/regress/screen-redraw-results/floating-mixed-config.result +++ b/regress/screen-redraw-results/floating-mixed-config.result @@ -1,12 +1,12 @@ -ba┌── one ─────────┐ - │ONE │ - │ │ - │ │ - │ │ - └──────────────┏━━━━━━━━━━━━━━━━┓ - ┃TWO ┃ - ┃ ┃ - ┃ ┃ - ┃ ┃ - ┗━━ two ━━━━━━━━━┛ +base + ┌── one ───────┐ + │ONE │ + │ │ + └──────────────┘ + + ┏━━━━━━━━━━━━━━┓ + ┃TWO ┃ + ┃ ┃ + ┗━━ two ━━━━━━━┛ + diff --git a/regress/screen-redraw-results/floating-outside.result b/regress/screen-redraw-results/floating-outside.result index f24f0f021..4fae5b138 100644 --- a/regress/screen-redraw-results/floating-outside.result +++ b/regress/screen-redraw-results/floating-outside.result @@ -1,12 +1,12 @@ base │··········· │··········· │··········· - ┌──────────│··········· - │OUT │··········· - │ │··········· - │ │··········· - │ │··········· -────────────────────────────┘··········· + │··········· + ┌─────────│··········· + │OUT │··········· + │ │··········· + │ │··········· +──────────────────└─────────┘··········· ········································ ········································ ········································ diff --git a/regress/screen-redraw-results/floating-over-scrollbar.result b/regress/screen-redraw-results/floating-over-scrollbar.result index 80ecc8c30..5e85eff82 100644 --- a/regress/screen-redraw-results/floating-over-scrollbar.result +++ b/regress/screen-redraw-results/floating-over-scrollbar.result @@ -1,12 +1,12 @@ base    - ┌────────────────┐ - │OVERSB  │ - │  │ - │  │ - │  │ - │  │ - │  │ - └────────────────┘ +  + ┌──────────────┐  + │OVERSB  │  + │  │  + │  │  + │  │  + └──────────────┘  +      diff --git a/regress/screen-redraw-results/floating-over-status.result b/regress/screen-redraw-results/floating-over-status.result index 76c6ebac9..b573e43b2 100644 --- a/regress/screen-redraw-results/floating-over-status.result +++ b/regress/screen-redraw-results/floating-over-status.result @@ -4,9 +4,9 @@ base - ┌────────────────────┐ - │OVERST │ - │ │ - │ │ - │ │ + + ┌──────────────────┐ + │OVERST │ + │ │ + │ │ diff --git a/regress/screen-redraw-results/floating-over-tiled-double.result b/regress/screen-redraw-results/floating-over-tiled-double.result index 5e286a90f..a42b97b37 100644 --- a/regress/screen-redraw-results/floating-over-tiled-double.result +++ b/regress/screen-redraw-results/floating-over-tiled-double.result @@ -1,12 +1,12 @@ TILE01 abcdefghij │TILE01 abcdefghij TILE02 abcdefghij │TILE02 abcdefghij -TILE03 abc╔════════════════╗bcdefghij -TILE04 abc║FLT ║bcdefghij - ║ ║ -──────────║ ║──────────── -TILE00 abc║ ║bcdefghij -TILE01 abc║ ║bcdefghij -TILE02 abc║ ║bcdefghij -TILE03 abc╚════════════════╝bcdefghij +TILE03 abcdefghij │TILE03 abcdefghij +TILE04 abcd╔══════════════╗abcdefghij + ║FLT ║ +───────────║ ║───────────── +TILE00 abcd║ ║abcdefghij +TILE01 abcd║ ║abcdefghij +TILE02 abcd╚══════════════╝abcdefghij +TILE03 abcdefghij │TILE03 abcdefghij TILE04 abcdefghij │TILE04 abcdefghij │ diff --git a/regress/screen-redraw-results/floating-over-tiled.result b/regress/screen-redraw-results/floating-over-tiled.result index 4332c27c0..e14dcfbef 100644 --- a/regress/screen-redraw-results/floating-over-tiled.result +++ b/regress/screen-redraw-results/floating-over-tiled.result @@ -1,12 +1,12 @@ TILE01 abcdefghij │TILE01 abcdefghij TILE02 abcdefghij │TILE02 abcdefghij -TILE03 abc┌────────────────┐bcdefghij -TILE04 abc│FLT │bcdefghij - │ │ -──────────│ │──────────── -TILE00 abc│ │bcdefghij -TILE01 abc│ │bcdefghij -TILE02 abc│ │bcdefghij -TILE03 abc└────────────────┘bcdefghij +TILE03 abcdefghij │TILE03 abcdefghij +TILE04 abcd┌──────────────┐abcdefghij + │FLT │ +───────────│ │───────────── +TILE00 abcd│ │abcdefghij +TILE01 abcd│ │abcdefghij +TILE02 abcd└──────────────┘abcdefghij +TILE03 abcdefghij │TILE03 abcdefghij TILE04 abcdefghij │TILE04 abcdefghij │ diff --git a/regress/screen-redraw-results/floating-overlap.result b/regress/screen-redraw-results/floating-overlap.result index 58f00b60f..fc4fae921 100644 --- a/regress/screen-redraw-results/floating-overlap.result +++ b/regress/screen-redraw-results/floating-overlap.result @@ -1,12 +1,12 @@ base - ┌────────────────┐ - │AAA │ - │ │ - │ │ - │ ┌────────────────┐ - │ │BBB │ - │ │ │ - └─────────│ │ - │ │ - │ │ - │ │ + + ┌──────────────┐ + │AAA │ + │ │ + │ │ + │ ┌──────────────┐ + └─────────│BBB │ + │ │ + │ │ + │ │ + └──────────────┘ diff --git a/regress/screen-redraw-results/floating-title.result b/regress/screen-redraw-results/floating-title.result index 896a715bf..ef373c0ac 100644 --- a/regress/screen-redraw-results/floating-title.result +++ b/regress/screen-redraw-results/floating-title.result @@ -1,12 +1,12 @@ ── base ──────────────────────────────── -base ┌── title ───────────┐ - │FLOAT │ - │ │ - │ │ - │ │ - │ │ - │ │ - └────────────────────┘ +base + ┌── title ─────────┐ + │FLOAT │ + │ │ + │ │ + │ │ + └──────────────────┘ + diff --git a/regress/screen-redraw-results/marked-pane-float.result b/regress/screen-redraw-results/marked-pane-float.result index 2be62da79..42e70cf67 100644 --- a/regress/screen-redraw-results/marked-pane-float.result +++ b/regress/screen-redraw-results/marked-pane-float.result @@ -1,12 +1,12 @@ - ┌────────────────────┐ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - └────────────────────┘ + + ┌──────────────────┐ + │ │ + │ │ + │ │ + │ │ + └──────────────────┘ + diff --git a/regress/screen-redraw-results/marked-pane-lr.result b/regress/screen-redraw-results/marked-pane-lr.result index 842f95c39..4f3d1af11 100644 --- a/regress/screen-redraw-results/marked-pane-lr.result +++ b/regress/screen-redraw-results/marked-pane-lr.result @@ -1,12 +1,12 @@ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ diff --git a/regress/screen-redraw-results/marked-pane-status.result b/regress/screen-redraw-results/marked-pane-status.result index 2e9830d21..ff99a0bc2 100644 --- a/regress/screen-redraw-results/marked-pane-status.result +++ b/regress/screen-redraw-results/marked-pane-status.result @@ -1,12 +1,12 @@ -── 0:left ──────────┬── 1:right ──────── - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ +── 0:left ──────────┬── 1:right ──────── + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ diff --git a/regress/screen-redraw-results/marked-pane-tb.result b/regress/screen-redraw-results/marked-pane-tb.result index 86181f199..23fe88924 100644 --- a/regress/screen-redraw-results/marked-pane-tb.result +++ b/regress/screen-redraw-results/marked-pane-tb.result @@ -4,7 +4,7 @@ -──────────────────────────────────────── +──────────────────────────────────────── diff --git a/regress/screen-redraw-results/marked-pane-three.result b/regress/screen-redraw-results/marked-pane-three.result index 3f4f671ec..f7b776713 100644 --- a/regress/screen-redraw-results/marked-pane-three.result +++ b/regress/screen-redraw-results/marked-pane-three.result @@ -1,12 +1,12 @@ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ diff --git a/regress/screen-redraw-results/scrollbar-floating.result b/regress/screen-redraw-results/scrollbar-floating.result index 9ed4caff1..51542ad0d 100644 --- a/regress/screen-redraw-results/scrollbar-floating.result +++ b/regress/screen-redraw-results/scrollbar-floating.result @@ -1,12 +1,12 @@ SB00 abcdefghij  SB01 abcdefghij  -SB02 ab┌────────────────────┐  -SB03 ab│FLOAT00 abcdef  │  -SB04 ab│FLOAT01 abcdef  │  -SB05 ab│FLOAT02 abcdef  │  -SB06 ab│FLOAT03 abcdef  │  -SB07 ab│FLOAT04 abcdef  │  -SB08 ab│  │  -SB09 ab└────────────────────┘  +SB02 abcdefghij  +SB03 abc┌──────────────────┐  +SB04 abc│FLOAT02 abcdef  │  +SB05 abc│FLOAT03 abcdef  │  +SB06 abc│FLOAT04 abcdef  │  +SB07 abc│  │  +SB08 abc└──────────────────┘  +SB09 abcdefghij  SB10 abcdefghij    diff --git a/regress/screen-redraw-results/scrollbar-split-left.result b/regress/screen-redraw-results/scrollbar-split-left.result index 23938119f..9d602a4d6 100644 --- a/regress/screen-redraw-results/scrollbar-split-left.result +++ b/regress/screen-redraw-results/scrollbar-split-left.result @@ -1,12 +1,12 @@ - SB00 abcdefghij │ SBL00 abcdefghij - SB01 abcdefghij │ SBL01 abcdefghij - SB02 abcdefghij │ SBL02 abcdefghij - SB03 abcdefghij │ SBL03 abcdefghij - SB04 abcdefghij │ SBL04 abcdefghij - SB05 abcdefghij │ SBL05 abcdefghij - SB06 abcdefghij │ SBL06 abcdefghij - SB07 abcdefghij │ SBL07 abcdefghij - SB08 abcdefghij │ SBL08 abcdefghij - SB09 abcdefghij │ SBL09 abcdefghij - SB10 abcdefghij │ SBL10 abcdefghij -  │  + SB00 abcdefghij │ SBL00 abcdefghij + SB01 abcdefghij │ SBL01 abcdefghij + SB02 abcdefghij │ SBL02 abcdefghij + SB03 abcdefghij │ SBL03 abcdefghij + SB04 abcdefghij │ SBL04 abcdefghij + SB05 abcdefghij │ SBL05 abcdefghij + SB06 abcdefghij │ SBL06 abcdefghij + SB07 abcdefghij │ SBL07 abcdefghij + SB08 abcdefghij │ SBL08 abcdefghij + SB09 abcdefghij │ SBL09 abcdefghij + SB10 abcdefghij │ SBL10 abcdefghij +  │  diff --git a/regress/screen-redraw-results/scrollbar-split-right.result b/regress/screen-redraw-results/scrollbar-split-right.result index ab6184619..ab1d465ef 100644 --- a/regress/screen-redraw-results/scrollbar-split-right.result +++ b/regress/screen-redraw-results/scrollbar-split-right.result @@ -1,12 +1,12 @@ -SB00 abcdefghij  │SBR00 abcdefghij  -SB01 abcdefghij  │SBR01 abcdefghij  -SB02 abcdefghij  │SBR02 abcdefghij  -SB03 abcdefghij  │SBR03 abcdefghij  -SB04 abcdefghij  │SBR04 abcdefghij  -SB05 abcdefghij  │SBR05 abcdefghij  -SB06 abcdefghij  │SBR06 abcdefghij  -SB07 abcdefghij  │SBR07 abcdefghij  -SB08 abcdefghij  │SBR08 abcdefghij  -SB09 abcdefghij  │SBR09 abcdefghij  -SB10 abcdefghij  │SBR10 abcdefghij  -  │  +SB00 abcdefghij  │SBR00 abcdefghij  +SB01 abcdefghij  │SBR01 abcdefghij  +SB02 abcdefghij  │SBR02 abcdefghij  +SB03 abcdefghij  │SBR03 abcdefghij  +SB04 abcdefghij  │SBR04 abcdefghij  +SB05 abcdefghij  │SBR05 abcdefghij  +SB06 abcdefghij  │SBR06 abcdefghij  +SB07 abcdefghij  │SBR07 abcdefghij  +SB08 abcdefghij  │SBR08 abcdefghij  +SB09 abcdefghij  │SBR09 abcdefghij  +SB10 abcdefghij  │SBR10 abcdefghij  +  │  diff --git a/regress/screen-redraw-results/window-style-active.result b/regress/screen-redraw-results/window-style-active.result index e32cc31ca..6210820aa 100644 --- a/regress/screen-redraw-results/window-style-active.result +++ b/regress/screen-redraw-results/window-style-active.result @@ -1,8 +1,8 @@ -STYLE00 abcdefghij │STYLE00 abcdefghij -STYLE01 abcdefghij │STYLE01 abcdefghij -STYLE02 abcdefghij │STYLE02 abcdefghij -STYLE03 abcdefghij │STYLE03 abcdefghij -STYLE04 abcdefghij │STYLE04 abcdefghij -STYLE05 abcdefghij │STYLE05 abcdefghij -STYLE06 abcdefghij │STYLE06 abcdefghij - │ +STYLE00 abcdefghij │STYLE00 abcdefghij +STYLE01 abcdefghij │STYLE01 abcdefghij +STYLE02 abcdefghij │STYLE02 abcdefghij +STYLE03 abcdefghij │STYLE03 abcdefghij +STYLE04 abcdefghij │STYLE04 abcdefghij +STYLE05 abcdefghij │STYLE05 abcdefghij +STYLE06 abcdefghij │STYLE06 abcdefghij + │ From 73fd01c9866062fdbbcc76f52f633515460c0f5f Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 11:36:22 +0000 Subject: [PATCH 46/71] Some colour fixes and tweaks (marked pane, cursor colours now work). --- mode-tree.c | 12 ++++++++---- options-table.c | 12 +++++++++++- prompt.c | 7 ++++++- tmux.1 | 9 +++++++-- tmux.h | 1 + tty.c | 4 +++- window-tree.c | 4 ++-- 7 files changed, 38 insertions(+), 11 deletions(-) diff --git a/mode-tree.c b/mode-tree.c index f6e3eb75b..06001a672 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -832,6 +832,7 @@ mode_tree_draw(struct mode_tree_data *mtd) const char *tag, *separator; size_t n; int keylen, alignlen[mtd->maxdepth + 1]; + int dfg, dfg0; if (mtd->line_size == 0) return; @@ -842,6 +843,9 @@ mode_tree_draw(struct mode_tree_data *mtd) memcpy(&box_gc, &grid_default_cell, sizeof box_gc); style_apply(&box_gc, oo, "tree-mode-border-style", NULL); + dfg = gc.fg; + dfg0 = gc0.fg; + w = mtd->width; h = mtd->height; @@ -927,8 +931,8 @@ mode_tree_draw(struct mode_tree_data *mtd) width = prefix_width + text_width; if (mti->tagged) { - gc.attr ^= GRID_ATTR_BRIGHT; - gc0.attr ^= GRID_ATTR_BRIGHT; + gc.fg = COLOUR_THEME_CYAN|COLOUR_FLAG_THEME; + gc0.fg = COLOUR_THEME_CYAN|COLOUR_FLAG_THEME; } if (i != mtd->current) { @@ -965,8 +969,8 @@ mode_tree_draw(struct mode_tree_data *mtd) free(prefix); if (mti->tagged) { - gc.attr ^= GRID_ATTR_BRIGHT; - gc0.attr ^= GRID_ATTR_BRIGHT; + gc.fg = dfg; + gc0.fg = dfg0; } } format_free(ft); diff --git a/options-table.c b/options-table.c index d47bd03ca..b9ae14970 100644 --- a/options-table.c +++ b/options-table.c @@ -274,6 +274,7 @@ const struct options_name_map options_other_names[] = { { "clock-mode-color", "clock-mode-colour" }, { "cursor-color", "cursor-colour" }, { "prompt-cursor-color", "prompt-cursor-colour" }, + { "prompt-command-cursor-color", "prompt-command-cursor-colour" }, { "pane-colors", "pane-colours" }, { NULL, NULL } }; @@ -586,7 +587,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "#{?#{e|>=:#{client_colours},256},darkseagreen,green}", + .default_str = "#{?#{e|>=:#{client_colours},256},yellowgreen,green}", .text = "Dark theme colour for green." }, @@ -1173,6 +1174,15 @@ const struct options_table_entry options_table[] = { .text = "Colour of the cursor when in the command prompt." }, + { .name = "prompt-command-cursor-colour", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SESSION, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "", + .text = "Colour of the cursor in the command prompt when in command " + "mode, if 'status-keys' is set to 'vi'." + }, + { .name = "prompt-cursor-style", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, diff --git a/prompt.c b/prompt.c index 57e267374..4612a2e5f 100644 --- a/prompt.c +++ b/prompt.c @@ -45,6 +45,7 @@ struct prompt { enum screen_cursor_style cstyle; enum screen_cursor_style command_cstyle; int ccolour; + int command_ccolour; int cmode; int command_cmode; @@ -123,6 +124,8 @@ prompt_set_options(struct prompt_create_data *pd, struct session *s) screen_set_cursor_style(n, &pd->command_cstyle, &pd->command_cmode); style_apply(&gc, oo, "prompt-cursor-colour", NULL); pd->ccolour = gc.fg; + style_apply(&gc, oo, "prompt-command-cursor-colour", NULL); + pd->command_ccolour = gc.fg; pd->message_format = options_get_string(oo, "message-format"); pd->keys = options_get_number(oo, "status-keys"); pd->word_separators = options_get_string(oo, "word-separators"); @@ -177,6 +180,7 @@ prompt_create(const struct prompt_create_data *pd) pr->cstyle = pd->cstyle; pr->command_cstyle = pd->command_cstyle; pr->ccolour = pd->ccolour; + pr->command_ccolour = pd->command_ccolour; pr->cmode = pd->cmode; pr->command_cmode = pd->command_cmode; pr->message_format = xstrdup(pd->message_format); @@ -466,12 +470,13 @@ prompt_draw(struct prompt *pr, struct prompt_draw_data *pd) memcpy(&gc, &pr->command_style, sizeof gc); s->default_cstyle = pr->command_cstyle; s->default_mode = pr->command_cmode; + s->default_ccolour = pr->command_ccolour; } else { memcpy(&gc, &pr->style, sizeof gc); s->default_cstyle = pr->cstyle; s->default_mode = pr->cmode; + s->default_ccolour = pr->ccolour; } - s->default_ccolour = pr->ccolour; expanded = prompt_expand(pr); start = format_width(expanded); diff --git a/tmux.1 b/tmux.1 index 4cc1ca335..25ed0f221 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5249,14 +5249,19 @@ is input before dismissing it. Can be set to zero to disable any timeout. .It Ic prompt\-cursor\-colour Ar colour Set the colour of the cursor in the command prompt. +.It Ic prompt\-command\-cursor\-colour Ar colour +Set the colour of the cursor in the command prompt when +.Xr vi 1 +keys are enabled and the prompt is in command mode. .It Ic prompt\-cursor\-style Ar style Set the style of the cursor in the command prompt. See the .Ic cursor\-style options for available styles. .It Ic prompt\-command\-cursor\-style Ar style -Set the style of the cursor in the command prompt when vi keys are enabled and -the prompt is in command mode. +Set the style of the cursor in the command prompt when +.Xr vi 1 +keys are enabled and the prompt is in command mode. See the .Ic cursor\-style options for available styles. diff --git a/tmux.h b/tmux.h index 266cf9c7a..c6ddaf6e7 100644 --- a/tmux.h +++ b/tmux.h @@ -2068,6 +2068,7 @@ struct prompt_create_data { enum screen_cursor_style cstyle; enum screen_cursor_style command_cstyle; int ccolour; + int command_ccolour; int cmode; int command_cmode; const char *message_format; diff --git a/tty.c b/tty.c index fa1b45076..ce5012191 100644 --- a/tty.c +++ b/tty.c @@ -748,8 +748,10 @@ tty_force_cursor_colour(struct tty *tty, int c) u_char r, g, b; char s[13]; - if (c != -1) + if (c != -1) { + c = tty_map_theme_colour(tty, c); c = colour_force_rgb(c); + } if (c == tty->ccolour) return; if (c == -1) diff --git a/window-tree.c b/window-tree.c index 31cef5dca..59e41122b 100644 --- a/window-tree.c +++ b/window-tree.c @@ -38,11 +38,11 @@ static void window_tree_key(struct window_mode_entry *, #define WINDOW_TREE_DEFAULT_FORMAT \ "#{?pane_format," \ - "#{?pane_marked,#[reverse],}#{?pane_floating_flag,#[italics],}" \ + "#{?pane_marked,#[fg=thememagenta],}#{?pane_floating_flag,#[underscore],}" \ "#{pane_current_command}#[fg=themelightgrey]#{pane_flags}" \ "#{?#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}},: \"#{pane_title}\",}" \ ",window_format," \ - "#{?window_marked_flag,#[reverse],}" \ + "#{?window_marked_flag,#[fg=thememagenta],}" \ "#{window_name}#[fg=themelightgrey]#{window_flags}" \ "#{?#{&&:#{==:#{window_panes},1},#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}}},: \"#{pane_title}\",}" \ "," \ From 4869a5165ad3d339c71512756120e09480959598 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 12:06:17 +0000 Subject: [PATCH 47/71] Add tree-mode-selection-style to continue the process of moving away from mode-style. --- mode-tree.c | 2 +- options-table.c | 9 +++++++++ tmux.1 | 8 ++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/mode-tree.c b/mode-tree.c index 06001a672..afa04bd0f 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -839,7 +839,7 @@ mode_tree_draw(struct mode_tree_data *mtd) memcpy(&gc0, &grid_default_cell, sizeof gc0); memcpy(&gc, &grid_default_cell, sizeof gc); - style_apply(&gc, oo, "mode-style", NULL); + style_apply(&gc, oo, "tree-mode-selection-style", NULL); memcpy(&box_gc, &grid_default_cell, sizeof box_gc); style_apply(&box_gc, oo, "tree-mode-border-style", NULL); diff --git a/options-table.c b/options-table.c index b9ae14970..e07b61fbb 100644 --- a/options-table.c +++ b/options-table.c @@ -1719,6 +1719,15 @@ const struct options_table_entry options_table[] = { .text = "Style of preview indicator in tree mode." }, + { .name = "tree-mode-selection-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "#{E:mode-style}", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the selected line in tree mode." + }, + { .name = "window-active-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, diff --git a/tmux.1 b/tmux.1 index 25ed0f221..3df69e546 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5913,6 +5913,14 @@ see the .Sx STYLES section. .Pp +.It Ic tree\-mode\-selection\-style Ar style +Set the style of the selected line in tree mode. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp .It Ic window\-status\-activity\-style Ar style Set status line style for windows with an activity alert. For how to specify From ec3c65122f01e8658f9c4146da925e97511e8eed Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 12:41:08 +0000 Subject: [PATCH 48/71] message-command-style should be inverse of message-style. --- options-table.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/options-table.c b/options-table.c index e07b61fbb..71c8b1377 100644 --- a/options-table.c +++ b/options-table.c @@ -911,7 +911,9 @@ const struct options_table_entry options_table[] = { { .name = "message-command-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=themegreen,fg=themeblack,fill=themegreen", + .default_str = "bg=themeblack,fg=themeyellow," + "#{?#{m/r:(^|#,)IS(PANE|MODE)($|#,),#{prompt_flags}},," + "fill=themeblack}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the command prompt when in command mode, if " From f59921ce184d27b4f11c62a4914bc2a8441b4667 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 12:52:36 +0000 Subject: [PATCH 49/71] Use underscore for current in status line. --- options-table.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/options-table.c b/options-table.c index 71c8b1377..f73335681 100644 --- a/options-table.c +++ b/options-table.c @@ -1152,7 +1152,7 @@ const struct options_table_entry options_table[] = { { .name = "pane-status-current-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default", + .default_str = "underscore", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the current pane in the status line." @@ -1205,7 +1205,7 @@ const struct options_table_entry options_table[] = { { .name = "session-status-current-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default", + .default_str = "underscore", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the current session in the status line." @@ -1803,7 +1803,7 @@ const struct options_table_entry options_table[] = { { .name = "window-status-current-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default", + .default_str = "underscore", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the current window in the status line." From 973c4ab56929eeaf83e3270c48fcaba513d9a8a0 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 13:58:00 +0000 Subject: [PATCH 50/71] Drop mouse movement events rather than redrawing unnecessarily. --- window.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/window.c b/window.c index 9d3106a22..ac71a72bd 100644 --- a/window.c +++ b/window.c @@ -1601,6 +1601,12 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s, wme = TAILQ_FIRST(&wp->modes); if (wme != NULL) { + /* + * No mode uses mouse motion events, so drop them here rather + * than passing them on and causing a redraw on every movement. + */ + if (KEYC_IS_TYPE(key, KEYC_TYPE_MOUSEMOVE)) + return (0); if (wme->mode->key != NULL && c != NULL) { key &= ~KEYC_MASK_FLAGS; wme->mode->key(wme, c, s, wl, key, m); From 575f84ce0fae5eb43cbd76b357834b8577616057 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 14:40:30 +0000 Subject: [PATCH 51/71] Add switch-mode a fast switcher with fuzzy searching, bound to Tab (for windows) or BTab (S-Tab, for sessions) by default. --- Makefile | 6 +- cmd-choose-tree.c | 15 ++ cmd.c | 2 + format.c | 45 ++++ fuzzy.c | 655 ++++++++++++++++++++++++++++++++++++++++++++++ key-bindings.c | 2 + mode-tree.c | 5 +- options-table.c | 9 + prompt.c | 11 +- tmux.1 | 85 +++++- tmux.h | 7 + window-switch.c | 636 ++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 1467 insertions(+), 11 deletions(-) create mode 100644 fuzzy.c create mode 100644 window-switch.c diff --git a/Makefile b/Makefile index cfab9c7bb..ec1f2ff56 100644 --- a/Makefile +++ b/Makefile @@ -78,6 +78,7 @@ SRCS= alerts.c \ environ.c \ file.c \ format.c \ + fuzzy.c \ format-draw.c \ grid-reader.c \ grid-view.c \ @@ -133,6 +134,7 @@ SRCS= alerts.c \ window-clock.c \ window-copy.c \ window-customize.c \ + window-switch.c \ window-visible.c \ window-tree.c \ window.c \ @@ -145,7 +147,7 @@ CDIAGFLAGS+= -Wundef -Wbad-function-cast -Winline -Wcast-align CFLAGS += -I${.CURDIR} -LDADD= -lutil -lcurses -levent -lm -DPADD= ${LIBUTIL} ${LIBCURSES} ${LIBEVENT} ${LIBM} +LDADD= -lutil -lcurses -levent -lm +DPADD= ${LIBUTIL} ${LIBCURSES} ${LIBEVENT} ${LIBM} .include diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 87c29419c..4a6bd152b 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -84,6 +84,19 @@ const struct cmd_entry cmd_customize_mode_entry = { .exec = cmd_choose_tree_exec }; +const struct cmd_entry cmd_switch_mode_entry = { + .name = "switch-mode", + .alias = NULL, + + .args = { "F:kst:wZ", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-kswZ] [-F format] " CMD_TARGET_PANE_USAGE " [command]", + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = 0, + .exec = cmd_choose_tree_exec +}; + static enum args_parse_type cmd_choose_tree_args_parse(__unused struct args *args, __unused u_int idx, __unused char **cause) @@ -116,6 +129,8 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item) mode = &window_client_mode; } else if (cmd_get_entry(self) == &cmd_customize_mode_entry) mode = &window_customize_mode; + else if (cmd_get_entry(self) == &cmd_switch_mode_entry) + mode = &window_switch_mode; else mode = &window_tree_mode; diff --git a/cmd.c b/cmd.c index d20b87366..a2d3fad70 100644 --- a/cmd.c +++ b/cmd.c @@ -116,6 +116,7 @@ extern const struct cmd_entry cmd_suspend_client_entry; extern const struct cmd_entry cmd_swap_pane_entry; extern const struct cmd_entry cmd_swap_window_entry; extern const struct cmd_entry cmd_switch_client_entry; +extern const struct cmd_entry cmd_switch_mode_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_wait_for_entry; @@ -209,6 +210,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_swap_pane_entry, &cmd_swap_window_entry, &cmd_switch_client_entry, + &cmd_switch_mode_entry, &cmd_unbind_key_entry, &cmd_unlink_window_entry, &cmd_wait_for_entry, diff --git a/format.c b/format.c index 75b00e2bf..8af01342d 100644 --- a/format.c +++ b/format.c @@ -4537,6 +4537,47 @@ format_build_modifiers(struct format_expand_state *es, const char **s, return (list); } +/* Match using the fuzzy matcher. */ +static char * +format_match_fuzzy(const char *pattern, const char *text, int positions) +{ + struct evbuffer *buffer; + bitstr_t *bs; + char *value; + size_t size; + u_int i, width; + + width = format_width(text); + if (width == 0) + width = 1; + bs = fuzzy_match(pattern, text, width, NULL); + if (bs == NULL) + return (xstrdup(positions ? "" : "0")); + + if (!positions) { + free(bs); + return (xstrdup("1")); + } + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + for (i = 0; i < width; i++) { + if (!bit_test(bs, i)) + continue; + if (EVBUFFER_LENGTH(buffer) != 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%u", i); + } + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + value = xmemdup(EVBUFFER_DATA(buffer), size); + else + value = xstrdup(""); + evbuffer_free(buffer); + free(bs); + return (value); +} + /* Match against an fnmatch(3) pattern or regular expression. */ static char * format_match(struct format_modifier *fm, const char *pattern, const char *text) @@ -4547,6 +4588,10 @@ format_match(struct format_modifier *fm, const char *pattern, const char *text) if (fm->argc >= 1) s = fm->argv[0]; + if (strchr(s, 'p') != NULL) + return (format_match_fuzzy(pattern, text, 1)); + if (strchr(s, 'z') != NULL) + return (format_match_fuzzy(pattern, text, 0)); if (strchr(s, 'r') == NULL) { if (strchr(s, 'i') != NULL) flags |= FNM_CASEFOLD; diff --git a/fuzzy.c b/fuzzy.c new file mode 100644 index 000000000..00fc90687 --- /dev/null +++ b/fuzzy.c @@ -0,0 +1,655 @@ +/* $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 +#include + +#include "tmux.h" + +/* + * Fuzzy matching in the style of fzf. The pattern is split into groups by | + * and each group is split on spaces into terms. A row matches if any group + * matches; within a group all positive terms must match and all inverse terms + * must not match. + * + * Plain positive terms are fuzzy subsequences. A leading ' makes a term an + * exact substring match, ^ anchors a term at the start and $ anchors it at + * the end. A leading ! inverts the term. Plain inverse terms are exact + * substring matches rather than inverse fuzzy matches, like fzf. + * + * Both the pattern and the text are UTF-8. The text may contain tmux style + * directives (#[...]); these and their contents are invisible to matching and + * occupy no columns, but align= styles do move the surrounding text and are + * accounted for exactly as format_draw lays it out (the no-list layout, see + * format_draw_none). Matching is smart-case: case is ignored unless the pattern + * contains an uppercase character (ASCII case folding only; other characters + * are compared exactly by their UTF-8 data). + * + * On a match a bitstr_t of the requested display width is returned with a bit + * set for every column occupied by a matched character, so the caller can + * highlight them; NULL is returned if there is no match. A cheap fzf-style + * score (matches at the start, after word boundaries and in contiguous runs + * score higher) is also produced so callers can rank best-match-first. + */ + +#define FUZZY_BONUS_EXACT 1000 +#define FUZZY_BONUS_PREFIX 200 +#define FUZZY_BONUS_SUFFIX 100 +#define FUZZY_BONUS_START 12 +#define FUZZY_BONUS_BOUNDARY 8 +#define FUZZY_BONUS_CONSECUTIVE 6 +#define FUZZY_PENALTY_LEADING 1 +#define FUZZY_PENALTY_LEADING_MAX 10 +#define FUZZY_PENALTY_GAP 1 + +/* A single visible character of the text. */ +struct fuzzy_char { + enum style_align align; + struct utf8_data ud; /* original UTF-8 data */ + u_int width; /* display width */ + u_int offset; /* within its alignment */ +}; + +/* One parsed query term. */ +struct fuzzy_term { + int inverse; + int exact; + int prefix; + int suffix; + const char *text; + size_t len; +}; + +/* Is this character a word boundary, so a match after it scores higher? */ +static int +fuzzy_is_boundary(const struct utf8_data *ud) +{ + static const char *boundary = " -_/.:"; + + if (ud->size != 1) + return (0); + return (strchr(boundary, ud->data[0]) != NULL); +} + +/* + * Compare two characters, folding ASCII case if wanted. UTF-8 is compared + * directly without case folding. + */ +static int +fuzzy_char_equal(const struct utf8_data *a, const struct utf8_data *b, int fold) +{ + if (fold && + a->size == 1 && + b->size == 1 && + a->data[0] < 0x80 && + b->data[0] < 0x80) + return (tolower(a->data[0]) == tolower(b->data[0])); + return (a->size == b->size && memcmp(a->data, b->data, a->size) == 0); +} + +/* Map a style alignment onto one of the four layout columns. */ +static enum style_align +fuzzy_align(enum style_align align) +{ + if (align == STYLE_ALIGN_DEFAULT) + return (STYLE_ALIGN_LEFT); + return (align); +} + +/* Add a visible character to the array, updating the alignment width. */ +static void +fuzzy_add(struct fuzzy_char **cs, u_int *ncs, u_int *alloc, enum style_align a, + const struct utf8_data *ud, u_int *widths) +{ + struct fuzzy_char *fc; + + if (*ncs == *alloc) { + *alloc = (*alloc == 0) ? 64 : *alloc * 2; + *cs = xreallocarray(*cs, *alloc, sizeof **cs); + } + fc = &(*cs)[(*ncs)++]; + fc->align = a; + memcpy(&fc->ud, ud, sizeof fc->ud); + fc->width = ud->width; + fc->offset = widths[a]; + widths[a] += ud->width; +} + +/* Decode a character as UTF-8. */ +static const char * +fuzzy_decode_one(const char *cp, const char *end, struct utf8_data *ud) +{ + enum utf8_state more; + const char *start = cp; + + if ((more = utf8_open(ud, (u_char)*cp)) == UTF8_MORE) { + while (++cp != end && more == UTF8_MORE) + more = utf8_append(ud, (u_char)*cp); + if (more == UTF8_DONE) + return (cp); + cp = start; + } + utf8_set(ud, (u_char)*cp); + return (cp + 1); +} + +/* + * Scan the text into an array of visible characters, skipping styles and + * recording the alignment and intra-alignment offset of each. Returns the + * array and its length and fills in the per-alignment widths. + */ +static struct fuzzy_char * +fuzzy_scan(const char *text, u_int *ncs, u_int *widths) +{ + struct fuzzy_char *cs = NULL; + u_int alloc = 0, n, leading, i; + enum style_align current = STYLE_ALIGN_LEFT; + struct style sy; + const char *cp = text, *textend = text + strlen(text); + const char *end; + struct utf8_data ud, hash, bracket; + char *tmp; + + *ncs = 0; + memset(widths, 0, sizeof *widths * (STYLE_ALIGN_ABSOLUTE_CENTRE + 1)); + style_set(&sy, &grid_default_cell); + utf8_set(&hash, '#'); + utf8_set(&bracket, '['); + + while (*cp != '\0') { + /* Handle a run of #s, which may introduce a style. */ + if (*cp == '#') { + for (n = 0; cp[n] == '#'; n++) + /* nothing */; + if (cp[n] != '[') { + /* Escaped #s: ##->#, so half (rounded up). */ + leading = (n % 2 == 0) ? n / 2 : n / 2 + 1; + for (i = 0; i < leading; i++) { + fuzzy_add(&cs, ncs, &alloc, current, + &hash, widths); + } + cp += n; + continue; + } + + /* Even count: all #s escaped, the [ is literal. */ + for (i = 0; i < n / 2; i++) + fuzzy_add(&cs, ncs, &alloc, current, &hash, + widths); + if (n % 2 == 0) { + fuzzy_add(&cs, ncs, &alloc, current, &bracket, + widths); + cp += n + 1; + continue; + } + + /* Odd count: this is a style, find and parse it. */ + end = format_skip(cp + n + 1, "]"); + if (end == NULL) + break; + tmp = xstrndup(cp + n + 1, end - (cp + n + 1)); + if (style_parse(&sy, &grid_default_cell, tmp) == 0) + current = fuzzy_align(sy.align); + free(tmp); + cp = end + 1; + continue; + } + + /* Decode one character, multibyte or single byte. */ + cp = fuzzy_decode_one(cp, textend, &ud); + + /* + * Skip non-printable single bytes (control characters and raw + * bytes left over from a failed decode); keep printable ASCII + * and any decoded UTF-8. + */ + if (ud.size == 1 && (ud.data[0] <= 0x1f || ud.data[0] >= 0x7f)) + continue; + fuzzy_add(&cs, ncs, &alloc, current, &ud, widths); + } + return (cs); +} + +/* + * Work out the display column of a visible character given the trimmed widths + * and start columns of each alignment. Returns 0 and sets the column if the + * character is visible, otherwise returns -1. + */ +static int +fuzzy_column(const struct fuzzy_char *fc, const u_int *start, const u_int *src, + const u_int *vis, u_int *column) +{ + enum style_align a = fc->align; + + if (fc->offset < src[a] || fc->offset >= src[a] + vis[a]) + return (-1); + *column = start[a] + (fc->offset - src[a]); + return (0); +} + +/* Decode a UTF-8 term into an array of characters. */ +static u_int +fuzzy_decode(const char *tok, size_t len, struct utf8_data *out) +{ + const char *cp = tok, *end = tok + len; + u_int n = 0; + + while (cp != end) + cp = fuzzy_decode_one(cp, end, &out[n++]); + return (n); +} + +/* Add the score for a fuzzy token matched at the given positions. */ +static int +fuzzy_score_positions(const u_int *pos, u_int npos, const struct fuzzy_char *cs) +{ + u_int i, gap, span; + int score = 0; + + if (npos == 0) + return (0); + if (pos[0] == 0) + score += FUZZY_BONUS_START; + else { + if (fuzzy_is_boundary(&cs[pos[0] - 1].ud)) + score += FUZZY_BONUS_BOUNDARY; + if (pos[0] < FUZZY_PENALTY_LEADING_MAX) + score -= pos[0] * FUZZY_PENALTY_LEADING; + else { + score -= FUZZY_PENALTY_LEADING_MAX * + FUZZY_PENALTY_LEADING; + } + } + for (i = 1; i < npos; i++) { + if (pos[i] == pos[i - 1] + 1) + score += FUZZY_BONUS_CONSECUTIVE; + else if (fuzzy_is_boundary(&cs[pos[i] - 1].ud)) + score += FUZZY_BONUS_BOUNDARY; + } + span = pos[npos - 1] - pos[0] + 1; + gap = span - npos; + score -= gap * FUZZY_PENALTY_GAP; + return (score); +} + +/* + * Match a token as a subsequence of the visible characters. Returns if the + * token matches. + */ +static int +fuzzy_match_fuzzy(const struct utf8_data *tok, u_int toklen, + struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched) +{ + u_int pi, ci, *pos; + int found, value; + + if (toklen == 0 || ncs == 0) + return (0); + pos = xcalloc(toklen, sizeof *pos); + + /* First find a subsequence from the start. */ + ci = 0; + for (pi = 0; pi < toklen; pi++) { + while (ci != ncs && + !fuzzy_char_equal(&tok[pi], &cs[ci].ud, fold)) + ci++; + if (ci == ncs) { + free(pos); + return (0); + } + pos[pi] = ci++; + } + + /* Then compact it backwards to prefer a shorter span. */ + ci = pos[toklen - 1]; + for (pi = toklen; pi > 0; pi--) { + found = 0; + for (;;) { + if (fuzzy_char_equal(&tok[pi - 1], &cs[ci].ud, fold)) { + pos[pi - 1] = ci; + found = 1; + break; + } + if (ci == 0) + break; + ci--; + } + if (!found) { + free(pos); + return (0); + } + if (pi != 1) + ci--; + } + + value = fuzzy_score_positions(pos, toklen, cs); + *score += value; + for (pi = 0; pi < toklen; pi++) + matched[pos[pi]] = 1; + free(pos); + return (1); +} + +/* Score an exact, prefix or suffix match. */ +static int +fuzzy_score_exact(u_int start, u_int toklen, u_int ncs, + const struct fuzzy_char *cs, int prefix, int suffix) +{ + int score; + + score = FUZZY_BONUS_EXACT + toklen * FUZZY_BONUS_CONSECUTIVE; + if (prefix) + score += FUZZY_BONUS_PREFIX; + if (suffix) + score += FUZZY_BONUS_SUFFIX; + if (start == 0) + score += FUZZY_BONUS_START; + else if (fuzzy_is_boundary(&cs[start - 1].ud)) + score += FUZZY_BONUS_BOUNDARY; + if (start < FUZZY_PENALTY_LEADING_MAX) + score -= start * FUZZY_PENALTY_LEADING; + else + score -= FUZZY_PENALTY_LEADING_MAX * FUZZY_PENALTY_LEADING; + if (!prefix && !suffix) + score -= ncs - (start + toklen); + return (score); +} + +/* Match an exact, prefix or suffix term against the visible characters. */ +static int +fuzzy_match_exact(const struct utf8_data *tok, u_int toklen, + struct fuzzy_char *cs, u_int ncs, int fold, int prefix, int suffix, + int *score, char *matched) +{ + u_int start, end, i, j, best = 0; + int ok, found = 0, value, bestscore = 0; + + if (toklen == 0 || toklen > ncs) + return (0); + + if (prefix && suffix) { + if (toklen != ncs) + return (0); + start = 0; + end = 1; + } else if (prefix) { + start = 0; + end = 1; + } else if (suffix) { + start = ncs - toklen; + end = start + 1; + } else { + start = 0; + end = ncs - toklen + 1; + } + + for (i = start; i < end; i++) { + ok = 1; + for (j = 0; j < toklen; j++) { + if (!fuzzy_char_equal(&tok[j], &cs[i + j].ud, fold)) { + ok = 0; + break; + } + } + if (!ok) + continue; + value = fuzzy_score_exact(i, toklen, ncs, cs, prefix, suffix); + if (!found || value > bestscore) { + found = 1; + best = i; + bestscore = value; + } + } + if (!found) + return (0); + *score += bestscore; + if (matched != NULL) { + for (i = 0; i < toklen; i++) + matched[best + i] = 1; + } + return (1); +} + +/* Parse one term. */ +static int +fuzzy_parse_term(const char *start, const char *end, struct fuzzy_term *term) +{ + memset(term, 0, sizeof *term); + if (start == end) + return (0); + if (*start == '!') { + term->inverse = 1; + start++; + } + if (start == end) + return (0); + if (*start == '\'') { + term->exact = 1; + start++; + } else if (*start == '^') { + term->exact = 1; + term->prefix = 1; + start++; + } + if (start == end) + return (0); + if (end[-1] == '$') { + term->exact = 1; + term->suffix = 1; + end--; + } + if (start == end) + return (0); + + if (term->inverse) + term->exact = 1; + term->text = start; + term->len = end - start; + return (1); +} + +/* Match one parsed term. */ +static int +fuzzy_match_term(const struct fuzzy_term *term, struct utf8_data *tok, + struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched) +{ + u_int toklen; + int value = 0, matched_term; + + toklen = fuzzy_decode(term->text, term->len, tok); + if (term->exact) { + matched_term = fuzzy_match_exact(tok, toklen, cs, ncs, fold, + term->prefix, term->suffix, &value, + term->inverse ? NULL : matched); + } else { + matched_term = fuzzy_match_fuzzy(tok, toklen, cs, ncs, fold, + &value, term->inverse ? NULL : matched); + } + + if (term->inverse) + return (!matched_term); + if (!matched_term) + return (0); + *score += value; + return (1); +} + +/* Match one AND group of terms. */ +static int +fuzzy_match_group(const char *start, const char *end, struct utf8_data *tok, + struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched) +{ + const char *cp = start, *sp; + struct fuzzy_term term; + int any = 0; + + *score = 0; + while (cp != end) { + while (cp != end && *cp == ' ') + cp++; + if (cp == end) + break; + sp = cp; + while (cp != end && *cp != ' ') + cp++; + if (!fuzzy_parse_term(sp, cp, &term)) + return (0); + any = 1; + if (!fuzzy_match_term(&term, tok, cs, ncs, fold, score, + matched)) + return (0); + } + return (any); +} + +/* + * Fuzzy match pattern against text, which is drawn into a region of the given + * display width. Returns a bitstr_t of width bits with a bit set for each + * column occupied by a matched character, or NULL if there is no match. A + * higher returned score is better. + */ +bitstr_t * +fuzzy_match(const char *pattern, const char *text, u_int width, u_int *score) +{ + struct fuzzy_char *cs; + char *matched = NULL, *best = NULL, *groupmatched; + struct utf8_data *tok; + bitstr_t *mask; + u_int ncs, i, j, column; + u_int widths[STYLE_ALIGN_ABSOLUTE_CENTRE + 1]; + u_int start[STYLE_ALIGN_ABSOLUTE_CENTRE + 1]; + u_int src[STYLE_ALIGN_ABSOLUTE_CENTRE + 1]; + u_int vis[STYLE_ALIGN_ABSOLUTE_CENTRE + 1]; + u_int wl, wc, wr, wa; + const char *cp, *sp; + int bestscore = 0, groupscore, found = 0, fold; + + if (width == 0) + return (NULL); + + /* An empty query matches everything, with nothing highlighted. */ + for (cp = pattern; *cp == ' ' || *cp == '|'; cp++) + /* nothing */; + if (*cp == '\0') { + if (score != NULL) + *score = 0; + return (bit_alloc(width)); + } + + /* Smart-case: fold unless the pattern has an uppercase character. */ + fold = 1; + for (cp = pattern; *cp != '\0'; cp++) { + if (*cp >= 'A' && *cp <= 'Z') { + fold = 0; + break; + } + } + + /* Scan the text into visible characters. */ + cs = fuzzy_scan(text, &ncs, widths); + matched = xcalloc(ncs == 0 ? 1 : ncs, sizeof *matched); + best = xcalloc(ncs == 0 ? 1 : ncs, sizeof *best); + tok = xreallocarray(NULL, strlen(pattern) + 1, sizeof *tok); + + /* Match each |-separated group and keep the best-scoring one. */ + cp = pattern; + while (*cp != '\0') { + while (*cp == ' ' || *cp == '|') + cp++; + if (*cp == '\0') + break; + sp = cp; + while (*cp != '\0' && *cp != '|') + cp++; + memset(matched, 0, ncs == 0 ? 1 : ncs); + groupmatched = matched; + if (fuzzy_match_group(sp, cp, tok, cs, ncs, fold, + &groupscore, groupmatched)) { + if (!found || groupscore > bestscore) { + found = 1; + bestscore = groupscore; + memcpy(best, matched, ncs == 0 ? 1 : ncs); + } + } + } + free(tok); + if (!found) { + free(best); + free(matched); + free(cs); + return (NULL); + } + + /* + * Work out the trimmed widths and start columns of each alignment, + * mirroring format_draw_none. + */ + wl = widths[STYLE_ALIGN_LEFT]; + wc = widths[STYLE_ALIGN_CENTRE]; + wr = widths[STYLE_ALIGN_RIGHT]; + wa = widths[STYLE_ALIGN_ABSOLUTE_CENTRE]; + while (wl + wc + wr > width) { + if (wc > 0) + wc--; + else if (wr > 0) + wr--; + else + wl--; + } + if (wa > width) + wa = width; + + start[STYLE_ALIGN_LEFT] = 0; + src[STYLE_ALIGN_LEFT] = 0; + vis[STYLE_ALIGN_LEFT] = wl; + + start[STYLE_ALIGN_RIGHT] = width - wr; + src[STYLE_ALIGN_RIGHT] = widths[STYLE_ALIGN_RIGHT] - wr; + vis[STYLE_ALIGN_RIGHT] = wr; + + start[STYLE_ALIGN_CENTRE] = + wl + ((width - wr) - wl) / 2 - wc / 2; + src[STYLE_ALIGN_CENTRE] = widths[STYLE_ALIGN_CENTRE] / 2 - wc / 2; + vis[STYLE_ALIGN_CENTRE] = wc; + + start[STYLE_ALIGN_ABSOLUTE_CENTRE] = (width - wa) / 2; + src[STYLE_ALIGN_ABSOLUTE_CENTRE] = 0; + vis[STYLE_ALIGN_ABSOLUTE_CENTRE] = wa; + + /* Set a bit for each column of each matched character. */ + mask = bit_alloc(width); + for (i = 0; i < ncs; i++) { + if (!best[i]) + continue; + if (fuzzy_column(&cs[i], start, src, vis, &column) != 0) + continue; + for (j = 0; j < cs[i].width && column + j < width; j++) + bit_set(mask, column + j); + } + + free(best); + free(matched); + free(cs); + + if (score != NULL) + *score = (bestscore < 0) ? 0 : (u_int)bestscore; + return (mask); +} diff --git a/key-bindings.c b/key-bindings.c index 02ec14dfc..e5a58a94e 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -402,6 +402,8 @@ key_bindings_init(void) "bind -N 'Redraw the current client' r { refresh-client }", "bind -N 'Choose a session from a list' s { choose-tree -Zs }", "bind -N 'Show a clock' t { clock-mode }", + "bind -N 'Switch to a window' Tab { new-pane -E -x75% -y30% -X0 -Y0; move-pane -P bottom-centre; switch-mode -wk }", + "bind -N 'Switch to a session' BTab { new-pane -E -x75% -y30% -X0 -Y0; move-pane -P bottom-centre; switch-mode -sk }", "bind -N 'Choose a window from a list' w { choose-tree -Zw }", "bind -N 'Kill the active pane' x { confirm-before -p\"kill-pane #P? (y/n)\" kill-pane }", "bind -N 'Zoom the active pane' z { resize-pane -Z }", diff --git a/mode-tree.c b/mode-tree.c index afa04bd0f..fae0f3d8a 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -794,8 +794,8 @@ mode_tree_no_tag(struct mode_tree_item *mti) } /* - * Set the alignment of mti->name: -1 to align left, 0 (default) to not align, - * or 1 to align right. + * Set the alignment of the item name: -1 to align left, 0 (default) to not + * align, or 1 to align right. */ void mode_tree_align(struct mode_tree_item *mti, int align) @@ -1144,6 +1144,7 @@ mode_tree_set_prompt(struct mode_tree_data *mtd, struct client *c, mtp = xcalloc(1, sizeof *mtp); mtp->mtd = mtd; + mtp->c = c; mtp->inputcb = inputcb; mtp->freecb = freecb; mtp->data = data; diff --git a/options-table.c b/options-table.c index f73335681..ddd8b4634 100644 --- a/options-table.c +++ b/options-table.c @@ -1673,6 +1673,15 @@ const struct options_table_entry options_table[] = { "history when clearing the whole screen." }, + { .name = "switch-mode-match-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .default_str = "bg=cyan fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of matched characters in switch mode." + }, + { .name = "synchronize-panes", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, diff --git a/prompt.c b/prompt.c index 4612a2e5f..1ab447ebe 100644 --- a/prompt.c +++ b/prompt.c @@ -98,6 +98,8 @@ prompt_flags_to_string(int flags) strlcat(tmp, "ISPANE,", sizeof tmp); if (flags & PROMPT_ISMODE) strlcat(tmp, "ISMODE,", sizeof tmp); + if (flags & PROMPT_EDITARROWS) + strlcat(tmp, "EDITARROWS,", sizeof tmp); if (*tmp != '\0') tmp[strlen(tmp) - 1] = '\0'; return (tmp); @@ -536,8 +538,6 @@ prompt_mouse(struct prompt *pr, u_int x, u_int ax, u_int aw, int *redraw) if (x < ax || x >= ax + aw) return (PROMPT_KEY_NOT_HANDLED); - if (pr->flags & PROMPT_INCREMENTAL) - return (PROMPT_KEY_HANDLED); start = prompt_width(pr, aw); left = aw - start; @@ -1060,11 +1060,14 @@ prompt_check_move(struct prompt *pr, key_code key) switch (key) { case KEYC_UP: case KEYC_DOWN: - case KEYC_LEFT: - case KEYC_RIGHT: case KEYC_PPAGE: case KEYC_NPAGE: break; + case KEYC_LEFT: + case KEYC_RIGHT: + if (pr->flags & PROMPT_EDITARROWS) + return (PROMPT_KEY_NOT_HANDLED); + break; default: return (PROMPT_KEY_NOT_HANDLED); } diff --git a/tmux.1 b/tmux.1 index 3df69e546..58d055cff 100644 --- a/tmux.1 +++ b/tmux.1 @@ -360,6 +360,8 @@ Choose the current window interactively. Kill the current pane. .It z Toggle zoom state of the current pane. +.It Tab +Choose a new window and session by fuzzy matching. .It { Move floating pane to top-left corner. .It } @@ -3086,6 +3088,51 @@ The .Ic customize-mode command works only if at least one client is attached. .It Xo +.Ic switch\-mode +.Op Fl kswZ +.Op Fl F Ar format +.Op Fl t Ar target\-pane +.Op Ar command +.Xc +Put a pane into switch mode, where a session or window may be chosen +interactively from a list. +Each session or window is shown on one line and the list is narrowed by typing: +the typed text is matched against each item with fuzzy +matching and only matching items are shown, sorted by how well they match. +.Fl s +lists sessions (the default) and +.Fl w +lists windows. +.Fl Z +zooms the pane. +The following keys may be used in switch mode: +.Bl -column "KeyXXX" "Function" -offset indent +.It Sy "Key" Ta Sy "Function" +.It Li "Enter" Ta "Choose the selected item" +.It Li "Up" Ta "Select the previous item" +.It Li "Down" Ta "Select the next item" +.It Li "Escape" Ta "Exit mode" +.El +.Pp +After a session or window is chosen, the first instance of +.Ql %% +and all instances of +.Ql %1 +are replaced by the target in +.Ar command +and the result executed as a command. +If +.Ar command +is not given, "switch\-client \-Zt \[aq]%%\[aq]" is used. +.Fl F +specifies the format for each item in the list. +.Fl k +kills the pane when the mode is exited. +.Pp +The appearance of matched characters is controlled by the +.Ic switch\-mode\-match\-style +option. +.It Xo .Tg displayp .Ic display\-panes .Op Fl bN @@ -6100,6 +6147,15 @@ is enabled. When the entire screen is cleared and this option is on, scroll the contents of the screen into history before clearing it. .Pp +.It Ic switch\-mode\-match\-style Ar style +Set the style of characters matched by the filter in +.Ic switch\-mode . +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp .It Xo Ic synchronize\-panes .Op Ic on | off .Xc @@ -6492,13 +6548,36 @@ An optional argument specifies flags: .Ql r means the pattern is a regular expression instead of the default .Xr glob 7 -pattern and +pattern; .Ql i -means to ignore case. +means to ignore case; +.Ql z +means to do a fuzzy match; +.Ql p +is like +.Ql z +but returns a list of matched positions. +A fuzzy match matches plain terms as sequences where each character must appear +in order but not necessarily consecutively; terms beginning with +.Ql ' +are exact substring matches, +.Ql ^ +anchors a term at the start, +.Ql $ +anchors it at the end, +.Ql ! +inverts a term and +.Ql | +separates alternative groups. For example: .Ql #{m:*foo*,#{host}} or -.Ql #{m/ri:\[ha]A,MYVAR} . +.Ql #{m/ri:\[ha]A,MYVAR} +or +.Ql #{m/z:dev bash,dev:1 bash} +or +.Ql #{m/z:^dev | ^prod,prod:1 ssh} . +.Pp A .Ql C performs a search for a diff --git a/tmux.h b/tmux.h index c6ddaf6e7..d80e9b793 100644 --- a/tmux.h +++ b/tmux.h @@ -2054,6 +2054,7 @@ typedef void (*prompt_free_cb)(void *); #define PROMPT_COMMANDMODE 0x200 #define PROMPT_ISPANE 0x400 #define PROMPT_ISMODE 0x800 +#define PROMPT_EDITARROWS 0x1000 /* Prompt create data. */ struct prompt_create_data { @@ -3269,6 +3270,9 @@ void colour_palette_from_option(struct colour_palette *, struct options *); const char *attributes_tostring(int); int attributes_fromstring(const char *); +/* fuzzy.c */ +bitstr_t *fuzzy_match(const char *, const char *, u_int, u_int *); + /* grid.c */ extern const struct grid_cell grid_default_cell; void grid_empty_line(struct grid *, u_int, u_int); @@ -3725,6 +3729,9 @@ extern const struct window_mode window_buffer_mode; /* window-tree.c */ extern const struct window_mode window_tree_mode; +/* window-switch.c */ +extern const struct window_mode window_switch_mode; + /* window-clock.c */ extern const struct window_mode window_clock_mode; extern const char window_clock_table[14][5][5]; diff --git a/window-switch.c b/window-switch.c new file mode 100644 index 000000000..99ea19985 --- /dev/null +++ b/window-switch.c @@ -0,0 +1,636 @@ +/* $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 +#include + +#include "tmux.h" + +static struct screen *window_switch_init(struct window_mode_entry *, + struct cmd_find_state *, struct args *); +static void window_switch_free(struct window_mode_entry *); +static void window_switch_resize(struct window_mode_entry *, u_int, + u_int); +static void window_switch_key(struct window_mode_entry *, + struct client *, struct session *, + struct winlink *, key_code, struct mouse_event *); +static enum prompt_result window_switch_prompt_callback(void *, const char *, + enum prompt_key_result); + +#define WINDOW_SWITCH_DEFAULT_COMMAND "switch-client -Zt '%%'" + +#define WINDOW_SWITCH_DEFAULT_FORMAT \ + "#{?window_format," \ + "#{window_name} " \ + "#[dim]#{session_name}:#{window_index}#{window_flags}#[default] " \ + "#[dim]#{pane_current_command}#[default] " \ + "#[dim]#{?#{!=:#{pane_title},#{host_short}},#{pane_title},}#[default]" \ + "," \ + "#{session_name} " \ + "#[dim]#{session_windows} windows#[default] " \ + "#{?session_attached,attached,#[dim]detached#[default]} " \ + "#[dim]#{window_name}#[default]" \ + "}" + +const struct window_mode window_switch_mode = { + .name = "switch-mode", + .default_format = WINDOW_SWITCH_DEFAULT_FORMAT, + + .init = window_switch_init, + .free = window_switch_free, + .resize = window_switch_resize, + .key = window_switch_key, +}; + +enum window_switch_type { + WINDOW_SWITCH_TYPE_SESSION, + WINDOW_SWITCH_TYPE_WINDOW +}; + +struct window_switch_itemdata { + enum window_switch_type type; + int session; + int winlink; + + uint64_t tag; + char *text; + bitstr_t *match; + + u_int score; + u_int order; +}; + +struct window_switch_modedata { + struct window_pane *wp; + struct screen screen; + int zoomed; + + char *format; + char *command; + + enum window_switch_type type; + char *filter; + struct prompt *prompt; + u_int prompt_cx; + + struct window_switch_itemdata **item_list; + u_int item_size; + + struct window_switch_itemdata **matches; + u_int matches_size; + + u_int current; + u_int offset; +}; + +static void +window_switch_free_item(struct window_switch_itemdata *item) +{ + free(item->match); + free(item->text); + free(item); +} + +static struct window_switch_itemdata * +window_switch_add_item(struct window_switch_modedata *data) +{ + struct window_switch_itemdata *item; + + data->item_list = xreallocarray(data->item_list, data->item_size + 1, + sizeof *data->item_list); + item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); + return (item); +} + +static void +window_switch_add_session(struct window_switch_modedata *data, + struct session *s, u_int *order) +{ + struct window_switch_itemdata *item; + struct format_tree *ft; + + ft = format_create(NULL, NULL, FORMAT_NONE, 0); + format_defaults(ft, NULL, s, NULL, NULL); + + item = window_switch_add_item(data); + item->type = WINDOW_SWITCH_TYPE_SESSION; + item->session = s->id; + item->winlink = -1; + item->tag = (uint64_t)s; + item->order = (*order)++; + item->text = format_expand(ft, data->format); + + format_free(ft); +} + +static void +window_switch_add_window(struct window_switch_modedata *data, + struct winlink *wl, u_int *order) +{ + struct window_switch_itemdata *item; + struct format_tree *ft; + + ft = format_create(NULL, NULL, FORMAT_NONE, 0); + format_defaults(ft, NULL, wl->session, wl, NULL); + + item = window_switch_add_item(data); + item->type = WINDOW_SWITCH_TYPE_WINDOW; + item->session = wl->session->id; + item->winlink = wl->idx; + item->tag = (uint64_t)wl; + item->order = (*order)++; + item->text = format_expand(ft, data->format); + + format_free(ft); +} + +static int +window_switch_compare(const void *a0, const void *b0) +{ + struct window_switch_itemdata *const *a = a0; + struct window_switch_itemdata *const *b = b0; + + if ((*a)->score > (*b)->score) + return (-1); + if ((*a)->score < (*b)->score) + return (1); + if ((*a)->order < (*b)->order) + return (-1); + if ((*a)->order > (*b)->order) + return (1); + return (0); +} + +static void +window_switch_build(struct window_switch_modedata *data) +{ + struct window_switch_itemdata *item, **m = NULL; + const char *f = data->filter; + u_int ns, nw, i, n = 0, order = 0; + u_int sx = screen_size_x(&data->screen); + struct session **sl; + struct winlink **wl; + struct sort_criteria sort_crit; + + sort_crit.order = SORT_NAME; + sort_crit.reversed = 0; + + for (i = 0; i < data->item_size; i++) + window_switch_free_item(data->item_list[i]); + free(data->item_list); + data->item_list = NULL; + data->item_size = 0; + + switch (data->type) { + case WINDOW_SWITCH_TYPE_SESSION: + sl = sort_get_sessions(&ns, &sort_crit); + for (i = 0; i < ns; i++) + window_switch_add_session(data, sl[i], &order); + break; + case WINDOW_SWITCH_TYPE_WINDOW: + wl = sort_get_winlinks(&nw, &sort_crit); + for (i = 0; i < nw; i++) + window_switch_add_window(data, wl[i], &order); + break; + } + + for (i = 0; i < data->item_size; i++) { + item = data->item_list[i]; + if (*f == '\0') { + m = xreallocarray(m, n + 1, sizeof *m); + m[n++] = item; + continue; + } + + item->match = fuzzy_match(f, item->text, sx, &item->score); + if (item->match == NULL) + continue; + m = xreallocarray(m, n + 1, sizeof *m); + m[n++] = item; + } + qsort(m, n, sizeof *m, window_switch_compare); + + free(data->matches); + data->matches = m; + data->matches_size = n; +} + +static u_int +window_switch_visible(struct window_switch_modedata *data) +{ + u_int sy = screen_size_y(&data->screen); + + if (sy <= 1) + return (0); + return (sy - 1); +} + +static void +window_switch_set_current(struct window_switch_modedata *data, u_int current) +{ + u_int visible = window_switch_visible(data); + + if (data->matches_size == 0) { + data->current = 0; + data->offset = 0; + return; + } + + if (current > data->matches_size - 1) + current = data->matches_size - 1; + data->current = current; + + if (data->current < data->offset) + data->offset = data->current; + else if (visible != 0 && data->current >= data->offset + visible) + data->offset = data->current - visible + 1; +} + +static void +window_switch_draw_screen(struct window_mode_entry *wme) +{ + struct window_pane *wp = wme->wp; + struct window_switch_modedata *data = wme->data; + struct options *oo = wp->options; + struct screen_write_ctx ctx; + struct screen *s = &data->screen; + u_int sx = screen_size_x(s), i, j; + u_int sy = screen_size_y(s), visible, idx; + struct window_switch_itemdata *item; + struct grid_cell mgc, sgc, gc; + const struct grid_cell *dgc = &grid_default_cell; + struct prompt_draw_data pdd; + screen_write_start(&ctx, s); + screen_write_clearscreen(&ctx, 8); + + if (sy <= 1) { + screen_write_stop(&ctx); + return; + } + + style_apply(&mgc, oo, "switch-mode-match-style", NULL); + style_apply(&sgc, oo, "mode-style", NULL); + + visible = window_switch_visible(data); + for (i = 0; i < visible; i++) { + idx = data->offset + i; + if (idx >= data->matches_size) + break; + item = data->matches[idx]; + + screen_write_cursormove(&ctx, 0, i, 0); + if (idx != data->current) + format_draw(&ctx, dgc, sx, item->text, NULL, 0); + else { + screen_write_clearendofline(&ctx, sgc.bg); + format_draw(&ctx, &sgc, sx, item->text, NULL, 0); + } + + if (item->match == NULL) + continue; + for (j = 0; j < sx; j++) { + if (!bit_test(item->match, j)) + continue; + grid_get_cell(s->grid, j, i, &gc); + gc.attr = mgc.attr; + gc.fg = mgc.fg; + gc.bg = mgc.bg; + screen_write_cursormove(&ctx, j, i, 0); + screen_write_cell(&ctx, &gc); + } + } + + if (data->prompt != NULL) { + pdd.ctx = &ctx; + pdd.cursor_x = &data->prompt_cx; + pdd.area_x = 0; + pdd.area_width = sx; + pdd.prompt_line = sy - 1; + s->mode |= MODE_CURSOR; + prompt_draw(data->prompt, &pdd); + screen_write_cursormove(&ctx, data->prompt_cx, sy - 1, 0); + } + screen_write_stop(&ctx); +} + +static struct screen * +window_switch_init(struct window_mode_entry *wme, + struct cmd_find_state *fs, struct args *args) +{ + struct window_pane *wp = wme->wp; + struct window_switch_modedata *data; + struct screen *s; + struct prompt_create_data pd; + + wme->data = data = xcalloc(1, sizeof *data); + data->wp = wp; + + if (args_has(args, 'w')) + data->type = WINDOW_SWITCH_TYPE_WINDOW; + else + data->type = WINDOW_SWITCH_TYPE_SESSION; + + data->filter = xstrdup(""); + if (args == NULL || !args_has(args, 'F')) + data->format = xstrdup(WINDOW_SWITCH_DEFAULT_FORMAT); + else + data->format = xstrdup(args_get(args, 'F')); + if (args == NULL || args_count(args) == 0) + data->command = xstrdup(WINDOW_SWITCH_DEFAULT_COMMAND); + else + data->command = xstrdup(args_string(args, 0)); + + memset(&pd, 0, sizeof pd); + prompt_set_options(&pd, fs->s); + pd.fs = fs; + pd.prompt = "(search) "; + pd.input = ""; + pd.type = PROMPT_TYPE_SEARCH; + pd.flags = PROMPT_INCREMENTAL|PROMPT_NOFORMAT|PROMPT_ISMODE| + PROMPT_EDITARROWS; + pd.inputcb = window_switch_prompt_callback; + pd.data = data; + data->prompt = prompt_create(&pd); + prompt_update(data->prompt, "(search) ", data->filter); + + if (!args_has(args, 'Z')) + data->zoomed = -1; + else { + data->zoomed = (wp->window->flags & WINDOW_ZOOMED); + if (!data->zoomed && window_zoom(wp) == 0) + server_redraw_window(wp->window); + } + + s = &data->screen; + screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); + + window_switch_build(data); + prompt_incremental_start(data->prompt); + window_switch_draw_screen(wme); + + return (s); +} + +static void +window_switch_free(struct window_mode_entry *wme) +{ + struct window_switch_modedata *data = wme->data; + u_int i; + + if (data->zoomed == 0) + server_unzoom_window(wme->wp->window); + + for (i = 0; i < data->item_size; i++) + window_switch_free_item(data->item_list[i]); + free(data->item_list); + + free(data->matches); + free(data->filter); + prompt_free(data->prompt); + free(data->format); + free(data->command); + screen_free(&data->screen); + + free(data); +} + +static void +window_switch_resize(struct window_mode_entry *wme, u_int sx, u_int sy) +{ + struct window_switch_modedata *data = wme->data; + struct screen *s = &data->screen; + + screen_resize(s, sx, sy, 0); + window_switch_build(data); + window_switch_set_current(data, data->current); + window_switch_draw_screen(wme); +} + +static int +window_switch_run_command(struct window_switch_modedata *data, struct client *c) +{ + struct window_switch_itemdata *item; + struct cmd_find_state fs; + struct session *s; + struct winlink *wl; + char *target = NULL; + struct cmdq_state *state; + char *command, *error; + enum cmd_parse_status status; + + if (data->matches_size == 0) + return (0); + item = data->matches[data->current]; + + cmd_find_clear_state(&fs, 0); + switch (item->type) { + case WINDOW_SWITCH_TYPE_SESSION: + s = session_find_by_id(item->session); + if (s != NULL) { + xasprintf(&target, "=%s:", s->name); + cmd_find_from_session(&fs, s, 0); + } + break; + case WINDOW_SWITCH_TYPE_WINDOW: + s = session_find_by_id(item->session); + if (s != NULL) { + wl = winlink_find_by_index(&s->windows, item->winlink); + if (s != NULL && wl != NULL) { + xasprintf(&target, "=%s:%u.", s->name, wl->idx); + cmd_find_from_winlink(&fs, wl, 0); + } + } + break; + } + if (target == NULL) + return (0); + + command = cmd_template_replace(data->command, target, 1); + if (command != NULL && *command != '\0') { + state = cmdq_new_state(&fs, NULL, 0); + status = cmd_parse_and_append(command, NULL, c, state, &error); + if (status == CMD_PARSE_ERROR) { + if (c != NULL) { + *error = toupper((u_char)*error); + status_message_set(c, -1, 1, 0, 0, "%s", error); + } + free(error); + } + cmdq_free_state(state); + } + free(command); + free(target); + return (1); +} + +static enum prompt_result +window_switch_prompt_callback(void *arg, const char *s, + enum prompt_key_result key) +{ + struct window_switch_modedata *data = arg; + + if (key != PROMPT_KEY_HANDLED) + return (PROMPT_CONTINUE); + + if (s == NULL) + s = ""; + else if (*s != '\0') + s++; + + free(data->filter); + data->filter = xstrdup(s); + window_switch_build(data); + data->current = 0; + data->offset = 0; + + return (PROMPT_CONTINUE); +} + +static void +window_switch_key(struct window_mode_entry *wme, struct client *c, + __unused struct session *s, __unused struct winlink *wl, key_code key, + struct mouse_event *m) +{ + struct window_pane *wp = wme->wp; + struct window_switch_modedata *data = wme->data; + u_int visible, current = data->current; + u_int x, y, size = data->matches_size; + enum prompt_key_result result; + int redraw = 0; + + if (KEYC_IS_MOUSE(key)) { + if (m == NULL || cmd_mouse_at(wp, m, &x, &y, 0) != 0) + return; + if (data->prompt != NULL && screen_size_y(&data->screen) != 0 && + y == screen_size_y(&data->screen) - 1 && + MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_1 && !MOUSE_DRAG(m->b) && + !MOUSE_RELEASE(m->b)) { + result = prompt_mouse(data->prompt, x, 0, + screen_size_x(&data->screen), &redraw); + if (redraw || result == PROMPT_KEY_HANDLED) { + window_switch_draw_screen(wme); + wp->flags |= PANE_REDRAW; + } + return; + } + switch (key) { + case KEYC_WHEELUP_PANE: + if (size != 0 && current != 0) + window_switch_set_current(data, current - 1); + goto moved; + case KEYC_WHEELDOWN_PANE: + if (size != 0 && current != size - 1) + window_switch_set_current(data, current + 1); + goto moved; + case KEYC_MOUSEDOWN1_PANE: + case KEYC_DOUBLECLICK1_PANE: + if (y >= window_switch_visible(data) || + data->offset + y >= size) + return; + window_switch_set_current(data, data->offset + y); + if (key == KEYC_DOUBLECLICK1_PANE) { + if (window_switch_run_command(data, c)) + window_pane_reset_mode(wp); + return; + } + goto moved; + } + return; + } + + switch (key) { + case 'p'|KEYC_CTRL: + case 'k'|KEYC_CTRL: + key = KEYC_UP; + break; + case 'n'|KEYC_CTRL: + case 'j'|KEYC_CTRL: + key = KEYC_DOWN; + break; + } + + switch (key) { + case '\r': + if (window_switch_run_command(data, c)) + window_pane_reset_mode(wp); + return; + case '\033': /* Escape */ + case '['|KEYC_CTRL: + case 'c'|KEYC_CTRL: + case 'g'|KEYC_CTRL: + window_pane_reset_mode(wp); + return; + } + + if (data->prompt != NULL) { + result = prompt_key(data->prompt, key, &redraw); + if (redraw) { + window_switch_draw_screen(wme); + wp->flags |= PANE_REDRAW; + } + if (result == PROMPT_KEY_HANDLED || + result == PROMPT_KEY_NOT_HANDLED) + return; + current = data->current; + size = data->matches_size; + } + + switch (key) { + case KEYC_UP: + if (size == 0) + goto moved; + if (current == 0) + window_switch_set_current(data, size - 1); + else + window_switch_set_current(data, current - 1); + goto moved; + case KEYC_DOWN: + if (size == 0) + goto moved; + if (current == size - 1) + window_switch_set_current(data, 0); + else + window_switch_set_current(data, current + 1); + goto moved; + case KEYC_PPAGE: + visible = window_switch_visible(data); + if (current >= visible) + window_switch_set_current(data, current - visible); + else + window_switch_set_current(data, 0); + goto moved; + case KEYC_NPAGE: + visible = window_switch_visible(data); + window_switch_set_current(data, current + visible); + goto moved; + case KEYC_HOME: + window_switch_set_current(data, 0); + goto moved; + case KEYC_END: + if (size > 0) + window_switch_set_current(data, size - 1); + goto moved; + } + +moved: + window_switch_draw_screen(wme); + wp->flags |= PANE_REDRAW; +} From a2915cfdeff37d3b7a56d059388cdb575bdbf6e9 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 26 Jun 2026 16:09:55 +0100 Subject: [PATCH 52/71] Fuzzy test. --- regress/format-fuzzy.sh | 106 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 regress/format-fuzzy.sh diff --git a/regress/format-fuzzy.sh b/regress/format-fuzzy.sh new file mode 100644 index 000000000..31a5b60cb --- /dev/null +++ b/regress/format-fuzzy.sh @@ -0,0 +1,106 @@ +#!/bin/sh + +# Tests of fuzzy format matching. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest -f/dev/null" + +# test_format $format $expected_result +test_format() +{ + fmt="$1" + exp="$2" + + out=$($TMUX display-message -p "$fmt") + + if [ "$out" != "$exp" ]; then + echo "Fuzzy format test failed for '$fmt'." + echo "Expected: '$exp'" + echo "But got '$out'" + exit 1 + fi +} + +$TMUX kill-server 2>/dev/null +$TMUX -f/dev/null new-session -d || exit 1 + +# Empty pattern matches everything but has no highlighted positions. +test_format '#{m/z:,abc}' '1' +test_format '#{m/p:,abc}' '' + +# Plain fuzzy matching and positions. +test_format '#{m/z:abc,a_b_c}' '1' +test_format '#{m/p:abc,a_b_c}' '0,2,4' +test_format '#{m/z:abc,acb}' '0' +test_format '#{m/p:abc,acb}' '' + +# Multiple terms are ANDed. Positions are for all positive matched terms. +test_format '#{m/z:dev bash,dev:1 bash}' '1' +test_format '#{m/p:dev bash,dev:1 bash}' '0,1,2,6,7,8,9' +test_format '#{m/z:dev bash,dev:1 sh}' '0' +test_format '#{m/p:dev bash,dev:1 sh}' '' + +# Lowercase patterns are case-insensitive for ASCII; uppercase makes the +# pattern case-sensitive. +test_format '#{m/z:abc,ABC}' '1' +test_format '#{m/p:abc,ABC}' '0,1,2' +test_format '#{m/z:ABC,abc}' '0' +test_format '#{m/p:ABC,abc}' '' + +# Exact substring matching with a leading quote. +test_format "#{m/z:'bash,dev bash}" '1' +test_format "#{m/p:'bash,dev bash}" '4,5,6,7' +test_format "#{m/z:'bash,b-a-s-h}" '0' +test_format "#{m/p:'bash,b-a-s-h}" '' + +# Prefix and suffix matching. +test_format '#{m/z:^dev,dev bash}' '1' +test_format '#{m/p:^dev,dev bash}' '0,1,2' +test_format '#{m/z:^dev,prod dev}' '0' +test_format '#{m/p:^dev,prod dev}' '' +test_format '#{m/z:bash$,dev bash}' '1' +test_format '#{m/p:bash$,dev bash}' '4,5,6,7' +test_format '#{m/z:bash$,bash dev}' '0' +test_format '#{m/p:bash$,bash dev}' '' + +# Inverse terms. Plain inverse terms are exact substring tests, not fuzzy. +test_format '#{m/z:!ssh,dev bash}' '1' +test_format '#{m/z:!ssh,dev ssh}' '0' +test_format '#{m/z:!ssh,s_s_h}' '1' +test_format '#{m/z:dev !ssh,dev bash}' '1' +test_format '#{m/p:dev !ssh,dev bash}' '0,1,2' +test_format '#{m/z:dev !ssh,dev ssh}' '0' +test_format '#{m/p:dev !ssh,dev ssh}' '' + +# OR separates groups; each group is an AND of its terms. +test_format '#{m/z:prod | dev,dev bash}' '1' +test_format '#{m/p:prod | dev,dev bash}' '0,1,2' +test_format '#{m/z:prod | dev,prod bash}' '1' +test_format '#{m/p:prod | dev,prod bash}' '0,1,2,3' +test_format '#{m/z:prod | dev,test bash}' '0' +test_format '#{m/p:prod | dev,test bash}' '' + +# Style sequences do not count towards display positions. +test_format '#{m/z:dev,#[bold]dev#[default]}' '1' +test_format '#{m/p:dev,#[bold]dev#[default]}' '0,1,2' +test_format '#{m/p:dev,#[fg=red#,bg=blue]dev#[default]}' '0,1,2' +test_format '#{m/p:dev,#[bold]d#[default]e#[underscore]v#[default]}' '0,1,2' +test_format '#{m/p:bash,#[bold]dev#[default] bash}' '4,5,6,7' + +# UTF-8. Non-ASCII matching is exact, even when ASCII would fold case. +test_format '#{m/z:é,café}' '1' +test_format '#{m/p:é,café}' '3' +test_format '#{m/z:é,É}' '0' +test_format '#{m/p:é,É}' '' +test_format '#{m/z:éx,éx}' '1' +test_format '#{m/p:éx,éx}' '0,1' + +# Wide UTF-8 characters occupy two display cells and both should be marked for +# highlighting. +test_format '#{m/z:界,a界b}' '1' +test_format '#{m/p:界,a界b}' '1,2' + +exit 0 From c0f213b05640ade3a338a9e6237e3d5c7a1bd008 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 17:43:15 +0000 Subject: [PATCH 53/71] Add info modes to tree mode like for clients. --- tmux.1 | 3 +- window-tree.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) diff --git a/tmux.1 b/tmux.1 index 58d055cff..a0ec81784 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2875,6 +2875,7 @@ The following keys may be used in client mode: .It Li "O" Ta "Change sort order" .It Li "r" Ta "Reverse sort order" .It Li "v" Ta "Toggle preview" +.It Li "i" Ta "Change view (preview and client information)" .It Li "F1 or C\-h" Ta "Display help" .It Li "q" Ta "Exit mode" .El @@ -2972,7 +2973,7 @@ The following keys may be used in tree mode: .It Li "O" Ta "Change sort order" .It Li "r" Ta "Reverse sort order" .It Li "v" Ta "Toggle preview" -.It Li "i" Ta "Change view (preview and client information)" +.It Li "i" Ta "Change view (preview and session, window and pane information)" .It Li "F1 or C\-h" Ta "Display help" .It Li "q" Ta "Exit mode" .El diff --git a/window-tree.c b/window-tree.c index 59e41122b..36d4ac405 100644 --- a/window-tree.c +++ b/window-tree.c @@ -114,6 +114,7 @@ struct window_tree_modedata { char *command; int squash_groups; int hide_preview_this_pane; + int preview_is_info; int prompt_flags; struct window_tree_itemdata **item_list; @@ -141,6 +142,82 @@ static enum sort_order window_tree_order_seq[] = { SORT_END, }; +#define WINDOW_TREE_FLAG(label, cond) \ + "#{?" cond ",#[fg=themegreen],#[fg=themelightgrey]}" label "#[default]" + +static const char *window_tree_pane_info_lines[] = { + "#[fg=themelightgrey]Pane #[#{E:tree-mode-border-style},acs]x#[default] " + "#{pane_index} #[fg=themelightgrey](#{pane_id})#[default]", + "#[fg=themelightgrey]Title #[#{E:tree-mode-border-style},acs]x#[default] " + "#{pane_title}", + "#[fg=themelightgrey]Command #[#{E:tree-mode-border-style},acs]x#[default] " + "#{pane_current_command} #[fg=themelightgrey](PID #{pane_pid})#[default]", + "#[fg=themelightgrey]Path #[#{E:tree-mode-border-style},acs]x#[default] " + "#{pane_current_path}", + "#[fg=themelightgrey]TTY #[#{E:tree-mode-border-style},acs]x#[default] " + "#{pane_tty}", + "#[fg=themelightgrey]Position #[#{E:tree-mode-border-style},acs]x#[default] " + "#{pane_x},#{pane_y} #{pane_width}x#{pane_height}", + "#[fg=themelightgrey]Started At #[#{E:tree-mode-border-style},acs]x#[default] " + "#{?pane_start_command,#{pane_start_command},none}", + "#[fg=themelightgrey]Mode #[#{E:tree-mode-border-style},acs]x#[default] " + "#{?pane_in_mode,#{pane_mode},none}", + "#[fg=themelightgrey]Flags #[#{E:tree-mode-border-style},acs]x#[default] " + WINDOW_TREE_FLAG("active", "pane_active") " " + WINDOW_TREE_FLAG("zoomed", "window_zoomed_flag") " " + WINDOW_TREE_FLAG("marked", "pane_marked") " " + WINDOW_TREE_FLAG("sync", "pane_synchronized") " " + WINDOW_TREE_FLAG("dead", "pane_dead") " " + WINDOW_TREE_FLAG("piped", "pane_pipe"), +}; + +static const char *window_tree_window_info_lines[] = { + "#[fg=themelightgrey]Window #[#{E:tree-mode-border-style},acs]x#[default] " + "#{window_index}: #{window_name} #[fg=themelightgrey](#{window_id})#[default]", + "#[fg=themelightgrey]Size #[#{E:tree-mode-border-style},acs]x#[default] " + "#{window_width}x#{window_height}", + "#[fg=themelightgrey]Panes #[#{E:tree-mode-border-style},acs]x#[default] " + "#{window_panes}", + "#[fg=themelightgrey]Activity Time #[#{E:tree-mode-border-style},acs]x#[default] " + "#{t:window_activity} #[fg=themelightgrey](#{t/r:window_activity})#[default]", + "#[fg=themelightgrey]Sessions #[#{E:tree-mode-border-style},acs]x#[default] " + "#{s/,/ /:window_active_sessions_list}", + "#[fg=themelightgrey]Flags #[#{E:tree-mode-border-style},acs]x#[default] " + WINDOW_TREE_FLAG("active", "window_active") " " + WINDOW_TREE_FLAG("last", "window_last_flag") " " + WINDOW_TREE_FLAG("bell", "window_bell_flag") " " + WINDOW_TREE_FLAG("activity", "window_activity_flag") " " + WINDOW_TREE_FLAG("silence", "window_silence_flag") " " + WINDOW_TREE_FLAG("zoomed", "window_zoomed_flag") " " + WINDOW_TREE_FLAG("marked", "window_marked_flag"), +}; + +static const char *window_tree_session_info_lines[] = { + "#[fg=themelightgrey]Session #[#{E:tree-mode-border-style},acs]x#[default] " + "#{session_name} #[fg=themelightgrey](#{session_id})#[default]", + "#[fg=themelightgrey]Created Time #[#{E:tree-mode-border-style},acs]x#[default] " + "#{t:session_created} #[fg=themelightgrey](#{t/r:session_created})#[default]", + "#[fg=themelightgrey]Activity Time #[#{E:tree-mode-border-style},acs]x#[default] " + "#{t:session_activity} #[fg=themelightgrey](#{t/r:session_activity})#[default]", + "#[fg=themelightgrey]Attached Time #[#{E:tree-mode-border-style},acs]x#[default] " + "#{?#{t:session_last_attached},#{t:session_last_attached} #[fg=themelightgrey](#{t/r:session_last_attached})#[default],never}", + "#[fg=themelightgrey]Clients #[#{E:tree-mode-border-style},acs]x#[default] " + "#{s/,/ /:session_attached_list}", + "#[fg=themelightgrey]Windows #[#{E:tree-mode-border-style},acs]x#[default] " + "#{session_windows}", + "#[fg=themelightgrey]Path #[#{E:tree-mode-border-style},acs]x#[default] " + "#{session_path}", + "#[fg=themelightgrey]Group #[#{E:tree-mode-border-style},acs]x#[default] " + "#{?session_grouped,#{session_group} (#{session_group_size}),none}", + "#[fg=themelightgrey]Flags #[#{E:tree-mode-border-style},acs]x#[default] " + WINDOW_TREE_FLAG("attached", "session_attached") " " + WINDOW_TREE_FLAG("grouped", "session_grouped") " " + WINDOW_TREE_FLAG("marked", "session_marked") " " + WINDOW_TREE_FLAG("bell", "session_bell_flag") " " + WINDOW_TREE_FLAG("activity", "session_activity_flag") " " + WINDOW_TREE_FLAG("silence", "session_silence_flag"), +}; + static void window_tree_pull_item(struct window_tree_itemdata *item, struct session **sp, struct winlink **wlp, struct window_pane **wp) @@ -726,6 +803,81 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, } } +static void +window_tree_draw_info(struct window_tree_modedata *data, void *itemdata, + struct screen_write_ctx *ctx, u_int sx, u_int sy) +{ + struct window_tree_itemdata *item = itemdata; + struct screen *s = ctx->s; + struct session *sp; + struct winlink *wl; + struct window_pane *wp; + struct grid_cell gc; + u_int cx = s->cx, cy = s->cy, i, j, k; + struct format_tree *ft; + char *expanded; + const char *const *lines[3]; + u_int count[3], n = 0; + + window_tree_pull_item(item, &sp, &wl, &wp); + if (sp == NULL || wp == NULL) + return; + + if (item->type == WINDOW_TREE_PANE) { + lines[n] = window_tree_pane_info_lines; + count[n++] = nitems(window_tree_pane_info_lines); + } + if (item->type == WINDOW_TREE_PANE || + item->type == WINDOW_TREE_WINDOW) { + lines[n] = window_tree_window_info_lines; + count[n++] = nitems(window_tree_window_info_lines); + } + lines[n] = window_tree_session_info_lines; + count[n++] = nitems(window_tree_session_info_lines); + + ft = format_create(NULL, NULL, FORMAT_NONE, 0); + format_defaults(ft, NULL, sp, wl, wp); + + i = 0; + for (j = 0; j < n; j++) { + if (j != 0) { + if (i == sy) + break; + window_tree_border_cell(&gc, data->wp->window->options, + NULL); + screen_write_cursormove(ctx, cx, cy + i, 0); + screen_write_hline(ctx, sx, 0, 0, BOX_LINES_DEFAULT, + &gc); + if (sx > 14) { + gc.attr |= GRID_ATTR_CHARSET; + screen_write_cursormove(ctx, cx + 14, cy + i, + 0); + screen_write_putc(ctx, &gc, 'n'); + } + i++; + } + for (k = 0; k < count[j]; k++) { + if (i == sy) + break; + expanded = format_expand(ft, lines[j][k]); + screen_write_cursormove(ctx, cx, cy + i, 0); + format_draw(ctx, &grid_default_cell, sx, expanded, NULL, + 0); + free(expanded); + i++; + } + if (i == sy) + break; + } + if (sx > 14 && i < sy) { + window_tree_border_cell(&gc, data->wp->window->options, NULL); + screen_write_cursormove(ctx, cx + 14, cy + i, 0); + screen_write_vline(ctx, sy - i, 0, 0, &gc); + } + + format_free(ft); +} + static void window_tree_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx, u_int sx, u_int sy) @@ -740,6 +892,11 @@ window_tree_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx, if (wp == NULL) return; + if (data->preview_is_info) { + window_tree_draw_info(data, item, ctx, sx, sy); + return; + } + switch (item->type) { case WINDOW_TREE_NONE: break; @@ -923,6 +1080,8 @@ static const char* window_tree_help_lines[] = { "#[fg=themelightgrey]" " M #[#{E:tree-mode-border-style},acs]x#[default] Clear the marked pane", "#[fg=themelightgrey]" + " i #[#{E:tree-mode-border-style},acs]x#[default] Toggle session, window and pane information", + "#[fg=themelightgrey]" " : #[#{E:tree-mode-border-style},acs]x#[default] Run a command for each tagged item", "#[fg=themelightgrey]" " f #[#{E:tree-mode-border-style},acs]x#[default] Enter a format", @@ -1320,6 +1479,13 @@ again: server_clear_marked(); mode_tree_build(data->data); break; + case 'i': + data->preview_is_info = !data->preview_is_info; + if (data->preview_is_info) + mode_tree_view_name(data->data, "info"); + else + mode_tree_view_name(data->data, "preview"); + break; case 'x': window_tree_pull_item(item, &ns, &nwl, &nwp); switch (item->type) { From 5be44a6c03672fad3729b803e43634e9e0fded44 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Jun 2026 19:53:47 +0000 Subject: [PATCH 54/71] Use linked sessions not active. --- window-tree.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/window-tree.c b/window-tree.c index 36d4ac405..19bd5569f 100644 --- a/window-tree.c +++ b/window-tree.c @@ -158,8 +158,6 @@ static const char *window_tree_pane_info_lines[] = { "#{pane_tty}", "#[fg=themelightgrey]Position #[#{E:tree-mode-border-style},acs]x#[default] " "#{pane_x},#{pane_y} #{pane_width}x#{pane_height}", - "#[fg=themelightgrey]Started At #[#{E:tree-mode-border-style},acs]x#[default] " - "#{?pane_start_command,#{pane_start_command},none}", "#[fg=themelightgrey]Mode #[#{E:tree-mode-border-style},acs]x#[default] " "#{?pane_in_mode,#{pane_mode},none}", "#[fg=themelightgrey]Flags #[#{E:tree-mode-border-style},acs]x#[default] " @@ -181,7 +179,7 @@ static const char *window_tree_window_info_lines[] = { "#[fg=themelightgrey]Activity Time #[#{E:tree-mode-border-style},acs]x#[default] " "#{t:window_activity} #[fg=themelightgrey](#{t/r:window_activity})#[default]", "#[fg=themelightgrey]Sessions #[#{E:tree-mode-border-style},acs]x#[default] " - "#{s/,/ /:window_active_sessions_list}", + "#{s/,/ /:window_linked_sessions_list}", "#[fg=themelightgrey]Flags #[#{E:tree-mode-border-style},acs]x#[default] " WINDOW_TREE_FLAG("active", "window_active") " " WINDOW_TREE_FLAG("last", "window_last_flag") " " From 7383c04205fc37c18f0e047a07d7273790784473 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 27 Jun 2026 10:05:38 +0000 Subject: [PATCH 55/71] Preseve prompt flags across line clear, from Aung Myo Kyaw. --- cmd-split-window.c | 4 ++-- screen-write.c | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index cdc42cefe..5d21895c6 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -41,8 +41,8 @@ const struct cmd_entry cmd_new_pane_entry = { .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] " + "[-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 " " diff --git a/screen-write.c b/screen-write.c index 45d126a6f..ba7ffd930 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1494,12 +1494,16 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg) struct grid_line *gl; u_int sx = screen_size_x(s); struct screen_write_citem *ci = ctx->item; + u_int flags; gl = grid_get_line(s->grid, s->grid->hsize + s->cy); if (gl->cellsize == 0 && COLOUR_DEFAULT(bg)) return; + flags = gl->flags & (GRID_LINE_START_PROMPT|GRID_LINE_START_OUTPUT); grid_view_clear(s->grid, 0, s->cy, sx, 1, bg); + gl = grid_get_line(s->grid, s->grid->hsize + s->cy); + gl->flags |= flags; screen_write_collect_clear(ctx, s->cy, 1); ci->x = 0; From dd02989e88b700b18ffa0230b49e245ca340ee48 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 27 Jun 2026 10:16:15 +0000 Subject: [PATCH 56/71] Invalidate scene when changing window Z index, from Dane Jensen. --- window.c | 1 + 1 file changed, 1 insertion(+) diff --git a/window.c b/window.c index ac71a72bd..1965685b6 100644 --- a/window.c +++ b/window.c @@ -624,6 +624,7 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); wp->flags |= PANE_REDRAW; + redraw_invalidate_scene(w); } wp = w->active; From e24969b4227266e5775bdc9688c14c937e2713df Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 27 Jun 2026 10:18:32 +0000 Subject: [PATCH 57/71] Add a helper to swap a node into the place of a leaf, from Dane Jensen. --- layout.c | 55 ++++++++++++++++++++++++++++++++++--------------------- tmux.h | 2 ++ 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/layout.c b/layout.c index 569616f1f..d5dcf1ccf 100644 --- a/layout.c +++ b/layout.c @@ -1184,6 +1184,32 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) } } +/* + * Replaces the provided layout cell with a new node of the specified type and + * inserts the cell into it. Used when creating new cells requires a different + * layout type, or when the root layout is a window pane. + */ +struct layout_cell * +layout_replace_with_node(struct window *w, struct layout_cell *lc, + enum layout_type type) +{ + struct layout_cell *lcparent; + + lcparent = layout_create_cell(lc->parent); + layout_make_node(lcparent, type); + layout_set_size(lcparent, lc->sx, lc->sy, lc->xoff, lc->yoff); + if (lc->parent == NULL) + w->layout_root = lcparent; + else + TAILQ_REPLACE(&lc->parent->cells, lc, lcparent, entry); + + /* Insert the old cell. */ + lc->parent = lcparent; + TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry); + + return (lcparent); +} + /* * Split a pane into two. size is a hint, or -1 for default half/half * split. This must be followed by layout_assign_pane before much else happens! @@ -1315,17 +1341,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, */ /* Create and insert the replacement parent. */ - lcparent = layout_create_cell(lc->parent); - layout_make_node(lcparent, type); - layout_set_size(lcparent, sx, sy, xoff, yoff); - if (lc->parent == NULL) - wp->window->layout_root = lcparent; - else - TAILQ_REPLACE(&lc->parent->cells, lc, lcparent, entry); - - /* Insert the old cell. */ - lc->parent = lcparent; - TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry); + lcparent = layout_replace_with_node(wp->window, lc, type); /* Create the new child cell. */ lcnew = layout_create_cell(lcparent); @@ -1384,14 +1400,7 @@ layout_floating_pane(struct window *w, struct window_pane *wp, u_int sx, * Adding a pane to a root that isn't node. Must create and * insert a new root. */ - lcparent = layout_create_cell(NULL); - layout_make_node(lcparent, LAYOUT_TOPBOTTOM); - layout_set_size(lcparent, w->sx, w->sy, 0, 0); - w->layout_root = lcparent; - - /* Insert the old cell. */ - lc->parent = lcparent; - TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry); + lcparent = layout_replace_with_node(w, lc, LAYOUT_TOPBOTTOM); } lcnew = layout_create_cell(lcparent); @@ -1712,7 +1721,11 @@ layout_remove_tile(struct window *w, struct layout_cell *lc) layout_resize_adjust(w, lcneighbour, type, change); } - /* Zeroing out the cell geometry until the cell is retiled. */ - layout_set_size(lc, 0, 0, 0, 0); + /* + * Zeroing out the cell geometry until the cell is retiled unless this + * is the top level node. + */ + if (lc->parent != NULL) + layout_set_size(lc, 0, 0, 0, 0); return (1); } diff --git a/tmux.h b/tmux.h index d80e9b793..b03525c7e 100644 --- a/tmux.h +++ b/tmux.h @@ -3643,6 +3643,8 @@ int layout_resize_floating_pane_to(struct window_pane *, enum layout_type, u_int, char **); void layout_assign_pane(struct layout_cell *, struct window_pane *, int); +struct layout_cell *layout_replace_with_node(struct window *, + struct layout_cell *, enum layout_type); struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type, int, int); struct layout_cell *layout_floating_pane(struct window *, struct window_pane *, From 5e0b0a2aa3cc5da7f724ef03eab5c60636545890 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 27 Jun 2026 10:21:26 +0000 Subject: [PATCH 58/71] Pulled code out of layout_split_pane for reuse and added layout_insert_tile. From Dane Jensen. --- layout.c | 221 ++++++++++++++++++++++++++++++++++++++++++++----------- tmux.h | 7 ++ 2 files changed, 184 insertions(+), 44 deletions(-) diff --git a/layout.c b/layout.c index d5dcf1ccf..279f504af 100644 --- a/layout.c +++ b/layout.c @@ -31,8 +31,9 @@ * a cell which contains a list of cells, and 'leaf' to refer to a cell that * contains a window pane. A leaf is considered to be 'tiled' if it is to be * drawn as a part of the tiled layout. A 'neighbour' is a sibling that is also - * tiled. A cell's 'split' size refers to the side that is shortened when - * splitting it, determined by the parent's type. + * tiled or a node that contains a tiled leaf in a subtree. A cell's 'split' + * size refers to the side that is shortened when splitting it, determined by + * the parent's type. * * Each window has a pointer to the root of its layout tree (containing its * panes), every pane has a pointer back to the cell containing it, and each @@ -296,6 +297,27 @@ layout_cell_is_first_tiled(struct layout_cell *lc) return (lcchild == lc); } +static struct layout_cell * +layout_cell_get_first_tiled(struct layout_cell *lc) +{ + struct layout_cell *lcchild, *lcchild2; + + if (layout_cell_is_tiled(lc)) + return (lc); + if (lc->type == LAYOUT_WINDOWPANE) + return (NULL); + + TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (layout_cell_is_tiled(lcchild)) + return (lcchild); + if (lcchild->type != LAYOUT_WINDOWPANE) { + lcchild2 = layout_cell_get_first_tiled(lcchild); + if (lcchild2 != NULL) + return (lcchild2); + } + } + return (NULL); +} /* Fix cell offsets for a child cell. */ static void @@ -609,6 +631,20 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, } } +/* Resizes a cell to a specified size */ +void +layout_resize_set_size(struct window *w, struct layout_cell *lc, + enum layout_type type, u_int size) +{ + int change; + + if (type == LAYOUT_LEFTRIGHT) + change = size - lc->sx; + else + change = size - lc->sy; + layout_resize_adjust(w, lc, type, change); +} + /* Find and return the nearest neighbour to a cell in a specific direction. */ static struct layout_cell * layout_cell_get_neighbour_direction(struct layout_cell *lc, int direction) @@ -1210,6 +1246,75 @@ layout_replace_with_node(struct window *w, struct layout_cell *lc, return (lcparent); } +/* Checks if there is enough space for two new panes. */ +int +layout_split_check_space(struct window_pane *wp, struct layout_cell *lc, + enum layout_type type) +{ + struct style *sb_style = &wp->scrollbar_style; + u_int minimum, sx = lc->sx, sy = lc->sy; + int scrollbars, status; + + if (lc->flags & LAYOUT_CELL_FLOATING) + fatalx("floating cells cannot be split"); + + status = window_get_pane_status(wp->window); + scrollbars = options_get_number(wp->window->options, "pane-scrollbars"); + + switch (type) { + case LAYOUT_LEFTRIGHT: + if (scrollbars) { + minimum = PANE_MINIMUM * 2 + sb_style->width + + sb_style->pad; + } else + minimum = PANE_MINIMUM * 2 + 1; + if (sx < minimum) + return (0); + break; + case LAYOUT_TOPBOTTOM: + if (layout_add_horizontal_border(wp->window, lc, status)) + minimum = PANE_MINIMUM * 2 + 2; + else + minimum = PANE_MINIMUM * 2 + 1; + if (sy < minimum) + return (0); + break; + default: + fatalx("bad layout type"); + } + + return (1); +} + +/* Calculates the new cell sizes when splitting a pane. */ +void +layout_split_sizes(struct layout_cell *lc, int size, int before, + enum layout_type type, u_int *size1, u_int *size2, u_int *saved_size) +{ + u_int s1, s2, ss; + u_int sx = lc->sx, sy = lc->sy; + + if (type == LAYOUT_LEFTRIGHT) + ss = sx; + else + ss = sy; + if (size < 0) + s2 = ((ss + 1) / 2) - 1; + else if (before) + s2 = ss - size - 1; + else + s2 = size; + if (s2 < PANE_MINIMUM) + s2 = PANE_MINIMUM; + else if (s2 > sx - 2) + s2 = ss - 2; + s1 = ss - 1 - s2; + + *size1 = s1; + *size2 = s2; + *saved_size = ss; +} + /* * Split a pane into two. size is a hint, or -1 for default half/half * split. This must be followed by layout_assign_pane before much else happens! @@ -1219,11 +1324,10 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, int flags) { struct layout_cell *lc, *lcparent, *lcnew, *lc1, *lc2; - struct style *sb_style = &wp->scrollbar_style; - u_int sx, sy, xoff, yoff, size1, size2, minimum; + u_int sx, sy, xoff, yoff, size1, size2; u_int new_size, saved_size, resize_first = 0; - int full_size = (flags & SPAWN_FULLSIZE), status; - int scrollbars; + int full_size = (flags & SPAWN_FULLSIZE); + int before = (flags & SPAWN_BEFORE); /* * If full_size is specified, add a new cell at the top of the window @@ -1233,8 +1337,6 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, lc = wp->window->layout_root; else lc = wp->layout_cell; - status = window_get_pane_status(wp->window); - scrollbars = options_get_number(wp->window->options, "pane-scrollbars"); /* Copy the old cell size. */ sx = lc->sx; @@ -1243,47 +1345,14 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, yoff = lc->yoff; /* Check there is enough space for the two new panes. */ - switch (type) { - case LAYOUT_LEFTRIGHT: - if (scrollbars) { - minimum = PANE_MINIMUM * 2 + sb_style->width + - sb_style->pad; - } else - minimum = PANE_MINIMUM * 2 + 1; - if (sx < minimum) - return (NULL); - break; - case LAYOUT_TOPBOTTOM: - if (layout_add_horizontal_border(wp->window, lc, status)) - minimum = PANE_MINIMUM * 2 + 2; - else - minimum = PANE_MINIMUM * 2 + 1; - if (sy < minimum) - return (NULL); - break; - default: - fatalx("bad layout type"); - } + if (!layout_split_check_space(wp, lc, type)) + return (NULL); /* * Calculate new cell sizes. size is the target size or -1 for middle * split, size1 is the size of the top/left and size2 the bottom/right. */ - if (type == LAYOUT_LEFTRIGHT) - saved_size = sx; - else - saved_size = sy; - if (size < 0) - size2 = ((saved_size + 1) / 2) - 1; - else if (flags & SPAWN_BEFORE) - size2 = saved_size - size - 1; - else - size2 = size; - if (size2 < PANE_MINIMUM) - size2 = PANE_MINIMUM; - else if (size2 > saved_size - 2) - size2 = saved_size - 2; - size1 = saved_size - 1 - size2; + layout_split_sizes(lc, size, before, type, &size1, &size2, &saved_size); /* Which size are we using? */ if (flags & SPAWN_BEFORE) @@ -1729,3 +1798,67 @@ layout_remove_tile(struct window *w, struct layout_cell *lc) layout_set_size(lc, 0, 0, 0, 0); return (1); } + +/* + * Inserts a cell back into the tiled layout by taking half the space from its + * nearest neighbour. + */ +int +layout_insert_tile(struct window *w, struct layout_cell *lc) +{ + struct layout_cell *lcneighbour, *lctiled, *lcparent; + enum layout_type type; + u_int size1, size2, saved_size; + + if (lc == NULL) + fatalx("layout cell cannot be null when tiling"); + + lcparent = lc->parent; + if (lc->flags & LAYOUT_CELL_FLOATING) + return (1); + + if (lcparent == NULL) { + /* Only pane in the layout. */ + layout_set_size(lc, w->sx, w->sy, 0, 0); + return (1); + } + + type = lcparent->type; + lcneighbour = layout_cell_get_neighbour(lc); + if (lcneighbour == NULL) { + /* + * This will become the only visible cell in the parent. + * Tile the parent, then set the child's 'split' size. + */ + layout_insert_tile(w, lcparent); + if (type == LAYOUT_LEFTRIGHT) + size1 = lcparent->sx; + else + size1 = lcparent->sy; + layout_resize_set_size(w, lc, type, size1); + } else { + /* + * If the neighbour is a node, a tiled child in the subtree of + * the neighbour is needed to check for space. + */ + lctiled = layout_cell_get_first_tiled(lcneighbour); + if (!layout_split_check_space(lctiled->wp, lcneighbour, type)) + return (0); + layout_split_sizes(lcneighbour, -1, 0, type, &size1, &size2, + &saved_size); + layout_resize_set_size(w, lc, type, size1); + layout_resize_set_size(w, lcneighbour, type, size2); + } + + /* Setting opposite of the 'split' size to that of the parent. */ + if (lcparent->type == LAYOUT_LEFTRIGHT) { + size1 = lcparent->sy; + type = LAYOUT_TOPBOTTOM; + } else { + size1 = lcparent->sx; + type = LAYOUT_LEFTRIGHT; + } + layout_resize_set_size(w, lc, type, size1); + + return (1); +} diff --git a/tmux.h b/tmux.h index b03525c7e..869936e68 100644 --- a/tmux.h +++ b/tmux.h @@ -3629,6 +3629,8 @@ void layout_fix_offsets(struct window *); void layout_fix_panes(struct window *, struct window_pane *); void layout_resize_adjust(struct window *, struct layout_cell *, enum layout_type, int); +void layout_resize_set_size(struct window *, struct layout_cell *, + enum layout_type, u_int); struct layout_cell *layout_cell_get_neighbour(struct layout_cell *); void layout_init(struct window *, struct window_pane *); void layout_free(struct window *); @@ -3643,6 +3645,10 @@ int layout_resize_floating_pane_to(struct window_pane *, enum layout_type, u_int, char **); void layout_assign_pane(struct layout_cell *, struct window_pane *, int); +int layout_split_check_space(struct window_pane *, + struct layout_cell *, enum layout_type); +void layout_split_sizes(struct layout_cell *, int, int, + enum layout_type, u_int *, u_int *, u_int *); struct layout_cell *layout_replace_with_node(struct window *, struct layout_cell *, enum layout_type); struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type, @@ -3661,6 +3667,7 @@ int layout_floating_args_parse(struct cmdq_item *, struct args *, enum pane_lines, struct window *, u_int *, u_int *, int *, int *, char **); int layout_remove_tile(struct window *, struct layout_cell *); +int layout_insert_tile(struct window *, struct layout_cell *); /* layout-custom.c */ char *layout_dump(struct window *, struct layout_cell *); From 79f0f97495ff9a53b14cec8b058312c3f506ea67 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 27 Jun 2026 10:23:14 +0000 Subject: [PATCH 59/71] Make split-window create a new floating pane if used in a floating pane, rather than just complaining. Also improve documentation. From Dane Jensen. --- cmd-split-window.c | 2 +- tmux.1 | 219 ++++++++++++++++++++++++++------------------- 2 files changed, 128 insertions(+), 93 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 5d21895c6..6c2c61252 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -96,7 +96,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) if (cmd_get_entry(self) == &cmd_new_pane_entry) is_floating = !args_has(args, 'L'); else - is_floating = 0; + is_floating = window_pane_is_floating(wp); flags = is_floating ? SPAWN_FLOATING : 0; if (args_has(args, 'b')) diff --git a/tmux.1 b/tmux.1 index a0ec81784..63809bac1 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2742,6 +2742,15 @@ 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. +The +.Fl x , +.Fl y , +.Fl X , +and +.Fl Y +options may be followed by +.Ql % +to specify a percentage of the window size. .Pp If the pane had previously been floating, the position and sizes are restored from the saved values not specified by the @@ -3586,7 +3595,7 @@ but a different format may be specified with .Fl F . .Tg newp .It Xo Ic new\-pane -.Op Fl bdefhIkPvWZ +.Op Fl bdefhIkLPvWZ .Op Fl B Ar border\-lines .Op Fl c Ar start\-directory .Op Fl e Ar environment @@ -3599,101 +3608,49 @@ but a different format may be specified with .Op Fl S Ar active\-border\-style .Op Fl t Ar target\-pane .Op Fl T Ar title +.Op Fl x Ar width +.Op Fl y Ar height +.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 is created by splitting -.Ar target\-pane . -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, 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 T -sets the pane title. +Creates a new floating pane. +The +.Fl x +and +.Fl y +options set the width and height of the floating pane in columns and lines +respectively. +The default is half the window width and a quarter the window 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. +The +.Fl x , +.Fl y , +.Fl X , +and +.Fl Y +options may be followed by +.Ql % +to specify a percentage of the window size. .Fl B sets the pane border lines for floating panes; see .Ic pane\-border\-lines . .Pp -.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. -.Fl p -is a shorthand option for this. -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 -.Fl k -keeps the pane open after the optional -.Ar shell\-command -exits and waits for a key to be pressed 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 -.Fl W -Waits until -.Ar shell\-command -exits, then returns its exit status. -For example: -.Bd -literal -offset indent -$ tmux new-pane -W 'vi afile' -$ echo $? -0 -.Ed -.Pp -.Fl E , -or an empty -.Ar shell\-command , -(\[aq]\[aq]) will create an empty pane with no command running in it; -.Ic display-message -.Fl I -can write to an empty pane. -The -.Fl I -flag will create an empty pane and forward any output from stdin to it. -For example: -.Bd -literal -offset indent -$ make 2>&1|tmux new\-pane \-dI & -.Ed +.Fl L +option makes +.Ic new\-pane +behave like +.Ic split\-window . .Pp All other options have the same meaning as for the -.Ic new\-window +.Ic split\-window command. .Tg nextl .It Ic next\-layout Op Fl t Ar target\-window @@ -4048,14 +4005,92 @@ the command behaves like .Op Ar shell\-command Op Ar argument ... .Xc .D1 Pq alias: Ic splitw -Creates a new pane by splitting +Create a new pane by splitting .Ar target\-pane . -Shares behavior with -.Ic new\-pane . +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, 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 T +sets the pane title. +.Pp +.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. +.Fl p +is a shorthand option for this. +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 +.Fl k +keeps the pane open after the optional +.Ar shell\-command +exits and waits for a key to be pressed 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 +.Fl W +Waits until +.Ar shell\-command +exits, then returns its exit status. +For example: +.Bd -literal -offset indent +$ tmux new-pane -W 'vi afile' +$ echo $? +0 +.Ed +.Pp +.Fl E , +or an empty +.Ar shell\-command , +(\[aq]\[aq]) will create an empty pane with no command running in it; +.Ic display-message +.Fl I +can write to an empty pane. +The +.Fl I +flag will create an empty pane and forward any output from stdin to it. +For example: +.Bd -literal -offset indent +$ make 2>&1|tmux new\-pane \-dI & +.Ed .Pp -See -.Ic new\-pane -for more details. .Tg swapp .It Xo Ic swap\-pane .Op Fl dDUZ From e0dd40f812fdbc9a8f7dfa2810d8db3666ce1c66 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 27 Jun 2026 12:27:43 +0000 Subject: [PATCH 60/71] Clear lines before writing in copy mode to avoid leaving stray text when new line is shorter than old. --- window-copy.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/window-copy.c b/window-copy.c index 4ba791e75..0a37c5e9d 100644 --- a/window-copy.c +++ b/window-copy.c @@ -5154,6 +5154,9 @@ window_copy_write_line(struct window_mode_entry *wme, else content_sx = sx; + screen_write_cursormove(ctx, 0, py, 0); + screen_write_clearline(ctx, 8); + ft = format_create_defaults(NULL, NULL, NULL, NULL, wp); style_apply(&gc, oo, "copy-mode-position-style", ft); From 7cd33ce0dd3d3e8afe50815a237ad14457d343fc Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 27 Jun 2026 12:37:36 +0000 Subject: [PATCH 61/71] Set view name immediately when entering mode. --- window-tree.c | 1 + 1 file changed, 1 insertion(+) diff --git a/window-tree.c b/window-tree.c index 19bd5569f..c37082c91 100644 --- a/window-tree.c +++ b/window-tree.c @@ -1138,6 +1138,7 @@ window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs, window_tree_get_key, window_tree_swap, window_tree_sort, window_tree_help, data, window_tree_menu_items, &s); mode_tree_zoom(data->data, args); + mode_tree_view_name(data->data, "preview"); mode_tree_build(data->data); mode_tree_draw(data->data); From b0db912bde8f77eefa112e00385e6fee8fdbd447 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 28 Jun 2026 16:52:28 +0100 Subject: [PATCH 62/71] Update check-names test. --- regress/check-names.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/regress/check-names.sh b/regress/check-names.sh index 104ff01b8..21e185ac1 100644 --- a/regress/check-names.sh +++ b/regress/check-names.sh @@ -110,20 +110,26 @@ must_fail $TMUX set-buffer -b "bad${invalid}name" data # Titles set by commands allow '#', ':' and '.'. $TMUX select-pane -T 'title#:.ok' || fail "command title rejected" must_equal "$($TMUX display-message -p '#{pane_title}')" 'title#:.ok' +$TMUX send-keys "printf '\\033]2;title#[fg=red]ok\\007'" Enter || exit 1 +sleep 1 +must_equal "$($TMUX display-message -p '#{pane_title}')" 'title#[fg=red]ok' +$TMUX send-keys "printf '\\033]2;title#(bad)\\007'" Enter || exit 1 +sleep 1 +must_equal "$($TMUX display-message -p '#{pane_title}')" 'title_(bad)' # Buffer names allow '#', ':' and '.'. $TMUX set-buffer -b 'buffer#:.ok' data || fail "buffer name rejected" must_equal "$($TMUX list-buffers -F '#{buffer_name}')" 'buffer#:.ok' -# Window names from escape sequences reject '#', ':' and '.' by cleaning them. +# Window names from escape sequences allow '#' except in '#('. $TMUX send-keys "printf '\\033kescape#:.ok\\033\\\\'" Enter || exit 1 sleep 1 -must_equal "$($TMUX display-message -p '#{window_name}')" 'escape___ok' +must_equal "$($TMUX display-message -p '#{window_name}')" 'escape#__ok' # Titles from escape sequences reject only '#'. $TMUX send-keys "printf '\\033]2;escape#:.ok\\007'" Enter || exit 1 sleep 1 -must_equal "$($TMUX display-message -p '#{pane_title}')" 'escape_:.ok' +must_equal "$($TMUX display-message -p '#{pane_title}')" 'escape#:.ok' # Invalid UTF-8 from escape sequences is ignored. $TMUX rename-window 'before-invalid' || exit 1 From 96899dc527999ee9130246eec86d76d79b1a8d97 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 28 Jun 2026 15:53:18 +0000 Subject: [PATCH 63/71] Only forbid #( in names and titles (styles are #[ and are useful). --- tmux.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tmux.c b/tmux.c index bc018da88..7236360bb 100644 --- a/tmux.c +++ b/tmux.c @@ -295,7 +295,10 @@ clean_name(const char *name, const char* forbid) return (NULL); copy = xstrdup(name); for (cp = copy; *cp != '\0'; cp++) { - if (strchr(forbid, *cp) != NULL) + if (*cp == '#' && strchr(forbid, '#') != NULL) { + if (cp[1] == '(') + *cp = '_'; + } else if (strchr(forbid, *cp) != NULL) *cp = '_'; } utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); From 2dc704abb6f6b72e3a5d452c821e53084ddcae2c Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 28 Jun 2026 17:01:52 +0100 Subject: [PATCH 64/71] Always set SIXEL aspect ratio to 1:1, GitHub issue 5291 from James Holderness. --- image-sixel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/image-sixel.c b/image-sixel.c index 2e6f247d2..a004d78f8 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -586,7 +586,7 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) len = 8192; buf = xmalloc(len); - tmplen = xsnprintf(tmp, sizeof tmp, "\033P0;%uq", si->p2); + tmplen = xsnprintf(tmp, sizeof tmp, "\033P9;%uq", si->p2); sixel_print_add(&buf, &len, &used, tmp, tmplen); if (si->set_ra) { From c29b41e5279f246669aae32f0c15c20cfa82c3c6 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 29 Jun 2026 07:45:09 +0000 Subject: [PATCH 65/71] Make pane scrollbars able to auto-hide after a short timeout. This replaces the previous "modal" behaviour where the pane would be resized, which tended to make a mess. Instead, the scrollbar appears when scrolling or when hovered over with the mouse and disappears a (configurable) short period later. From Michael Grant. --- cmd-resize-pane.c | 4 +- layout.c | 6 +-- options-table.c | 14 ++++- options.c | 5 ++ regsub.c | 3 +- screen-redraw.c | 41 ++++++++++---- screen-write.c | 6 ++- server-client.c | 123 +++++++++++++++++++++++++++++++++++++++--- sort.c | 4 +- tmux.1 | 35 ++++++++---- tmux.h | 12 +++++ window-copy.c | 44 +++++++++++---- window-visible.c | 2 +- window.c | 133 +++++++++++++++++++++++++++++++++++++++++++++- 14 files changed, 379 insertions(+), 53 deletions(-) diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 734280af1..185844f2e 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -251,10 +251,10 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c, sb_pos = options_get_number(w->options, "pane-scrollbars-position"); left = wp->xoff - 1; right = wp->xoff + sx; - if (window_pane_show_scrollbar(wp, scrollbars) && + if (window_pane_scrollbar_reserve(wp, scrollbars) && sb_pos == PANE_SCROLLBARS_LEFT) { left -= wp->scrollbar_style.width + wp->scrollbar_style.pad; - } else if (window_pane_show_scrollbar(wp, scrollbars) && + } else if (window_pane_scrollbar_reserve(wp, scrollbars) && sb_pos == PANE_SCROLLBARS_RIGHT) { right += wp->scrollbar_style.width + wp->scrollbar_style.pad; } diff --git a/layout.c b/layout.c index 279f504af..a65cd9d86 100644 --- a/layout.c +++ b/layout.c @@ -462,7 +462,7 @@ layout_fix_panes(struct window *w, struct window_pane *skip) sy--; } - if (window_pane_show_scrollbar(wp, scrollbars)) { + if (window_pane_scrollbar_reserve(wp, scrollbars)) { sb_w = wp->scrollbar_style.width; sb_pad = wp->scrollbar_style.pad; if (sb_w < 1) @@ -535,7 +535,7 @@ layout_resize_check(struct window *w, struct layout_cell *lc, /* Space available in this cell only. */ if (type == LAYOUT_LEFTRIGHT) { available = lc->sx; - if (scrollbars) + if (scrollbars == PANE_SCROLLBARS_ALWAYS) minimum = PANE_MINIMUM + sb_style->width + sb_style->pad; else @@ -1263,7 +1263,7 @@ layout_split_check_space(struct window_pane *wp, struct layout_cell *lc, switch (type) { case LAYOUT_LEFTRIGHT: - if (scrollbars) { + if (scrollbars == PANE_SCROLLBARS_ALWAYS) { minimum = PANE_MINIMUM * 2 + sb_style->width + sb_style->pad; } else diff --git a/options-table.c b/options-table.c index ddd8b4634..5019bb244 100644 --- a/options-table.c +++ b/options-table.c @@ -65,7 +65,7 @@ static const char *options_table_cursor_style_list[] = { "blinking-bar", "bar", NULL }; static const char *options_table_pane_scrollbars_list[] = { - "off", "modal", "on", NULL + "off", "modal", "on", "auto-hide", NULL }; static const char *options_table_pane_scrollbars_position_list[] = { "right", "left", NULL @@ -1595,7 +1595,17 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_pane_scrollbars_list, .default_num = PANE_SCROLLBARS_OFF, - .text = "Pane scrollbar state." + .text = "Pane scrollbar state: off, on, modal, or auto-hide." + }, + + { .name = "pane-scrollbars-timeout", + .type = OPTIONS_TABLE_NUMBER, + .scope = OPTIONS_TABLE_WINDOW, + .minimum = 0, + .maximum = INT_MAX, + .default_num = 500, + .unit = "milliseconds", + .text = "Time before modal and auto-hide pane scrollbars disappear." }, { .name = "pane-scrollbars-style", diff --git a/options.c b/options.c index db0f221a3..e05930463 100644 --- a/options.c +++ b/options.c @@ -1264,6 +1264,7 @@ options_push_changes(const char *name) strcmp(name, "pane-border-lines") == 0 || strcmp(name, "pane-border-status") == 0 || strcmp(name, "pane-scrollbars") == 0 || + strcmp(name, "pane-scrollbars-timeout") == 0 || strcmp(name, "pane-scrollbars-position") == 0 || strcmp(name, "pane-scrollbars-style") == 0) redraw_invalidate_all_scenes(); @@ -1288,6 +1289,10 @@ options_push_changes(const char *name) RB_FOREACH(w, windows, &windows) layout_fix_panes(w, NULL); } + if (strcmp(name, "pane-scrollbars") == 0) { + RB_FOREACH(wp, window_pane_tree, &all_window_panes) + window_pane_scrollbar_hide(wp); + } if (strcmp(name, "pane-scrollbars-style") == 0) { RB_FOREACH(wp, window_pane_tree, &all_window_panes) { style_set_scrollbar_style_from_option( diff --git a/regsub.c b/regsub.c index a863f9fd8..62d750d8a 100644 --- a/regsub.c +++ b/regsub.c @@ -24,7 +24,8 @@ #include "tmux.h" static void -regsub_copy(char **buf, ssize_t *len, const char *text, size_t start, size_t end) +regsub_copy(char **buf, ssize_t *len, const char *text, size_t start, + size_t end) { size_t add = end - start; diff --git a/screen-redraw.c b/screen-redraw.c index e8dd8ec0f..813d4e563 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -81,6 +81,7 @@ enum redraw_span_type { #define REDRAW_BORDER_IS_ARROW 0x1 #define REDRAW_SCROLLBAR_LEFT 0x2 #define REDRAW_SCROLLBAR_RIGHT 0x4 +#define REDRAW_SCROLLBAR_OVERLAY 0x8 /* Draw operations. */ #define REDRAW_PANE 0x1 @@ -447,7 +448,7 @@ redraw_mark_pane_inside(struct redraw_build_ctx *bctx, struct window_pane *wp) /* Mark scrollbar data. */ static void redraw_mark_pane_scrollbar(struct redraw_build_ctx *bctx, - struct window_pane *wp, int sb_w, int sb_left) + struct window_pane *wp, int sb_w, int sb_left, int overlay) { struct redraw_build_cell *bc; u_int x, y; @@ -457,7 +458,13 @@ redraw_mark_pane_scrollbar(struct redraw_build_ctx *bctx, if (sb_w == 0) return; - if (sb_left) { + if (overlay && sb_left) { + sx = wp->xoff; + ex = sx + sb_w - 1; + } else if (overlay) { + ex = wp->xoff + (int)wp->sx - 1; + sx = ex - sb_w + 1; + } else if (sb_left) { sx = wp->xoff - sb_w; ex = wp->xoff - 1; } else { @@ -481,6 +488,8 @@ redraw_mark_pane_scrollbar(struct redraw_build_ctx *bctx, bc->data.sb.flags |= REDRAW_SCROLLBAR_LEFT; else bc->data.sb.flags |= REDRAW_SCROLLBAR_RIGHT; + if (overlay) + bc->data.sb.flags |= REDRAW_SCROLLBAR_OVERLAY; } } } @@ -755,19 +764,30 @@ redraw_mark_pane_borders(struct redraw_build_ctx *bctx, struct window_pane *wp, static void redraw_mark_pane(struct redraw_build_ctx *bctx, struct window_pane *wp) { - int sb_w = 0, sb_left = 0; + int sb_w = 0, sb_left = 0, overlay = 0; if (!window_pane_is_visible(wp)) return; - if (window_pane_show_scrollbar(wp, bctx->sb)) - sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; + if (window_pane_scrollbar_visible(wp, bctx->sb)) { + overlay = window_pane_scrollbar_overlay(wp, bctx->sb); + if (overlay) { + sb_w = wp->scrollbar_style.width + + wp->scrollbar_style.pad; + if (sb_w > (int)wp->sx) { + sb_w = wp->scrollbar_style.width; + if (sb_w > (int)wp->sx) + sb_w = wp->sx; + } + } else + sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; + } if (sb_w != 0 && bctx->sbp == PANE_SCROLLBARS_LEFT) sb_left = 1; redraw_mark_pane_inside(bctx, wp); - redraw_mark_pane_borders(bctx, wp, sb_w, sb_left); - redraw_mark_pane_scrollbar(bctx, wp, sb_w, sb_left); + redraw_mark_pane_borders(bctx, wp, overlay ? 0 : sb_w, sb_left); + redraw_mark_pane_scrollbar(bctx, wp, sb_w, sb_left, overlay); } /* Choose the pane that will provide the border style for two-pane layouts. */ @@ -1220,7 +1240,7 @@ redraw_draw_scrollbar_span(struct redraw_draw_ctx *dctx, struct screen *s = wp->screen; struct tty *tty = &scene->c->tty; struct style *sb_style = &wp->scrollbar_style; - struct grid_cell gc, slgc, *gcp; + struct grid_cell gc, slgc, pad_gc, *gcp; double pct_view; u_int total_height, slider_h, slider_y; u_int sb_h = span->data.sb.height; @@ -1260,6 +1280,7 @@ redraw_draw_scrollbar_span(struct redraw_draw_ctx *dctx, memcpy(&slgc, &gc, sizeof slgc); slgc.fg = gc.bg; slgc.bg = gc.fg; + tty_default_colours(&pad_gc, wp, NULL); sb_w = sb_style->width; sb_pad = sb_style->pad; @@ -1269,12 +1290,12 @@ redraw_draw_scrollbar_span(struct redraw_draw_ctx *dctx, for (i = 0; i < n; i++) { if (span->data.sb.flags & REDRAW_SCROLLBAR_LEFT) { if (off + i >= sb_w && off + i < sb_w + sb_pad) { - tty_cell(tty, &grid_default_cell, NULL); + tty_cell(tty, &pad_gc, NULL); continue; } } else { if (off + i < sb_pad) { - tty_cell(tty, &grid_default_cell, NULL); + tty_cell(tty, &pad_gc, NULL); continue; } } diff --git a/screen-write.c b/screen-write.c index ba7ffd930..31d177ef0 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2096,6 +2096,10 @@ screen_write_collect_flush_scrolled(struct screen_write_ctx *ctx) screen_write_redraw_pane(ctx, &ttyctx); return 0; } + if (wp != NULL && window_pane_scrollbar_overlay_visible(wp)) { + wp->flags |= PANE_REDRAW; + return 0; + } log_debug("%s: scrolled %u (region %u-%u)", __func__, ctx->scrolled, s->rupper, s->rlower); @@ -2109,7 +2113,7 @@ screen_write_collect_flush_scrolled(struct screen_write_ctx *ctx) tty_write(tty_cmd_scrollup, &ttyctx); if (wp != NULL) - wp->flags |= PANE_REDRAWSCROLLBAR; + window_pane_scrollbar_redraw(wp); return 1; } diff --git a/server-client.c b/server-client.c index 5b2651658..43bf7600d 100644 --- a/server-client.c +++ b/server-client.c @@ -52,6 +52,8 @@ static void server_client_dispatch(struct imsg *, void *); static int server_client_dispatch_command(struct client *, struct imsg *); static int server_client_dispatch_identify(struct client *, struct imsg *); static int server_client_dispatch_shell(struct client *); +static void server_client_update_scrollbar_hover(struct client *, int, int, + int); static void server_client_report_theme(struct client *, enum client_theme); /* Compare client windows. */ @@ -605,6 +607,60 @@ server_client_exec(struct client *c, const char *cmd) free(msg); } +/* Is this point inside the auto-hide scrollbar interaction area? */ +static int +server_client_in_scrollbar_area(struct window_pane *wp, int px, int py) +{ + struct window *w = wp->window; + u_int width, pad, total; + int sb, sb_pos, start, end; + + sb = options_get_number(w->options, "pane-scrollbars"); + if (!window_pane_scrollbar_overlay(wp, sb)) + return (0); + if (py < wp->yoff || py >= wp->yoff + (int)wp->sy) + return (0); + + width = wp->scrollbar_style.width; + pad = wp->scrollbar_style.pad; + total = width + pad; + if (total == 0 || total > wp->sx) + total = wp->sx; + + sb_pos = options_get_number(w->options, "pane-scrollbars-position"); + if (sb_pos == PANE_SCROLLBARS_LEFT) { + start = wp->xoff; + end = wp->xoff + (int)total - 1; + } else { + end = wp->xoff + (int)wp->sx - 1; + start = end - (int)total + 1; + } + return (px >= start && px <= end); +} + +/* Update auto-hide scrollbars for a mouse movement. */ +static void +server_client_update_scrollbar_hover(struct client *c, int type, int px, int py) +{ + struct window *w = c->session->curw->window; + struct window_pane *wp; + + if (type != KEYC_TYPE_MOUSEMOVE) + return; + + TAILQ_FOREACH(wp, &w->panes, entry) { + if (!window_pane_is_visible(wp)) + continue; + if (server_client_in_scrollbar_area(wp, px, py)) { + wp->sb_auto_hover = 1; + window_pane_scrollbar_show(wp, 1); + } else { + wp->sb_auto_hover = 0; + window_pane_scrollbar_start_timer(wp); + } + } +} + /* 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, @@ -615,14 +671,18 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, int pane_status, sb, sb_pos, sb_w, sb_pad; int pane_status_line, sl_top, sl_bottom; int bdr_bottom, bdr_top, bdr_left, bdr_right; + int sb_start, sb_end, sb_overlay; sb = options_get_number(w->options, "pane-scrollbars"); sb_pos = options_get_number(w->options, "pane-scrollbars-position"); pane_status = window_pane_get_pane_status(wp); + sb_overlay = window_pane_scrollbar_overlay(wp, sb); - if (window_pane_show_scrollbar(wp, sb)) { + if (window_pane_scrollbar_visible(wp, sb)) { sb_w = wp->scrollbar_style.width; sb_pad = wp->scrollbar_style.pad; + if (sb_overlay && sb_w > (int)wp->sx) + sb_w = wp->sx; } else { sb_w = 0; sb_pad = 0; @@ -635,9 +695,34 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, else pane_status_line = -1; /* not used */ bdr_left = wp->xoff - 1; - if (sb_pos == PANE_SCROLLBARS_LEFT) + if (!sb_overlay && sb_pos == PANE_SCROLLBARS_LEFT) bdr_left -= sb_pad + sb_w; + if (sb_overlay && sb_w != 0 && + py >= wp->yoff && py < wp->yoff + (int)wp->sy && + px >= wp->xoff && px < wp->xoff + (int)wp->sx) { + if (sb_pos == PANE_SCROLLBARS_LEFT) { + sb_start = wp->xoff; + sb_end = sb_start + sb_w - 1; + } else { + sb_end = wp->xoff + (int)wp->sx - 1; + sb_start = sb_end - sb_w + 1; + } + if (px >= sb_start && px <= sb_end) { + 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) + return (KEYC_MOUSE_LOCATION_SCROLLBAR_UP); + else if (py >= sl_top && py <= sl_bottom) { + *sl_mpos = (py - wp->sb_slider_y - wp->yoff); + return (KEYC_MOUSE_LOCATION_SCROLLBAR_SLIDER); + } else + return (KEYC_MOUSE_LOCATION_SCROLLBAR_DOWN); + } + return (KEYC_MOUSE_LOCATION_PANE); + } + /* Check if point is within the pane or scrollbar. */ if (((pane_status != PANE_STATUS_OFF && py != pane_status_line && py != wp->yoff + (int)wp->sy) || @@ -685,7 +770,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, if (window_pane_is_floating(fwp) && window_pane_get_pane_lines(fwp) == PANE_LINES_NONE) continue; - if (window_pane_show_scrollbar(fwp, sb)) { + if (window_pane_scrollbar_reserve(fwp, sb)) { sb_w = fwp->scrollbar_style.width; sb_pad = fwp->scrollbar_style.pad; } else { @@ -915,10 +1000,14 @@ have_event: tty_window_offset(&c->tty, &m->ox, &m->oy, &sx, &sy); log_debug("mouse window @%u at %u,%u (%ux%u)", w->id, m->ox, m->oy, sx, sy); - if (px > sx || py > sy) + if (px > sx || py > sy) { + server_client_update_scrollbar_hover(c, type, + -1, -1); return (KEYC_UNKNOWN); + } px = px + m->ox; py = py + m->oy; + server_client_update_scrollbar_hover(c, type, px, py); if (type == KEYC_TYPE_MOUSEDRAG && lwp != NULL) { /* Use pane from last mouse event. */ @@ -951,7 +1040,8 @@ have_event: m->wp = wp->id; m->w = wp->window->id; } - } + } else + server_client_update_scrollbar_hover(c, type, -1, -1); /* Reset click type or add a click timer if needed. */ if (type == KEYC_TYPE_MOUSEDOWN || @@ -1911,8 +2001,9 @@ server_client_reset_state(struct client *c) struct window_pane *wp = server_client_get_pane(c), *loop; struct screen *s = NULL; struct options *oo = c->session->options; - int mode = 0, cursor, flags, pane_mode = 0; + int mode = 0, cursor, flags, pane_mode = 0, sb; u_int cx = 0, cy = 0, ox, oy, sx, sy, prompt = 0; + u_int sb_w; struct visible_ranges *r; if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) @@ -1965,6 +2056,21 @@ server_client_reset_state(struct client *c) if (!window_position_is_visible(r, cx)) cursor = 0; + if (window_pane_scrollbar_overlay_visible(wp)) { + sb_w = wp->scrollbar_style.width; + if (sb_w > wp->sx) + sb_w = wp->sx; + if (sb_w != 0 && + options_get_number(w->options, + "pane-scrollbars-position") == + PANE_SCROLLBARS_LEFT) { + if (s->cx < sb_w) + cursor = 0; + } else if (sb_w != 0 && + s->cx >= wp->sx - sb_w) + cursor = 0; + } + if (status_at_line(c) == 0) cy += status_line_size(c); } @@ -1992,7 +2098,10 @@ server_client_reset_state(struct client *c) mode |= MODE_MOUSE_ALL; } } - if (options_get_number(oo, "focus-follows-mouse")) + sb = options_get_number(w->options, "pane-scrollbars"); + if (options_get_number(oo, "focus-follows-mouse") || + sb == PANE_SCROLLBARS_MODAL || + sb == PANE_SCROLLBARS_AUTOHIDE) mode |= MODE_MOUSE_ALL; else if (~mode & MODE_MOUSE_ALL) mode |= MODE_MOUSE_BUTTON; diff --git a/sort.c b/sort.c index 212da22ea..c416b8425 100644 --- a/sort.c +++ b/sort.c @@ -26,8 +26,8 @@ static struct sort_criteria *sort_criteria; static void -sort_qsort(void *l, u_int len, u_int size, int (*cmp)(const void *, const void *), - struct sort_criteria *sort_crit) +sort_qsort(void *l, u_int len, u_int size, int (*cmp)(const void *, + const void *), struct sort_criteria *sort_crit) { u_int i; void *tmp, **ll; diff --git a/tmux.1 b/tmux.1 index 63809bac1..8f74205f8 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5893,7 +5893,7 @@ and will fall back to standard ACS line drawing when UTF\-8 is not supported. .Pp .It Xo Ic pane\-scrollbars -.Op Ic off | modal | on +.Op Ic off | modal | on | auto\-hide .Xc When enabled, a character based scrollbar appears on the left or right of each pane. @@ -5903,19 +5903,29 @@ represents the position and size of the visible part of the pane content. .Pp If set to .Ic on -the scrollbar is visible all the time. +the scrollbar is visible all the time and the pane is narrowed by the width of +the scrollbar. If set to .Ic modal -the scrollbar only appears when the pane is in copy mode or view mode. -When the scrollbar is visible, the pane is narrowed by the width of the -scrollbar and the text in the pane is reflowed. +the scrollbar only appears when the pane is in copy mode or view mode and +auto-hides when not in use. If set to -.Ic modal , -the pane is narrowed only when the scrollbar is visible. +.Ic auto\-hide +the scrollbar is available all the time but auto-hides when not in use. +With +.Ic modal +and +.Ic auto\-hide , +the scrollbar overlays the pane and does not narrow or reflow the pane. .Pp See also .Ic pane\-scrollbars\-style . .Pp +.It Xo Ic pane\-scrollbars\-position +.Op Ic left | right +.Xc +Sets which side of the pane to display pane scrollbars on. +.Pp .It Ic pane\-scrollbars\-style Ar style Set the scrollbars style. For how to specify @@ -5932,10 +5942,13 @@ attribute sets the width of the scrollbar and the attribute the padding between the scrollbar and the pane. Other attributes are ignored. .Pp -.It Xo Ic pane\-scrollbars\-position -.Op Ic left | right -.Xc -Sets which side of the pane to display pane scrollbars on. +.It Ic pane\-scrollbars\-timeout Ar time +Set the time in milliseconds before scrollbars are hidden when +.Ic pane\-scrollbars +is +.Ic modal +or +.Ic auto\-hide . .Pp .It Ic pane\-status\-current\-style Ar style Set status line style for the currently active pane. diff --git a/tmux.h b/tmux.h index 869936e68..f34b3b324 100644 --- a/tmux.h +++ b/tmux.h @@ -1266,6 +1266,9 @@ struct window_pane { u_int sb_slider_y; u_int sb_slider_h; + int sb_auto_visible; + int sb_auto_hover; + struct event sb_auto_timer; int argc; char **argv; @@ -1440,6 +1443,7 @@ TAILQ_HEAD(winlink_stack, winlink); #define PANE_SCROLLBARS_OFF 0 #define PANE_SCROLLBARS_MODAL 1 #define PANE_SCROLLBARS_ALWAYS 2 +#define PANE_SCROLLBARS_AUTOHIDE 3 /* Pane scrollbars position option. */ #define PANE_SCROLLBARS_RIGHT 0 @@ -3581,6 +3585,14 @@ void window_set_fill_character(struct window *); void window_pane_default_cursor(struct window_pane *); int window_pane_mode(struct window_pane *); int window_pane_show_scrollbar(struct window_pane *, int); +int window_pane_scrollbar_reserve(struct window_pane *, int); +int window_pane_scrollbar_visible(struct window_pane *, int); +int window_pane_scrollbar_overlay(struct window_pane *, int); +int window_pane_scrollbar_overlay_visible(struct window_pane *); +void window_pane_scrollbar_show(struct window_pane *, int); +void window_pane_scrollbar_hide(struct window_pane *); +void window_pane_scrollbar_start_timer(struct window_pane *); +void window_pane_scrollbar_redraw(struct window_pane *); int window_pane_get_bg(struct window_pane *); int window_pane_get_fg(struct window_pane *); int window_pane_get_fg_control_client(struct window_pane *); diff --git a/window-copy.c b/window-copy.c index 0a37c5e9d..aedbba99b 100644 --- a/window-copy.c +++ b/window-copy.c @@ -61,8 +61,10 @@ static int window_copy_line_number_mode(struct window_mode_entry *); static int window_copy_line_number_is_absolute(struct window_mode_entry *); static int window_copy_line_numbers_active(struct window_mode_entry *); static u_int window_copy_line_number_width(struct window_mode_entry *); -static u_int window_copy_cursor_offset(struct window_mode_entry *, u_int, u_int); -static u_int window_copy_cursor_unoffset(struct window_mode_entry *, u_int, u_int); +static u_int window_copy_cursor_offset(struct window_mode_entry *, u_int, + u_int); +static u_int window_copy_cursor_unoffset(struct window_mode_entry *, u_int, + u_int); static void window_copy_write_line(struct window_mode_entry *, struct screen_write_ctx *, u_int); static void window_copy_write_lines(struct window_mode_entry *, @@ -860,6 +862,7 @@ window_copy_scroll1(struct window_mode_entry *wme, struct window_pane *wp, if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); window_copy_update_selection(wme, 1, 0); + window_pane_scrollbar_show(wp, 1); window_copy_redraw_screen(wme); } @@ -913,6 +916,7 @@ window_copy_pageup1(struct window_mode_entry *wme, int half_page) if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); window_copy_update_selection(wme, 1, 0); + window_pane_scrollbar_show(wme->wp, 1); window_copy_redraw_screen(wme); } @@ -973,6 +977,7 @@ window_copy_pagedown1(struct window_mode_entry *wme, int half_page, if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); window_copy_update_selection(wme, 1, 0); + window_pane_scrollbar_show(wme->wp, 1); window_copy_redraw_screen(wme); return (0); } @@ -1789,7 +1794,7 @@ window_copy_cmd_history_bottom(struct window_copy_cmd_state *cs) struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; struct screen *s = data->backing; - u_int oy; + u_int oy, old_oy = data->oy; oy = screen_hsize(s) + data->cy - data->oy; if (data->lineflag == LINE_SEL_RIGHT_LEFT && oy == data->endsely) @@ -1802,6 +1807,8 @@ window_copy_cmd_history_bottom(struct window_copy_cmd_state *cs) if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); window_copy_update_selection(wme, 1, 0); + if (data->oy != old_oy) + window_pane_scrollbar_show(wme->wp, 1); return (WINDOW_COPY_CMD_REDRAW); } @@ -1810,7 +1817,7 @@ window_copy_cmd_history_top(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; - u_int oy; + u_int oy, old_oy = data->oy; oy = screen_hsize(data->backing) + data->cy - data->oy; if (data->lineflag == LINE_SEL_LEFT_RIGHT && oy == data->sely) @@ -1823,6 +1830,8 @@ window_copy_cmd_history_top(struct window_copy_cmd_state *cs) if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); window_copy_update_selection(wme, 1, 0); + if (data->oy != old_oy) + window_pane_scrollbar_show(wme->wp, 1); return (WINDOW_COPY_CMD_REDRAW); } @@ -3772,7 +3781,7 @@ window_copy_scroll_to(struct window_mode_entry *wme, u_int px, u_int py, { struct window_copy_mode_data *data = wme->data; struct grid *gd = data->backing->grid; - u_int offset, gap; + u_int offset, gap, old_oy = data->oy; data->cx = px; @@ -3796,6 +3805,8 @@ window_copy_scroll_to(struct window_mode_entry *wme, u_int px, u_int py, if (!no_redraw && data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); window_copy_update_selection(wme, 1, 0); + if (data->oy != old_oy) + window_pane_scrollbar_show(wme->wp, 1); if (!no_redraw) window_copy_redraw_screen(wme); } @@ -5278,7 +5289,10 @@ window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny) return; } - screen_write_start_pane(&ctx, wp, NULL); + if (window_pane_scrollbar_overlay_visible(wp)) + screen_write_start(&ctx, &data->screen); + else + screen_write_start_pane(&ctx, wp, NULL); for (i = py; i < py + ny; i++) window_copy_write_line(wme, &ctx, i); screen_write_cursormove(&ctx, @@ -5286,7 +5300,7 @@ window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny) 0); screen_write_stop(&ctx); - wp->flags |= PANE_REDRAWSCROLLBAR; + window_pane_scrollbar_redraw(wp); } static void @@ -6542,6 +6556,7 @@ window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) if (ny == 0) return; data->oy -= ny; + window_pane_scrollbar_show(wp, 1); if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); @@ -6573,7 +6588,10 @@ window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) return; } - screen_write_start_pane(&ctx, wp, NULL); + if (window_pane_scrollbar_overlay_visible(wp)) + screen_write_start(&ctx, &data->screen); + else + screen_write_start_pane(&ctx, wp, NULL); screen_write_cursormove(&ctx, 0, 0, 0); screen_write_deleteline(&ctx, ny, 8); window_copy_write_lines(wme, &ctx, screen_size_y(s) - ny, ny); @@ -6588,7 +6606,7 @@ window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) window_copy_cursor_offset(wme, data->cx, screen_size_x(s)), data->cy, 0); screen_write_stop(&ctx); - wp->flags |= PANE_REDRAWSCROLLBAR; + window_pane_scrollbar_redraw(wp); } static void @@ -6607,6 +6625,7 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) if (ny == 0) return; data->oy += ny; + window_pane_scrollbar_show(wp, 1); if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); @@ -6633,7 +6652,10 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) return; } - screen_write_start_pane(&ctx, wp, NULL); + if (window_pane_scrollbar_overlay_visible(wp)) + screen_write_start(&ctx, &data->screen); + else + screen_write_start_pane(&ctx, wp, NULL); screen_write_cursormove(&ctx, 0, 0, 0); screen_write_insertline(&ctx, ny, 8); window_copy_write_lines(wme, &ctx, 0, ny); @@ -6644,7 +6666,7 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) screen_write_cursormove(&ctx, window_copy_cursor_offset(wme, data->cx, screen_size_x(s)), data->cy, 0); screen_write_stop(&ctx); - wp->flags |= PANE_REDRAWSCROLLBAR; + window_pane_scrollbar_redraw(wp); } static void diff --git a/window-visible.c b/window-visible.c index f5b034475..7ec7fcfb5 100644 --- a/window-visible.c +++ b/window-visible.c @@ -126,7 +126,7 @@ window_visible_ranges(struct window_pane *base_wp, int px, int py, u_int width, continue; sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - if (!window_pane_show_scrollbar(wp, sb)) + if (!window_pane_scrollbar_reserve(wp, sb)) sb_w = sb_pos = 0; for (i = 0; i < r->used; i++) { diff --git a/window.c b/window.c index 1965685b6..a3c228039 100644 --- a/window.c +++ b/window.c @@ -73,6 +73,7 @@ struct window_pane_input_data { static struct window_pane *window_pane_create(struct window *, u_int, u_int, u_int); static void window_pane_destroy(struct window_pane *); +static void window_pane_scrollbar_timer(int, short, void *); static void window_pane_full_size_offset(struct window_pane *wp, int *xoff, int *yoff, u_int *sx, u_int *sy); @@ -1105,6 +1106,7 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) screen_init(&wp->status_screen, 1, 1, 0); style_ranges_init(&wp->border_status_line.ranges); + evtimer_set(&wp->sb_auto_timer, window_pane_scrollbar_timer, wp); if (gethostname(host, sizeof host) == 0) screen_set_title(&wp->base, host, 0); @@ -1151,6 +1153,52 @@ window_pane_free_modes(struct window_pane *wp) wp->screen = &wp->base; } +static void +window_pane_scrollbar_timer(__unused int fd, __unused short events, void *arg) +{ + struct window_pane *wp = arg; + + wp->sb_auto_hover = 0; + window_pane_scrollbar_hide(wp); +} + +static int +window_pane_scrollbar_auto_hide(struct window_pane *wp) +{ + int sb; + + sb = options_get_number(wp->window->options, "pane-scrollbars"); + return (sb == PANE_SCROLLBARS_MODAL || sb == PANE_SCROLLBARS_AUTOHIDE); +} + +int +window_pane_scrollbar_overlay_visible(struct window_pane *wp) +{ + int sb; + + sb = options_get_number(wp->window->options, "pane-scrollbars"); + return (window_pane_scrollbar_overlay(wp, sb) && + window_pane_scrollbar_visible(wp, sb)); +} + +void +window_pane_scrollbar_redraw(struct window_pane *wp) +{ + if (window_pane_scrollbar_overlay_visible(wp)) { + wp->flags |= PANE_REDRAW; + return; + } + wp->flags |= PANE_REDRAWSCROLLBAR; +} + +static void +window_pane_scrollbar_redraw_visibility(struct window_pane *wp) +{ + redraw_invalidate_scene(wp->window); + wp->flags |= PANE_REDRAW; + server_redraw_window(wp->window); +} + static void window_pane_destroy(struct window_pane *wp) { @@ -1182,6 +1230,8 @@ window_pane_destroy(struct window_pane *wp) event_del(&wp->resize_timer); if (event_initialized(&wp->sync_timer)) event_del(&wp->sync_timer); + if (event_initialized(&wp->sb_auto_timer)) + event_del(&wp->sb_auto_timer); window_pane_clear_resizes(wp, NULL); RB_REMOVE(window_pane_tree, &all_window_panes, wp); @@ -1251,7 +1301,8 @@ window_pane_set_event(struct window_pane *wp) } void -window_pane_clear_resizes(struct window_pane *wp, struct window_pane_resize *except) +window_pane_clear_resizes(struct window_pane *wp, + struct window_pane_resize *except) { struct window_pane_resize *r, *r1; @@ -1724,7 +1775,7 @@ window_pane_full_size_offset(struct window_pane *wp, int *xoff, int *yoff, pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); sb_pos = options_get_number(w->options, "pane-scrollbars-position"); - if (window_pane_show_scrollbar(wp, pane_scrollbars)) + if (window_pane_scrollbar_reserve(wp, pane_scrollbars)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; else sb_w = 0; @@ -2151,12 +2202,90 @@ window_pane_show_scrollbar(struct window_pane *wp, int sb_option) if (SCREEN_IS_ALTERNATE(&wp->base)) return (0); if (sb_option == PANE_SCROLLBARS_ALWAYS || + sb_option == PANE_SCROLLBARS_AUTOHIDE || (sb_option == PANE_SCROLLBARS_MODAL && window_pane_mode(wp) != WINDOW_PANE_NO_MODE)) return (1); return (0); } +int +window_pane_scrollbar_reserve(struct window_pane *wp, int sb_option) +{ + if (!window_pane_show_scrollbar(wp, sb_option)) + return (0); + return (sb_option == PANE_SCROLLBARS_ALWAYS); +} + +int +window_pane_scrollbar_overlay(struct window_pane *wp, int sb_option) +{ + if (!window_pane_show_scrollbar(wp, sb_option)) + return (0); + return (window_pane_scrollbar_auto_hide(wp)); +} + +int +window_pane_scrollbar_visible(struct window_pane *wp, int sb_option) +{ + if (!window_pane_show_scrollbar(wp, sb_option)) + return (0); + if (!window_pane_scrollbar_auto_hide(wp)) + return (1); + return (wp->sb_auto_visible); +} + +void +window_pane_scrollbar_start_timer(struct window_pane *wp) +{ + struct timeval tv; + u_int delay; + + if (!window_pane_scrollbar_auto_hide(wp) || !wp->sb_auto_visible) + return; + + delay = options_get_number(wp->window->options, + "pane-scrollbars-timeout"); + tv.tv_sec = delay / 1000; + tv.tv_usec = (delay % 1000) * 1000L; + evtimer_del(&wp->sb_auto_timer); + evtimer_add(&wp->sb_auto_timer, &tv); +} + +void +window_pane_scrollbar_show(struct window_pane *wp, int start_timer) +{ + int changed = 0; + int sb; + + if (!window_pane_scrollbar_auto_hide(wp)) + return; + sb = options_get_number(wp->window->options, "pane-scrollbars"); + if (!window_pane_show_scrollbar(wp, sb)) + return; + if (!wp->sb_auto_visible) { + wp->sb_auto_visible = 1; + changed = 1; + } + evtimer_del(&wp->sb_auto_timer); + if (start_timer) + window_pane_scrollbar_start_timer(wp); + if (changed) + window_pane_scrollbar_redraw_visibility(wp); +} + +void +window_pane_scrollbar_hide(struct window_pane *wp) +{ + if (event_initialized(&wp->sb_auto_timer)) + evtimer_del(&wp->sb_auto_timer); + wp->sb_auto_hover = 0; + if (!wp->sb_auto_visible) + return; + wp->sb_auto_visible = 0; + window_pane_scrollbar_redraw_visibility(wp); +} + int window_pane_get_bg(struct window_pane *wp) { From ad74c5ed7f62913d38543ff6843f38d14689b56d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 29 Jun 2026 10:20:38 +0100 Subject: [PATCH 66/71] Add additional grid consistency checks for macOS. --- grid.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/grid.c b/grid.c index 44efa574d..28680cbdd 100644 --- a/grid.c +++ b/grid.c @@ -18,6 +18,9 @@ #include +#ifdef __APPLE__ +#include +#endif #include #include @@ -56,6 +59,28 @@ static const struct grid_cell_entry grid_cleared_entry = { { .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED }; +#ifdef __APPLE__ +static void +grid_check_lines(struct grid *gd) +{ + u_int i, j; + + for (i = 0; i < gd->hsize + gd->sy; i++) { + for (j = i + 1; j < gd->hsize + gd->sy; j++) { + if (gd->linedata[i].celldata != NULL) + assert(gd->linedata[i].celldata != gd->linedata[j].celldata); + if (gd->linedata[i].extddata != NULL) + assert(gd->linedata[i].extddata != gd->linedata[j].extddata); + } + } +} +#else +static void +grid_check_lines(__unused struct grid *gd) +{ +} +#endif + /* Store cell in entry. */ static void grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc, @@ -287,9 +312,17 @@ grid_set_tab(struct grid_cell *gc, u_int width) static void grid_free_line(struct grid *gd, u_int py) { - free(gd->linedata[py].celldata); - free(gd->linedata[py].extddata); - memset(&gd->linedata[py], 0, sizeof gd->linedata[py]); + struct grid_line *gl = &gd->linedata[py]; + +#ifdef __APPLE__ + assert(gl->cellused <= gl->cellsize); + assert(gl->extdsize == 0 || gl->extddata != NULL); + assert(gl->cellsize == 0 || gl->celldata != NULL); +#endif + + free(gl->celldata); + free(gl->extddata); + memset(gl, 0, sizeof *gl); } /* Free several lines. */ @@ -449,6 +482,8 @@ grid_scroll_history(struct grid *gd, u_int bg) gd->linedata[gd->hsize].time = current_time; gd->hsize++; gd->scroll_added++; + + grid_check_lines(gd); } /* Clear the history. */ @@ -498,6 +533,8 @@ grid_scroll_history_region(struct grid *gd, u_int upper, u_int lower, u_int bg) gd->hscrolled++; gd->hsize++; gd->scroll_added++; + + grid_check_lines(gd); } /* Expand line to fit to cell. */ @@ -759,6 +796,8 @@ grid_move_lines(struct grid *gd, u_int dy, u_int py, u_int ny, u_int bg) } if (py != 0 && (py < dy || py >= dy + ny)) gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED; + + grid_check_lines(gd); } /* Move a group of cells. */ @@ -1248,6 +1287,8 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy, sy++; dy++; } + + grid_check_lines(dst); } /* Mark line as dead. */ @@ -1544,6 +1585,8 @@ grid_reflow(struct grid *gd, u_int sx) gd->linedata = target->linedata; free(target); gd->scroll_generation++; + + grid_check_lines(gd); } /* Convert to position based on wrapped lines. */ From 5df51a06f5f830ee53ab78a50ed51d4901b4f72c Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 29 Jun 2026 16:20:45 +0000 Subject: [PATCH 67/71] Allow empty window and session names. --- tmux.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tmux.c b/tmux.c index 7236360bb..9ea6b7fea 100644 --- a/tmux.c +++ b/tmux.c @@ -291,7 +291,7 @@ clean_name(const char *name, const char* forbid) { char *copy, *cp, *new_name; - if (*name == '\0' || !utf8_isvalid(name)) + if (!utf8_isvalid(name)) return (NULL); copy = xstrdup(name); for (cp = copy; *cp != '\0'; cp++) { @@ -316,7 +316,7 @@ check_name(const char *name, const char *forbid) { const char *cp; - if (*name == '\0' || !utf8_isvalid(name)) + if (!utf8_isvalid(name)) return (0); for (cp = name; *cp != '\0'; cp++) { if (strchr(forbid, *cp) != NULL) From 95719e342df89cf5c9e0c5f14994c6b681905dd6 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 29 Jun 2026 16:44:06 +0000 Subject: [PATCH 68/71] Limit hyperlink URIs to 1024 bytes which seems enough and allows us not to have to worry about gigantic URIs in styles, part of a change from Moritz Angermann. --- hyperlinks.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hyperlinks.c b/hyperlinks.c index c8fd0ca2f..a71827272 100644 --- a/hyperlinks.c +++ b/hyperlinks.c @@ -42,6 +42,7 @@ */ #define MAX_HYPERLINKS 5000 +#define MAX_HYPERLINK_URI 1024 static long long hyperlinks_next_external_id = 1; static u_int global_hyperlinks_count; @@ -146,9 +147,13 @@ hyperlinks_put(struct hyperlinks *hl, const char *uri_in, internal_id_in = ""; utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE); - utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE); + if (strlen(uri) > MAX_HYPERLINK_URI) { + free(uri); + return (0); + } - if (*internal_id_in != '\0') { + utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE); + if (*internal_id != '\0') { find.uri = uri; find.internal_id = internal_id; From bcd54d12d852f97586fc76ac93da9002d0804ef5 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 29 Jun 2026 17:08:52 +0000 Subject: [PATCH 69/71] Add link=/nolink to styles to specific a hyperlink. GitHub issue 4280 from Moritz Angermann. --- format-draw.c | 14 +++++++++++++- style.c | 48 +++++++++++++++++++++++++++++++++++++++++++----- tmux.1 | 22 ++++++++++++++++++++++ tmux.h | 3 +++ tty-features.c | 1 + 5 files changed, 82 insertions(+), 6 deletions(-) diff --git a/format-draw.c b/format-draw.c index 510f28e08..47bf192e6 100644 --- a/format-draw.c +++ b/format-draw.c @@ -710,6 +710,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, "AFTER" }; size_t size = strlen(expanded); struct screen *os = octx->s, s[TOTAL]; + struct hyperlinks *hl = os->hyperlinks; struct screen_write_ctx ctx[TOTAL]; u_int ocx = os->cx, ocy = os->cy, n, i, width[TOTAL]; u_int map[] = { LEFT, @@ -723,7 +724,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, struct grid_cell gc, current_default, base_default; struct style sy, saved_sy; struct utf8_data *ud = &sy.gc.data; - const char *cp, *end; + const char *cp, *end, *link_uri; enum utf8_state more; char *tmp; struct format_range *fr = NULL, *fr1; @@ -838,6 +839,17 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, sy.gc.fg = base->fg; } + /* + * Resolve any hyperlink and store it in the cell. The URI + * doubles as the internal ID so repeated links share one entry + * and the ID stays stable across redraws. + */ + link_uri = style_link(&sy); + if (link_uri != NULL && hl != NULL) + sy.gc.link = hyperlinks_put(hl, link_uri, link_uri); + else + sy.gc.link = 0; + /* If this style has a fill colour, store it for later. */ if (sy.fill != 8) fill = sy.fill; diff --git a/style.c b/style.c index 25b8b0196..2ca5e37af 100644 --- a/style.c +++ b/style.c @@ -42,9 +42,17 @@ static struct style style_default = { STYLE_WIDTH_DEFAULT, 0, STYLE_PAD_DEFAULT, - STYLE_DEFAULT_BASE + STYLE_DEFAULT_BASE, + + 0 }; +/* + * Global hyperlink set holding the URIs for #[link=...] styles, so a style + * only needs to store a small ID rather than the URI itself. + */ +static struct hyperlinks *style_hyperlinks; + /* Set range string. */ static void style_set_range_string(struct style *sy, const char *s) @@ -91,6 +99,7 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) sy->gc.us = base->us; sy->gc.attr = base->attr; sy->gc.flags = base->flags; + sy->link = 0; } else if (strcasecmp(tmp, "ignore") == 0) sy->ignore = 1; else if (strcasecmp(tmp, "noignore") == 0) @@ -234,7 +243,9 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) } else if (strcasecmp(tmp, "none") == 0) sy->gc.attr = 0; else if (end > 2 && strncasecmp(tmp, "no", 2) == 0) { - if (strcmp(tmp + 2, "attr") == 0) + if (strcmp(tmp + 2, "link") == 0) + sy->link = 0; + else if (strcmp(tmp + 2, "attr") == 0) sy->gc.attr |= GRID_ATTR_NOATTR; else { value = attributes_fromstring(tmp + 2); @@ -262,6 +273,15 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) if (errstr != NULL) goto error; sy->pad = (int)n; + } else if (strncasecmp(tmp, "link=", 5) == 0) { + if (tmp[5] == '\0') + sy->link = 0; + else { + if (style_hyperlinks == NULL) + style_hyperlinks = hyperlinks_init(); + sy->link = hyperlinks_put(style_hyperlinks, + tmp + 5, tmp + 5); + } } else { if ((value = attributes_fromstring(tmp)) == -1) goto error; @@ -284,8 +304,8 @@ style_tostring(struct style *sy) { struct grid_cell *gc = &sy->gc; int off = 0; - const char *comma = "", *tmp = ""; - static char s[1024]; + const char *comma = "", *tmp = "", *uri; + static char s[2048]; char b[21]; *s = '\0'; @@ -389,15 +409,33 @@ style_tostring(struct style *sy) comma = ","; } if (sy->pad >= 0) { - xsnprintf(s + off, sizeof s - off, "%spad=%u", comma, + off += xsnprintf(s + off, sizeof s - off, "%spad=%u", comma, sy->pad); comma = ","; } + uri = style_link(sy); + if (uri != NULL) { + xsnprintf(s + off, sizeof s - off, "%slink=%s", comma, uri); + comma = ","; + } if (*s == '\0') return ("default"); return (s); } +/* Get the hyperlink URI for a style, or NULL if it has none. */ +const char * +style_link(struct style *sy) +{ + const char *uri; + + if (sy->link == 0 || style_hyperlinks == NULL) + return (NULL); + if (!hyperlinks_get(style_hyperlinks, sy->link, &uri, NULL, NULL)) + return (NULL); + return (uri); +} + /* Apply a style on top of the given style. */ struct style * style_add(struct grid_cell *gc, struct options *oo, const char *name, diff --git a/tmux.1 b/tmux.1 index 8f74205f8..2e4d2bceb 100644 --- a/tmux.1 +++ b/tmux.1 @@ -7266,6 +7266,28 @@ Set the width of the styled area. .Ar N may be a column count or a percentage (for example .Ql 50% ) . +.It Xo Ic link=uri +(or +.Ic nolink ) +.Xc +Make the styled text an OSC 8 hyperlink to +.Ar uri , +for example +.Ql #[link=https://example.com]text#[nolink] . +This is emitted only to terminals with the +.Ic hyperlinks +feature (see +.Ic terminal-features ) ; +it works in the status line and in other formats drawn with styles. +The link continues until +.Ic nolink , +.Ic default , +or an empty +.Ic link= +is given. +The +.Ar uri +may not contain spaces or commas and is limited in length. .It Xo Ic list=on , .Ic list=focus , .Ic list=left\-marker , diff --git a/tmux.h b/tmux.h index f34b3b324..4bfdf4300 100644 --- a/tmux.h +++ b/tmux.h @@ -984,6 +984,8 @@ struct style { int pad; enum style_default_type default_type; + + u_int link; }; /* Cursor style. */ @@ -3960,6 +3962,7 @@ int style_parse(struct style *,const struct grid_cell *, int style_parse_colour(struct style *, const struct grid_cell *, const char *); const char *style_tostring(struct style *); +const char *style_link(struct style *); struct style *style_add(struct grid_cell *, struct options *, const char *, struct format_tree *); void style_apply(struct grid_cell *, struct options *, diff --git a/tty-features.c b/tty-features.c index 4f0537b14..b321af279 100644 --- a/tty-features.c +++ b/tty-features.c @@ -570,6 +570,7 @@ tty_default_features(int *feat, const char *name, u_int version) "cstyle," "extkeys," "focus," + "hyperlinks," "usstyle" }, { .name = "XTerm", From 65a032b2051a2aad80d6fdc8470ed20f77c91750 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 29 Jun 2026 18:17:28 +0000 Subject: [PATCH 70/71] Allow :. in names again, forbidding them is overly pernickety. Only forbid invalid UTF-8 and #(. --- cmd-break-pane.c | 6 +++--- cmd-new-session.c | 12 ++++++------ cmd-new-window.c | 4 ++-- cmd-rename-session.c | 4 ++-- cmd-rename-window.c | 4 ++-- input.c | 14 ++++++-------- names.c | 4 ++-- paste.c | 4 ++-- screen.c | 10 ++-------- tmux.c | 20 +++----------------- tmux.h | 12 +++--------- window.c | 4 ++-- 12 files changed, 35 insertions(+), 63 deletions(-) diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 8453c7e86..7eb371c25 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -108,7 +108,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'W')) return (cmd_break_pane_float(item, args, w, wp)); - if (name != NULL && !check_name(name, WINDOW_NAME_FORBID)) { + if (name != NULL && !check_name(name)) { cmdq_error(item, "invalid window name: %s", name); return (CMD_RETURN_ERROR); } @@ -132,7 +132,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } if (name != NULL) { - window_set_name(w, name, WINDOW_NAME_FORBID); + window_set_name(w, name, 0); options_set_number(w->options, "automatic-rename", 0); } server_unlink_window(src_s, wl); @@ -162,7 +162,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) if (name == NULL) { newname = default_window_name(w); - window_set_name(w, newname, WINDOW_NAME_FORBID); + window_set_name(w, newname, 0); free(newname); } else { window_set_name(w, name, 0); diff --git a/cmd-new-session.c b/cmd-new-session.c index dbee4e559..ba02c72d4 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -101,22 +101,22 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if ((tmp = args_get(args, 'n')) != NULL) { ename = format_single(item, tmp, c, NULL, NULL, NULL); - if (!check_name(ename, WINDOW_NAME_FORBID)) { + if (!check_name(ename)) { cmdq_error(item, "invalid window name: %s", ename); free(ename); return (CMD_RETURN_ERROR); } - wname = clean_name(ename, WINDOW_NAME_FORBID); + wname = clean_name(ename, 0); free(ename); } if ((tmp = args_get(args, 's')) != NULL) { ename = format_single(item, tmp, c, NULL, NULL, NULL); - if (!check_name(ename, SESSION_NAME_FORBID)) { + if (!check_name(ename)) { cmdq_error(item, "invalid session name: %s", ename); free(ename); goto fail; } - sname = clean_name(ename, SESSION_NAME_FORBID); + sname = clean_name(ename, 0); free(ename); } if (args_has(args, 'A')) { @@ -152,12 +152,12 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) else if (groupwith != NULL) prefix = xstrdup(groupwith->name); else { - if (!check_name(group, SESSION_NAME_FORBID)) { + if (!check_name(group)) { cmdq_error(item, "invalid session group name: %s", group); goto fail; } - prefix = clean_name(group, SESSION_NAME_FORBID); + prefix = clean_name(group, 0); } } diff --git a/cmd-new-window.c b/cmd-new-window.c index c65745a26..bb6113fd6 100644 --- a/cmd-new-window.c +++ b/cmd-new-window.c @@ -73,12 +73,12 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) name = args_get(args, 'n'); if (name != NULL) { expanded = format_single(item, name, c, s, NULL, NULL); - if (!check_name(expanded, WINDOW_NAME_FORBID)) { + if (!check_name(expanded)) { cmdq_error(item, "invalid window name: %s", expanded); free(expanded); return (CMD_RETURN_ERROR); } - wname = clean_name(expanded, WINDOW_NAME_FORBID); + wname = clean_name(expanded, 0); free(expanded); } if (args_has(args, 'S') && wname != NULL && target->idx == -1) { diff --git a/cmd-rename-session.c b/cmd-rename-session.c index ca6a2eacb..b399483b8 100644 --- a/cmd-rename-session.c +++ b/cmd-rename-session.c @@ -52,12 +52,12 @@ cmd_rename_session_exec(struct cmd *self, struct cmdq_item *item) char *newname, *tmp; tmp = format_single_from_target(item, args_string(args, 0)); - if (!check_name(tmp, SESSION_NAME_FORBID)) { + if (!check_name(tmp)) { cmdq_error(item, "invalid session name: %s", tmp); free(tmp); return (CMD_RETURN_ERROR); } - newname = clean_name(tmp, SESSION_NAME_FORBID); + newname = clean_name(tmp, 0); free(tmp); if (strcmp(newname, s->name) == 0) { free(newname); diff --git a/cmd-rename-window.c b/cmd-rename-window.c index 8428e23f3..8ab9b658a 100644 --- a/cmd-rename-window.c +++ b/cmd-rename-window.c @@ -51,13 +51,13 @@ cmd_rename_window_exec(struct cmd *self, struct cmdq_item *item) char *name; name = format_single_from_target(item, args_string(args, 0)); - if (!check_name(name, WINDOW_NAME_FORBID)) { + if (!check_name(name)) { cmdq_error(item, "invalid window name: %s", name); free(name); return (CMD_RETURN_ERROR); } - window_set_name(wl->window, name, WINDOW_NAME_FORBID); + window_set_name(wl->window, name, 0); options_set_number(wl->window->options, "automatic-rename", 0); free(name); diff --git a/input.c b/input.c index cbf54060a..cbedfba41 100644 --- a/input.c +++ b/input.c @@ -1949,16 +1949,14 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) case 2004: screen_write_mode_clear(sctx, MODE_BRACKETPASTE); break; + case 2026: + screen_write_stop_sync(ictx->wp); + break; case 2031: screen_write_mode_clear(sctx, MODE_THEME_UPDATES); if (ictx->wp != NULL) ictx->wp->flags &= ~PANE_THEMECHANGED; break; - case 2026: /* synchronized output */ - screen_write_stop_sync(ictx->wp); - if (ictx->wp != NULL) - ictx->wp->flags |= PANE_REDRAW; - break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; @@ -2061,7 +2059,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) ictx->wp->flags &= ~PANE_THEMECHANGED; } break; - case 2026: /* synchronized output */ + case 2026: screen_write_start_sync(ictx->wp); break; default: @@ -2785,10 +2783,10 @@ input_exit_rename(struct input_ctx *ictx) if (o != NULL) options_remove_or_default(o, -1, NULL); if (!options_get_number(w->options, "automatic-rename")) - window_set_name(w, "", WINDOW_NAME_FORBID_EXT); + window_set_name(w, "", 1); } else { options_set_number(w->options, "automatic-rename", 0); - window_set_name(w, ictx->input_buf, WINDOW_NAME_FORBID_EXT); + window_set_name(w, ictx->input_buf, 1); } server_redraw_window_borders(w); server_status_window(w); diff --git a/names.c b/names.c index 756af5b07..cee310366 100644 --- a/names.c +++ b/names.c @@ -95,7 +95,7 @@ check_window_name(struct window *w) name = format_window_name(w); if (strcmp(name, w->name) != 0) { log_debug("@%u new name %s (was %s)", w->id, name, w->name); - window_set_name(w, name, WINDOW_NAME_FORBID_EXT); + window_set_name(w, name, 1); server_redraw_window_borders(w); server_status_window(w); } else @@ -166,7 +166,7 @@ parse_window_name(const char *in) if (*name == '/') name = basename(name); - name = clean_name(name, WINDOW_NAME_FORBID); + name = clean_name(name, 0); free(copy); if (name == NULL) return (xstrdup("")); diff --git a/paste.c b/paste.c index 8c604c840..6b8a1cb47 100644 --- a/paste.c +++ b/paste.c @@ -220,7 +220,7 @@ paste_rename(const char *oldname, const char *newname, char **cause) return (-1); } - name = clean_name(newname, ""); + name = clean_name(newname, 0); if (name == NULL) { if (cause != NULL) xasprintf(cause, "invalid buffer name: %s", newname); @@ -288,7 +288,7 @@ paste_set(char *data, size_t size, const char *name, char **cause) return (-1); } - newname = clean_name(name, ""); + newname = clean_name(name, 0); if (newname == NULL) { if (cause != NULL) xasprintf(cause, "invalid buffer name: %s", name); diff --git a/screen.c b/screen.c index cea934fad..8b988adee 100644 --- a/screen.c +++ b/screen.c @@ -237,10 +237,7 @@ screen_set_title(struct screen *s, const char *title, int untrusted) { char *new_title; - if (untrusted) - new_title = clean_name(title, "#"); - else - new_title = clean_name(title, ""); + new_title = clean_name(title, untrusted); if (new_title == NULL) return (0); free(s->title); @@ -254,10 +251,7 @@ screen_set_path(struct screen *s, const char *path, int untrusted) { char *new_path; - if (untrusted) - new_path = clean_name(path, "#"); - else - new_path = clean_name(path, ""); + new_path = clean_name(path, untrusted); if (new_path == NULL) return (0); free(s->path); diff --git a/tmux.c b/tmux.c index 9ea6b7fea..c08110c95 100644 --- a/tmux.c +++ b/tmux.c @@ -287,7 +287,7 @@ get_timer(void) } char * -clean_name(const char *name, const char* forbid) +clean_name(const char *name, int untrusted) { char *copy, *cp, *new_name; @@ -295,10 +295,7 @@ clean_name(const char *name, const char* forbid) return (NULL); copy = xstrdup(name); for (cp = copy; *cp != '\0'; cp++) { - if (*cp == '#' && strchr(forbid, '#') != NULL) { - if (cp[1] == '(') - *cp = '_'; - } else if (strchr(forbid, *cp) != NULL) + if (untrusted && cp[0] == '#' && cp[1] == '(') *cp = '_'; } utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); @@ -306,22 +303,11 @@ clean_name(const char *name, const char* forbid) return (new_name); } -/* - * Check a name given by a command: reject it if it is empty, not valid UTF-8, - * or contains a forbidden character. Other characters that clean_name would - * change (for example with utf8_stravis) are allowed and fixed silently. - */ int -check_name(const char *name, const char *forbid) +check_name(const char *name) { - const char *cp; - if (!utf8_isvalid(name)) return (0); - for (cp = name; *cp != '\0'; cp++) { - if (strchr(forbid, *cp) != NULL) - return (0); - } return (1); } diff --git a/tmux.h b/tmux.h index 4bfdf4300..0abacb9c0 100644 --- a/tmux.h +++ b/tmux.h @@ -91,12 +91,6 @@ struct winlink; #define TMUX_TERM "screen" #endif -/* Forbidden characters in names. */ -#define WINDOW_NAME_FORBID ":." -#define WINDOW_NAME_FORBID_EXT ":.#" -#define SESSION_NAME_FORBID ":." -#define SESSION_NAME_FORBID_EXT ":.#" - /* Minimum and maximum layout cell size, NOT including border lines. */ #define PANE_MINIMUM 1 #define PANE_MAXIMUM 10000 @@ -2458,8 +2452,8 @@ int checkshell(const char *); void setblocking(int, int); char *shell_argv0(const char *, int); uint64_t get_timer(void); -char *clean_name(const char *, const char *); -int check_name(const char *, const char *); +char *clean_name(const char *, int); +int check_name(const char *); const char *sig2name(int); const char *find_cwd(void); const char *find_home(void); @@ -3572,7 +3566,7 @@ void window_pane_stack_push(struct window_panes *, struct window_pane *); void window_pane_stack_remove(struct window_panes *, struct window_pane *); -void window_set_name(struct window *, const char *, const char *); +void window_set_name(struct window *, const char *, int); void window_add_ref(struct window *, const char *); void window_remove_ref(struct window *, const char *); void winlink_clear_flags(struct winlink *); diff --git a/window.c b/window.c index a3c228039..6a613ae98 100644 --- a/window.c +++ b/window.c @@ -422,11 +422,11 @@ window_remove_ref(struct window *w, const char *from) } void -window_set_name(struct window *w, const char *new_name, const char *forbid) +window_set_name(struct window *w, const char *new_name, int untrusted) { char *name; - name = clean_name(new_name, forbid); + name = clean_name(new_name, untrusted); if (name != NULL) { free(w->name); w->name = name; From e06207c914f6ebeee270b9f5eb1fea7ee67624c7 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 29 Jun 2026 19:03:34 +0000 Subject: [PATCH 71/71] Cache scrollbar options in window to avoid a slow lookup when working out visible ranges. --- cmd-resize-pane.c | 12 +++++------ layout.c | 18 +++++++---------- options.c | 7 ++++++- screen-redraw.c | 13 ++++-------- server-client.c | 48 +++++++++++++++++++------------------------- tmux.h | 11 ++++++---- window-visible.c | 8 ++++---- window.c | 51 ++++++++++++++++++----------------------------- 8 files changed, 73 insertions(+), 95 deletions(-) diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 185844f2e..348923528 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -235,7 +235,7 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c, struct window_pane *wp; struct layout_cell *lc; int y, ly, x, lx, sx, sy, new_sx, new_sy; - int scrollbars, sb_pos, left, right; + int left, right; int new_xoff, new_yoff, resizes = 0; wp = cmd_mouse_pane(m, NULL, &wl); @@ -247,15 +247,13 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c, lc = wp->layout_cell; sx = wp->sx; sy = wp->sy; - scrollbars = options_get_number(w->options, "pane-scrollbars"); - sb_pos = options_get_number(w->options, "pane-scrollbars-position"); left = wp->xoff - 1; right = wp->xoff + sx; - if (window_pane_scrollbar_reserve(wp, scrollbars) && - sb_pos == PANE_SCROLLBARS_LEFT) { + if (window_pane_scrollbar_reserve(wp) && + w->sb_pos == PANE_SCROLLBARS_LEFT) { left -= wp->scrollbar_style.width + wp->scrollbar_style.pad; - } else if (window_pane_scrollbar_reserve(wp, scrollbars) && - sb_pos == PANE_SCROLLBARS_RIGHT) { + } else if (window_pane_scrollbar_reserve(wp) && + w->sb_pos == PANE_SCROLLBARS_RIGHT) { right += wp->scrollbar_style.width + wp->scrollbar_style.pad; } diff --git a/layout.c b/layout.c index a65cd9d86..c38197b44 100644 --- a/layout.c +++ b/layout.c @@ -433,13 +433,11 @@ layout_fix_panes(struct window *w, struct window_pane *skip) { struct window_pane *wp; struct layout_cell *lc; - int status, scrollbars, sb_pos, sb_w, sb_pad; + int status, sb_w, sb_pad; int old_xoff, old_yoff, changed = 0; u_int sx, sy, old_sx, old_sy; status = window_get_pane_status(w); - scrollbars = options_get_number(w->options, "pane-scrollbars"); - sb_pos = options_get_number(w->options, "pane-scrollbars-position"); TAILQ_FOREACH(wp, &w->panes, entry) { if ((lc = wp->layout_cell) == NULL || wp == skip) @@ -462,14 +460,14 @@ layout_fix_panes(struct window *w, struct window_pane *skip) sy--; } - if (window_pane_scrollbar_reserve(wp, scrollbars)) { + if (window_pane_scrollbar_reserve(wp)) { sb_w = wp->scrollbar_style.width; sb_pad = wp->scrollbar_style.pad; if (sb_w < 1) sb_w = 1; if (sb_pad < 0) sb_pad = 0; - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb_pos == PANE_SCROLLBARS_LEFT) { if ((int)sx - sb_w - sb_pad < PANE_MINIMUM) { wp->xoff = wp->xoff + (int)sx - PANE_MINIMUM; @@ -526,16 +524,15 @@ layout_resize_check(struct window *w, struct layout_cell *lc, struct layout_cell *lcchild; struct style *sb_style = &w->active->scrollbar_style; u_int available, minimum; - int status, scrollbars; + int status; status = window_get_pane_status(w); - scrollbars = options_get_number(w->options, "pane-scrollbars"); if (lc->type == LAYOUT_WINDOWPANE) { /* Space available in this cell only. */ if (type == LAYOUT_LEFTRIGHT) { available = lc->sx; - if (scrollbars == PANE_SCROLLBARS_ALWAYS) + if (w->sb == PANE_SCROLLBARS_ALWAYS) minimum = PANE_MINIMUM + sb_style->width + sb_style->pad; else @@ -1253,17 +1250,16 @@ layout_split_check_space(struct window_pane *wp, struct layout_cell *lc, { struct style *sb_style = &wp->scrollbar_style; u_int minimum, sx = lc->sx, sy = lc->sy; - int scrollbars, status; + int status; if (lc->flags & LAYOUT_CELL_FLOATING) fatalx("floating cells cannot be split"); status = window_get_pane_status(wp->window); - scrollbars = options_get_number(wp->window->options, "pane-scrollbars"); switch (type) { case LAYOUT_LEFTRIGHT: - if (scrollbars == PANE_SCROLLBARS_ALWAYS) { + if (wp->window->sb == PANE_SCROLLBARS_ALWAYS) { minimum = PANE_MINIMUM * 2 + sb_style->width + sb_style->pad; } else diff --git a/options.c b/options.c index e05930463..e34320869 100644 --- a/options.c +++ b/options.c @@ -1286,8 +1286,13 @@ options_push_changes(const char *name) if (strcmp(name, "pane-border-status") == 0 || strcmp(name, "pane-scrollbars") == 0 || strcmp(name, "pane-scrollbars-position") == 0) { - RB_FOREACH(w, windows, &windows) + RB_FOREACH(w, windows, &windows) { + w->sb = options_get_number(w->options, + "pane-scrollbars"); + w->sb_pos = options_get_number(w->options, + "pane-scrollbars-position"); layout_fix_panes(w, NULL); + } } if (strcmp(name, "pane-scrollbars") == 0) { RB_FOREACH(wp, window_pane_tree, &all_window_panes) diff --git a/screen-redraw.c b/screen-redraw.c index 813d4e563..1f9beb576 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -212,8 +212,6 @@ struct redraw_build_ctx { u_int sx; u_int sy; - int sb; - int sbp; int ind; struct redraw_build_cell *cells; @@ -285,16 +283,13 @@ redraw_set_context(struct client *c, struct redraw_build_ctx *bctx) { struct session *s = c->session; struct window *w = s->curw->window; - struct options *oo = w->options; memset(bctx, 0, sizeof *bctx); bctx->c = c; bctx->w = w; redraw_get_window_offset(c, &bctx->ox, &bctx->oy, &bctx->sx, &bctx->sy); - bctx->sb = options_get_number(oo, "pane-scrollbars"); - bctx->sbp = options_get_number(oo, "pane-scrollbars-position"); - bctx->ind = options_get_number(oo, "pane-border-indicators"); + bctx->ind = options_get_number(w->options, "pane-border-indicators"); } /* Return a cell. */ @@ -769,8 +764,8 @@ redraw_mark_pane(struct redraw_build_ctx *bctx, struct window_pane *wp) if (!window_pane_is_visible(wp)) return; - if (window_pane_scrollbar_visible(wp, bctx->sb)) { - overlay = window_pane_scrollbar_overlay(wp, bctx->sb); + if (window_pane_scrollbar_visible(wp)) { + overlay = window_pane_scrollbar_overlay(wp); if (overlay) { sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; @@ -782,7 +777,7 @@ redraw_mark_pane(struct redraw_build_ctx *bctx, struct window_pane *wp) } else sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; } - if (sb_w != 0 && bctx->sbp == PANE_SCROLLBARS_LEFT) + if (sb_w != 0 && bctx->w->sb_pos == PANE_SCROLLBARS_LEFT) sb_left = 1; redraw_mark_pane_inside(bctx, wp); diff --git a/server-client.c b/server-client.c index 43bf7600d..3dc294c99 100644 --- a/server-client.c +++ b/server-client.c @@ -613,10 +613,9 @@ server_client_in_scrollbar_area(struct window_pane *wp, int px, int py) { struct window *w = wp->window; u_int width, pad, total; - int sb, sb_pos, start, end; + int start, end; - sb = options_get_number(w->options, "pane-scrollbars"); - if (!window_pane_scrollbar_overlay(wp, sb)) + if (!window_pane_scrollbar_overlay(wp)) return (0); if (py < wp->yoff || py >= wp->yoff + (int)wp->sy) return (0); @@ -627,8 +626,7 @@ server_client_in_scrollbar_area(struct window_pane *wp, int px, int py) if (total == 0 || total > wp->sx) total = wp->sx; - sb_pos = options_get_number(w->options, "pane-scrollbars-position"); - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb_pos == PANE_SCROLLBARS_LEFT) { start = wp->xoff; end = wp->xoff + (int)total - 1; } else { @@ -668,17 +666,15 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, { struct window *w = wp->window; struct window_pane *fwp; - int pane_status, sb, sb_pos, sb_w, sb_pad; + int pane_status, sb_w, sb_pad; int pane_status_line, sl_top, sl_bottom; int bdr_bottom, bdr_top, bdr_left, bdr_right; int sb_start, sb_end, sb_overlay; - sb = options_get_number(w->options, "pane-scrollbars"); - sb_pos = options_get_number(w->options, "pane-scrollbars-position"); pane_status = window_pane_get_pane_status(wp); - sb_overlay = window_pane_scrollbar_overlay(wp, sb); + sb_overlay = window_pane_scrollbar_overlay(wp); - if (window_pane_scrollbar_visible(wp, sb)) { + if (window_pane_scrollbar_visible(wp)) { sb_w = wp->scrollbar_style.width; sb_pad = wp->scrollbar_style.pad; if (sb_overlay && sb_w > (int)wp->sx) @@ -695,13 +691,13 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, else pane_status_line = -1; /* not used */ bdr_left = wp->xoff - 1; - if (!sb_overlay && sb_pos == PANE_SCROLLBARS_LEFT) + if (!sb_overlay && w->sb_pos == PANE_SCROLLBARS_LEFT) bdr_left -= sb_pad + sb_w; if (sb_overlay && sb_w != 0 && py >= wp->yoff && py < wp->yoff + (int)wp->sy && px >= wp->xoff && px < wp->xoff + (int)wp->sx) { - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb_pos == PANE_SCROLLBARS_LEFT) { sb_start = wp->xoff; sb_end = sb_start + sb_w - 1; } else { @@ -728,15 +724,15 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, py != pane_status_line && py != wp->yoff + (int)wp->sy) || (wp->yoff == 0 && py < (int)wp->sy) || (py >= wp->yoff && py < wp->yoff + (int)wp->sy)) && - ((sb_pos == PANE_SCROLLBARS_RIGHT && + ((w->sb_pos == PANE_SCROLLBARS_RIGHT && px < wp->xoff + (int)wp->sx + sb_pad + sb_w) || - (sb_pos == PANE_SCROLLBARS_LEFT && + (w->sb_pos == PANE_SCROLLBARS_LEFT && px < wp->xoff + (int)wp->sx - sb_pad - sb_w))) { /* Check if in the scrollbar. */ - if ((sb_pos == PANE_SCROLLBARS_RIGHT && + if ((w->sb_pos == PANE_SCROLLBARS_RIGHT && (px >= wp->xoff + (int)wp->sx + sb_pad && px < wp->xoff + (int)wp->sx + sb_pad + sb_w)) || - (sb_pos == PANE_SCROLLBARS_LEFT && + (w->sb_pos == PANE_SCROLLBARS_LEFT && (px >= wp->xoff - sb_pad - sb_w && px < wp->xoff - sb_pad))) { /* Check where inside the scrollbar. */ @@ -770,7 +766,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, if (window_pane_is_floating(fwp) && window_pane_get_pane_lines(fwp) == PANE_LINES_NONE) continue; - if (window_pane_scrollbar_reserve(fwp, sb)) { + if (window_pane_scrollbar_reserve(fwp)) { sb_w = fwp->scrollbar_style.width; sb_pad = fwp->scrollbar_style.pad; } else { @@ -780,7 +776,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, bdr_top = fwp->yoff - 1; bdr_bottom = fwp->yoff + fwp->sy; bdr_left = fwp->xoff - 1; - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb_pos == PANE_SCROLLBARS_LEFT) { bdr_left -= sb_pad + sb_w; bdr_right = fwp->xoff + fwp->sx; } else { @@ -2001,7 +1997,7 @@ server_client_reset_state(struct client *c) struct window_pane *wp = server_client_get_pane(c), *loop; struct screen *s = NULL; struct options *oo = c->session->options; - int mode = 0, cursor, flags, pane_mode = 0, sb; + int mode = 0, cursor, flags, pane_mode = 0; u_int cx = 0, cy = 0, ox, oy, sx, sy, prompt = 0; u_int sb_w; struct visible_ranges *r; @@ -2061,9 +2057,8 @@ server_client_reset_state(struct client *c) if (sb_w > wp->sx) sb_w = wp->sx; if (sb_w != 0 && - options_get_number(w->options, - "pane-scrollbars-position") == - PANE_SCROLLBARS_LEFT) { + w->sb_pos == + PANE_SCROLLBARS_LEFT) { if (s->cx < sb_w) cursor = 0; } else if (sb_w != 0 && @@ -2098,11 +2093,10 @@ server_client_reset_state(struct client *c) mode |= MODE_MOUSE_ALL; } } - sb = options_get_number(w->options, "pane-scrollbars"); - if (options_get_number(oo, "focus-follows-mouse") || - sb == PANE_SCROLLBARS_MODAL || - sb == PANE_SCROLLBARS_AUTOHIDE) - mode |= MODE_MOUSE_ALL; + if (options_get_number(oo, "focus-follows-mouse") || + w->sb == PANE_SCROLLBARS_MODAL || + w->sb == PANE_SCROLLBARS_AUTOHIDE) + mode |= MODE_MOUSE_ALL; else if (~mode & MODE_MOUSE_ALL) mode |= MODE_MOUSE_BUTTON; } diff --git a/tmux.h b/tmux.h index 0abacb9c0..61a2cbf91 100644 --- a/tmux.h +++ b/tmux.h @@ -1380,6 +1380,9 @@ struct window { u_int last_new_pane_x; u_int last_new_pane_y; + int sb; + int sb_pos; + struct utf8_data *fill_character; int flags; #define WINDOW_BELL 0x1 @@ -3580,10 +3583,10 @@ void window_pane_update_used_data(struct window_pane *, void window_set_fill_character(struct window *); void window_pane_default_cursor(struct window_pane *); int window_pane_mode(struct window_pane *); -int window_pane_show_scrollbar(struct window_pane *, int); -int window_pane_scrollbar_reserve(struct window_pane *, int); -int window_pane_scrollbar_visible(struct window_pane *, int); -int window_pane_scrollbar_overlay(struct window_pane *, int); +int window_pane_show_scrollbar(struct window_pane *); +int window_pane_scrollbar_reserve(struct window_pane *); +int window_pane_scrollbar_visible(struct window_pane *); +int window_pane_scrollbar_overlay(struct window_pane *); int window_pane_scrollbar_overlay_visible(struct window_pane *); void window_pane_scrollbar_show(struct window_pane *, int); void window_pane_scrollbar_hide(struct window_pane *); diff --git a/window-visible.c b/window-visible.c index 7ec7fcfb5..f46967ec7 100644 --- a/window-visible.c +++ b/window-visible.c @@ -55,7 +55,7 @@ window_visible_ranges(struct window_pane *base_wp, int px, int py, u_int width, 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_w, sb_pos; int lb, rb, tb, bb, sx, ex, no_border; u_int i, s; @@ -95,8 +95,6 @@ window_visible_ranges(struct window_pane *base_wp, int px, int py, u_int width, r->used = 1; } - sb = options_get_number(w->options, "pane-scrollbars"); - sb_pos = options_get_number(w->options, "pane-scrollbars-position"); found_self = 0; TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { @@ -126,7 +124,9 @@ window_visible_ranges(struct window_pane *base_wp, int px, int py, u_int width, continue; sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - if (!window_pane_scrollbar_reserve(wp, sb)) + if (window_pane_scrollbar_reserve(wp)) + sb_pos = w->sb_pos; + else sb_w = sb_pos = 0; for (i = 0; i < r->used; i++) { diff --git a/window.c b/window.c index 6a613ae98..1ee39ce3b 100644 --- a/window.c +++ b/window.c @@ -1165,20 +1165,15 @@ window_pane_scrollbar_timer(__unused int fd, __unused short events, void *arg) static int window_pane_scrollbar_auto_hide(struct window_pane *wp) { - int sb; - - sb = options_get_number(wp->window->options, "pane-scrollbars"); - return (sb == PANE_SCROLLBARS_MODAL || sb == PANE_SCROLLBARS_AUTOHIDE); + return (wp->window->sb == PANE_SCROLLBARS_MODAL || + wp->window->sb == PANE_SCROLLBARS_AUTOHIDE); } int window_pane_scrollbar_overlay_visible(struct window_pane *wp) { - int sb; - - sb = options_get_number(wp->window->options, "pane-scrollbars"); - return (window_pane_scrollbar_overlay(wp, sb) && - window_pane_scrollbar_visible(wp, sb)); + return (window_pane_scrollbar_overlay(wp) && + window_pane_scrollbar_visible(wp)); } void @@ -1769,17 +1764,13 @@ window_pane_full_size_offset(struct window_pane *wp, int *xoff, int *yoff, u_int *sx, u_int *sy) { struct window *w = wp->window; - int pane_scrollbars; - u_int sb_w, sb_pos; + u_int sb_w; - pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); - sb_pos = options_get_number(w->options, "pane-scrollbars-position"); - - if (window_pane_scrollbar_reserve(wp, pane_scrollbars)) + if (window_pane_scrollbar_reserve(wp)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; else sb_w = 0; - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb_pos == PANE_SCROLLBARS_LEFT) { *xoff = wp->xoff - sb_w; *sx = wp->sx + sb_w; } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ @@ -2195,40 +2186,39 @@ window_pane_mode(struct window_pane *wp) return (WINDOW_PANE_NO_MODE); } -/* Return 1 if scrollbar is or should be displayed. */ int -window_pane_show_scrollbar(struct window_pane *wp, int sb_option) +window_pane_show_scrollbar(struct window_pane *wp) { if (SCREEN_IS_ALTERNATE(&wp->base)) return (0); - if (sb_option == PANE_SCROLLBARS_ALWAYS || - sb_option == PANE_SCROLLBARS_AUTOHIDE || - (sb_option == PANE_SCROLLBARS_MODAL && + if (wp->window->sb == PANE_SCROLLBARS_ALWAYS || + wp->window->sb == PANE_SCROLLBARS_AUTOHIDE || + (wp->window->sb == PANE_SCROLLBARS_MODAL && window_pane_mode(wp) != WINDOW_PANE_NO_MODE)) return (1); return (0); } int -window_pane_scrollbar_reserve(struct window_pane *wp, int sb_option) +window_pane_scrollbar_reserve(struct window_pane *wp) { - if (!window_pane_show_scrollbar(wp, sb_option)) + if (!window_pane_show_scrollbar(wp)) return (0); - return (sb_option == PANE_SCROLLBARS_ALWAYS); + return (wp->window->sb == PANE_SCROLLBARS_ALWAYS); } int -window_pane_scrollbar_overlay(struct window_pane *wp, int sb_option) +window_pane_scrollbar_overlay(struct window_pane *wp) { - if (!window_pane_show_scrollbar(wp, sb_option)) + if (!window_pane_show_scrollbar(wp)) return (0); return (window_pane_scrollbar_auto_hide(wp)); } int -window_pane_scrollbar_visible(struct window_pane *wp, int sb_option) +window_pane_scrollbar_visible(struct window_pane *wp) { - if (!window_pane_show_scrollbar(wp, sb_option)) + if (!window_pane_show_scrollbar(wp)) return (0); if (!window_pane_scrollbar_auto_hide(wp)) return (1); @@ -2256,12 +2246,9 @@ void window_pane_scrollbar_show(struct window_pane *wp, int start_timer) { int changed = 0; - int sb; - if (!window_pane_scrollbar_auto_hide(wp)) return; - sb = options_get_number(wp->window->options, "pane-scrollbars"); - if (!window_pane_show_scrollbar(wp, sb)) + if (!window_pane_show_scrollbar(wp)) return; if (!wp->sb_auto_visible) { wp->sb_auto_visible = 1;