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 diff --git a/Makefile.am b/Makefile.am index e20a8d621..e3c18def2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -163,6 +163,7 @@ dist_tmux_SOURCES = \ file.c \ format.c \ format-draw.c \ + fuzzy.c \ grid-reader.c \ grid-view.c \ grid.c \ @@ -185,6 +186,8 @@ dist_tmux_SOURCES = \ paste.c \ popup.c \ proc.c \ + prompt.c \ + prompt-history.c \ regsub.c \ resize.c \ screen-redraw.c \ @@ -210,11 +213,13 @@ dist_tmux_SOURCES = \ 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-switch.c \ window-tree.c \ window-visible.c \ window.c \ diff --git a/arguments.c b/arguments.c index f96f04718..ee311a013 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); } @@ -998,7 +1010,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 +1078,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/cfg.c b/cfg.c index d469625e2..bb19f54ce 100644 --- a/cfg.c +++ b/cfg.c @@ -56,7 +56,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-break-pane.c b/cmd-break-pane.c index 32268093a..9eb626a9c 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -54,6 +54,7 @@ cmd_break_pane_float(struct cmdq_item *item, struct args *args, 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"); @@ -68,13 +69,13 @@ cmd_break_pane_float(struct cmdq_item *item, struct args *args, return (CMD_RETURN_ERROR); } - layout_remove_tile(w, lc); - layout_cell_floating_args_parse(item, args, w, &sx, &sy, &ox, &oy, &cause); - if (cause != NULL) { + 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; @@ -111,7 +112,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); } @@ -135,7 +136,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); @@ -165,7 +166,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-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-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-command-prompt.c b/cmd-command-prompt.c index c5f958cda..045ef0b12 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -34,16 +34,16 @@ 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 = { .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,18 +176,27 @@ 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); return (CMD_RETURN_WAIT); } -static int +static enum prompt_result cmd_command_prompt_callback(struct client *c, void *data, const char *s, - int done) + enum prompt_key_result key) { struct cmd_command_prompt_cdata *cdata = data; char *error; @@ -188,26 +206,30 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, int argc = 0; char **argv = NULL; - if (s == NULL) + if (s == NULL || key == PROMPT_KEY_MOVE) goto out; - if (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); + 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); } } argc = cdata->argc; argv = cmd_copy_argv(cdata->argc, cdata->argv); - if (!done) + if (key != PROMPT_KEY_CLOSE) 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); @@ -226,13 +248,20 @@ 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); + /* + * 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 (0); + } + return (PROMPT_CLOSE); } static void @@ -241,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-confirm-before.c b/cmd-confirm-before.c index 72e9d87d1..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 done) + __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/cmd-display-panes.c b/cmd-display-panes.c index 56e13333f..f8991dfe1 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; @@ -123,7 +133,8 @@ cmd_display_panes_draw_pane(struct screen_redraw_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; @@ -181,18 +192,16 @@ cmd_display_panes_draw_pane(struct screen_redraw_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)); @@ -254,17 +263,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 != 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-join-pane.c b/cmd-join-pane.c index 86f3eb9fe..9c1a7939c 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -202,24 +202,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++) { 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/cmd-resize-pane.c b/cmd-resize-pane.c index bc91ab2bc..348923528 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; @@ -94,16 +94,15 @@ 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); 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); @@ -112,7 +111,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); @@ -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); @@ -147,8 +145,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) { @@ -164,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); @@ -234,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); @@ -246,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_show_scrollbar(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_show_scrollbar(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/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-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/cmd-split-window.c b/cmd-split-window.c index 8ff52a04e..604618b80 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -40,8 +40,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 " " @@ -87,7 +87,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); @@ -95,7 +95,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')) @@ -117,8 +117,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) { @@ -155,10 +167,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'); @@ -166,7 +175,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); @@ -178,7 +187,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'); @@ -187,22 +196,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')) { @@ -227,10 +225,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; @@ -272,4 +267,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/cmd-swap-pane.c b/cmd-swap-pane.c index 8a83a67af..24a73fbac 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -143,9 +143,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/cmd.c b/cmd.c index ea1b3ea49..bab65a931 100644 --- a/cmd.c +++ b/cmd.c @@ -117,6 +117,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; @@ -212,6 +213,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/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/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/format.c b/format.c index 9724ec926..f3c8a0356 100644 --- a/format.c +++ b/format.c @@ -1477,6 +1477,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) @@ -3228,6 +3255,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 }, @@ -4542,6 +4572,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) @@ -4552,6 +4623,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/grid.c b/grid.c index 52da173a1..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, @@ -86,7 +111,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 +244,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; @@ -286,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. */ @@ -448,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. */ @@ -497,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. */ @@ -758,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. */ @@ -796,9 +836,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_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; @@ -845,9 +892,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_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; @@ -894,9 +948,19 @@ 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_256) { + if (gc->us & COLOUR_FLAG_THEME) { + 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; values[n++] = gc->us & 0xff; @@ -1223,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. */ @@ -1519,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. */ diff --git a/hyperlinks.c b/hyperlinks.c index 4850b4b8d..1ea55f3b1 100644 --- a/hyperlinks.c +++ b/hyperlinks.c @@ -41,6 +41,7 @@ */ #define MAX_HYPERLINKS 5000 +#define MAX_HYPERLINK_URI 1024 static long long hyperlinks_next_external_id = 1; static u_int global_hyperlinks_count; @@ -145,9 +146,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; 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) { diff --git a/input.c b/input.c index cd664360b..5f7f9d132 100644 --- a/input.c +++ b/input.c @@ -1953,16 +1953,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; @@ -2065,7 +2063,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: @@ -2822,10 +2820,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/key-bindings.c b/key-bindings.c index af1de77cf..b1f0fd8a0 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -405,6 +405,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 }", @@ -517,8 +519,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 }", @@ -526,18 +528,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", @@ -553,15 +555,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 }", @@ -602,25 +604,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 }", @@ -629,14 +631,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 }", @@ -647,7 +649,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/layout.c b/layout.c index 70d1f1a4a..592805b3f 100644 --- a/layout.c +++ b/layout.c @@ -20,6 +20,7 @@ #include #include +#include #include "tmux.h" @@ -443,18 +444,22 @@ 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 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 || !window_pane_is_visible(wp)) 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; @@ -467,15 +472,15 @@ layout_fix_panes(struct window *w, struct window_pane *skip) sy--; } - if (window_pane_show_scrollbar(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 ((int)sx - sb_w < PANE_MINIMUM) { + if (w->sb_pos == PANE_SCROLLBARS_LEFT) { + if ((int)sx - sb_w - sb_pad < PANE_MINIMUM) { wp->xoff = wp->xoff + (int)sx - PANE_MINIMUM; sx = PANE_MINIMUM; @@ -492,7 +497,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. */ @@ -523,16 +536,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) + if (w->sb == PANE_SCROLLBARS_ALWAYS) minimum = PANE_MINIMUM + sb_style->width + sb_style->pad; else @@ -858,7 +870,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) { @@ -866,7 +878,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 && @@ -874,17 +886,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 (type == LAYOUT_TOPBOTTOM) { + if (lc->sy == size) + return (0); lc->sy = size; - else + } else { + if (lc->sx == size) + 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) { @@ -893,14 +912,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 (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) @@ -909,12 +930,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. */ @@ -1278,6 +1301,100 @@ layout_split_sizes(struct layout_cell *lc, int size, int before, } +/* + * 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); +} + +/* 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 status; + + if (lc->flags & LAYOUT_CELL_FLOATING) + fatalx("floating cells cannot be split"); + + status = window_get_pane_status(wp->window); + + switch (type) { + case LAYOUT_LEFTRIGHT: + if (wp->window->sb == PANE_SCROLLBARS_ALWAYS) { + 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! @@ -1373,17 +1490,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); @@ -1442,14 +1549,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); @@ -1574,6 +1674,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"); @@ -1600,15 +1701,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); } @@ -1625,11 +1727,30 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, return (lc); } -void -layout_cell_floating_args_parse(struct cmdq_item *item, struct args *args, - struct window *w, u_int *sxp, u_int *syp, int *oxp, int *oyp, char **cause) +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) { - int sx, sy, ox, oy; + struct layout_cell *lcnew; + u_int sx = UINT_MAX, sy = UINT_MAX; + int ox = INT_MAX, oy = INT_MAX; + + 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; @@ -1637,28 +1758,44 @@ layout_cell_floating_args_parse(struct cmdq_item *item, struct args *args, oy = *oyp == INT_MAX ? INT_MAX : *oyp; if (args_has(args, 'x')) { - sx = args_percentage_and_expand(args, 'x', 0, w->sx - 1, w->sx, - item, cause); - if (*cause != NULL) - return; + 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 (-1); + } + 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, cause); - if (*cause != NULL) - return; + 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 (-1); + } + if (lines != PANE_LINES_NONE) + sy -= 2; } if (args_has(args, 'X')) { - ox = args_percentage_and_expand(args, 'X', -(int)w->sx, w->sx, - w->sx, item, cause); - if (*cause != NULL) - return; + ox = args_percentage_and_expand(args, 'X', -sx, w->sx, + w->sx, item, &error); + if (error != NULL) { + xasprintf(cause, "position %s", error); + free(error); + return (-1); + } } if (args_has(args, 'Y')) { - oy = args_percentage_and_expand(args, 'Y', -(int)w->sy, w->sy, - w->sy, item, cause); - if (*cause != NULL) - return; + oy = args_percentage_and_expand(args, 'Y', -sy, w->sy, + w->sy, item, &error); + if (error != NULL) { + xasprintf(cause, "position %s", error); + free(error); + return (-1); + } } if (ox == INT_MAX) { @@ -1670,7 +1807,9 @@ layout_cell_floating_args_parse(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; @@ -1680,39 +1819,24 @@ layout_cell_floating_args_parse(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"); + return (-1); } + if (sy < PANE_MINIMUM || sy > PANE_MAXIMUM) { + *cause = xstrdup("invalid height"); + return (-1); + } + *sxp = sx; *syp = sy; *oxp = ox; *oyp = oy; -} - -/* 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) -{ - struct layout_cell *lcnew; - u_int sx = UINT_MAX, sy = UINT_MAX; - int ox = INT_MAX, oy = INT_MAX; - - layout_cell_floating_args_parse(item, args, w, &sx, &sy, &ox, &oy, - cause); - if (*cause != NULL) { - return (NULL); - } - - if (sx < PANE_MINIMUM || sx > PANE_MAXIMUM) { - *cause = xstrdup("invalid width"); - return (NULL); - } - if (sy < PANE_MINIMUM || sy > PANE_MAXIMUM) { - *cause = xstrdup("invalid height"); - return (NULL); - } - - lcnew = layout_floating_pane(w, wp, sx, sy, ox, oy); - return (lcnew); + return (0); } /* @@ -1746,8 +1870,76 @@ 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); +} + +/* + * 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/menu.c b/menu.c index b124486b1..756bb5693 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; @@ -636,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); } diff --git a/mode-tree.c b/mode-tree.c index ff31edb47..fae0f3d8a 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -36,7 +36,26 @@ 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); struct mode_tree_data { @@ -51,11 +70,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 +96,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 +147,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 }, @@ -138,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 @@ -382,6 +443,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 +577,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; @@ -641,6 +711,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); @@ -723,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) @@ -753,25 +824,34 @@ 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]; + int dfg, dfg0; if (mtd->line_size == 0) return; 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); + + dfg = gc.fg; + dfg0 = gc0.fg; w = mtd->width; h = mtd->height; screen_write_start(&ctx, s); screen_write_clearscreen(&ctx, 8); + ft = format_create_defaults(NULL, NULL, NULL, NULL, wp); keylen = 0; for (i = 0; i < mtd->line_size; i++) { @@ -802,81 +882,98 @@ 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; - 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) { 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; + gc.fg = dfg; + gc0.fg = dfg0; } } + format_free(ft); if (mtd->preview == MODE_TREE_PREVIEW_OFF) goto done; @@ -891,7 +988,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, @@ -904,21 +1001,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); @@ -933,10 +1030,149 @@ 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->c = c; + 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) { @@ -1057,41 +1293,37 @@ 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 done) + 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 -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 done) + 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); @@ -1104,13 +1336,20 @@ 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 -mode_tree_filter_free(void *data) +mode_tree_clear_filter(struct mode_tree_data *mtd) { - mode_tree_remove_ref(data); + free(mtd->filter); + mtd->filter = NULL; + + mode_tree_build(mtd); + mode_tree_draw(mtd); + mtd->wp->flags |= PANE_REDRAW; } static void @@ -1181,12 +1420,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); @@ -1198,10 +1439,12 @@ 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; + u_int x, y, w, h = 0, box_w, box_h; if (mtd->helpcb == NULL) w = MODE_TREE_HELP_DEFAULT_WIDTH; @@ -1224,18 +1467,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 @@ -1251,15 +1498,75 @@ 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; + 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; @@ -1461,11 +1768,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; @@ -1476,10 +1782,13 @@ 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': switch (mtd->preview) { 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/options-table.c b/options-table.c index fd4c4f425..4116da61b 100644 --- a/options-table.c +++ b/options-table.c @@ -64,7 +64,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 @@ -108,6 +108,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 }; @@ -270,6 +273,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 } }; @@ -327,9 +331,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." }, @@ -444,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." }, @@ -453,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." }, @@ -461,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." @@ -536,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},yellowgreen,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},darkgoldenrod,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},indianred,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},skyblue3,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},cadetblue,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},mediumpurple,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},seagreen,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},indianred4,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},steelblue,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},purple4,magenta}", + .text = "Light theme colour for magenta." + }, + { .name = "user-keys", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, @@ -634,16 +808,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 = "themered", .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 = "themeblue", .text = "Colour of not active panes for 'display-panes'." }, @@ -734,7 +910,9 @@ 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=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 " @@ -763,7 +941,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=themeyellow,fg=themeblack," + "#{?#{m/r:(^|#,)IS(PANE|MODE)($|#,),#{prompt_flags}},," + "fill=themeyellow}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of messages and the command prompt. " @@ -962,7 +1142,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." @@ -971,7 +1151,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." @@ -988,12 +1168,22 @@ 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." }, + { .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, @@ -1014,7 +1204,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." @@ -1142,9 +1332,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 = "themeblue", .text = "Colour of the clock in clock mode." }, @@ -1159,7 +1350,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." @@ -1168,7 +1359,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." @@ -1177,7 +1368,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." @@ -1216,7 +1407,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." @@ -1225,7 +1416,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." @@ -1296,7 +1487,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." }, @@ -1345,7 +1536,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." @@ -1404,7 +1597,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." @@ -1423,13 +1616,23 @@ 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", .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." @@ -1446,7 +1649,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." @@ -1455,7 +1658,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 = "bg=themedarkgrey,fg=themelightgrey", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Default style of popup borders." @@ -1501,6 +1704,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, @@ -1518,6 +1730,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, @@ -1533,13 +1754,22 @@ 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." }, + { .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, @@ -1613,7 +1843,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." diff --git a/options.c b/options.c index a847ad9c1..e34320869 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) { @@ -977,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)) @@ -988,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); @@ -1029,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); } @@ -1186,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) @@ -1219,6 +1258,16 @@ 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-timeout") == 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 || @@ -1237,8 +1286,17 @@ 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) + window_pane_scrollbar_hide(wp); } if (strcmp(name, "pane-scrollbars-style") == 0) { RB_FOREACH(wp, window_pane_tree, &all_window_panes) { diff --git a/paste.c b/paste.c index 081e3dbeb..5ee80afd1 100644 --- a/paste.c +++ b/paste.c @@ -219,7 +219,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); @@ -287,7 +287,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/popup.c b/popup.c index c11f44833..26b89c613 100644 --- a/popup.c +++ b/popup.c @@ -273,7 +273,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; @@ -328,7 +328,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/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..1ab447ebe --- /dev/null +++ b/prompt.c @@ -0,0 +1,1605 @@ +/* $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 command_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 (flags & PROMPT_EDITARROWS) + strlcat(tmp, "EDITARROWS,", 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; + struct grid_cell gc; + 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); + 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"); +} + +/* 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->command_ccolour = pd->command_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; + 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; + } + + 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); + + 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_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); + } + 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/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 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 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 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 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 + │ 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 092ebcaa7..1cd314bb7 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,392 @@ #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 +#define REDRAW_SCROLLBAR_OVERLAY 0x8 + +/* 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 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; + + 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->ind = options_get_number(w->options, "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,1171 +416,839 @@ 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, int overlay) +{ + struct redraw_build_cell *bc; + u_int x, y; + int wx, wy, sx, ex; + u_int sy; + + if (sb_w == 0) + return; + + 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 { + 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; + if (overlay) + bc->data.sb.flags |= REDRAW_SCROLLBAR_OVERLAY; + } + } +} + +/* + * 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); - if (window_pane_is_floating(wp)) - style_add(&gc, wp->options, "floating-pane-border-style", ft); - fmt = options_get_string(wp->options, "pane-border-format"); - - 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, overlay = 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 (!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); -} - -/* Get border cell style. */ -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) -{ - 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; - - 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) - return; - if (i != wp->xoff + 1 && j != wp->yoff + 1) - return; - - if (wp != active) { - if (window_pane_is_floating(active)) - return; - if (window_pane_is_floating(wp)) - return; - } - - value = options_get_number(oo, "pane-border-indicators"); - if (value != PANE_BORDER_ARROWS && value != PANE_BORDER_BOTH) - return; - - 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; - else - border = SCREEN_REDRAW_BORDER_TOP; - arrows = 1; + if (window_pane_scrollbar_visible(wp)) { + overlay = window_pane_scrollbar_overlay(wp); + 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 { - 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; - else - border = SCREEN_REDRAW_BORDER_LEFT; - arrows = 1; - } - } 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]); + } else + sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; } + if (sb_w != 0 && bctx->w->sb_pos == PANE_SCROLLBARS_LEFT) + sb_left = 1; + + redraw_mark_pane_inside(bctx, wp); + redraw_mark_pane_borders(bctx, wp, overlay ? 0 : sb_w, sb_left); + redraw_mark_pane_scrollbar(bctx, wp, sb_w, sb_left, overlay); } -/* Draw a border cell. */ +/* Choose the pane that will provide the border style for two-pane layouts. */ static void -screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) +redraw_mark_two_pane_colours(struct redraw_build_ctx *bctx) { - 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_build_cell *bc; + struct redraw_span_data *sd; + enum layout_type type; + u_int x, y, wx, wy; - if (c->overlay_check != NULL) { - r = c->overlay_check(c, c->overlay_data, x, y, 1); - if (server_client_ranges_is_empty(r)) - return; - } - - cell_type = screen_redraw_check_cell(ctx, x, y, &wp); - if (cell_type == CELL_INSIDE || cell_type == CELL_SCROLLBAR) + if (bctx->ind != PANE_BORDER_COLOUR && bctx->ind != PANE_BORDER_BOTH) + return; + if (!redraw_check_two_pane_colours(bctx->w, &type)) 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 < 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; + + wx = bctx->ox + x; + wy = bctx->oy + y; + + 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 + 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 + sd->b.style_wp = sd->b.bottom_wp; + } } - 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); } -/* Draw the borders. */ -static void -screen_redraw_draw_borders(struct screen_redraw_ctx *ctx) +/* 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 redraw_span_data *ad = &a->data, *bd = &b->data; + + 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; - u_int i, j; + size_t ncells; + u_int x, y; - 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; + 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; } - 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); + 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); } -/* Draw the panes. */ -static void -screen_redraw_draw_panes(struct screen_redraw_ctx *ctx) +/* + * 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 client *c = ctx->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; + + 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); + } + } + } + free(scene->lines); + free(scene); +} + +/* Mark a window's cached redraw scenes as out of date. */ +void +redraw_invalidate_scene(struct window *w) +{ + w->redraw_scene_generation++; +} + +/* Mark all cached redraw scenes as out of date. */ +void +redraw_invalidate_all_scenes(void) +{ + 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); - } - } - -#ifdef ENABLE_SIXEL - tty_draw_images(c, wp, s); -#endif + 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, pad_gc, *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) @@ -1286,138 +1256,527 @@ 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; + tty_default_colours(&pad_gc, wp, NULL); - 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)) + 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, &pad_gc, NULL); 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_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); + } + } else { + if (off + i < sb_pad) { + tty_cell(tty, &pad_gc, 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 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) +{ + 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 != 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_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 != 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); + +#ifdef ENABLE_SIXEL + if (wp != NULL) + tty_draw_images(c, wp); + else { + TAILQ_FOREACH(loop, &scene->w->panes, entry) + tty_draw_images(c, loop); + } +#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. */ +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 dd46e9a1a..0e5a50c97 100644 --- a/screen-write.c +++ b/screen-write.c @@ -732,19 +732,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); @@ -752,7 +752,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; @@ -761,7 +762,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'); @@ -847,26 +851,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); @@ -1523,6 +1527,7 @@ 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)) @@ -1533,7 +1538,10 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg) ctx->wp->flags |= PANE_REDRAW; #endif + 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; @@ -2180,6 +2188,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); @@ -2193,7 +2205,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/screen.c b/screen.c index 0a761660a..8e513758e 100644 --- a/screen.c +++ b/screen.c @@ -190,10 +190,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; @@ -249,10 +250,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); @@ -266,10 +264,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/server-client.c b/server-client.c index d001fe5de..f170396ec 100644 --- a/server-client.c +++ b/server-client.c @@ -48,6 +48,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. */ @@ -287,6 +289,7 @@ struct client * server_client_create(int fd) { struct client *c; + u_int i; setblocking(fd, 0); @@ -309,6 +312,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); @@ -360,6 +366,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); } @@ -485,10 +492,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); @@ -530,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) { @@ -598,6 +603,58 @@ 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 start, end; + + if (!window_pane_scrollbar_overlay(wp)) + 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; + + if (w->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, @@ -605,17 +662,19 @@ 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); - if (window_pane_show_scrollbar(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) + sb_w = wp->sx; } else { sb_w = 0; sb_pad = 0; @@ -628,23 +687,48 @@ 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 && 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 (w->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) || (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. */ @@ -678,7 +762,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_w = fwp->scrollbar_style.width; sb_pad = fwp->scrollbar_style.pad; } else { @@ -688,7 +772,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 { @@ -908,10 +992,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. */ @@ -944,7 +1032,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 || @@ -1079,6 +1168,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) @@ -1459,6 +1594,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)) @@ -1488,6 +1624,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: @@ -1497,10 +1634,40 @@ server_client_handle_key0(struct client *c, struct key_event *event, return (0); } } + server_client_clear_overlay(c); - if (c->prompt_string != NULL) { - if (status_prompt_key(c, event->key) == 0) + if (c->prompt != NULL) { + switch (status_prompt_key(c, event->key, &event->m)) { + case PROMPT_KEY_HANDLED: + case PROMPT_KEY_CLOSE: return (0); + case PROMPT_KEY_NOT_HANDLED: + case PROMPT_KEY_MOVE: + 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; + } } } @@ -1773,6 +1940,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 @@ -1791,7 +1994,8 @@ 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; + u_int sb_w; struct visible_ranges *r; if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) @@ -1805,7 +2009,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; @@ -1821,42 +2025,50 @@ 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 (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 && + w->sb_pos == + 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); + } + + 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) { @@ -1877,14 +2089,16 @@ server_client_reset_state(struct client *c) mode |= MODE_MOUSE_ALL; } } - if (options_get_number(oo, "focus-follows-mouse")) - 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; } /* 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. */ @@ -2101,11 +2315,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); } } } @@ -2120,7 +2334,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. */ @@ -2869,6 +3083,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); @@ -2877,6 +3093,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/server.c b/server.c index f00fbdc38..7780ff519 100644 --- a/server.c +++ b/server.c @@ -254,7 +254,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/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/status.c b/status.c index 2dfad5198..bb0c3bef9 100644 --- a/status.c +++ b/status.c @@ -29,152 +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); -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[] = { - "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) @@ -188,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); @@ -455,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, @@ -518,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; @@ -577,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. */ @@ -627,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); @@ -638,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 @@ -665,104 +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; - if (c->prompt_string != NULL) { - c->prompt_inputcb(c, c->prompt_data, "y", 1); - 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; + 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; - c->prompt_mode = PROMPT_ENTRY; + 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) - c->prompt_inputcb(c, c->prompt_data, "=", 0); + 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 */ @@ -774,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. */ @@ -852,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); @@ -872,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_mode == PROMPT_COMMAND) - 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_mode == PROMPT_COMMAND) - 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_mode == PROMPT_COMMAND); - 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) { @@ -969,1304 +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_mode == PROMPT_ENTRY) { - 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_mode = PROMPT_COMMAND; - 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_mode = PROMPT_ENTRY; - c->flags |= CLIENT_REDRAWSTATUS; - break; /* switch mode and... */ - case 'S': - c->prompt_mode = PROMPT_ENTRY; - c->flags |= CLIENT_REDRAWSTATUS; - *new_key = 'u'|KEYC_CTRL; - return (1); - case 'i': - c->prompt_mode = PROMPT_ENTRY; - 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; + *cy = status_prompt_screen_line(c); + *cx = c->status.prompt_cx; } /* Handle keys in prompt. */ -int -status_prompt_key(struct client *c, key_code key) +enum prompt_key_result +status_prompt_key(struct client *c, key_code key, struct mouse_event *m) { - struct options *oo = c->session->options; - char *s, *cp, prefix = '='; - const char *histstr, *separators = NULL, *keystring; - size_t size, idx; - struct utf8_data tmp; - int keys, word_is_separators; + enum prompt_key_result result; + u_int ax, aw; + int redraw = 0; - if (c->prompt_flags & PROMPT_KEY) { - keystring = key_string_lookup_key(key, 0); - c->prompt_inputcb(c, c->prompt_data, keystring, 1); + 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 (0); - } - 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, c->prompt_data, s, 1); - status_prompt_clear(c); - free(s); - return (1); - } - - 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 (0); - 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 (0); - } - } - -process_key: - 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) { - if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) - status_prompt_clear(c); - break; - } - 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); - if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) - status_prompt_clear(c); - 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); - break; - 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 (0); - -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 (0); - - 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); - else { - s = utf8_tocstr(c->prompt_buffer); - if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) - status_prompt_clear(c); - 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, c->prompt_data, cp, 0); - free(cp); - free(s); - } - return (0); -} - -/* 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, int at_start) -{ - 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 - }; - - *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); - } - } - 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); -} - -/* 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; - 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); - 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++) - 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, char flag) -{ - 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; - spm->flag = flag; - - 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); -} - -/* 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) -{ - const char **aa = (const char **)a, **bb = (const char **)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; - - if (*word == '\0' && - c->prompt_type != PROMPT_TYPE_TARGET && - c->prompt_type != PROMPT_TYPE_WINDOW_TARGET) - 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; - } - - 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++) - 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, flag)) { - 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]); + return (result); } diff --git a/style.c b/style.c index b79d32916..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[256]; + 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, @@ -434,6 +472,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) @@ -454,22 +515,33 @@ 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; + char *style, *expanded; - sy = options_string_to_style(oo, "pane-scrollbars-style", NULL); - if (sy == NULL) { - style_set(sb_style, &grid_default_cell); - sb_style->width = PANE_SCROLLBARS_DEFAULT_WIDTH; - 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); + 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); + 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) { + 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) + sb_style->pad = PANE_SCROLLBARS_DEFAULT_PADDING; + utf8_set(&sb_style->gc.data, PANE_SCROLLBARS_CHARACTER); } /* Initialize style ranges. */ diff --git a/tmux.1 b/tmux.1 index 7b3d87943..ea23cbaa8 100644 --- a/tmux.1 +++ b/tmux.1 @@ -364,6 +364,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 } @@ -2744,6 +2746,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 @@ -2873,9 +2884,11 @@ 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" +.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 @@ -2968,11 +2981,12 @@ 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" .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 @@ -3066,6 +3080,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" @@ -3087,6 +3102,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 @@ -3571,7 +3631,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 @@ -3584,101 +3644,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 splitw \-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 @@ -4033,14 +4041,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 @@ -4653,6 +4739,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 @@ -5242,14 +5379,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. @@ -5834,7 +5976,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. @@ -5844,19 +5986,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 @@ -5873,10 +6025,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. @@ -5918,6 +6073,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 @@ -5929,6 +6092,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 @@ -6108,6 +6279,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 @@ -6500,13 +6680,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 @@ -6831,6 +7034,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" @@ -7146,6 +7350,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 , @@ -7478,7 +7704,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 @@ -7558,6 +7784,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 @@ -8016,6 +8244,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" diff --git a/tmux.c b/tmux.c index c06a24350..132c39217 100644 --- a/tmux.c +++ b/tmux.c @@ -282,15 +282,15 @@ get_timer(void) } char * -clean_name(const char *name, const char* forbid) +clean_name(const char *name, int untrusted) { 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++) { - 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); @@ -298,22 +298,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 (*name == '\0' || !utf8_isvalid(name)) + 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 ccc0c21bd..28eede8d7 100644 --- a/tmux.h +++ b/tmux.h @@ -63,6 +63,10 @@ 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; struct screen_write_cline; struct screen_write_ctx; @@ -96,12 +100,6 @@ struct winlink; #define TMUX_LOCK_CMD "lock -np" #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 @@ -733,10 +731,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; @@ -796,20 +810,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. */ @@ -970,6 +987,8 @@ struct style { int pad; enum style_default_type default_type; + + u_int link; }; #ifdef ENABLE_SIXEL @@ -1113,27 +1132,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) @@ -1285,7 +1283,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 @@ -1305,6 +1303,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; @@ -1347,13 +1348,16 @@ struct window_pane { struct screen base; struct screen status_screen; - size_t status_size; TAILQ_HEAD(, window_mode_entry) modes; 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; @@ -1412,9 +1416,14 @@ 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; + int sb; + int sb_pos; + struct utf8_data *fill_character; int flags; #define WINDOW_BELL 0x1 @@ -1474,6 +1483,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 @@ -1513,6 +1523,12 @@ struct layout_cell { int xoff, saved_xoff; int yoff, saved_yoff; + u_int saved_sx; + u_int saved_sy; + + int saved_xoff; + int saved_yoff; + struct window_pane *wp; struct layout_cells cells; @@ -2000,20 +2016,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 *); @@ -2054,18 +2062,98 @@ RB_HEAD(client_windows, client_window); /* Maximum time to be pasting. */ #define CLIENT_PASTE_TIME_LIMIT 5 -/* Client connection. */ -typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); +/* 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, + 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)(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 *); -typedef struct visible_ranges *(*overlay_check_cb)(struct client*, void *, + +/* 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 +#define PROMPT_EDITARROWS 0x1000 + +/* 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 command_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 *, 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 *); + +/* Client connection. */ struct client { const char *name; struct tmuxpeer *peer; @@ -2108,6 +2196,8 @@ struct client { size_t discarded; size_t redraw; + struct redraw_scene *redraw_scene; + struct event repeat_timer; struct event click_timer; @@ -2198,38 +2288,15 @@ 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]; - enum { - PROMPT_ENTRY, - PROMPT_COMMAND - } prompt_mode; - 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 - int prompt_flags; - enum prompt_type prompt_type; - int prompt_cursor; + struct prompt *prompt; struct session *session; struct session *last_session; int references; + int theme_colours[COLOUR_THEME_COUNT]; + void *pan_window; u_int pan_ox; u_int pan_oy; @@ -2318,6 +2385,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; @@ -2436,8 +2504,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); @@ -2621,6 +2689,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 *); @@ -2775,7 +2844,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 *); @@ -3126,6 +3195,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 *); @@ -3150,8 +3220,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 *); @@ -3167,16 +3235,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 *); -int 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); @@ -3219,6 +3309,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 *); @@ -3234,6 +3326,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); @@ -3339,7 +3434,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 *); @@ -3395,8 +3491,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); @@ -3501,6 +3602,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, @@ -3515,7 +3626,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 *); @@ -3529,7 +3640,15 @@ 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_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 *); +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 *); @@ -3538,6 +3657,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, @@ -3545,6 +3665,16 @@ struct style_range *window_pane_status_get_range(struct window_pane *, u_int, int window_pane_is_floating(struct window_pane *); int window_pane_is_hidden(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, @@ -3581,9 +3711,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); @@ -3591,6 +3721,8 @@ 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, int, int); struct layout_cell *layout_floating_pane(struct window *, struct window_pane *, @@ -3601,10 +3733,11 @@ void layout_spread_out(struct window_pane *); void layout_cell_floating_args_parse(struct cmdq_item *, struct args *, struct window *, u_int *, u_int *, int *, int *, char **); struct layout_cell *layout_get_floating_cell(struct cmdq_item *, struct args *, - struct window *, struct window_pane *, char **); -struct layout_cell *layout_get_tiled_cell(struct cmdq_item *, struct args *, - struct window *, struct window_pane *, int, char **); -int layout_cell_is_tiled(struct layout_cell *); + enum pane_lines, struct window *, struct window_pane *, + 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 *); int layout_insert_tile(struct window *, struct layout_cell *); @@ -3634,6 +3767,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); @@ -3662,6 +3796,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 *); @@ -3671,6 +3810,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]; @@ -3855,8 +3997,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 *); @@ -3877,7 +4018,10 @@ 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 *); +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/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 + } +} +' diff --git a/tty-features.c b/tty-features.c index 01b9bff38..d8b3ce3b4 100644 --- a/tty-features.c +++ b/tty-features.c @@ -580,6 +580,7 @@ tty_default_features(int *feat, const char *name, u_int version) "cstyle," "extkeys," "focus," + "hyperlinks," "usstyle" }, { .name = "XTerm", diff --git a/tty.c b/tty.c index bd1b289cf..36cff2606 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 *); @@ -752,8 +753,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) @@ -1545,20 +1548,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; @@ -2780,6 +2783,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); @@ -2929,6 +2935,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) @@ -2951,6 +2979,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) { @@ -3011,6 +3040,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) { @@ -3061,6 +3091,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)) { 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-buffer.c b/window-buffer.c index 436435de4..639cb5ede 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..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}," \ @@ -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} " - "#[dim](PID #{client_pid})#[default]", - "Session #[acs]x#[default] " + "#[fg=themelightgrey]Client Name #[#{E:tree-mode-border-style},acs]x#[default] " + "#{client_name} #[fg=themelightgrey]" + "#[fg=themelightgrey](PID #{client_pid})#[default]", + "#[fg=themelightgrey]Session #[#{E:tree-mode-border-style},acs]x#[default] " "#{session_name}", - "Attach Time #[acs]x#[default] " + "#[fg=themelightgrey]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]", + "#[fg=themelightgrey]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]", + "#[fg=themelightgrey]Terminal Type #[#{E:tree-mode-border-style},acs]x#[default] " "#{?client_termtype,#{client_termtype},Unknown}", - "TERM #[acs]x#[default] " + "#[fg=themelightgrey]TERM #[#{E:tree-mode-border-style},acs]x#[default] " "#{client_termname}", - "Size #[acs]x#[default] " + "#[fg=themelightgrey]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]", + "#[fg=themelightgrey]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] " + "#[fg=themelightgrey]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] " + "#[fg=themelightgrey]prefix #[#{E:tree-mode-border-style},acs]x#[default] " "#{prefix}", - "mouse #[acs]x#[default] " - "#{?mouse,#{?#{I/c:kmous},,#[fg=red]}on,#[dim]off} " + "#[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 #[acs]x#[default] " - "#{?#{!=:#{set-clipboard},off},#{?#{I/f:clipboard},,#[fg=red]}#{set-clipboard},#[dim]off} " + "#[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 #[acs]x#[default] " - "#{?#{!=:#{get-clipboard},off},#{?#{I/f:clipboard},,#[fg=red]}#{get-clipboard},#[dim]off} " + "#[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 #[acs]x#[default] " - "#{?focus-events,#{?#{I/f:focus},,#[fg=red]}on,#[dim]off} " + "#[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 #[acs]x#[default] " - "#{?#{!=:#{extended-keys},off},#{?#{I/f:extkeys},,#[fg=red]}#{extended-keys},#[dim]off} " + "#[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 #[acs]x#[default] " - "#{?set-titles,on,#[dim]off}", + "#[fg=themelightgrey]set-titles #[#{E:tree-mode-border-style},acs]x#[default] " + "#{?set-titles,on,#[fg=themelightgrey]off}", - "escape-time #[acs]x#[default] " + "#[fg=themelightgrey]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-clock.c b/window-clock.c index 88b71df50..ace160a8b 100644 --- a/window-clock.c +++ b/window-clock.c @@ -222,18 +222,23 @@ 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; 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-copy.c b/window-copy.c index 272e2e9e7..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 *, @@ -800,7 +802,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); @@ -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); } @@ -5154,6 +5165,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); @@ -5275,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, @@ -5283,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 @@ -6539,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); @@ -6570,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); @@ -6585,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 @@ -6604,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); @@ -6630,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); @@ -6641,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-customize.c b/window-customize.c index 71ca206fb..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}" \ "}" @@ -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); @@ -740,8 +741,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); } @@ -766,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: ")) @@ -869,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 }; @@ -976,9 +997,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 done) + const char *s, __unused enum prompt_key_result key) { struct window_customize_itemdata *item = itemdata; struct window_customize_modedata *data = item->data; @@ -990,12 +1011,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)) { @@ -1017,13 +1038,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 @@ -1142,10 +1163,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); @@ -1190,9 +1211,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 done) + const char *s, __unused enum prompt_key_result key) { struct window_customize_itemdata *item = itemdata; struct window_customize_modedata *data = item->data; @@ -1201,9 +1222,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) { @@ -1220,27 +1241,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 done) + 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); @@ -1249,7 +1270,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 @@ -1280,10 +1301,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) { @@ -1296,11 +1317,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); } } @@ -1367,17 +1388,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 done) + 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) { @@ -1400,19 +1421,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 done) + 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); @@ -1420,7 +1441,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 @@ -1474,11 +1495,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': @@ -1488,11 +1509,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': @@ -1505,11 +1526,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': @@ -1519,11 +1540,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-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; +} diff --git a/window-tree.c b/window-tree.c index 16cc6411b..13c15c233 100644 --- a/window-tree.c +++ b/window-tree.c @@ -38,15 +38,15 @@ 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_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_name}#{window_flags}" \ + "#{?window_marked_flag,#[fg=thememagenta],}" \ + "#{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})," \ @@ -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,80 @@ 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]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_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") " " + 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) @@ -401,7 +476,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 +496,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 +521,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 +579,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 +615,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 +637,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 +645,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 +662,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 +726,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 +763,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 +785,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 +793,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++; @@ -711,6 +801,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) @@ -725,6 +890,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; @@ -889,18 +1059,32 @@ 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]" + " 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", + "#[fg=themelightgrey]" + " H #[#{E:tree-mode-border-style},acs]x#[default] Jump to the starting pane", NULL }; @@ -954,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); @@ -1080,14 +1265,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 done) + __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 +1282,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 +1324,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 done) + 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 +1342,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 done) + 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 +1363,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 @@ -1293,6 +1478,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) { @@ -1317,10 +1509,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 +1522,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 +1536,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-visible.c b/window-visible.c index f5b034475..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_show_scrollbar(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 273f5d4c7..45206cd61 100644 --- a/window.c +++ b/window.c @@ -71,6 +71,7 @@ struct window_pane_input_data { static struct window_pane *window_pane_create(struct window *, u_int, u_int, u_int); static void window_pane_destroy(struct window_pane *); +static void window_pane_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); @@ -78,6 +79,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) { @@ -411,11 +420,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; @@ -635,6 +644,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; @@ -772,6 +782,7 @@ window_zoom(struct window_pane *wp) w->flags |= WINDOW_ZOOMED; notify_window("window-layout-changed", w); + redraw_invalidate_scene(w); return (0); } @@ -798,6 +809,7 @@ window_unzoom(struct window *w, int notify) if (notify) notify_window("window-layout-changed", w); + redraw_invalidate_scene(w); return (0); } @@ -854,6 +866,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); } @@ -895,6 +908,7 @@ window_lost_pane(struct window *w, struct window_pane *wp) notify_window("window-pane-changed", w); window_update_focus(w); } + redraw_invalidate_scene(w); } void @@ -903,6 +917,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); } @@ -1126,6 +1141,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); @@ -1157,13 +1173,71 @@ 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_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) +{ + return (wp->window->sb == PANE_SCROLLBARS_MODAL || + wp->window->sb == PANE_SCROLLBARS_AUTOHIDE); +} + +int +window_pane_scrollbar_overlay_visible(struct window_pane *wp) +{ + return (window_pane_scrollbar_overlay(wp) && + window_pane_scrollbar_visible(wp)); +} + +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) { window_pane_wait_finish(wp); spawn_editor_finish(wp); - window_pane_reset_mode_all(wp); + window_pane_clear_prompt(wp); + + window_pane_free_modes(wp); free(wp->searchstr); if (wp->fd != -1) { @@ -1190,6 +1264,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); @@ -1259,7 +1335,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; @@ -1379,6 +1456,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) { @@ -1386,6 +1464,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) { @@ -1450,6 +1687,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); @@ -1563,17 +1806,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_show_scrollbar(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 */ @@ -1989,19 +2228,93 @@ 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_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) +{ + if (!window_pane_show_scrollbar(wp)) + return (0); + return (wp->window->sb == PANE_SCROLLBARS_ALWAYS); +} + +int +window_pane_scrollbar_overlay(struct window_pane *wp) +{ + if (!window_pane_show_scrollbar(wp)) + return (0); + return (window_pane_scrollbar_auto_hide(wp)); +} + +int +window_pane_scrollbar_visible(struct window_pane *wp) +{ + if (!window_pane_show_scrollbar(wp)) + 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; + if (!window_pane_scrollbar_auto_hide(wp)) + return; + if (!window_pane_show_scrollbar(wp)) + 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) { @@ -2202,6 +2515,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) {