From 51d037e881a4b795ef782186ed070a61e1c46595 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 25 Jun 2026 11:39:11 +0000 Subject: [PATCH] Major rework of prompts. The basic prompt mechanics (draw, editing, etc) are now wrapped up in prompt*.c and do not depend on a client. These functions are used to provide the original client prompt but also to allow panes to have their own prompts, which works much much better for floating panes. The mode prompts for both the tree modes and copy mode are switched over to be per pane. There are some visible changes (some of these may be changed if they don't seem to be working well): - Prompts in modes now appear in the bottom line, covering whatever content was there. - command-prompt has a -P flag to open a pane prompt. - Because they cover the content, the default style for prompts in modes now does not fill the entire line; the main command prompt stays the same. - The old completion menu has gone, and completions are now shown after the text. Builtin aliases are no longer completed. - Clicking the mouse on the prompt now moves the cursor or selects a completion. --- Makefile | 2 + cfg.c | 2 +- cmd-command-prompt.c | 55 +- cmd-display-panes.c | 2 +- cmd-show-prompt-history.c | 46 +- key-bindings.c | 64 +- mode-tree.c | 261 +++++- options-table.c | 4 +- prompt-history.c | 264 ++++++ prompt.c | 1595 +++++++++++++++++++++++++++++++++++ screen-redraw.c | 71 +- server-client.c | 131 ++- server.c | 2 +- status.c | 1645 +++---------------------------------- tmux.1 | 4 +- tmux.h | 157 +++- window-customize.c | 50 +- window-tree.c | 19 +- window.c | 170 ++++ 19 files changed, 2811 insertions(+), 1733 deletions(-) create mode 100644 prompt-history.c create mode 100644 prompt.c diff --git a/Makefile b/Makefile index de8cd2414..cfab9c7bb 100644 --- a/Makefile +++ b/Makefile @@ -102,6 +102,8 @@ SRCS= alerts.c \ popup.c \ proc.c \ procname.c \ + prompt.c \ + prompt-history.c \ regsub.c \ resize.c \ screen-redraw.c \ diff --git a/cfg.c b/cfg.c index 7d2bbe490..450ab88fb 100644 --- a/cfg.c +++ b/cfg.c @@ -57,7 +57,7 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data) if (cfg_item != NULL) cmdq_continue(cfg_item); - status_prompt_load_history(); + prompt_load_history(); return (CMD_RETURN_NORMAL); } diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index 5d5e122fd..045ef0b12 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -42,8 +42,8 @@ const struct cmd_entry cmd_command_prompt_entry = { .name = "command-prompt", .alias = NULL, - .args = { "1CbeFiklI:Np:t:T:", 0, 1, cmd_command_prompt_args_parse }, - .usage = "[-1CbeFiklN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE + .args = { "1CbeFiklI:NPp:t:T:", 0, 1, cmd_command_prompt_args_parse }, + .usage = "[-1CbeFiklNP] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " [-T prompt-type] [template]", .flags = CMD_CLIENT_TFLAG, @@ -62,6 +62,8 @@ struct cmd_command_prompt_cdata { int flags; enum prompt_type prompt_type; + struct window_pane *wp; + struct cmd_command_prompt_prompt *prompts; u_int count; u_int current; @@ -87,10 +89,15 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) struct cmd_command_prompt_cdata *cdata; char *tmp, *prompts, *prompt, *next_prompt; char *inputs = NULL, *next_input; + struct window_pane *wp = target->wp; u_int count = args_count(args); int wait = !args_has(args, 'b'), space = 1; + int pane = args_has(args, 'P'); - if (tc->prompt_string != NULL) + if (pane) { + if (wp == NULL || window_pane_has_prompt(wp)) + return (CMD_RETURN_NORMAL); + } else if (tc->prompt != NULL) return (CMD_RETURN_NORMAL); if (args_has(args, 'i')) wait = 0; @@ -98,6 +105,8 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) cdata = xcalloc(1, sizeof *cdata); if (wait) cdata->item = item; + if (pane) + cdata->wp = wp; cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait, args_has(args, 'F')); @@ -146,7 +155,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) } if ((type = args_get(args, 'T')) != NULL) { - cdata->prompt_type = status_prompt_type(type); + cdata->prompt_type = prompt_type(type); if (cdata->prompt_type == PROMPT_TYPE_INVALID) { cmdq_error(item, "unknown type: %s", type); cmd_command_prompt_free(cdata); @@ -167,9 +176,18 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) cdata->flags |= PROMPT_BSPACE_EXIT; if (args_has(args, 'C')) cdata->flags |= PROMPT_NOFREEZE; - status_prompt_set(tc, target, cdata->prompts[0].prompt, - cdata->prompts[0].input, cmd_command_prompt_callback, - cmd_command_prompt_free, cdata, cdata->flags, cdata->prompt_type); + if (pane) { + cdata->flags |= PROMPT_ISPANE; + window_pane_set_prompt(wp, tc, target, cdata->prompts[0].prompt, + cdata->prompts[0].input, cmd_command_prompt_callback, + cmd_command_prompt_free, cdata, cdata->flags, + cdata->prompt_type); + } else { + status_prompt_set(tc, target, cdata->prompts[0].prompt, + cdata->prompts[0].input, cmd_command_prompt_callback, + cmd_command_prompt_free, cdata, cdata->flags, + cdata->prompt_type); + } if (!wait) return (CMD_RETURN_NORMAL); @@ -197,7 +215,12 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, cmd_append_argv(&cdata->argc, &cdata->argv, s); if (++cdata->current != cdata->count) { prompt = &cdata->prompts[cdata->current]; - status_prompt_update(c, prompt->prompt, prompt->input); + if (cdata->wp != NULL) { + window_pane_update_prompt(cdata->wp, + prompt->prompt, prompt->input); + } else + status_prompt_update(c, prompt->prompt, + prompt->input); return (PROMPT_CONTINUE); } } @@ -225,12 +248,19 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, } cmd_free_argv(argc, argv); - if (c->prompt_inputcb != cmd_command_prompt_callback) + /* + * An incremental prompt fires its callback on every edit but must stay + * open for further typing; only an explicit close (handled above) ends + * it. + */ + if (cdata->flags & PROMPT_INCREMENTAL) return (PROMPT_CONTINUE); out: - if (item != NULL) + if (item != NULL) { + cdata->item = NULL; cmdq_continue(item); + } return (PROMPT_CLOSE); } @@ -240,6 +270,11 @@ cmd_command_prompt_free(void *data) struct cmd_command_prompt_cdata *cdata = data; u_int i; + if (cdata->item != NULL) { + cmdq_continue(cdata->item); + cdata->item = NULL; + } + for (i = 0; i < cdata->count; i++) { free(cdata->prompts[i].prompt); free(cdata->prompts[i].input); diff --git a/cmd-display-panes.c b/cmd-display-panes.c index 24195266e..c9f185d59 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -279,7 +279,7 @@ cmd_display_panes_draw(struct client *c, __unused void *data) tty_window_offset(&c->tty, &ctx.ox, &ctx.oy, &ctx.sx, &ctx.sy); if (options_get_number(s->options, "status-position") == 0) { lines = status_line_size(c); - if (c->message_string != NULL || c->prompt_string != NULL) + if (c->message_string != NULL || c->prompt != NULL) lines = (lines == 0 ? 1 : lines); ctx.statuslines = lines; ctx.statustop = 1; diff --git a/cmd-show-prompt-history.c b/cmd-show-prompt-history.c index 5b88499c0..741d22588 100644 --- a/cmd-show-prompt-history.c +++ b/cmd-show-prompt-history.c @@ -55,56 +55,44 @@ cmd_show_prompt_history_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); const char *typestr = args_get(args, 'T'); enum prompt_type type; - u_int tidx, hidx; + u_int t, h; + const char *v; if (cmd_get_entry(self) == &cmd_clear_prompt_history_entry) { if (typestr == NULL) { - for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { - for (hidx = 0; hidx < status_prompt_hsize[tidx]; - hidx++) - free(status_prompt_hlist[tidx][hidx]); - free(status_prompt_hlist[tidx]); - status_prompt_hlist[tidx] = NULL; - status_prompt_hsize[tidx] = 0; - } + for (t = 0; t < PROMPT_NTYPES; t++) + prompt_history_clear(t); } else { - type = status_prompt_type(typestr); + type = prompt_type(typestr); if (type == PROMPT_TYPE_INVALID) { cmdq_error(item, "invalid type: %s", typestr); return (CMD_RETURN_ERROR); } - for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) - free(status_prompt_hlist[type][hidx]); - free(status_prompt_hlist[type]); - status_prompt_hlist[type] = NULL; - status_prompt_hsize[type] = 0; + prompt_history_clear(type); } - return (CMD_RETURN_NORMAL); } if (typestr == NULL) { - for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { - cmdq_print(item, "History for %s:\n", - status_prompt_type_string(tidx)); - for (hidx = 0; hidx < status_prompt_hsize[tidx]; - hidx++) { - cmdq_print(item, "%d: %s", hidx + 1, - status_prompt_hlist[tidx][hidx]); + for (t = 0; t < PROMPT_NTYPES; t++) { + typestr = prompt_type_string(t); + cmdq_print(item, "History for %s:\n", typestr); + for (h = 0; h < prompt_history_size(t); h++) { + v = prompt_history_get(t, h); + cmdq_print(item, "%d: %s", h + 1, v); } cmdq_print(item, "%s", ""); } } else { - type = status_prompt_type(typestr); + type = prompt_type(typestr); if (type == PROMPT_TYPE_INVALID) { cmdq_error(item, "invalid type: %s", typestr); return (CMD_RETURN_ERROR); } - cmdq_print(item, "History for %s:\n", - status_prompt_type_string(type)); - for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) { - cmdq_print(item, "%d: %s", hidx + 1, - status_prompt_hlist[type][hidx]); + cmdq_print(item, "History for %s:\n", prompt_type_string(type)); + for (h = 0; h < prompt_history_size(type); h++) { + v = prompt_history_get(type, h); + cmdq_print(item, "%d: %s", h + 1, v); } cmdq_print(item, "%s", ""); } diff --git a/key-bindings.c b/key-bindings.c index 5a2c3d96d..02ec14dfc 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -514,8 +514,8 @@ key_bindings_init(void) "bind -Tcopy-mode M-l { send -X cursor-centre-horizontal }", "bind -Tcopy-mode C-n { send -X cursor-down }", "bind -Tcopy-mode C-p { send -X cursor-up }", - "bind -Tcopy-mode C-r { command-prompt -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }", - "bind -Tcopy-mode C-s { command-prompt -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }", + "bind -Tcopy-mode C-r { command-prompt -P -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }", + "bind -Tcopy-mode C-s { command-prompt -P -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }", "bind -Tcopy-mode C-v { send -X page-down }", "bind -Tcopy-mode C-w { send -X copy-pipe-and-cancel }", "bind -Tcopy-mode Escape { send -X cancel }", @@ -523,18 +523,18 @@ key_bindings_init(void) "bind -Tcopy-mode Space { send -X page-down }", "bind -Tcopy-mode , { send -X jump-reverse }", "bind -Tcopy-mode \\; { send -X jump-again }", - "bind -Tcopy-mode F { command-prompt -1p'(jump backward)' { send -X jump-backward -- '%%' } }", + "bind -Tcopy-mode F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }", "bind -Tcopy-mode N { send -X search-reverse }", "bind -Tcopy-mode P { send -X toggle-position }", "bind -Tcopy-mode R { send -X rectangle-toggle }", - "bind -Tcopy-mode T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", + "bind -Tcopy-mode T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", "bind -Tcopy-mode X { send -X set-mark }", - "bind -Tcopy-mode f { command-prompt -1p'(jump forward)' { send -X jump-forward -- '%%' } }", - "bind -Tcopy-mode g { command-prompt -p'(goto line)' { send -X goto-line -- '%%' } }", + "bind -Tcopy-mode f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }", + "bind -Tcopy-mode g { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }", "bind -Tcopy-mode n { send -X search-again }", "bind -Tcopy-mode q { send -X cancel }", "bind -Tcopy-mode r { send -X refresh-toggle }", - "bind -Tcopy-mode t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", + "bind -Tcopy-mode t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", "bind -Tcopy-mode Home { send -X start-of-line }", "bind -Tcopy-mode End { send -X end-of-line }", "bind -Tcopy-mode MouseDown1Pane select-pane", @@ -550,15 +550,15 @@ key_bindings_init(void) "bind -Tcopy-mode Down { send -X cursor-down }", "bind -Tcopy-mode Left { send -X cursor-left }", "bind -Tcopy-mode Right { send -X cursor-right }", - "bind -Tcopy-mode M-1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }", - "bind -Tcopy-mode M-2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }", - "bind -Tcopy-mode M-3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }", - "bind -Tcopy-mode M-4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }", - "bind -Tcopy-mode M-5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }", - "bind -Tcopy-mode M-6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }", - "bind -Tcopy-mode M-7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }", - "bind -Tcopy-mode M-8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }", - "bind -Tcopy-mode M-9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }", + "bind -Tcopy-mode M-1 { command-prompt -P -Np'(repeat)' -I1 { send -N '%%' } }", + "bind -Tcopy-mode M-2 { command-prompt -P -Np'(repeat)' -I2 { send -N '%%' } }", + "bind -Tcopy-mode M-3 { command-prompt -P -Np'(repeat)' -I3 { send -N '%%' } }", + "bind -Tcopy-mode M-4 { command-prompt -P -Np'(repeat)' -I4 { send -N '%%' } }", + "bind -Tcopy-mode M-5 { command-prompt -P -Np'(repeat)' -I5 { send -N '%%' } }", + "bind -Tcopy-mode M-6 { command-prompt -P -Np'(repeat)' -I6 { send -N '%%' } }", + "bind -Tcopy-mode M-7 { command-prompt -P -Np'(repeat)' -I7 { send -N '%%' } }", + "bind -Tcopy-mode M-8 { command-prompt -P -Np'(repeat)' -I8 { send -N '%%' } }", + "bind -Tcopy-mode M-9 { command-prompt -P -Np'(repeat)' -I9 { send -N '%%' } }", "bind -Tcopy-mode M-< { send -X history-top }", "bind -Tcopy-mode M-> { send -X history-bottom }", "bind -Tcopy-mode M-R { send -X top-line }", @@ -599,25 +599,25 @@ key_bindings_init(void) "bind -Tcopy-mode-vi Space { send -X begin-selection }", "bind -Tcopy-mode-vi '$' { send -X end-of-line }", "bind -Tcopy-mode-vi , { send -X jump-reverse }", - "bind -Tcopy-mode-vi / { command-prompt -T search -p'(search down)' { send -X search-forward -- '%%' } }", + "bind -Tcopy-mode-vi / { command-prompt -P -T search -p'(search down)' { send -X search-forward -- '%%' } }", "bind -Tcopy-mode-vi 0 { send -X start-of-line }", - "bind -Tcopy-mode-vi 1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }", - "bind -Tcopy-mode-vi 2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }", - "bind -Tcopy-mode-vi 3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }", - "bind -Tcopy-mode-vi 4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }", - "bind -Tcopy-mode-vi 5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }", - "bind -Tcopy-mode-vi 6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }", - "bind -Tcopy-mode-vi 7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }", - "bind -Tcopy-mode-vi 8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }", - "bind -Tcopy-mode-vi 9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }", - "bind -Tcopy-mode-vi : { command-prompt -p'(goto line)' { send -X goto-line -- '%%' } }", + "bind -Tcopy-mode-vi 1 { command-prompt -P -Np'(repeat)' -I1 { send -N '%%' } }", + "bind -Tcopy-mode-vi 2 { command-prompt -P -Np'(repeat)' -I2 { send -N '%%' } }", + "bind -Tcopy-mode-vi 3 { command-prompt -P -Np'(repeat)' -I3 { send -N '%%' } }", + "bind -Tcopy-mode-vi 4 { command-prompt -P -Np'(repeat)' -I4 { send -N '%%' } }", + "bind -Tcopy-mode-vi 5 { command-prompt -P -Np'(repeat)' -I5 { send -N '%%' } }", + "bind -Tcopy-mode-vi 6 { command-prompt -P -Np'(repeat)' -I6 { send -N '%%' } }", + "bind -Tcopy-mode-vi 7 { command-prompt -P -Np'(repeat)' -I7 { send -N '%%' } }", + "bind -Tcopy-mode-vi 8 { command-prompt -P -Np'(repeat)' -I8 { send -N '%%' } }", + "bind -Tcopy-mode-vi 9 { command-prompt -P -Np'(repeat)' -I9 { send -N '%%' } }", + "bind -Tcopy-mode-vi : { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }", "bind -Tcopy-mode-vi \\; { send -X jump-again }", - "bind -Tcopy-mode-vi ? { command-prompt -T search -p'(search up)' { send -X search-backward -- '%%' } }", + "bind -Tcopy-mode-vi ? { command-prompt -P -T search -p'(search up)' { send -X search-backward -- '%%' } }", "bind -Tcopy-mode-vi A { send -X append-selection-and-cancel }", "bind -Tcopy-mode-vi B { send -X previous-space }", "bind -Tcopy-mode-vi D { send -X copy-pipe-end-of-line-and-cancel }", "bind -Tcopy-mode-vi E { send -X next-space-end }", - "bind -Tcopy-mode-vi F { command-prompt -1p'(jump backward)' { send -X jump-backward -- '%%' } }", + "bind -Tcopy-mode-vi F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }", "bind -Tcopy-mode-vi G { send -X history-bottom }", "bind -Tcopy-mode-vi H { send -X top-line }", "bind -Tcopy-mode-vi J { send -X scroll-down }", @@ -626,14 +626,14 @@ key_bindings_init(void) "bind -Tcopy-mode-vi M { send -X middle-line }", "bind -Tcopy-mode-vi N { send -X search-reverse }", "bind -Tcopy-mode-vi P { send -X toggle-position }", - "bind -Tcopy-mode-vi T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", + "bind -Tcopy-mode-vi T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", "bind -Tcopy-mode-vi V { send -X select-line }", "bind -Tcopy-mode-vi W { send -X next-space }", "bind -Tcopy-mode-vi X { send -X set-mark }", "bind -Tcopy-mode-vi ^ { send -X back-to-indentation }", "bind -Tcopy-mode-vi b { send -X previous-word }", "bind -Tcopy-mode-vi e { send -X next-word-end }", - "bind -Tcopy-mode-vi f { command-prompt -1p'(jump forward)' { send -X jump-forward -- '%%' } }", + "bind -Tcopy-mode-vi f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }", "bind -Tcopy-mode-vi g { send -X history-top }", "bind -Tcopy-mode-vi h { send -X cursor-left }", "bind -Tcopy-mode-vi j { send -X cursor-down }", @@ -644,7 +644,7 @@ key_bindings_init(void) "bind -Tcopy-mode-vi o { send -X other-end }", "bind -Tcopy-mode-vi q { send -X cancel }", "bind -Tcopy-mode-vi r { send -X refresh-toggle }", - "bind -Tcopy-mode-vi t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", + "bind -Tcopy-mode-vi t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", "bind -Tcopy-mode-vi v { send -X rectangle-toggle }", "bind -Tcopy-mode-vi w { send -X next-word }", "bind -Tcopy-mode-vi '{' { send -X previous-paragraph }", diff --git a/mode-tree.c b/mode-tree.c index 204a5e3ff..149f9412b 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -37,6 +37,7 @@ enum mode_tree_preview { }; struct mode_tree_item; +struct mode_tree_prompt; TAILQ_HEAD(mode_tree_list, mode_tree_item); struct mode_tree_data { @@ -51,11 +52,11 @@ struct mode_tree_data { struct sort_criteria sort_crit; const char *view_name; - mode_tree_build_cb buildcb; - mode_tree_draw_cb drawcb; - mode_tree_search_cb searchcb; - mode_tree_menu_cb menucb; - mode_tree_height_cb heightcb; + mode_tree_build_cb buildcb; + mode_tree_draw_cb drawcb; + mode_tree_search_cb searchcb; + mode_tree_menu_cb menucb; + mode_tree_height_cb heightcb; mode_tree_key_cb keycb; mode_tree_swap_cb swapcb; mode_tree_sort_cb sortcb; @@ -77,6 +78,10 @@ struct mode_tree_data { u_int current; struct screen screen; + struct prompt *prompt; + struct mode_tree_prompt *prompt_data; + u_int prompt_cx; + int prompt_top; int preview; char *search; @@ -124,9 +129,25 @@ struct mode_tree_menu { u_int line; }; -static void mode_tree_free_items(struct mode_tree_list *); -static void mode_tree_draw_help(struct mode_tree_data *, - struct screen_write_ctx *); +/* + * Wrapper around a prompt owned by a mode tree. The mode tree holds a reference + * while the prompt is alive; the wrapper callbacks forward to the caller's + * callbacks and drop that reference when the prompt is freed. + */ +struct mode_tree_prompt { + struct mode_tree_data *mtd; + struct client *c; + mode_tree_prompt_input_cb inputcb; + prompt_free_cb freecb; + void *data; +}; + +static void mode_tree_free_items(struct mode_tree_list *); +static void mode_tree_draw_help(struct mode_tree_data *, + struct screen_write_ctx *); +static void mode_tree_draw_prompt(struct mode_tree_data *, + struct screen_write_ctx *); +static enum cmd_retval mode_tree_prompt_accept(struct cmdq_item *, void *); static const struct menu_item mode_tree_menu_items[] = { { "Scroll Left", '<', NULL }, @@ -650,6 +671,7 @@ mode_tree_free(struct mode_tree_data *mtd) if (mtd->zoomed == 0) server_unzoom_window(wp->window); + mode_tree_clear_prompt(mtd); mode_tree_free_items(&mtd->children); mode_tree_clear_lines(mtd); screen_free(&mtd->screen); @@ -942,10 +964,148 @@ mode_tree_draw(struct mode_tree_data *mtd) done: if (mtd->help) mode_tree_draw_help(mtd, &ctx); - screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0); + if (mtd->prompt != NULL) + mode_tree_draw_prompt(mtd, &ctx); + else { + s->mode &= ~MODE_CURSOR; + screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0); + } screen_write_stop(&ctx); } +static void +mode_tree_draw_prompt(struct mode_tree_data *mtd, struct screen_write_ctx *ctx) +{ + struct screen *s = &mtd->screen; + struct prompt_draw_data pdd; + u_int sx = screen_size_x(s), sy = screen_size_y(s); + u_int py; + + if (sx == 0 || sy == 0) + return; + + if (mtd->prompt_top) + py = 0; + else + py = sy - 1; + + pdd.ctx = ctx; + pdd.cursor_x = &mtd->prompt_cx; + pdd.area_x = 0; + pdd.area_width = sx; + pdd.prompt_line = py; + + s->mode |= MODE_CURSOR; + prompt_draw(mtd->prompt, &pdd); + screen_write_cursormove(ctx, mtd->prompt_cx, py, 0); +} + +void +mode_tree_clear_prompt(struct mode_tree_data *mtd) +{ + struct prompt *prompt = mtd->prompt; + + if (mtd->prompt != NULL) { + mtd->prompt = NULL; + prompt_free(prompt); + mtd->screen.mode &= ~MODE_CURSOR; + } +} + +int +mode_tree_has_prompt(struct mode_tree_data *mtd) +{ + return (mtd->prompt != NULL); +} + +static enum cmd_retval +mode_tree_prompt_accept(struct cmdq_item *item, void *data) +{ + struct mode_tree_data *mtd = data; + struct client *c = cmdq_get_client(item); + key_code key = 'y'; + + if (mtd->prompt != NULL && c != NULL) + mode_tree_key(mtd, c, &key, NULL, NULL, NULL); + + mode_tree_remove_ref(mtd); + return (CMD_RETURN_NORMAL); +} + +static enum prompt_result +mode_tree_prompt_input_callback(void *data, const char *s, + enum prompt_key_result key) +{ + struct mode_tree_prompt *mtp = data; + + if (mtp->inputcb != NULL) + return (mtp->inputcb(mtp->c, mtp->data, s, key)); + return (PROMPT_CLOSE); +} + +static void +mode_tree_prompt_free_callback(void *data) +{ + struct mode_tree_prompt *mtp = data; + + if (mtp->mtd->prompt_data == mtp) + mtp->mtd->prompt_data = NULL; + if (mtp->freecb != NULL) + mtp->freecb(mtp->data); + mode_tree_remove_ref(mtp->mtd); + free(mtp); +} + +void +mode_tree_set_prompt(struct mode_tree_data *mtd, struct client *c, + const char *prompt, const char *input, enum prompt_type type, int flags, + mode_tree_prompt_input_cb inputcb, prompt_free_cb freecb, void *data) +{ + struct session *s; + struct options *oo; + struct prompt_create_data pd; + struct mode_tree_prompt *mtp; + + if (c != NULL && c->session != NULL) { + s = c->session; + oo = s->options; + } else { + s = NULL; + oo = global_s_options; + } + + mode_tree_clear_prompt(mtd); + + mtp = xcalloc(1, sizeof *mtp); + mtp->mtd = mtd; + mtp->inputcb = inputcb; + mtp->freecb = freecb; + mtp->data = data; + + mtd->references++; + mtd->prompt_top = options_get_number(oo, "status-position") == 0; + + memset(&pd, 0, sizeof pd); + prompt_set_options(&pd, s); + pd.prompt = prompt; + pd.input = input; + pd.type = type; + pd.flags = flags|PROMPT_ISMODE; + pd.inputcb = mode_tree_prompt_input_callback; + pd.freecb = mode_tree_prompt_free_callback; + pd.data = mtp; + mtd->prompt = prompt_create(&pd); + mtd->prompt_data = mtp; + + mode_tree_draw(mtd); + mtd->wp->flags |= PANE_REDRAW; + + if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT) && c != NULL) { + mtd->references++; + cmdq_append(c, cmdq_get_callback(mode_tree_prompt_accept, mtd)); + } +} + static struct mode_tree_item * mode_tree_search_backward(struct mode_tree_data *mtd) { @@ -1089,12 +1249,6 @@ mode_tree_search_callback(__unused struct client *c, void *data, const char *s, return (PROMPT_CLOSE); } -static void -mode_tree_search_free(void *data) -{ - mode_tree_remove_ref(data); -} - static enum prompt_result mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, enum prompt_key_result key) @@ -1120,12 +1274,6 @@ mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, return (PROMPT_CLOSE); } -static void -mode_tree_filter_free(void *data) -{ - mode_tree_remove_ref(data); -} - static void mode_tree_clear_filter(struct mode_tree_data *mtd) { @@ -1225,7 +1373,7 @@ mode_tree_draw_help(struct mode_tree_data *mtd, struct screen_write_ctx *ctx) struct grid_cell gc; const char **line, **lines = NULL, *item = "item"; u_int sx = screen_size_x(s), sy = screen_size_y(s); - u_int x, y, w, h = 0, box_w, box_h; + u_int x, y, w, h = 0, box_w, box_h; if (mtd->helpcb == NULL) w = MODE_TREE_HELP_DEFAULT_WIDTH; @@ -1275,14 +1423,66 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, { struct mode_tree_line *line; struct mode_tree_item *current, *parent, *mti; - u_int i, x, y; + u_int i, x, y, py, sx; int choice, preview; + enum prompt_key_result result; + int redraw; + struct prompt *prompt; + struct mode_tree_prompt *mtp; if (mtd->line_size == 0) { *key = KEYC_NONE; return (1); } + if (mtd->prompt != NULL) { + redraw = 0; + prompt = mtd->prompt; + + mtp = mtd->prompt_data; + if (mtp != NULL) + mtp->c = c; + if (KEYC_IS_MOUSE(*key)) { + if (m == NULL || + MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1 || + MOUSE_DRAG(m->b) || MOUSE_RELEASE(m->b) || + cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) + result = PROMPT_KEY_NOT_HANDLED; + else { + sx = screen_size_x(&mtd->screen); + if (mtd->prompt_top) + py = 0; + else + py = screen_size_y(&mtd->screen) - 1; + if (y == py) { + result = prompt_mouse(prompt, x, 0, sx, + &redraw); + } else + result = PROMPT_KEY_NOT_HANDLED; + } + } else + result = prompt_key(prompt, *key, &redraw); + if (mtd->prompt_data == mtp && mtp != NULL) + mtp->c = NULL; + + /* + * Only an explicit close or the prompt marking itself closed + * ends it; cursor movement and editing keep it open. + */ + if (mtd->prompt == prompt && + (result == PROMPT_KEY_CLOSE || prompt_closed(prompt))) + mode_tree_clear_prompt(mtd); + + if (redraw || mtd->prompt != prompt) { + mode_tree_draw(mtd); + mtd->wp->flags |= PANE_REDRAW; + } + if (result != PROMPT_KEY_NOT_HANDLED) { + *key = KEYC_NONE; + return (0); + } + } + if (mtd->help) { if (KEYC_IS_MOUSE(*key)) { *key = KEYC_NONE; @@ -1489,11 +1689,10 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, case '?': case '/': case 's'|KEYC_CTRL: - mtd->references++; mtd->search_dir = MODE_TREE_SEARCH_FORWARD; - status_prompt_set(c, NULL, "(search) ", "", - mode_tree_search_callback, mode_tree_search_free, mtd, - PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); + mode_tree_set_prompt(mtd, c, "(search) ", "", + PROMPT_TYPE_SEARCH, PROMPT_NOFORMAT, + mode_tree_search_callback, NULL, mtd); break; case 'n': mtd->search_dir = MODE_TREE_SEARCH_FORWARD; @@ -1504,12 +1703,12 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mode_tree_search_set(mtd); break; case 'f': - mtd->references++; - status_prompt_set(c, NULL, "(filter) ", mtd->filter, - mode_tree_filter_callback, mode_tree_filter_free, mtd, - PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); + mode_tree_set_prompt(mtd, c, "(filter) ", mtd->filter, + PROMPT_TYPE_SEARCH, PROMPT_NOFORMAT, + mode_tree_filter_callback, NULL, mtd); break; case 'c': + mode_tree_clear_prompt(mtd); mode_tree_clear_filter(mtd); break; case 'v': diff --git a/options-table.c b/options-table.c index ed90bd923..0d7b20c53 100644 --- a/options-table.c +++ b/options-table.c @@ -764,7 +764,9 @@ const struct options_table_entry options_table[] = { { .name = "message-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=yellow,fg=black,fill=yellow", + .default_str = "bg=yellow,fg=black," + "#{?#{m/r:(^|#,)IS(PANE|MODE)($|#,),#{prompt_flags}},," + "fill=yellow}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of messages and the command prompt. " diff --git a/prompt-history.c b/prompt-history.c new file mode 100644 index 000000000..ac99bdb95 --- /dev/null +++ b/prompt-history.c @@ -0,0 +1,264 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2026 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include + +#include "tmux.h" + +static char *prompt_find_history_file(void); +static void prompt_add_typed_history(char *); + +/* Prompt history. */ +static char **prompt_hlist[PROMPT_NTYPES]; +static u_int prompt_hsize[PROMPT_NTYPES]; + +/* Find the history file to load/save from/to. */ +static char * +prompt_find_history_file(void) +{ + const char *home, *history_file; + char *path; + + history_file = options_get_string(global_options, "history-file"); + if (*history_file == '\0') + return (NULL); + if (*history_file == '/') + return (xstrdup(history_file)); + + if (history_file[0] != '~' || history_file[1] != '/') + return (NULL); + if ((home = find_home()) == NULL) + return (NULL); + xasprintf(&path, "%s%s", home, history_file + 1); + return (path); +} + +/* Add loaded history item to the appropriate list. */ +static void +prompt_add_typed_history(char *line) +{ + char *typestr; + enum prompt_type type = PROMPT_TYPE_INVALID; + + typestr = strsep(&line, ":"); + if (line != NULL) + type = prompt_type(typestr); + if (type == PROMPT_TYPE_INVALID) { + /* + * Invalid types are not expected, but this provides backward + * compatibility with old history files. + */ + if (line != NULL) + *(--line) = ':'; + prompt_add_history(typestr, PROMPT_TYPE_COMMAND); + } else + prompt_add_history(line, type); +} + +/* Load prompt history from file. */ +void +prompt_load_history(void) +{ + FILE *f; + char *history_file, *line, *tmp; + size_t length; + + if ((history_file = prompt_find_history_file()) == NULL) + return; + log_debug("loading history from %s", history_file); + + f = fopen(history_file, "r"); + if (f == NULL) { + log_debug("%s: %s", history_file, strerror(errno)); + free(history_file); + return; + } + free(history_file); + + for (;;) { + if ((line = fgetln(f, &length)) == NULL) + break; + + if (length > 0) { + if (line[length - 1] == '\n') { + line[length - 1] = '\0'; + prompt_add_typed_history(line); + } else { + tmp = xmalloc(length + 1); + memcpy(tmp, line, length); + tmp[length] = '\0'; + prompt_add_typed_history(tmp); + free(tmp); + } + } + } + fclose(f); +} + +/* Save prompt history to file. */ +void +prompt_save_history(void) +{ + FILE *f; + u_int i, type; + char *history_file; + + if ((history_file = prompt_find_history_file()) == NULL) + return; + log_debug("saving history to %s", history_file); + + f = fopen(history_file, "w"); + if (f == NULL) { + log_debug("%s: %s", history_file, strerror(errno)); + free(history_file); + return; + } + free(history_file); + + for (type = 0; type < PROMPT_NTYPES; type++) { + for (i = 0; i < prompt_hsize[type]; i++) { + fputs(prompt_type_string(type), f); + fputc(':', f); + fputs(prompt_hlist[type][i], f); + fputc('\n', f); + } + } + fclose(f); + +} + +/* Get previous line from the history. */ +const char * +prompt_up_history(u_int *idx, u_int type) +{ + /* + * History runs from 0 to size - 1. Index is from 0 to size. Zero is + * empty. + */ + + if (type >= PROMPT_NTYPES) + return (NULL); + if (prompt_hsize[type] == 0 || idx[type] == prompt_hsize[type]) + return (NULL); + idx[type]++; + return (prompt_hlist[type][prompt_hsize[type] - idx[type]]); +} + +/* Get next line from the history. */ +const char * +prompt_down_history(u_int *idx, u_int type) +{ + if (type >= PROMPT_NTYPES) + return (""); + if (prompt_hsize[type] == 0 || idx[type] == 0) + return (""); + idx[type]--; + if (idx[type] == 0) + return (""); + return (prompt_hlist[type][prompt_hsize[type] - idx[type]]); +} + +/* Add line to the history. */ +void +prompt_add_history(const char *line, u_int type) +{ + u_int i, oldsize, newsize, freecount, hlimit, new = 1; + size_t movesize; + + if (type >= PROMPT_NTYPES) + return; + + oldsize = prompt_hsize[type]; + if (oldsize > 0 && + strcmp(prompt_hlist[type][oldsize - 1], line) == 0) + new = 0; + + hlimit = options_get_number(global_options, "prompt-history-limit"); + if (hlimit > oldsize) { + if (new == 0) + return; + newsize = oldsize + new; + } else { + newsize = hlimit; + freecount = oldsize + new - newsize; + if (freecount > oldsize) + freecount = oldsize; + if (freecount == 0) + return; + for (i = 0; i < freecount; i++) + free(prompt_hlist[type][i]); + movesize = (oldsize - freecount) * + sizeof *prompt_hlist[type]; + if (movesize > 0) { + memmove(&prompt_hlist[type][0], + &prompt_hlist[type][freecount], movesize); + } + } + + if (newsize == 0) { + free(prompt_hlist[type]); + prompt_hlist[type] = NULL; + } else if (newsize != oldsize) { + prompt_hlist[type] = + xreallocarray(prompt_hlist[type], newsize, + sizeof *prompt_hlist[type]); + } + + if (new == 1 && newsize > 0) + prompt_hlist[type][newsize - 1] = xstrdup(line); + prompt_hsize[type] = newsize; +} + +/* Get history size. */ +u_int +prompt_history_size(enum prompt_type type) +{ + if (type >= PROMPT_NTYPES) + return (0); + return (prompt_hsize[type]); +} + +/* Get history entry. */ +const char * +prompt_history_get(enum prompt_type type, u_int idx) +{ + if (type >= PROMPT_NTYPES) + return (NULL); + if (idx >= prompt_hsize[type]) + return (NULL); + return (prompt_hlist[type][idx]); +} + +/* Clear prompt history. */ +void +prompt_history_clear(enum prompt_type type) +{ + u_int idx; + + if (type >= PROMPT_NTYPES) + return; + for (idx = 0; idx < prompt_hsize[type]; idx++) + free(prompt_hlist[type][idx]); + free(prompt_hlist[type]); + prompt_hlist[type] = NULL; + prompt_hsize[type] = 0; +} diff --git a/prompt.c b/prompt.c new file mode 100644 index 000000000..54cbeb13b --- /dev/null +++ b/prompt.c @@ -0,0 +1,1595 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2026 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include + +#include "tmux.h" + +struct prompt { + char *string; + struct utf8_data *buffer; + struct cmd_find_state state; + char *last; + size_t index; + + prompt_input_cb inputcb; + prompt_free_cb freecb; + void *data; + + char *message_format; + int keys; + char *word_separators; + struct grid_cell style; + struct grid_cell command_style; + enum screen_cursor_style cstyle; + enum screen_cursor_style command_cstyle; + int ccolour; + int cmode; + int command_cmode; + + enum prompt_type type; + int flags; + int closed; + + u_int hindex[PROMPT_NTYPES]; + struct utf8_data *copied; + + char **complete_list; + u_int complete_size; + char *complete_display; +}; + +static char *prompt_complete(struct prompt *, const char *, u_int); +static void prompt_clear_complete(struct prompt *); +static char *prompt_expand(struct prompt *); +static int prompt_replace_complete(struct prompt *, const char *); +static u_int prompt_width(struct prompt *, u_int); + +/* Get prompt flags as a string. */ +static const char * +prompt_flags_to_string(int flags) +{ + static char tmp[256]; + + *tmp = '\0'; + if (flags & PROMPT_SINGLE) + strlcat(tmp, "SINGLE,", sizeof tmp); + if (flags & PROMPT_NUMERIC) + strlcat(tmp, "NUMERIC,", sizeof tmp); + if (flags & PROMPT_INCREMENTAL) + strlcat(tmp, "INCREMENTAL,", sizeof tmp); + if (flags & PROMPT_NOFORMAT) + strlcat(tmp, "NOFORMAT,", sizeof tmp); + if (flags & PROMPT_KEY) + strlcat(tmp, "KEY,", sizeof tmp); + if (flags & PROMPT_ACCEPT) + strlcat(tmp, "ACCEPT,", sizeof tmp); + if (flags & PROMPT_QUOTENEXT) + strlcat(tmp, "QUOTENEXT,", sizeof tmp); + if (flags & PROMPT_BSPACE_EXIT) + strlcat(tmp, "BSPACE_EXIT,", sizeof tmp); + if (flags & PROMPT_NOFREEZE) + strlcat(tmp, "NOFREEZE,", sizeof tmp); + if (flags & PROMPT_COMMANDMODE) + strlcat(tmp, "COMMANDMODE,", sizeof tmp); + if (flags & PROMPT_ISPANE) + strlcat(tmp, "ISPANE,", sizeof tmp); + if (flags & PROMPT_ISMODE) + strlcat(tmp, "ISMODE,", sizeof tmp); + if (*tmp != '\0') + tmp[strlen(tmp) - 1] = '\0'; + return (tmp); +} + +/* Set prompt options from session options. */ +void +prompt_set_options(struct prompt_create_data *pd, struct session *s) +{ + struct options *oo; + u_int n; + + if (s != NULL) + oo = s->options; + else + oo = global_s_options; + + style_apply(&pd->style, oo, "message-style", NULL); + style_apply(&pd->command_style, oo, "message-command-style", NULL); + n = options_get_number(oo, "prompt-cursor-style"); + screen_set_cursor_style(n, &pd->cstyle, &pd->cmode); + n = options_get_number(oo, "prompt-command-cursor-style"); + screen_set_cursor_style(n, &pd->command_cstyle, &pd->command_cmode); + pd->ccolour = options_get_number(oo, "prompt-cursor-colour"); + pd->message_format = options_get_string(oo, "message-format"); + pd->keys = options_get_number(oo, "status-keys"); + pd->word_separators = options_get_string(oo, "word-separators"); +} + +/* Create prompt. */ +struct prompt * +prompt_create(const struct prompt_create_data *pd) +{ + struct prompt *pr; + struct format_tree *ft; + const char *input = pd->input; + char *tmp; + + pr = xcalloc(1, sizeof *pr); + + if (pd->fs != NULL) { + ft = format_create_from_state(NULL, NULL, pd->fs); + cmd_find_copy_state(&pr->state, pd->fs); + } else { + ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL); + cmd_find_clear_state(&pr->state, 0); + } + + if (input == NULL) + input = ""; + pr->string = xstrdup(pd->prompt); + if (pd->flags & PROMPT_NOFORMAT) + tmp = xstrdup(input); + else + tmp = format_expand_time(ft, input); + if (pd->flags & PROMPT_INCREMENTAL) { + pr->last = xstrdup(tmp); + pr->buffer = utf8_fromcstr(""); + } else { + pr->last = NULL; + pr->buffer = utf8_fromcstr(tmp); + } + pr->index = utf8_strlen(pr->buffer); + free(tmp); + + pr->inputcb = pd->inputcb; + pr->freecb = pd->freecb; + pr->data = pd->data; + + pr->flags = pd->flags; + pr->type = pd->type; + + memcpy(&pr->style, &pd->style, sizeof pr->style); + memcpy(&pr->command_style, &pd->command_style, + sizeof pr->command_style); + pr->cstyle = pd->cstyle; + pr->command_cstyle = pd->command_cstyle; + pr->ccolour = pd->ccolour; + pr->cmode = pd->cmode; + pr->command_cmode = pd->command_cmode; + pr->message_format = xstrdup(pd->message_format); + pr->keys = pd->keys; + pr->word_separators = xstrdup(pd->word_separators); + + format_free(ft); + return (pr); +} + +/* Free prompt. */ +void +prompt_free(struct prompt *pr) +{ + if (pr != NULL) { + if (pr->freecb != NULL && pr->data != NULL) + pr->freecb(pr->data); + free(pr->message_format); + free(pr->word_separators); + free(pr->last); + free(pr->string); + free(pr->buffer); + free(pr->copied); + prompt_clear_complete(pr); + free(pr); + } +} + +/* + * Fire the input callback. Returns one if the prompt is finished or zero if + * still open. + */ +static int +prompt_fire_callback(struct prompt *pr, const char *s, + enum prompt_key_result type, int *redraw) +{ + enum prompt_result result; + + result = pr->inputcb(pr->data, s, type); + if (result == PROMPT_CLOSE) { + pr->closed = 1; + return (1); + } + if (redraw != NULL) + *redraw = 1; + return (0); +} + +/* Start incremental prompt. */ +void +prompt_incremental_start(struct prompt *pr) +{ + char *tmp, *cp; + + if (pr->flags & PROMPT_INCREMENTAL) { + tmp = utf8_tocstr(pr->buffer); + xasprintf(&cp, "=%s", tmp); + prompt_fire_callback(pr, cp, PROMPT_KEY_HANDLED, NULL); + free(cp); + free(tmp); + } +} + +/* Update prompt. */ +void +prompt_update(struct prompt *pr, const char *msg, const char *input) +{ + struct format_tree *ft; + char *tmp; + + if (cmd_find_valid_state(&pr->state)) + ft = format_create_from_state(NULL, NULL, &pr->state); + else + ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL); + + free(pr->string); + pr->string = xstrdup(msg); + + if (input == NULL) + input = ""; + free(pr->buffer); + if (pr->flags & PROMPT_NOFORMAT) + tmp = xstrdup(input); + else + tmp = format_expand_time(ft, input); + pr->buffer = utf8_fromcstr(tmp); + pr->index = utf8_strlen(pr->buffer); + free(tmp); + + memset(pr->hindex, 0, sizeof pr->hindex); + pr->closed = 0; + prompt_clear_complete(pr); + + format_free(ft); +} + +/* Is this prompt closed? */ +int +prompt_closed(struct prompt *pr) +{ + return (pr->closed); +} + +/* Redraw character. Return 1 if can continue redrawing, 0 otherwise. */ +static int +prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset, + u_int pwidth, u_int *width, struct grid_cell *gc, + const struct utf8_data *ud) +{ + u_char ch; + + if (*width < offset) { + *width += ud->width; + return (1); + } + if (*width >= offset + pwidth) + return (0); + *width += ud->width; + if (*width > offset + pwidth) + return (0); + + ch = *ud->data; + if (ud->size == 1 && (ch <= 0x1f || ch == 0x7f)) { + gc->data.data[0] = '^'; + gc->data.data[1] = (ch == 0x7f) ? '?' : ch|0x40; + gc->data.size = gc->data.have = 2; + gc->data.width = 2; + } else + utf8_copy(&gc->data, ud); + screen_write_cell(ctx, gc); + return (1); +} + +/* + * Redraw quote indicator '^' if necessary. Return 1 if can continue redrawing, + * 0 otherwise. + */ +static int +prompt_redraw_quote(const struct prompt *pr, u_int pcursor, + struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width, + struct grid_cell *gc) +{ + struct utf8_data ud; + + if (pr->flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) { + utf8_set(&ud, '^'); + return (prompt_redraw_character(ctx, offset, pwidth, + width, gc, &ud)); + } + return (1); +} + +/* Draw the stored completion matches. */ +static void +prompt_draw_complete(struct prompt *pr, struct screen_write_ctx *ctx, u_int ax, + u_int aw, u_int cx, u_int py, const struct grid_cell *base) +{ + struct grid_cell gc; + struct utf8_data *ud; + u_int avail, width, i; + + if (pr->complete_display == NULL) + return; + if (pr->index != utf8_strlen(pr->buffer)) + return; + if (cx < ax || cx - ax >= aw) + return; + avail = aw - (cx - ax); + + memcpy(&gc, base, sizeof gc); + gc.attr |= GRID_ATTR_UNDERSCORE; + screen_write_cursormove(ctx, cx, py, 0); + + width = 0; + ud = utf8_fromcstr(pr->complete_display); + for (i = 0; ud[i].size != 0; i++) { + if (width + ud[i].width > avail) + break; + utf8_copy(&gc.data, &ud[i]); + screen_write_cell(ctx, &gc); + width += ud[i].width; + } + free(ud); +} + +/* Expand prompt string using the current input. */ +static char * +prompt_expand(struct prompt *pr) +{ + struct format_tree *ft; + char *expanded, *prompt, *tmp; + + if (cmd_find_valid_state(&pr->state)) + ft = format_create_from_state(NULL, NULL, &pr->state); + else + ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL); + tmp = utf8_tocstr(pr->buffer); + format_add(ft, "prompt_input", "%s", tmp); + free(tmp); + + format_add(ft, "prompt_flags", "%s", prompt_flags_to_string(pr->flags)); + format_add(ft, "prompt_type", "%s", prompt_type_string(pr->type)); + prompt = format_expand_time(ft, pr->string); + format_add(ft, "message", "%s", prompt); + if (pr->flags & PROMPT_COMMANDMODE) + format_add(ft, "command_prompt", "1"); + else + format_add(ft, "command_prompt", "0"); + expanded = format_expand_time(ft, pr->message_format); + free(prompt); + format_free(ft); + return (expanded); +} + +/* Work out the width used by the prompt string. */ +static u_int +prompt_width(struct prompt *pr, u_int aw) +{ + char *expanded; + u_int start; + + expanded = prompt_expand(pr); + start = format_width(expanded); + if (start > aw) + start = aw; + free(expanded); + return (start); +} + +/* Choose a completion from a mouse position. */ +static enum prompt_key_result +prompt_mouse_complete(struct prompt *pr, u_int x, u_int cx, u_int ax, u_int aw, + int *redraw) +{ + char *replace; + u_int avail, clicked, end, i, start, width; + + if (pr->complete_display == NULL || pr->complete_size == 0) + return (PROMPT_KEY_NOT_HANDLED); + if (pr->index != utf8_strlen(pr->buffer)) + return (PROMPT_KEY_NOT_HANDLED); + if (cx < ax || cx - ax >= aw || x < cx) + return (PROMPT_KEY_NOT_HANDLED); + + avail = aw - (cx - ax); + clicked = x - cx; + width = utf8_cstrwidth(pr->complete_display); + if (width > avail) + width = avail; + if (clicked >= width) + return (PROMPT_KEY_NOT_HANDLED); + + end = 0; + for (i = 0; i < pr->complete_size; i++) { + start = end + 1; + end = start + utf8_cstrwidth(pr->complete_list[i]); + if (clicked < start || clicked >= end) + continue; + + xasprintf(&replace, "%s ", pr->complete_list[i]); + if (prompt_replace_complete(pr, replace)) { + prompt_clear_complete(pr); + if (redraw != NULL) + *redraw = 1; + } + free(replace); + return (PROMPT_KEY_HANDLED); + } + return (PROMPT_KEY_HANDLED); +} + +/* Draw prompt. */ +void +prompt_draw(struct prompt *pr, struct prompt_draw_data *pd) +{ + struct screen_write_ctx *ctx = pd->ctx; + struct screen *s = ctx->s; + u_int ax = pd->area_x, py = pd->prompt_line; + u_int aw = pd->area_width, *cx = pd->cursor_x; + struct grid_cell gc; + u_int i, offset, left, start, width; + u_int pcursor, pwidth; + char *expanded; + + /* Choose the cursor colour and style for this prompt. */ + if (pr->flags & PROMPT_COMMANDMODE) { + memcpy(&gc, &pr->command_style, sizeof gc); + s->default_cstyle = pr->command_cstyle; + s->default_mode = pr->command_cmode; + } else { + memcpy(&gc, &pr->style, sizeof gc); + s->default_cstyle = pr->cstyle; + s->default_mode = pr->cmode; + } + s->default_ccolour = pr->ccolour; + + expanded = prompt_expand(pr); + start = format_width(expanded); + if (start > aw) + start = aw; + *cx = ax + start; + + screen_write_cursormove(ctx, ax, py, 0); + format_draw(ctx, &gc, aw, expanded, NULL, 0); + screen_write_cursormove(ctx, ax + start, py, 0); + free(expanded); + + left = aw - start; + if (left == 0) + return; + + pcursor = utf8_strwidth(pr->buffer, pr->index); + pwidth = utf8_strwidth(pr->buffer, -1); + if (pr->flags & PROMPT_QUOTENEXT) + pwidth++; + if (pcursor >= left) { + /* + * The cursor would be outside the screen so start drawing + * with it on the right. + */ + offset = (pcursor - left) + 1; + pwidth = left; + } else + offset = 0; + if (pwidth > left) + pwidth = left; + *cx = ax + start + pcursor - offset; + + width = 0; + for (i = 0; pr->buffer[i].size != 0; i++) { + if (!prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth, + &width, &gc)) + break; + if (!prompt_redraw_character(ctx, offset, pwidth, &width, &gc, + &pr->buffer[i])) + break; + } + prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth, &width, &gc); + + prompt_draw_complete(pr, ctx, ax, aw, *cx, py, &gc); +} + +/* Move cursor in prompt from a mouse position. */ +enum prompt_key_result +prompt_mouse(struct prompt *pr, u_int x, u_int ax, u_int aw, int *redraw) +{ + struct utf8_data *ud; + enum prompt_key_result result; + u_int cx, start, left, pcursor, pwidth, offset, width; + u_int target; + size_t idx; + + if (x < ax || x >= ax + aw) + return (PROMPT_KEY_NOT_HANDLED); + if (pr->flags & PROMPT_INCREMENTAL) + return (PROMPT_KEY_HANDLED); + + start = prompt_width(pr, aw); + left = aw - start; + if (left == 0) + return (PROMPT_KEY_HANDLED); + + pcursor = utf8_strwidth(pr->buffer, pr->index); + pwidth = utf8_strwidth(pr->buffer, -1); + if (pr->flags & PROMPT_QUOTENEXT) + pwidth++; + if (pcursor >= left) + offset = (pcursor - left) + 1; + else + offset = 0; + + cx = ax + start + pcursor - offset; + result = prompt_mouse_complete(pr, x, cx, ax, aw, redraw); + if (result != PROMPT_KEY_NOT_HANDLED) + return (result); + + if (x <= ax + start) + target = offset; + else + target = offset + x - (ax + start); + if (target > pwidth) + target = pwidth; + + width = 0; + for (idx = 0; pr->buffer[idx].size != 0; idx++) { + ud = &pr->buffer[idx]; + if (width >= target) + break; + width += ud->width; + } + if (idx == pr->index) + return (PROMPT_KEY_HANDLED); + + pr->index = idx; + prompt_clear_complete(pr); + if (redraw != NULL) + *redraw = 1; + + return (PROMPT_KEY_HANDLED); +} + +/* Is this a separator? */ +static int +prompt_in_list(const char *ws, const struct utf8_data *ud) +{ + if (ud->size != 1 || ud->width != 1) + return (0); + return (strchr(ws, *ud->data) != NULL); +} + +/* Is this a space? */ +static int +prompt_space(const struct utf8_data *ud) +{ + if (ud->size != 1 || ud->width != 1) + return (0); + return (*ud->data == ' '); +} + +/* Is this a keypad key? */ +static key_code +prompt_keypad_key(key_code key) +{ + if (key & KEYC_MASK_MODIFIERS) + return (key); + + switch (key) { + case KEYC_KP_SLASH: + return ('/'); + case KEYC_KP_STAR: + return ('*'); + case KEYC_KP_MINUS: + return ('-'); + case KEYC_KP_SEVEN: + return ('7'); + case KEYC_KP_EIGHT: + return ('8'); + case KEYC_KP_NINE: + return ('9'); + case KEYC_KP_PLUS: + return ('+'); + case KEYC_KP_FOUR: + return ('4'); + case KEYC_KP_FIVE: + return ('5'); + case KEYC_KP_SIX: + return ('6'); + case KEYC_KP_ONE: + return ('1'); + case KEYC_KP_TWO: + return ('2'); + case KEYC_KP_THREE: + return ('3'); + case KEYC_KP_ENTER: + return ('\r'); + case KEYC_KP_ZERO: + return ('0'); + case KEYC_KP_PERIOD: + return ('.'); + } + return (key); +} + +/* + * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key + * as an emacs key; return 2 to append to the buffer. Set *redraw if the + * translation changed something the host needs to redraw (such as switching + * between insert and command mode). + */ +static int +prompt_translate_key(struct prompt *pr, key_code key, key_code *new_key, + int *redraw) +{ + if (~pr->flags & PROMPT_COMMANDMODE) { + switch (key) { + case 'a'|KEYC_CTRL: + case 'c'|KEYC_CTRL: + case 'e'|KEYC_CTRL: + case 'g'|KEYC_CTRL: + case 'h'|KEYC_CTRL: + case '\011': /* Tab */ + case 'k'|KEYC_CTRL: + case 'n'|KEYC_CTRL: + case 'p'|KEYC_CTRL: + case 't'|KEYC_CTRL: + case 'u'|KEYC_CTRL: + case 'v'|KEYC_CTRL: + case 'w'|KEYC_CTRL: + case 'y'|KEYC_CTRL: + case '\n': + case '\r': + case KEYC_LEFT|KEYC_CTRL: + case KEYC_RIGHT|KEYC_CTRL: + case KEYC_BSPACE: + case KEYC_DC: + case KEYC_DOWN: + case KEYC_END: + case KEYC_HOME: + case KEYC_LEFT: + case KEYC_RIGHT: + case KEYC_UP: + *new_key = key; + return (1); + case '\033': /* Escape */ + case '['|KEYC_CTRL: + pr->flags |= PROMPT_COMMANDMODE; + if (pr->index != 0) + pr->index--; + *redraw = 1; + return (0); + } + *new_key = key; + return (2); + } + + switch (key) { + case KEYC_BSPACE: + *new_key = KEYC_LEFT; + return (1); + case 'A': + case 'I': + case 'C': + case 's': + case 'a': + pr->flags &= ~PROMPT_COMMANDMODE; + *redraw = 1; + break; /* switch mode and... */ + case 'S': + pr->flags &= ~PROMPT_COMMANDMODE; + *redraw = 1; + *new_key = 'u'|KEYC_CTRL; + return (1); + case 'i': + pr->flags &= ~PROMPT_COMMANDMODE; + *redraw = 1; + return (0); + case '\033': /* Escape */ + case '['|KEYC_CTRL: + return (0); + } + + switch (key) { + case 'A': + case '$': + *new_key = KEYC_END; + return (1); + case 'I': + case '0': + case '^': + *new_key = KEYC_HOME; + return (1); + case 'C': + case 'D': + *new_key = 'k'|KEYC_CTRL; + return (1); + case KEYC_BSPACE: + case 'X': + *new_key = KEYC_BSPACE; + return (1); + case 'b': + *new_key = 'b'|KEYC_META; + return (1); + case 'B': + *new_key = 'B'|KEYC_VI; + return (1); + case 'd': + *new_key = 'u'|KEYC_CTRL; + return (1); + case 'e': + *new_key = 'e'|KEYC_VI; + return (1); + case 'E': + *new_key = 'E'|KEYC_VI; + return (1); + case 'w': + *new_key = 'w'|KEYC_VI; + return (1); + case 'W': + *new_key = 'W'|KEYC_VI; + return (1); + case 'p': + *new_key = 'y'|KEYC_CTRL; + return (1); + case 'q': + *new_key = 'c'|KEYC_CTRL; + return (1); + case 's': + case KEYC_DC: + case 'x': + *new_key = KEYC_DC; + return (1); + case KEYC_DOWN: + case 'j': + *new_key = KEYC_DOWN; + return (1); + case KEYC_LEFT: + case 'h': + *new_key = KEYC_LEFT; + return (1); + case 'a': + case KEYC_RIGHT: + case 'l': + *new_key = KEYC_RIGHT; + return (1); + case KEYC_UP: + case 'k': + *new_key = KEYC_UP; + return (1); + case 'h'|KEYC_CTRL: + case 'c'|KEYC_CTRL: + case '\n': + case '\r': + return (1); + } + return (0); +} + +/* Paste into prompt. */ +static int +prompt_paste(struct prompt *pr) +{ + struct paste_buffer *pb; + const char *bufdata; + size_t size, n, bufsize; + u_int i; + struct utf8_data *ud, *udp; + enum utf8_state more; + + size = utf8_strlen(pr->buffer); + if (pr->copied != NULL) { + ud = pr->copied; + n = utf8_strlen(pr->copied); + } else { + if ((pb = paste_get_top(NULL)) == NULL) + return (0); + bufdata = paste_buffer_data(pb, &bufsize); + ud = udp = xreallocarray(NULL, bufsize + 1, sizeof *ud); + for (i = 0; i != bufsize; /* nothing */) { + more = utf8_open(udp, bufdata[i]); + if (more == UTF8_MORE) { + while (++i != bufsize && more == UTF8_MORE) + more = utf8_append(udp, bufdata[i]); + if (more == UTF8_DONE) { + udp++; + continue; + } + i -= udp->have; + } + if (bufdata[i] <= 31 || bufdata[i] >= 127) + break; + utf8_set(udp, bufdata[i]); + udp++; + i++; + } + udp->size = 0; + n = udp - ud; + } + if (n != 0) { + pr->buffer = xreallocarray(pr->buffer, size + n + 1, + sizeof *pr->buffer); + if (pr->index == size) { + memcpy(pr->buffer + pr->index, ud, + n * sizeof *pr->buffer); + pr->index += n; + pr->buffer[pr->index].size = 0; + } else { + memmove(pr->buffer + pr->index + n, + pr->buffer + pr->index, + (size + 1 - pr->index) * + sizeof *pr->buffer); + memcpy(pr->buffer + pr->index, ud, + n * sizeof *pr->buffer); + pr->index += n; + } + } + if (ud != pr->copied) + free(ud); + return (1); +} + +/* Finish completion. */ +static int +prompt_replace_complete(struct prompt *pr, const char *s) +{ + char word[64], *allocated = NULL; + size_t size, n, off, idx, used; + struct utf8_data *first, *last, *ud; + + /* Work out where the cursor currently is. */ + idx = pr->index; + if (idx != 0) + idx--; + size = utf8_strlen(pr->buffer); + + /* Find the word we are in. */ + first = &pr->buffer[idx]; + while (first > pr->buffer && !prompt_space(first)) + first--; + while (first->size != 0 && prompt_space(first)) + first++; + last = &pr->buffer[idx]; + while (last->size != 0 && !prompt_space(last)) + last++; + while (last > pr->buffer && prompt_space(last)) + last--; + if (last->size != 0) + last++; + if (last < first) + return (0); + if (s == NULL) { + used = 0; + for (ud = first; ud < last; ud++) { + if (used + ud->size >= sizeof word) + break; + memcpy(word + used, ud->data, ud->size); + used += ud->size; + } + if (ud != last) + return (0); + word[used] = '\0'; + } + + /* Try to complete it. */ + if (s == NULL) { + allocated = prompt_complete(pr, word, first - pr->buffer); + if (allocated == NULL) + return (0); + s = allocated; + } + + /* Trim out word. */ + n = size - (last - pr->buffer) + 1; /* with \0 */ + memmove(first, last, n * sizeof *pr->buffer); + size -= last - first; + + /* Insert the new word. */ + size += strlen(s); + off = first - pr->buffer; + pr->buffer = xreallocarray(pr->buffer, size + 1, + sizeof *pr->buffer); + first = pr->buffer + off; + memmove(first + strlen(s), first, n * sizeof *pr->buffer); + for (idx = 0; idx < strlen(s); idx++) + utf8_set(&first[idx], s[idx]); + pr->index = (first - pr->buffer) + strlen(s); + + free(allocated); + return (1); +} + +/* Prompt forward to the next beginning of a word. */ +static void +prompt_forward_word(struct prompt *pr, size_t size, int vi, + const char *separators) +{ + size_t idx = pr->index; + int word_is_separators; + + /* In emacs mode, skip until the first non-whitespace character. */ + if (!vi) { + while (idx != size && prompt_space(&pr->buffer[idx])) + idx++; + } + + /* Can't move forward if we're already at the end. */ + if (idx == size) { + pr->index = idx; + return; + } + + /* Determine the current character class (separators or not). */ + word_is_separators = prompt_in_list(separators, &pr->buffer[idx]) && + !prompt_space(&pr->buffer[idx]); + + /* Skip ahead until the first space or opposite character class. */ + do { + idx++; + if (prompt_space(&pr->buffer[idx])) { + /* In vi mode, go to the start of the next word. */ + if (vi) { + while (idx != size && + prompt_space(&pr->buffer[idx])) + idx++; + } + break; + } + } while (idx != size && word_is_separators == prompt_in_list( + separators, &pr->buffer[idx])); + + pr->index = idx; +} + +/* Prompt forward to the next end of a word. */ +static void +prompt_end_word(struct prompt *pr, size_t size, const char *separators) +{ + size_t idx = pr->index; + int word_is_separators; + + /* Can't move forward if we're already at the end. */ + if (idx == size) + return; + + /* Find the next word. */ + do { + idx++; + if (idx == size) { + pr->index = idx; + return; + } + } while (prompt_space(&pr->buffer[idx])); + + /* Determine the character class (separators or not). */ + word_is_separators = prompt_in_list(separators, + &pr->buffer[idx]); + + /* Skip ahead until the next space or opposite character class. */ + do { + idx++; + if (idx == size) + break; + } while (!prompt_space(&pr->buffer[idx]) && + word_is_separators == prompt_in_list(separators, &pr->buffer[idx])); + + /* Back up to the previous character to stop at the end of the word. */ + pr->index = idx - 1; +} + +/* Prompt backward to the previous beginning of a word. */ +static void +prompt_backward_word(struct prompt *pr, const char *separators) +{ + size_t idx = pr->index; + int word_is_separators; + + /* Find non-whitespace. */ + while (idx != 0) { + --idx; + if (!prompt_space(&pr->buffer[idx])) + break; + } + word_is_separators = prompt_in_list(separators, + &pr->buffer[idx]); + + /* Find the character before the beginning of the word. */ + while (idx != 0) { + --idx; + if (prompt_space(&pr->buffer[idx]) || + word_is_separators != prompt_in_list(separators, + &pr->buffer[idx])) { + /* Go back to the word. */ + idx++; + break; + } + } + pr->index = idx; +} + +/* Fire input callback when done. */ +static enum prompt_key_result +prompt_done(struct prompt *pr, const char *s, int *redraw) +{ + if (prompt_fire_callback(pr, s, PROMPT_KEY_CLOSE, redraw)) + return (PROMPT_KEY_CLOSE); + return (PROMPT_KEY_HANDLED); +} + +/* Check for a movement key. */ +static enum prompt_key_result +prompt_check_move(struct prompt *pr, key_code key) +{ + char *s; + + if (~pr->flags & PROMPT_INCREMENTAL) + return (PROMPT_KEY_NOT_HANDLED); + switch (key) { + case KEYC_UP: + case KEYC_DOWN: + case KEYC_LEFT: + case KEYC_RIGHT: + case KEYC_PPAGE: + case KEYC_NPAGE: + break; + default: + return (PROMPT_KEY_NOT_HANDLED); + } + s = utf8_tocstr(pr->buffer); + if (prompt_fire_callback(pr, s, PROMPT_KEY_MOVE, NULL)) { + free(s); + return (PROMPT_KEY_CLOSE); + } + free(s); + return (PROMPT_KEY_MOVE); +} + +/* Handle keys in prompt. */ +enum prompt_key_result +prompt_key(struct prompt *pr, key_code key, int *redraw) +{ + char *s, *cp, prefix = '='; + const char *histstr, *ks; + size_t size, idx; + struct utf8_data tmp; + enum prompt_key_result result = PROMPT_KEY_HANDLED; + int word_is_separators; + + pr->closed = 0; + + /* + * Drop any inline completion matches; the Tab handler rebuilds them if + * completion is still applicable. + */ + prompt_clear_complete(pr); + + if (pr->flags & PROMPT_KEY) { + ks = key_string_lookup_key(key, 0); + if (!prompt_fire_callback(pr, ks, PROMPT_KEY_CLOSE, NULL)) + pr->closed = 1; + return (PROMPT_KEY_CLOSE); + } + size = utf8_strlen(pr->buffer); + + key &= ~KEYC_MASK_FLAGS; + key = prompt_keypad_key(key); + + if (pr->flags & PROMPT_NUMERIC) { + if (key >= '0' && key <= '9') + goto append_key; + s = utf8_tocstr(pr->buffer); + if (!prompt_fire_callback(pr, s, PROMPT_KEY_CLOSE, NULL)) + pr->closed = 1; + free(s); + return (PROMPT_KEY_NOT_HANDLED); + } + + if (pr->flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) { + if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) + key = 0x7f; + else if ((key & KEYC_MASK_KEY) > 0x7f) { + if (!KEYC_IS_UNICODE(key)) + return (PROMPT_KEY_HANDLED); + key &= KEYC_MASK_KEY; + } else + key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY; + pr->flags &= ~PROMPT_QUOTENEXT; + goto append_key; + } + + if (pr->keys == MODEKEY_VI) { + switch (prompt_translate_key(pr, key, &key, redraw)) { + case 1: + goto process_key; + case 2: + goto append_key; + default: + return (PROMPT_KEY_HANDLED); + } + } + +process_key: + result = prompt_check_move(pr, key); + if (result != PROMPT_KEY_NOT_HANDLED) + return (result); + result = PROMPT_KEY_HANDLED; + + switch (key) { + case KEYC_LEFT: + case 'b'|KEYC_CTRL: + if (pr->index > 0) { + pr->index--; + break; + } + break; + case KEYC_RIGHT: + case 'f'|KEYC_CTRL: + if (pr->index < size) { + pr->index++; + break; + } + break; + case KEYC_HOME: + case 'a'|KEYC_CTRL: + if (pr->index != 0) { + pr->index = 0; + break; + } + break; + case KEYC_END: + case 'e'|KEYC_CTRL: + if (pr->index != size) { + pr->index = size; + break; + } + break; + case '\011': /* Tab */ + if (prompt_replace_complete(pr, NULL)) + goto changed; + break; + case KEYC_BSPACE: + case 'h'|KEYC_CTRL: + if (pr->flags & PROMPT_BSPACE_EXIT && size == 0) + return (prompt_done(pr, NULL, redraw)); + if (pr->index != 0) { + if (pr->index == size) + pr->buffer[--pr->index].size = 0; + else { + memmove(pr->buffer + pr->index - 1, + pr->buffer + pr->index, + (size + 1 - pr->index) * + sizeof *pr->buffer); + pr->index--; + } + goto changed; + } + break; + case KEYC_DC: + case 'd'|KEYC_CTRL: + if (pr->index != size) { + memmove(pr->buffer + pr->index, + pr->buffer + pr->index + 1, + (size + 1 - pr->index) * + sizeof *pr->buffer); + goto changed; + } + break; + case 'u'|KEYC_CTRL: + pr->buffer[0].size = 0; + pr->index = 0; + goto changed; + case 'k'|KEYC_CTRL: + if (pr->index < size) { + pr->buffer[pr->index].size = 0; + goto changed; + } + break; + case 'w'|KEYC_CTRL: + /* Find non-whitespace. */ + idx = pr->index; + while (idx != 0) { + idx--; + if (!prompt_space(&pr->buffer[idx])) + break; + } + word_is_separators = prompt_in_list(pr->word_separators, + &pr->buffer[idx]); + + /* Find the character before the beginning of the word. */ + while (idx != 0) { + idx--; + if (prompt_space(&pr->buffer[idx]) || + word_is_separators != prompt_in_list( + pr->word_separators, &pr->buffer[idx])) { + /* Go back to the word. */ + idx++; + break; + } + } + + free(pr->copied); + pr->copied = xcalloc(sizeof *pr->buffer, + (pr->index - idx) + 1); + memcpy(pr->copied, pr->buffer + idx, + (pr->index - idx) * sizeof *pr->buffer); + + memmove(pr->buffer + idx, pr->buffer + pr->index, + (size + 1 - pr->index) * sizeof *pr->buffer); + memset(pr->buffer + size - (pr->index - idx), '\0', + (pr->index - idx) * sizeof *pr->buffer); + pr->index = idx; + + goto changed; + case KEYC_RIGHT|KEYC_CTRL: + case 'f'|KEYC_META: + prompt_forward_word(pr, size, 0, pr->word_separators); + goto changed; + case 'E'|KEYC_VI: + prompt_end_word(pr, size, ""); + goto changed; + case 'e'|KEYC_VI: + prompt_end_word(pr, size, pr->word_separators); + goto changed; + case 'W'|KEYC_VI: + prompt_forward_word(pr, size, 1, ""); + goto changed; + case 'w'|KEYC_VI: + prompt_forward_word(pr, size, 1, pr->word_separators); + goto changed; + case 'B'|KEYC_VI: + prompt_backward_word(pr, ""); + goto changed; + case KEYC_LEFT|KEYC_CTRL: + case 'b'|KEYC_META: + prompt_backward_word(pr, pr->word_separators); + goto changed; + case KEYC_UP: + case 'p'|KEYC_CTRL: + histstr = prompt_up_history(pr->hindex, + pr->type); + if (histstr == NULL) + break; + free(pr->buffer); + pr->buffer = utf8_fromcstr(histstr); + pr->index = utf8_strlen(pr->buffer); + goto changed; + case KEYC_DOWN: + case 'n'|KEYC_CTRL: + histstr = prompt_down_history(pr->hindex, pr->type); + if (histstr == NULL) + break; + free(pr->buffer); + pr->buffer = utf8_fromcstr(histstr); + pr->index = utf8_strlen(pr->buffer); + goto changed; + case 'y'|KEYC_CTRL: + if (prompt_paste(pr)) + goto changed; + break; + case 't'|KEYC_CTRL: + idx = pr->index; + if (idx < size) + idx++; + if (idx >= 2) { + utf8_copy(&tmp, &pr->buffer[idx - 2]); + utf8_copy(&pr->buffer[idx - 2], &pr->buffer[idx - 1]); + utf8_copy(&pr->buffer[idx - 1], &tmp); + pr->index = idx; + goto changed; + } + break; + case '\r': + case '\n': + s = utf8_tocstr(pr->buffer); + if (*s != '\0') + prompt_add_history(s, pr->type); + result = prompt_done(pr, s, redraw); + free(s); + return (result); + case '\033': /* Escape */ + case '['|KEYC_CTRL: + case 'c'|KEYC_CTRL: + case 'g'|KEYC_CTRL: + return (prompt_done(pr, NULL, redraw)); + case 'r'|KEYC_CTRL: + if (~pr->flags & PROMPT_INCREMENTAL) + break; + if (pr->buffer[0].size == 0) { + prefix = '='; + free(pr->buffer); + pr->buffer = utf8_fromcstr(pr->last); + pr->index = utf8_strlen(pr->buffer); + } else + prefix = '-'; + goto changed; + case 's'|KEYC_CTRL: + if (~pr->flags & PROMPT_INCREMENTAL) + break; + if (pr->buffer[0].size == 0) { + prefix = '='; + free(pr->buffer); + pr->buffer = utf8_fromcstr(pr->last); + pr->index = utf8_strlen(pr->buffer); + } else + prefix = '+'; + goto changed; + case 'v'|KEYC_CTRL: + pr->flags |= PROMPT_QUOTENEXT; + break; + default: + goto append_key; + } + + *redraw = 1; + return (PROMPT_KEY_HANDLED); + +append_key: + if (key <= 0x7f) { + utf8_set(&tmp, key); + if (key <= 0x1f || key == 0x7f) + tmp.width = 2; + } else if (KEYC_IS_UNICODE(key)) + utf8_to_data(key, &tmp); + else + return (PROMPT_KEY_HANDLED); + + pr->buffer = xreallocarray(pr->buffer, size + 2, + sizeof *pr->buffer); + + if (pr->index == size) { + utf8_copy(&pr->buffer[pr->index], &tmp); + pr->index++; + pr->buffer[pr->index].size = 0; + } else { + memmove(pr->buffer + pr->index + 1, + pr->buffer + pr->index, + (size + 1 - pr->index) * + sizeof *pr->buffer); + utf8_copy(&pr->buffer[pr->index], &tmp); + pr->index++; + } + + if (pr->flags & PROMPT_SINGLE) { + if (utf8_strlen(pr->buffer) != 1) { + pr->closed = 1; + result = PROMPT_KEY_CLOSE; + } else { + s = utf8_tocstr(pr->buffer); + result = prompt_done(pr, s, redraw); + free(s); + } + } + +changed: + *redraw = 1; + if (pr->flags & PROMPT_INCREMENTAL) { + s = utf8_tocstr(pr->buffer); + xasprintf(&cp, "%c%s", prefix, s); + prompt_fire_callback(pr, cp, PROMPT_KEY_HANDLED, NULL); + free(cp); + free(s); + } + return (result); +} + +/* Add to completion list. */ +static void +prompt_complete_add(char ***list, u_int *size, const char *s) +{ + u_int i; + + for (i = 0; i < *size; i++) { + if (strcmp((*list)[i], s) == 0) + return; + } + *list = xreallocarray(*list, (*size) + 1, sizeof **list); + (*list)[(*size)++] = xstrdup(s); +} + +/* Build completion list. */ +static char ** +prompt_complete_commands(u_int *size, const char *s) +{ + char **list = NULL, *tmp; + const char *value, *cp; + const struct cmd_entry **cmdent; + size_t slen = strlen(s), valuelen; + struct options_entry *o; + struct options_array_item *a; + + *size = 0; + for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { + if (strncmp((*cmdent)->name, s, slen) == 0) + prompt_complete_add(&list, size, (*cmdent)->name); + } + o = options_get_only(global_options, "command-alias"); + if (o != NULL) { + a = options_array_first(o); + while (a != NULL) { + value = options_array_item_value(a)->string; + if ((cp = strchr(value, '=')) == NULL) + goto next; + valuelen = cp - value; + if (slen > valuelen || strncmp(value, s, slen) != 0) + goto next; + + xasprintf(&tmp, "%.*s", (int)valuelen, value); + prompt_complete_add(&list, size, tmp); + free(tmp); + + next: + a = options_array_next(a); + } + } + return (list); +} + +/* Find longest prefix. */ +static char * +prompt_complete_prefix(char **list, u_int size) +{ + char *out; + u_int i; + size_t j; + + if (list == NULL || size == 0) + return (NULL); + out = xstrdup(list[0]); + for (i = 1; i < size; i++) { + for (j = 0; out[j] != '\0' && list[i][j] != '\0'; j++) { + if (out[j] != list[i][j]) + break; + } + out[j] = '\0'; + } + return (out); +} + +/* Sort complete list. */ +static int +prompt_complete_sort(const void *a, const void *b) +{ + const char **aa = (const char **)a, **bb = (const char **)b; + + return (strcmp(*aa, *bb)); +} + +/* Free the stored inline completion matches. */ +static void +prompt_clear_complete(struct prompt *pr) +{ + u_int i; + + for (i = 0; i < pr->complete_size; i++) + free(pr->complete_list[i]); + free(pr->complete_list); + pr->complete_list = NULL; + pr->complete_size = 0; + + free(pr->complete_display); + pr->complete_display = NULL; +} + +/* + * Store the match list for inline display and build the dim suffix string: a + * leading space then the matches separated by spaces. + */ +static void +prompt_store_complete(struct prompt *pr, char **list, u_int size) +{ + char *display, *cp; + u_int i; + + prompt_clear_complete(pr); + pr->complete_list = list; + pr->complete_size = size; + + display = xstrdup(""); + for (i = 0; i < size; i++) { + xasprintf(&cp, "%s %s", display, list[i]); + free(display); + display = cp; + } + pr->complete_display = display; +} + +/* + * Complete word. Returns the text to insert when a unique match or a longer + * common prefix is available; otherwise stores the match list for inline + * display (and returns NULL) or returns NULL if there is nothing to do. + */ +static char * +prompt_complete(struct prompt *pr, const char *word, u_int offset) +{ + char **list = NULL, *out = NULL; + u_int size = 0, i; + + if (pr->type != PROMPT_TYPE_COMMAND || offset != 0 || + *word == '\0') + return (NULL); + + list = prompt_complete_commands(&size, word); + if (size == 0) { + free(list); + return (NULL); + } + qsort(list, size, sizeof *list, prompt_complete_sort); + for (i = 0; i < size; i++) + log_debug("complete %u: %s", i, list[i]); + + if (size == 1) + xasprintf(&out, "%s ", list[0]); + else + out = prompt_complete_prefix(list, size); + if (out != NULL && strcmp(word, out) == 0) { + free(out); + out = NULL; + } + + if (out != NULL || size <= 1) { + /* Inserting (or nothing to show): drop the list. */ + for (i = 0; i < size; i++) + free(list[i]); + free(list); + return (out); + } + + /* Multiple matches but nothing to insert: keep them for redraw. */ + prompt_store_complete(pr, list, size); + return (NULL); +} + + +/* Return the type of the prompt as an enum. */ +enum prompt_type +prompt_type(const char *type) +{ + u_int i; + + for (i = 0; i < PROMPT_NTYPES; i++) { + if (strcmp(type, prompt_type_string(i)) == 0) + return (i); + } + return (PROMPT_TYPE_INVALID); +} + +/* Get prompt type as a string. */ +const char * +prompt_type_string(enum prompt_type type) +{ + switch (type) { + case PROMPT_TYPE_COMMAND: + return ("command"); + case PROMPT_TYPE_SEARCH: + return ("search"); + case PROMPT_TYPE_INVALID: + return ("invalid"); + } + return ("unknown"); +} diff --git a/screen-redraw.c b/screen-redraw.c index 24072ea8c..e8dd8ec0f 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1505,6 +1505,62 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx, dctx->flags |= REDRAW_ISOLATES; } +/* Draw a pane's prompt over its content. */ +static void +redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp) +{ + struct redraw_scene *scene = dctx->scene; + struct client *c = scene->c; + struct tty *tty = &c->tty; + struct screen screen; + struct screen_write_ctx ctx; + struct prompt_draw_data pdd; + int ox = scene->ox, oy = scene->oy; + int sx = scene->sx, sy = scene->sy; + int line, cy, px, offset, width, wy; + + if (wp->prompt == NULL || wp->sx == 0 || wp->sy == 0) + return; + + if (~dctx->flags & REDRAW_STATUS_TOP) + wy = wp->yoff + (int)wp->sy - 1; + else + wy = wp->yoff; + if (wy < oy || wy >= oy + sy) + return; + line = wy - oy; + if (dctx->flags & REDRAW_STATUS_TOP) + cy = dctx->status_lines + line; + else + cy = line; + + if (wp->xoff + (int)wp->sx <= ox || wp->xoff >= ox + sx) + return; + if (wp->xoff < ox) { + offset = ox - wp->xoff; + px = 0; + } else { + offset = 0; + px = wp->xoff - ox; + } + width = wp->sx - offset; + if (px + width > sx) + width = sx - px; + + screen_init(&screen, wp->sx, 1, 0); + screen_write_start(&ctx, &screen); + pdd.ctx = &ctx; + pdd.cursor_x = &wp->prompt_cx; + pdd.area_x = 0; + pdd.area_width = wp->sx; + pdd.prompt_line = 0; + prompt_draw(wp->prompt, &pdd); + screen_write_stop(&ctx); + + tty_draw_line(tty, &screen, 0, offset, width, px, cy, NULL); + screen_free(&screen); +} + /* Draw scene to client. */ static void redraw_draw(struct client *c, struct window_pane *wp, int flags) @@ -1526,7 +1582,7 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) if (flags & REDRAW_STATUS) { if (c->message_string != NULL) redraw = status_message_redraw(c); - else if (c->prompt_string != NULL) + else if (c->prompt != NULL) redraw = status_prompt_redraw(c); else redraw = status_redraw(c); @@ -1600,9 +1656,20 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) else redraw_draw_lines(&dctx, flags); + if (flags & REDRAW_PANE) { + if (wp != NULL) + redraw_draw_pane_prompt(&dctx, wp); + else { + TAILQ_FOREACH(loop, &scene->w->panes, entry) { + if (window_pane_is_visible(loop)) + redraw_draw_pane_prompt(&dctx, loop); + } + } + } + if (flags & REDRAW_STATUS) { lines = dctx.status_lines; - if (c->message_string != NULL || c->prompt_string != NULL) + if (c->message_string != NULL || c->prompt != NULL) lines = (lines == 0 ? 1 : lines); if (dctx.flags & REDRAW_STATUS_TOP) y = 0; diff --git a/server-client.c b/server-client.c index a0ffcc215..60fdab2a6 100644 --- a/server-client.c +++ b/server-client.c @@ -489,10 +489,7 @@ server_client_lost(struct client *c) free(c->message_string); if (event_initialized(&c->message_timer)) evtimer_del(&c->message_timer); - - free(c->prompt_saved); - free(c->prompt_string); - free(c->prompt_buffer); + prompt_free(c->prompt); format_lost_client(c); environ_free(c->environ); @@ -1464,6 +1461,7 @@ server_client_handle_key0(struct client *c, struct key_event *event, { struct session *s = c->session; struct cmdq_item *item; + struct window_pane *wp; /* Check the client is good to accept input. */ if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) @@ -1493,6 +1491,7 @@ server_client_handle_key0(struct client *c, struct key_event *event, return (0); status_message_clear(c); } + if (c->overlay_key != NULL) { switch (c->overlay_key(c, c->overlay_data, event)) { case 0: @@ -1502,9 +1501,10 @@ server_client_handle_key0(struct client *c, struct key_event *event, return (0); } } + server_client_clear_overlay(c); - if (c->prompt_string != NULL) { - switch (status_prompt_key(c, event->key)) { + if (c->prompt != NULL) { + switch (status_prompt_key(c, event->key, &event->m)) { case PROMPT_KEY_HANDLED: case PROMPT_KEY_CLOSE: return (0); @@ -1513,6 +1513,29 @@ server_client_handle_key0(struct client *c, struct key_event *event, break; } } + + wp = s->curw->window->active; + if (wp == NULL || !window_pane_has_prompt(wp)) { + TAILQ_FOREACH(wp, &s->curw->window->panes, entry) { + if (window_pane_has_prompt(wp) && + window_pane_is_visible(wp)) + break; + } + } + if (wp != NULL && + window_pane_has_prompt(wp) && + window_pane_is_visible(wp)) { + switch (window_pane_prompt_key(wp, c, event->key, &event->m)) { + case PROMPT_KEY_HANDLED: + case PROMPT_KEY_CLOSE: + case PROMPT_KEY_MOVE: + return (0); + case PROMPT_KEY_NOT_HANDLED: + if (KEYC_IS_MOUSE(event->key)) + return (0); + break; + } + } } /* @@ -1784,6 +1807,42 @@ out: bufferevent_enable(wp->event, EV_READ); } +/* Move cursor for pane prompt. */ +static int +server_client_prompt_cursor(struct client *c, struct window_pane *wp, int *mode, + u_int *cx, u_int *cy) +{ + struct tty *tty = &c->tty; + struct visible_ranges *r; + u_int ox, oy, sx, sy; + int px, py; + + if (!window_pane_has_prompt(wp)) + return (0); + *mode &= ~MODE_CURSOR; + + tty_window_offset(tty, &ox, &oy, &sx, &sy); + if (status_at_line(c) == 0) + py = wp->yoff; + else + py = wp->yoff + wp->sy - 1; + px = wp->xoff + wp->prompt_cx; + if (px < (int)ox || px > (int)(ox + sx) || + py < (int)oy || py > (int)(oy + sy)) + return (1); + + *cx = px - ox; + *cy = py - oy; + + r = window_visible_ranges(wp, *cx, *cy, 1, NULL); + if (window_position_is_visible(r, *cx)) { + if (status_at_line(c) == 0) + *cy += status_line_size(c); + *mode |= MODE_CURSOR; + } + return (1); +} + /* * Update cursor position and mode settings. The scroll region and attributes * are cleared when idle (waiting for an event) as this is the most likely time @@ -1802,7 +1861,7 @@ server_client_reset_state(struct client *c) struct screen *s = NULL; struct options *oo = c->session->options; int mode = 0, cursor, flags, pane_mode = 0; - u_int cx = 0, cy = 0, ox, oy, sx, sy, n; + u_int cx = 0, cy = 0, ox, oy, sx, sy, prompt = 0; struct visible_ranges *r; if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) @@ -1816,7 +1875,7 @@ server_client_reset_state(struct client *c) if (c->overlay_draw != NULL) { if (c->overlay_mode != NULL) s = c->overlay_mode(c, c->overlay_data, &cx, &cy); - } else if (wp != NULL && c->prompt_string == NULL) + } else if (wp != NULL && c->prompt == NULL) s = wp->screen; else s = c->status.active; @@ -1832,42 +1891,36 @@ server_client_reset_state(struct client *c) tty_margin_off(tty); /* Move cursor to pane cursor and offset. */ - if (c->prompt_string != NULL) { - n = options_get_number(oo, "status-position"); - if (n == 0) - cy = status_prompt_line_at(c); - else { - n = status_line_size(c) - status_prompt_line_at(c); - if (n <= tty->sy) - cy = tty->sy - n; - else - cy = tty->sy - 1; - } - cx = c->prompt_cursor; + if (c->prompt != NULL) { + prompt = 1; + status_prompt_cursor(c, &cx, &cy); } else if (wp != NULL && c->overlay_draw == NULL) { - cursor = 0; - pane_mode = wp->base.mode; + prompt = server_client_prompt_cursor(c, wp, &mode, &cx, &cy); + if (!prompt) { + cursor = 0; + pane_mode = wp->base.mode; - tty_window_offset(tty, &ox, &oy, &sx, &sy); - if (wp->xoff + (int)s->cx >= (int)ox && - wp->xoff + (int)s->cx <= (int)ox + (int)sx && - wp->yoff + (int)s->cy >= (int)oy && - wp->yoff + (int)s->cy <= (int)oy + (int)sy) { - cursor = 1; + tty_window_offset(tty, &ox, &oy, &sx, &sy); + if (wp->xoff + (int)s->cx >= (int)ox && + wp->xoff + (int)s->cx <= (int)ox + (int)sx && + wp->yoff + (int)s->cy >= (int)oy && + wp->yoff + (int)s->cy <= (int)oy + (int)sy) { + cursor = 1; - cx = wp->xoff + (int)s->cx - (int)ox; - cy = wp->yoff + (int)s->cy - (int)oy; + cx = wp->xoff + (int)s->cx - (int)ox; + cy = wp->yoff + (int)s->cy - (int)oy; - r = window_visible_ranges(wp, cx, cy, 1, NULL); - if (!window_position_is_visible(r, cx)) - cursor = 0; + r = window_visible_ranges(wp, cx, cy, 1, NULL); + if (!window_position_is_visible(r, cx)) + cursor = 0; - if (status_at_line(c) == 0) - cy += status_line_size(c); + if (status_at_line(c) == 0) + cy += status_line_size(c); + } + + if ((pane_mode & MODE_SYNC) || !cursor) + mode &= ~MODE_CURSOR; } - - if ((pane_mode & MODE_SYNC) || !cursor) - mode &= ~MODE_CURSOR; } else if (c->overlay_mode == NULL || s == NULL) mode &= ~MODE_CURSOR; if (~pane_mode & MODE_SYNC) { @@ -1895,7 +1948,7 @@ server_client_reset_state(struct client *c) } /* Clear bracketed paste mode if at the prompt. */ - if (c->overlay_draw == NULL && c->prompt_string != NULL) + if (c->overlay_draw == NULL && prompt) mode &= ~MODE_BRACKETPASTE; /* Set the terminal mode and reset attributes. */ diff --git a/server.c b/server.c index e3f923d8a..edabc8a79 100644 --- a/server.c +++ b/server.c @@ -248,7 +248,7 @@ server_start(struct tmuxproc *client, uint64_t flags, struct event_base *base, proc_loop(server_proc, server_loop); job_kill_all(); - status_prompt_save_history(); + prompt_save_history(); exit(0); } diff --git a/status.c b/status.c index 64afc1455..bb0c3bef9 100644 --- a/status.c +++ b/status.c @@ -29,149 +29,10 @@ #include "tmux.h" +static void status_message_area(struct client *, u_int *, u_int *); static void status_message_callback(int, short, void *); static void status_timer_callback(int, short, void *); -static char *status_prompt_find_history_file(void); -static const char *status_prompt_up_history(u_int *, u_int); -static const char *status_prompt_down_history(u_int *, u_int); -static void status_prompt_add_history(const char *, u_int); - -static char *status_prompt_complete(struct client *, const char *, u_int); - -struct status_prompt_menu { - struct client *c; - u_int start; - u_int size; - char **list; -}; - -static const char *prompt_type_strings[] = { - "command", - "search", - "target", - "window-target" -}; - -/* Status prompt history. */ -char **status_prompt_hlist[PROMPT_NTYPES]; -u_int status_prompt_hsize[PROMPT_NTYPES]; - -/* Find the history file to load/save from/to. */ -static char * -status_prompt_find_history_file(void) -{ - const char *home, *history_file; - char *path; - - history_file = options_get_string(global_options, "history-file"); - if (*history_file == '\0') - return (NULL); - if (*history_file == '/') - return (xstrdup(history_file)); - - if (history_file[0] != '~' || history_file[1] != '/') - return (NULL); - if ((home = find_home()) == NULL) - return (NULL); - xasprintf(&path, "%s%s", home, history_file + 1); - return (path); -} - -/* Add loaded history item to the appropriate list. */ -static void -status_prompt_add_typed_history(char *line) -{ - char *typestr; - enum prompt_type type = PROMPT_TYPE_INVALID; - - typestr = strsep(&line, ":"); - if (line != NULL) - type = status_prompt_type(typestr); - if (type == PROMPT_TYPE_INVALID) { - /* - * Invalid types are not expected, but this provides backward - * compatibility with old history files. - */ - if (line != NULL) - *(--line) = ':'; - status_prompt_add_history(typestr, PROMPT_TYPE_COMMAND); - } else - status_prompt_add_history(line, type); -} - -/* Load status prompt history from file. */ -void -status_prompt_load_history(void) -{ - FILE *f; - char *history_file, *line, *tmp; - size_t length; - - if ((history_file = status_prompt_find_history_file()) == NULL) - return; - log_debug("loading history from %s", history_file); - - f = fopen(history_file, "r"); - if (f == NULL) { - log_debug("%s: %s", history_file, strerror(errno)); - free(history_file); - return; - } - free(history_file); - - for (;;) { - if ((line = fgetln(f, &length)) == NULL) - break; - - if (length > 0) { - if (line[length - 1] == '\n') { - line[length - 1] = '\0'; - status_prompt_add_typed_history(line); - } else { - tmp = xmalloc(length + 1); - memcpy(tmp, line, length); - tmp[length] = '\0'; - status_prompt_add_typed_history(tmp); - free(tmp); - } - } - } - fclose(f); -} - -/* Save status prompt history to file. */ -void -status_prompt_save_history(void) -{ - FILE *f; - u_int i, type; - char *history_file; - - if ((history_file = status_prompt_find_history_file()) == NULL) - return; - log_debug("saving history to %s", history_file); - - f = fopen(history_file, "w"); - if (f == NULL) { - log_debug("%s: %s", history_file, strerror(errno)); - free(history_file); - return; - } - free(history_file); - - for (type = 0; type < PROMPT_NTYPES; type++) { - for (i = 0; i < status_prompt_hsize[type]; i++) { - fputs(prompt_type_strings[type], f); - fputc(':', f); - fputs(status_prompt_hlist[type][i], f); - fputc('\n', f); - } - } - fclose(f); - -} - /* Status timer callback. */ static void status_timer_callback(__unused int fd, __unused short events, void *arg) @@ -185,7 +46,7 @@ status_timer_callback(__unused int fd, __unused short events, void *arg) if (s == NULL) return; - if (c->message_string == NULL && c->prompt_string == NULL) + if (c->message_string == NULL && c->prompt == NULL) c->flags |= CLIENT_REDRAWSTATUS; timerclear(&tv); @@ -452,6 +313,28 @@ status_redraw(struct client *c) return (force || changed); } +/* Escape # characters in a string so format_draw treats them as literal. */ +static char * +status_message_escape(const char *s) +{ + const char *cp; + char *out, *p; + size_t n = 0; + + for (cp = s; *cp != '\0'; cp++) { + if (*cp == '#') + n++; + } + p = out = xmalloc(strlen(s) + n + 1); + for (cp = s; *cp != '\0'; cp++) { + if (*cp == '#') + *p++ = '#'; + *p++ = *cp; + } + *p = '\0'; + return (out); +} + /* Set a status line message. */ void status_message_set(struct client *c, int delay, int ignore_styles, @@ -515,28 +398,19 @@ status_message_clear(struct client *c) free(c->message_string); c->message_string = NULL; - if (c->prompt_string == NULL) + if (c->prompt == NULL) c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ status_pop_screen(c); } -/* Clear status line message after timer expires. */ -static void -status_message_callback(__unused int fd, __unused short event, void *data) -{ - struct client *c = data; - - status_message_clear(c); -} - /* * Calculate prompt/message area geometry from the style's width and align * directives: x offset and available width within the status line. */ static void -status_prompt_area(struct client *c, u_int *area_x, u_int *area_w) +status_message_area(struct client *c, u_int *area_x, u_int *area_w) { struct session *s = c->session; struct style *sy; @@ -574,26 +448,13 @@ status_prompt_area(struct client *c, u_int *area_x, u_int *area_w) *area_w = w; } -/* Escape # characters in a string so format_draw treats them as literal. */ -static char * -status_prompt_escape(const char *s) +/* Clear status line message after timer expires. */ +static void +status_message_callback(__unused int fd, __unused short event, void *data) { - const char *cp; - char *out, *p; - size_t n = 0; + struct client *c = data; - for (cp = s; *cp != '\0'; cp++) { - if (*cp == '#') - n++; - } - p = out = xmalloc(strlen(s) + n + 1); - for (cp = s; *cp != '\0'; cp++) { - if (*cp == '#') - *p++ = '#'; - *p++ = *cp; - } - *p = '\0'; - return (out); + status_message_clear(c); } /* Draw client message on status line of present else on last line. */ @@ -624,7 +485,7 @@ status_message_redraw(struct client *c) if (messageline > lines - 1) messageline = lines - 1; - status_prompt_area(c, &ax, &aw); + status_message_area(c, &ax, &aw); ft = format_create_defaults(NULL, c, NULL, NULL, NULL); memcpy(&gc, &grid_default_cell, sizeof gc); @@ -635,7 +496,7 @@ status_message_redraw(struct client *c) * as literal text. */ if (c->message_ignore_styles) { - msg = status_prompt_escape(c->message_string); + msg = status_message_escape(c->message_string); format_add(ft, "message", "%s", msg); free(msg); } else @@ -662,109 +523,103 @@ status_message_redraw(struct client *c) return (1); } + +struct status_prompt_data { + struct client *c; + status_prompt_input_cb inputcb; + prompt_free_cb freecb; + void *data; +}; + +static enum prompt_result +status_prompt_input_callback(void *data, const char *s, + enum prompt_key_result key) +{ + struct status_prompt_data *spd = data; + struct client *c = spd->c; + status_prompt_input_cb inputcb = spd->inputcb; + void *arg = spd->data; + + if (inputcb != NULL) + return (inputcb(c, arg, s, key)); + return (PROMPT_CLOSE); +} + +static void +status_prompt_free_callback(void *data) +{ + struct status_prompt_data *spd = data; + prompt_free_cb freecb = spd->freecb; + void *arg = spd->data; + + if (freecb != NULL) + freecb(arg); + free(spd); +} + /* Accept prompt immediately. */ static enum cmd_retval status_prompt_accept(__unused struct cmdq_item *item, void *data) { struct client *c = data; - void *pd = c->prompt_data; - if (c->prompt_string != NULL) { - c->prompt_inputcb(c, pd, "y", PROMPT_KEY_CLOSE); - status_prompt_clear(c); - } + if (c->prompt != NULL) + status_prompt_key(c, 'y', NULL); return (CMD_RETURN_NORMAL); } /* Enable status line prompt. */ void status_prompt_set(struct client *c, struct cmd_find_state *fs, - const char *msg, const char *input, prompt_input_cb inputcb, + const char *msg, const char *input, status_prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type) { - struct format_tree *ft; - char *tmp, *cp; + struct prompt_create_data pd; + struct status_prompt_data *spd; server_client_clear_overlay(c); - if (fs != NULL) { - ft = format_create_from_state(NULL, c, fs); - cmd_find_copy_state(&c->prompt_state, fs); - } else { - ft = format_create_defaults(NULL, c, NULL, NULL, NULL); - cmd_find_clear_state(&c->prompt_state, 0); - } - - if (input == NULL) - input = ""; - status_message_clear(c); status_prompt_clear(c); status_push_screen(c); - c->prompt_string = xstrdup (msg); + spd = xcalloc(1, sizeof *spd); + spd->c = c; + spd->inputcb = inputcb; + spd->freecb = freecb; + spd->data = data; - if (flags & PROMPT_NOFORMAT) - tmp = xstrdup(input); - else - tmp = format_expand_time(ft, input); - if (flags & PROMPT_INCREMENTAL) { - c->prompt_last = xstrdup(tmp); - c->prompt_buffer = utf8_fromcstr(""); - } else { - c->prompt_last = NULL; - c->prompt_buffer = utf8_fromcstr(tmp); - } - c->prompt_index = utf8_strlen(c->prompt_buffer); - free(tmp); - - c->prompt_inputcb = inputcb; - c->prompt_freecb = freecb; - c->prompt_data = data; - - memset(c->prompt_hindex, 0, sizeof c->prompt_hindex); - - c->prompt_flags = flags; - c->prompt_type = prompt_type; + memset(&pd, 0, sizeof pd); + prompt_set_options(&pd, c->session); + pd.fs = fs; + pd.prompt = msg; + pd.input = input; + pd.type = prompt_type; + pd.flags = flags; + pd.inputcb = status_prompt_input_callback; + pd.freecb = status_prompt_free_callback; + pd.data = spd; + c->prompt = prompt_create(&pd); if ((~flags & PROMPT_INCREMENTAL) && (~flags & PROMPT_NOFREEZE)) c->tty.flags |= TTY_FREEZE; c->flags |= CLIENT_REDRAWSTATUS; - if (flags & PROMPT_INCREMENTAL) { - tmp = utf8_tocstr(c->prompt_buffer); - xasprintf(&cp, "=%s", tmp); - c->prompt_inputcb(c, c->prompt_data, cp, PROMPT_KEY_HANDLED); - free(cp); - free(tmp); - } + prompt_incremental_start(c->prompt); if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT)) cmdq_append(c, cmdq_get_callback(status_prompt_accept, c)); - format_free(ft); } /* Remove status line prompt. */ void status_prompt_clear(struct client *c) { - if (c->prompt_string == NULL) + if (c->prompt == NULL) return; - if (c->prompt_freecb != NULL && c->prompt_data != NULL) - c->prompt_freecb(c->prompt_data); - - free(c->prompt_last); - c->prompt_last = NULL; - - free(c->prompt_string); - c->prompt_string = NULL; - - free(c->prompt_buffer); - c->prompt_buffer = NULL; - - free(c->prompt_saved); - c->prompt_saved = NULL; + prompt_free(c->prompt); + c->prompt = NULL; c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ @@ -776,76 +631,25 @@ status_prompt_clear(struct client *c) void status_prompt_update(struct client *c, const char *msg, const char *input) { - struct format_tree *ft; - char *tmp; - - if (cmd_find_valid_state(&c->prompt_state)) - ft = format_create_from_state(NULL, c, &c->prompt_state); - else - ft = format_create_defaults(NULL, c, NULL, NULL, NULL); - - free(c->prompt_string); - c->prompt_string = xstrdup(msg); - - free(c->prompt_buffer); - tmp = format_expand_time(ft, input); - c->prompt_buffer = utf8_fromcstr(tmp); - c->prompt_index = utf8_strlen(c->prompt_buffer); - free(tmp); - - memset(c->prompt_hindex, 0, sizeof c->prompt_hindex); - + if (c->prompt == NULL) + return; + prompt_update(c->prompt, msg, input); c->flags |= CLIENT_REDRAWSTATUS; - format_free(ft); } -/* Redraw character. Return 1 if can continue redrawing, 0 otherwise. */ -static int -status_prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset, - u_int pwidth, u_int *width, struct grid_cell *gc, - const struct utf8_data *ud) +/* Get the screen line on which the prompt is drawn. */ +static u_int +status_prompt_screen_line(struct client *c) { - u_char ch; + struct tty *tty = &c->tty; + u_int n; - if (*width < offset) { - *width += ud->width; - return (1); - } - if (*width >= offset + pwidth) - return (0); - *width += ud->width; - if (*width > offset + pwidth) - return (0); - - ch = *ud->data; - if (ud->size == 1 && (ch <= 0x1f || ch == 0x7f)) { - gc->data.data[0] = '^'; - gc->data.data[1] = (ch == 0x7f) ? '?' : ch|0x40; - gc->data.size = gc->data.have = 2; - gc->data.width = 2; - } else - utf8_copy(&gc->data, ud); - screen_write_cell(ctx, gc); - return (1); -} - -/* - * Redraw quote indicator '^' if necessary. Return 1 if can continue redrawing, - * 0 otherwise. - */ -static int -status_prompt_redraw_quote(const struct client *c, u_int pcursor, - struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width, - struct grid_cell *gc) -{ - struct utf8_data ud; - - if (c->prompt_flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) { - utf8_set(&ud, '^'); - return (status_prompt_redraw_character(ctx, offset, pwidth, - width, gc, &ud)); - } - return (1); + if (options_get_number(c->session->options, "status-position") == 0) + return (status_prompt_line_at(c)); + n = status_line_size(c) - status_prompt_line_at(c); + if (n <= tty->sy) + return (tty->sy - n); + return (tty->sy - 1); } /* Draw client prompt on status line of present else on last line. */ @@ -854,16 +658,9 @@ status_prompt_redraw(struct client *c) { struct status_line *sl = &c->status; struct screen_write_ctx ctx; - struct session *s = c->session; - struct options *oo = s->options; struct screen old_screen; - u_int i, lines, offset, left, start, width, n; - u_int pcursor, pwidth, promptline; - u_int ax, aw; - struct grid_cell gc; - struct format_tree *ft; - const char *msgfmt; - char *expanded, *prompt, *tmp; + struct prompt_draw_data pdd; + u_int lines, ax, aw, promptline; if (c->tty.sx == 0 || c->tty.sy == 0) return (0); @@ -874,93 +671,22 @@ status_prompt_redraw(struct client *c) lines = 1; screen_init(sl->active, c->tty.sx, lines, 0); - if (cmd_find_valid_state(&c->prompt_state)) - ft = format_create_from_state(NULL, c, &c->prompt_state); - else - ft = format_create_defaults(NULL, c, NULL, NULL, NULL); - - n = options_get_number(s->options, "prompt-cursor-colour"); - sl->active->default_ccolour = n; - if (c->prompt_flags & PROMPT_COMMANDMODE) - n = options_get_number(oo, "prompt-command-cursor-style"); - else - n = options_get_number(oo, "prompt-cursor-style"); - screen_set_cursor_style(n, &sl->active->default_cstyle, - &sl->active->default_mode); - promptline = status_prompt_line_at(c); if (promptline > lines - 1) promptline = lines - 1; - if (c->prompt_flags & PROMPT_COMMANDMODE) - style_apply(&gc, oo, "message-command-style", NULL); - else - style_apply(&gc, oo, "message-style", NULL); - - status_prompt_area(c, &ax, &aw); - - tmp = utf8_tocstr(c->prompt_buffer); - format_add(ft, "prompt_input", "%s", tmp); - prompt = format_expand_time(ft, c->prompt_string); - free(tmp); - - /* - * Set #{message} to the prompt string and expand message-format. - * format_draw handles fill, alignment, and decorations in one call. - */ - format_add(ft, "message", "%s", prompt); - format_add(ft, "command_prompt", "%d", - !!(c->prompt_flags & PROMPT_COMMANDMODE)); - msgfmt = options_get_string(oo, "message-format"); - expanded = format_expand_time(ft, msgfmt); - free(prompt); - - start = format_width(expanded); - if (start > aw) - start = aw; + status_message_area(c, &ax, &aw); screen_write_start(&ctx, sl->active); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines); - screen_write_cursormove(&ctx, ax, promptline, 0); - format_draw(&ctx, &gc, aw, expanded, NULL, 0); - screen_write_cursormove(&ctx, ax + start, promptline, 0); - free(expanded); + pdd.ctx = &ctx; + pdd.area_x = ax; + pdd.area_width = aw; + pdd.prompt_line = promptline; + pdd.cursor_x = &sl->prompt_cx; + prompt_draw(c->prompt, &pdd); - left = aw - start; - if (left == 0) - goto finished; - - pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index); - pwidth = utf8_strwidth(c->prompt_buffer, -1); - if (c->prompt_flags & PROMPT_QUOTENEXT) - pwidth++; - if (pcursor >= left) { - /* - * The cursor would be outside the screen so start drawing - * with it on the right. - */ - offset = (pcursor - left) + 1; - pwidth = left; - } else - offset = 0; - if (pwidth > left) - pwidth = left; - c->prompt_cursor = ax + start + pcursor - offset; - - width = 0; - for (i = 0; c->prompt_buffer[i].size != 0; i++) { - if (!status_prompt_redraw_quote(c, pcursor, &ctx, offset, - pwidth, &width, &gc)) - break; - if (!status_prompt_redraw_character(&ctx, offset, pwidth, - &width, &gc, &c->prompt_buffer[i])) - break; - } - status_prompt_redraw_quote(c, pcursor, &ctx, offset, pwidth, &width, - &gc); - -finished: screen_write_stop(&ctx); if (grid_compare(sl->active->grid, old_screen.grid) == 0) { @@ -971,1133 +697,34 @@ finished: return (1); } -/* Is this a separator? */ -static int -status_prompt_in_list(const char *ws, const struct utf8_data *ud) +/* Work out the tty cursor position for the prompt. */ +void +status_prompt_cursor(struct client *c, u_int *cx, u_int *cy) { - if (ud->size != 1 || ud->width != 1) - return (0); - return (strchr(ws, *ud->data) != NULL); -} - -/* Is this a space? */ -static int -status_prompt_space(const struct utf8_data *ud) -{ - if (ud->size != 1 || ud->width != 1) - return (0); - return (*ud->data == ' '); -} - -static key_code -status_prompt_keypad_key(key_code key) -{ - if (key & KEYC_MASK_MODIFIERS) - return (key); - - switch (key) { - case KEYC_KP_SLASH: - return ('/'); - case KEYC_KP_STAR: - return ('*'); - case KEYC_KP_MINUS: - return ('-'); - case KEYC_KP_SEVEN: - return ('7'); - case KEYC_KP_EIGHT: - return ('8'); - case KEYC_KP_NINE: - return ('9'); - case KEYC_KP_PLUS: - return ('+'); - case KEYC_KP_FOUR: - return ('4'); - case KEYC_KP_FIVE: - return ('5'); - case KEYC_KP_SIX: - return ('6'); - case KEYC_KP_ONE: - return ('1'); - case KEYC_KP_TWO: - return ('2'); - case KEYC_KP_THREE: - return ('3'); - case KEYC_KP_ENTER: - return ('\r'); - case KEYC_KP_ZERO: - return ('0'); - case KEYC_KP_PERIOD: - return ('.'); - } - return (key); -} - -/* - * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key - * as an emacs key; return 2 to append to the buffer. - */ -static int -status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) -{ - if (~c->prompt_flags & PROMPT_COMMANDMODE) { - switch (key) { - case 'a'|KEYC_CTRL: - case 'c'|KEYC_CTRL: - case 'e'|KEYC_CTRL: - case 'g'|KEYC_CTRL: - case 'h'|KEYC_CTRL: - case '\011': /* Tab */ - case 'k'|KEYC_CTRL: - case 'n'|KEYC_CTRL: - case 'p'|KEYC_CTRL: - case 't'|KEYC_CTRL: - case 'u'|KEYC_CTRL: - case 'v'|KEYC_CTRL: - case 'w'|KEYC_CTRL: - case 'y'|KEYC_CTRL: - case '\n': - case '\r': - case KEYC_LEFT|KEYC_CTRL: - case KEYC_RIGHT|KEYC_CTRL: - case KEYC_BSPACE: - case KEYC_DC: - case KEYC_DOWN: - case KEYC_END: - case KEYC_HOME: - case KEYC_LEFT: - case KEYC_RIGHT: - case KEYC_UP: - *new_key = key; - return (1); - case '\033': /* Escape */ - case '['|KEYC_CTRL: - c->prompt_flags |= PROMPT_COMMANDMODE; - if (c->prompt_index != 0) - c->prompt_index--; - c->flags |= CLIENT_REDRAWSTATUS; - return (0); - } - *new_key = key; - return (2); - } - - switch (key) { - case KEYC_BSPACE: - *new_key = KEYC_LEFT; - return (1); - case 'A': - case 'I': - case 'C': - case 's': - case 'a': - c->prompt_flags &= ~PROMPT_COMMANDMODE; - c->flags |= CLIENT_REDRAWSTATUS; - break; /* switch mode and... */ - case 'S': - c->prompt_flags &= ~PROMPT_COMMANDMODE; - c->flags |= CLIENT_REDRAWSTATUS; - *new_key = 'u'|KEYC_CTRL; - return (1); - case 'i': - c->prompt_flags &= ~PROMPT_COMMANDMODE; - c->flags |= CLIENT_REDRAWSTATUS; - return (0); - case '\033': /* Escape */ - case '['|KEYC_CTRL: - return (0); - } - - switch (key) { - case 'A': - case '$': - *new_key = KEYC_END; - return (1); - case 'I': - case '0': - case '^': - *new_key = KEYC_HOME; - return (1); - case 'C': - case 'D': - *new_key = 'k'|KEYC_CTRL; - return (1); - case KEYC_BSPACE: - case 'X': - *new_key = KEYC_BSPACE; - return (1); - case 'b': - *new_key = 'b'|KEYC_META; - return (1); - case 'B': - *new_key = 'B'|KEYC_VI; - return (1); - case 'd': - *new_key = 'u'|KEYC_CTRL; - return (1); - case 'e': - *new_key = 'e'|KEYC_VI; - return (1); - case 'E': - *new_key = 'E'|KEYC_VI; - return (1); - case 'w': - *new_key = 'w'|KEYC_VI; - return (1); - case 'W': - *new_key = 'W'|KEYC_VI; - return (1); - case 'p': - *new_key = 'y'|KEYC_CTRL; - return (1); - case 'q': - *new_key = 'c'|KEYC_CTRL; - return (1); - case 's': - case KEYC_DC: - case 'x': - *new_key = KEYC_DC; - return (1); - case KEYC_DOWN: - case 'j': - *new_key = KEYC_DOWN; - return (1); - case KEYC_LEFT: - case 'h': - *new_key = KEYC_LEFT; - return (1); - case 'a': - case KEYC_RIGHT: - case 'l': - *new_key = KEYC_RIGHT; - return (1); - case KEYC_UP: - case 'k': - *new_key = KEYC_UP; - return (1); - case 'h'|KEYC_CTRL: - case 'c'|KEYC_CTRL: - case '\n': - case '\r': - return (1); - } - return (0); -} - -/* Paste into prompt. */ -static int -status_prompt_paste(struct client *c) -{ - struct paste_buffer *pb; - const char *bufdata; - size_t size, n, bufsize; - u_int i; - struct utf8_data *ud, *udp; - enum utf8_state more; - - size = utf8_strlen(c->prompt_buffer); - if (c->prompt_saved != NULL) { - ud = c->prompt_saved; - n = utf8_strlen(c->prompt_saved); - } else { - if ((pb = paste_get_top(NULL)) == NULL) - return (0); - bufdata = paste_buffer_data(pb, &bufsize); - ud = udp = xreallocarray(NULL, bufsize + 1, sizeof *ud); - for (i = 0; i != bufsize; /* nothing */) { - more = utf8_open(udp, bufdata[i]); - if (more == UTF8_MORE) { - while (++i != bufsize && more == UTF8_MORE) - more = utf8_append(udp, bufdata[i]); - if (more == UTF8_DONE) { - udp++; - continue; - } - i -= udp->have; - } - if (bufdata[i] <= 31 || bufdata[i] >= 127) - break; - utf8_set(udp, bufdata[i]); - udp++; - i++; - } - udp->size = 0; - n = udp - ud; - } - if (n != 0) { - c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, - sizeof *c->prompt_buffer); - if (c->prompt_index == size) { - memcpy(c->prompt_buffer + c->prompt_index, ud, - n * sizeof *c->prompt_buffer); - c->prompt_index += n; - c->prompt_buffer[c->prompt_index].size = 0; - } else { - memmove(c->prompt_buffer + c->prompt_index + n, - c->prompt_buffer + c->prompt_index, - (size + 1 - c->prompt_index) * - sizeof *c->prompt_buffer); - memcpy(c->prompt_buffer + c->prompt_index, ud, - n * sizeof *c->prompt_buffer); - c->prompt_index += n; - } - } - if (ud != c->prompt_saved) - free(ud); - return (1); -} - -/* Finish completion. */ -static int -status_prompt_replace_complete(struct client *c, const char *s) -{ - char word[64], *allocated = NULL; - size_t size, n, off, idx, used; - struct utf8_data *first, *last, *ud; - - /* Work out where the cursor currently is. */ - idx = c->prompt_index; - if (idx != 0) - idx--; - size = utf8_strlen(c->prompt_buffer); - - /* Find the word we are in. */ - first = &c->prompt_buffer[idx]; - while (first > c->prompt_buffer && !status_prompt_space(first)) - first--; - while (first->size != 0 && status_prompt_space(first)) - first++; - last = &c->prompt_buffer[idx]; - while (last->size != 0 && !status_prompt_space(last)) - last++; - while (last > c->prompt_buffer && status_prompt_space(last)) - last--; - if (last->size != 0) - last++; - if (last < first) - return (0); - if (s == NULL) { - used = 0; - for (ud = first; ud < last; ud++) { - if (used + ud->size >= sizeof word) - break; - memcpy(word + used, ud->data, ud->size); - used += ud->size; - } - if (ud != last) - return (0); - word[used] = '\0'; - } - - /* Try to complete it. */ - if (s == NULL) { - allocated = status_prompt_complete(c, word, - first - c->prompt_buffer); - if (allocated == NULL) - return (0); - s = allocated; - } - - /* Trim out word. */ - n = size - (last - c->prompt_buffer) + 1; /* with \0 */ - memmove(first, last, n * sizeof *c->prompt_buffer); - size -= last - first; - - /* Insert the new word. */ - size += strlen(s); - off = first - c->prompt_buffer; - c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, - sizeof *c->prompt_buffer); - first = c->prompt_buffer + off; - memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); - for (idx = 0; idx < strlen(s); idx++) - utf8_set(&first[idx], s[idx]); - c->prompt_index = (first - c->prompt_buffer) + strlen(s); - - free(allocated); - return (1); -} - -/* Prompt forward to the next beginning of a word. */ -static void -status_prompt_forward_word(struct client *c, size_t size, int vi, - const char *separators) -{ - size_t idx = c->prompt_index; - int word_is_separators; - - /* In emacs mode, skip until the first non-whitespace character. */ - if (!vi) - while (idx != size && - status_prompt_space(&c->prompt_buffer[idx])) - idx++; - - /* Can't move forward if we're already at the end. */ - if (idx == size) { - c->prompt_index = idx; - return; - } - - /* Determine the current character class (separators or not). */ - word_is_separators = status_prompt_in_list(separators, - &c->prompt_buffer[idx]) && - !status_prompt_space(&c->prompt_buffer[idx]); - - /* Skip ahead until the first space or opposite character class. */ - do { - idx++; - if (status_prompt_space(&c->prompt_buffer[idx])) { - /* In vi mode, go to the start of the next word. */ - if (vi) - while (idx != size && - status_prompt_space(&c->prompt_buffer[idx])) - idx++; - break; - } - } while (idx != size && word_is_separators == status_prompt_in_list( - separators, &c->prompt_buffer[idx])); - - c->prompt_index = idx; -} - -/* Prompt forward to the next end of a word. */ -static void -status_prompt_end_word(struct client *c, size_t size, const char *separators) -{ - size_t idx = c->prompt_index; - int word_is_separators; - - /* Can't move forward if we're already at the end. */ - if (idx == size) - return; - - /* Find the next word. */ - do { - idx++; - if (idx == size) { - c->prompt_index = idx; - return; - } - } while (status_prompt_space(&c->prompt_buffer[idx])); - - /* Determine the character class (separators or not). */ - word_is_separators = status_prompt_in_list(separators, - &c->prompt_buffer[idx]); - - /* Skip ahead until the next space or opposite character class. */ - do { - idx++; - if (idx == size) - break; - } while (!status_prompt_space(&c->prompt_buffer[idx]) && - word_is_separators == status_prompt_in_list(separators, - &c->prompt_buffer[idx])); - - /* Back up to the previous character to stop at the end of the word. */ - c->prompt_index = idx - 1; -} - -/* Prompt backward to the previous beginning of a word. */ -static void -status_prompt_backward_word(struct client *c, const char *separators) -{ - size_t idx = c->prompt_index; - int word_is_separators; - - /* Find non-whitespace. */ - while (idx != 0) { - --idx; - if (!status_prompt_space(&c->prompt_buffer[idx])) - break; - } - word_is_separators = status_prompt_in_list(separators, - &c->prompt_buffer[idx]); - - /* Find the character before the beginning of the word. */ - while (idx != 0) { - --idx; - if (status_prompt_space(&c->prompt_buffer[idx]) || - word_is_separators != status_prompt_in_list(separators, - &c->prompt_buffer[idx])) { - /* Go back to the word. */ - idx++; - break; - } - } - c->prompt_index = idx; -} - -/* Fire input callback when done. */ -static enum prompt_key_result -status_prompt_done(struct client *c, const char *s) -{ - struct prompt_data *pd = c->prompt_data; - - if (c->prompt_inputcb(c, pd, s, PROMPT_KEY_CLOSE) == PROMPT_CLOSE) { - status_prompt_clear(c); - return (PROMPT_KEY_CLOSE); - } - c->flags |= CLIENT_REDRAWSTATUS; - return (PROMPT_KEY_HANDLED); -} - -/* Check for a movement key. */ -static enum prompt_key_result -status_prompt_check_move(struct client *c, key_code key) -{ - struct prompt_data *pd = c->prompt_data; - char *s; - - if (~c->prompt_flags & PROMPT_INCREMENTAL) - return (PROMPT_KEY_NOT_HANDLED); - switch (key) { - case KEYC_UP: - case KEYC_DOWN: - case KEYC_LEFT: - case KEYC_RIGHT: - case KEYC_PPAGE: - case KEYC_NPAGE: - break; - default: - return (PROMPT_KEY_NOT_HANDLED); - } - s = utf8_tocstr(c->prompt_buffer); - if (c->prompt_inputcb(c, pd, s, PROMPT_KEY_MOVE) == PROMPT_CLOSE) { - status_prompt_clear(c); - free(s); - return (PROMPT_KEY_CLOSE); - } - free(s); - return (PROMPT_KEY_MOVE); + *cy = status_prompt_screen_line(c); + *cx = c->status.prompt_cx; } /* Handle keys in prompt. */ enum prompt_key_result -status_prompt_key(struct client *c, key_code key) +status_prompt_key(struct client *c, key_code key, struct mouse_event *m) { - struct prompt_data *pd = c->prompt_data; - struct options *oo = c->session->options; - char *s, *cp, prefix = '='; - const char *histstr, *separators = NULL, *ks; - size_t size, idx; - struct utf8_data tmp; - enum prompt_key_result result = PROMPT_KEY_HANDLED; - int keys, word_is_separators; + enum prompt_key_result result; + u_int ax, aw; + int redraw = 0; - if (c->prompt_flags & PROMPT_KEY) { - ks = key_string_lookup_key(key, 0); - c->prompt_inputcb(c, pd, ks, PROMPT_KEY_CLOSE); + if (KEYC_IS_MOUSE(key)) { + if (m == NULL || MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1 || + MOUSE_DRAG(m->b) || MOUSE_RELEASE(m->b) || + m->y != status_prompt_screen_line(c)) + return (PROMPT_KEY_NOT_HANDLED); + status_message_area(c, &ax, &aw); + result = prompt_mouse(c->prompt, m->x, ax, aw, &redraw); + } else + result = prompt_key(c->prompt, key, &redraw); + if (redraw && c->prompt != NULL) + c->flags |= CLIENT_REDRAWSTATUS; + if (c->prompt != NULL && prompt_closed(c->prompt)) status_prompt_clear(c); - return (PROMPT_KEY_CLOSE); - } - size = utf8_strlen(c->prompt_buffer); - - key &= ~KEYC_MASK_FLAGS; - key = status_prompt_keypad_key(key); - - if (c->prompt_flags & PROMPT_NUMERIC) { - if (key >= '0' && key <= '9') - goto append_key; - s = utf8_tocstr(c->prompt_buffer); - c->prompt_inputcb(c, pd, s, PROMPT_KEY_CLOSE); - status_prompt_clear(c); - free(s); - return (PROMPT_KEY_NOT_HANDLED); - } - - if (c->prompt_flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) { - if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) - key = 0x7f; - else if ((key & KEYC_MASK_KEY) > 0x7f) { - if (!KEYC_IS_UNICODE(key)) - return (PROMPT_KEY_HANDLED); - key &= KEYC_MASK_KEY; - } else - key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY; - c->prompt_flags &= ~PROMPT_QUOTENEXT; - goto append_key; - } - - keys = options_get_number(c->session->options, "status-keys"); - if (keys == MODEKEY_VI) { - switch (status_prompt_translate_key(c, key, &key)) { - case 1: - goto process_key; - case 2: - goto append_key; - default: - return (PROMPT_KEY_HANDLED); - } - } - -process_key: - result = status_prompt_check_move(c, key); - if (result != PROMPT_KEY_NOT_HANDLED) - return (result); - switch (key) { - case KEYC_LEFT: - case 'b'|KEYC_CTRL: - if (c->prompt_index > 0) { - c->prompt_index--; - break; - } - break; - case KEYC_RIGHT: - case 'f'|KEYC_CTRL: - if (c->prompt_index < size) { - c->prompt_index++; - break; - } - break; - case KEYC_HOME: - case 'a'|KEYC_CTRL: - if (c->prompt_index != 0) { - c->prompt_index = 0; - break; - } - break; - case KEYC_END: - case 'e'|KEYC_CTRL: - if (c->prompt_index != size) { - c->prompt_index = size; - break; - } - break; - case '\011': /* Tab */ - if (status_prompt_replace_complete(c, NULL)) - goto changed; - break; - case KEYC_BSPACE: - case 'h'|KEYC_CTRL: - if (c->prompt_flags & PROMPT_BSPACE_EXIT && size == 0) - return (status_prompt_done(c, NULL)); - if (c->prompt_index != 0) { - if (c->prompt_index == size) - c->prompt_buffer[--c->prompt_index].size = 0; - else { - memmove(c->prompt_buffer + c->prompt_index - 1, - c->prompt_buffer + c->prompt_index, - (size + 1 - c->prompt_index) * - sizeof *c->prompt_buffer); - c->prompt_index--; - } - goto changed; - } - break; - case KEYC_DC: - case 'd'|KEYC_CTRL: - if (c->prompt_index != size) { - memmove(c->prompt_buffer + c->prompt_index, - c->prompt_buffer + c->prompt_index + 1, - (size + 1 - c->prompt_index) * - sizeof *c->prompt_buffer); - goto changed; - } - break; - case 'u'|KEYC_CTRL: - c->prompt_buffer[0].size = 0; - c->prompt_index = 0; - goto changed; - case 'k'|KEYC_CTRL: - if (c->prompt_index < size) { - c->prompt_buffer[c->prompt_index].size = 0; - goto changed; - } - break; - case 'w'|KEYC_CTRL: - separators = options_get_string(oo, "word-separators"); - idx = c->prompt_index; - - /* Find non-whitespace. */ - while (idx != 0) { - idx--; - if (!status_prompt_space(&c->prompt_buffer[idx])) - break; - } - word_is_separators = status_prompt_in_list(separators, - &c->prompt_buffer[idx]); - - /* Find the character before the beginning of the word. */ - while (idx != 0) { - idx--; - if (status_prompt_space(&c->prompt_buffer[idx]) || - word_is_separators != status_prompt_in_list( - separators, &c->prompt_buffer[idx])) { - /* Go back to the word. */ - idx++; - break; - } - } - - free(c->prompt_saved); - c->prompt_saved = xcalloc(sizeof *c->prompt_buffer, - (c->prompt_index - idx) + 1); - memcpy(c->prompt_saved, c->prompt_buffer + idx, - (c->prompt_index - idx) * sizeof *c->prompt_buffer); - - memmove(c->prompt_buffer + idx, - c->prompt_buffer + c->prompt_index, - (size + 1 - c->prompt_index) * - sizeof *c->prompt_buffer); - memset(c->prompt_buffer + size - (c->prompt_index - idx), - '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer); - c->prompt_index = idx; - - goto changed; - case KEYC_RIGHT|KEYC_CTRL: - case 'f'|KEYC_META: - separators = options_get_string(oo, "word-separators"); - status_prompt_forward_word(c, size, 0, separators); - goto changed; - case 'E'|KEYC_VI: - status_prompt_end_word(c, size, ""); - goto changed; - case 'e'|KEYC_VI: - separators = options_get_string(oo, "word-separators"); - status_prompt_end_word(c, size, separators); - goto changed; - case 'W'|KEYC_VI: - status_prompt_forward_word(c, size, 1, ""); - goto changed; - case 'w'|KEYC_VI: - separators = options_get_string(oo, "word-separators"); - status_prompt_forward_word(c, size, 1, separators); - goto changed; - case 'B'|KEYC_VI: - status_prompt_backward_word(c, ""); - goto changed; - case KEYC_LEFT|KEYC_CTRL: - case 'b'|KEYC_META: - separators = options_get_string(oo, "word-separators"); - status_prompt_backward_word(c, separators); - goto changed; - case KEYC_UP: - case 'p'|KEYC_CTRL: - histstr = status_prompt_up_history(c->prompt_hindex, - c->prompt_type); - if (histstr == NULL) - break; - free(c->prompt_buffer); - c->prompt_buffer = utf8_fromcstr(histstr); - c->prompt_index = utf8_strlen(c->prompt_buffer); - goto changed; - case KEYC_DOWN: - case 'n'|KEYC_CTRL: - histstr = status_prompt_down_history(c->prompt_hindex, - c->prompt_type); - if (histstr == NULL) - break; - free(c->prompt_buffer); - c->prompt_buffer = utf8_fromcstr(histstr); - c->prompt_index = utf8_strlen(c->prompt_buffer); - goto changed; - case 'y'|KEYC_CTRL: - if (status_prompt_paste(c)) - goto changed; - break; - case 't'|KEYC_CTRL: - idx = c->prompt_index; - if (idx < size) - idx++; - if (idx >= 2) { - utf8_copy(&tmp, &c->prompt_buffer[idx - 2]); - utf8_copy(&c->prompt_buffer[idx - 2], - &c->prompt_buffer[idx - 1]); - utf8_copy(&c->prompt_buffer[idx - 1], &tmp); - c->prompt_index = idx; - goto changed; - } - break; - case '\r': - case '\n': - s = utf8_tocstr(c->prompt_buffer); - if (*s != '\0') - status_prompt_add_history(s, c->prompt_type); - result = status_prompt_done(c, s); - free(s); - return (result); - case '\033': /* Escape */ - case '['|KEYC_CTRL: - case 'c'|KEYC_CTRL: - case 'g'|KEYC_CTRL: - return (status_prompt_done(c, NULL)); - case 'r'|KEYC_CTRL: - if (~c->prompt_flags & PROMPT_INCREMENTAL) - break; - if (c->prompt_buffer[0].size == 0) { - prefix = '='; - free(c->prompt_buffer); - c->prompt_buffer = utf8_fromcstr(c->prompt_last); - c->prompt_index = utf8_strlen(c->prompt_buffer); - } else - prefix = '-'; - goto changed; - case 's'|KEYC_CTRL: - if (~c->prompt_flags & PROMPT_INCREMENTAL) - break; - if (c->prompt_buffer[0].size == 0) { - prefix = '='; - free(c->prompt_buffer); - c->prompt_buffer = utf8_fromcstr(c->prompt_last); - c->prompt_index = utf8_strlen(c->prompt_buffer); - } else - prefix = '+'; - goto changed; - case 'v'|KEYC_CTRL: - c->prompt_flags |= PROMPT_QUOTENEXT; - break; - default: - goto append_key; - } - - c->flags |= CLIENT_REDRAWSTATUS; - return (PROMPT_KEY_HANDLED); - -append_key: - if (key <= 0x7f) { - utf8_set(&tmp, key); - if (key <= 0x1f || key == 0x7f) - tmp.width = 2; - } else if (KEYC_IS_UNICODE(key)) - utf8_to_data(key, &tmp); - else - return (PROMPT_KEY_HANDLED); - - c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, - sizeof *c->prompt_buffer); - - if (c->prompt_index == size) { - utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); - c->prompt_index++; - c->prompt_buffer[c->prompt_index].size = 0; - } else { - memmove(c->prompt_buffer + c->prompt_index + 1, - c->prompt_buffer + c->prompt_index, - (size + 1 - c->prompt_index) * - sizeof *c->prompt_buffer); - utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); - c->prompt_index++; - } - - if (c->prompt_flags & PROMPT_SINGLE) { - if (utf8_strlen(c->prompt_buffer) != 1) { - status_prompt_clear(c); - result = PROMPT_KEY_CLOSE; - } else { - s = utf8_tocstr(c->prompt_buffer); - result = status_prompt_done(c, s); - free(s); - } - } - -changed: - c->flags |= CLIENT_REDRAWSTATUS; - if (c->prompt_flags & PROMPT_INCREMENTAL) { - s = utf8_tocstr(c->prompt_buffer); - xasprintf(&cp, "%c%s", prefix, s); - c->prompt_inputcb(c, pd, cp, PROMPT_KEY_HANDLED); - free(cp); - free(s); - } return (result); } - -/* Get previous line from the history. */ -static const char * -status_prompt_up_history(u_int *idx, u_int type) -{ - /* - * History runs from 0 to size - 1. Index is from 0 to size. Zero is - * empty. - */ - - if (status_prompt_hsize[type] == 0 || - idx[type] == status_prompt_hsize[type]) - return (NULL); - idx[type]++; - return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]); -} - -/* Get next line from the history. */ -static const char * -status_prompt_down_history(u_int *idx, u_int type) -{ - if (status_prompt_hsize[type] == 0 || idx[type] == 0) - return (""); - idx[type]--; - if (idx[type] == 0) - return (""); - return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]); -} - -/* Add line to the history. */ -static void -status_prompt_add_history(const char *line, u_int type) -{ - u_int i, oldsize, newsize, freecount, hlimit, new = 1; - size_t movesize; - - oldsize = status_prompt_hsize[type]; - if (oldsize > 0 && - strcmp(status_prompt_hlist[type][oldsize - 1], line) == 0) - new = 0; - - hlimit = options_get_number(global_options, "prompt-history-limit"); - if (hlimit > oldsize) { - if (new == 0) - return; - newsize = oldsize + new; - } else { - newsize = hlimit; - freecount = oldsize + new - newsize; - if (freecount > oldsize) - freecount = oldsize; - if (freecount == 0) - return; - for (i = 0; i < freecount; i++) - free(status_prompt_hlist[type][i]); - movesize = (oldsize - freecount) * - sizeof *status_prompt_hlist[type]; - if (movesize > 0) { - memmove(&status_prompt_hlist[type][0], - &status_prompt_hlist[type][freecount], movesize); - } - } - - if (newsize == 0) { - free(status_prompt_hlist[type]); - status_prompt_hlist[type] = NULL; - } else if (newsize != oldsize) { - status_prompt_hlist[type] = - xreallocarray(status_prompt_hlist[type], newsize, - sizeof *status_prompt_hlist[type]); - } - - if (new == 1 && newsize > 0) - status_prompt_hlist[type][newsize - 1] = xstrdup(line); - status_prompt_hsize[type] = newsize; -} - -/* Add to completion list. */ -static void -status_prompt_add_list(char ***list, u_int *size, const char *s) -{ - u_int i; - - for (i = 0; i < *size; i++) { - if (strcmp((*list)[i], s) == 0) - return; - } - *list = xreallocarray(*list, (*size) + 1, sizeof **list); - (*list)[(*size)++] = xstrdup(s); -} - -/* Build completion list. */ -static char ** -status_prompt_complete_list(u_int *size, const char *s) -{ - char **list = NULL, *tmp; - const char *value, *cp; - const struct cmd_entry **cmdent; - size_t slen = strlen(s), valuelen; - struct options_entry *o; - struct options_array_item *a; - - *size = 0; - for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { - if (strncmp((*cmdent)->name, s, slen) == 0) - status_prompt_add_list(&list, size, (*cmdent)->name); - if ((*cmdent)->alias != NULL && - strncmp((*cmdent)->alias, s, slen) == 0) - status_prompt_add_list(&list, size, (*cmdent)->alias); - } - o = options_get_only(global_options, "command-alias"); - if (o != NULL) { - a = options_array_first(o); - while (a != NULL) { - value = options_array_item_value(a)->string; - if ((cp = strchr(value, '=')) == NULL) - goto next; - valuelen = cp - value; - if (slen > valuelen || strncmp(value, s, slen) != 0) - goto next; - - xasprintf(&tmp, "%.*s", (int)valuelen, value); - status_prompt_add_list(&list, size, tmp); - free(tmp); - - next: - a = options_array_next(a); - } - } - return (list); -} - -/* Find longest prefix. */ -static char * -status_prompt_complete_prefix(char **list, u_int size) -{ - char *out; - u_int i; - size_t j; - - if (list == NULL || size == 0) - return (NULL); - out = xstrdup(list[0]); - for (i = 1; i < size; i++) { - j = strlen(list[i]); - if (j > strlen(out)) - j = strlen(out); - for (; j > 0; j--) { - if (out[j - 1] != list[i][j - 1]) - out[j - 1] = '\0'; - } - } - return (out); -} - -/* Complete word menu callback. */ -static void -status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key, - void *data) -{ - struct status_prompt_menu *spm = data; - struct client *c = spm->c; - u_int i; - - if (key != KEYC_NONE) { - idx += spm->start; - if (status_prompt_replace_complete(c, spm->list[idx])) - c->flags |= CLIENT_REDRAWSTATUS; - } - - for (i = 0; i < spm->size; i++) - free(spm->list[i]); - free(spm->list); -} - -/* Show complete word menu. */ -static int -status_prompt_complete_list_menu(struct client *c, char **list, u_int size, - u_int offset) -{ - struct menu *menu; - struct menu_item item; - struct status_prompt_menu *spm; - u_int lines = status_line_size(c), height, i; - u_int py, ax, aw; - - if (size <= 1) - return (0); - if (c->tty.sy - lines < 3) - return (0); - - spm = xmalloc(sizeof *spm); - spm->c = c; - spm->size = size; - spm->list = list; - - height = c->tty.sy - lines - 2; - if (height > 10) - height = 10; - if (height > size) - height = size; - spm->start = size - height; - - menu = menu_create(""); - for (i = spm->start; i < size; i++) { - item.name = list[i]; - item.key = '0' + (i - spm->start); - item.command = NULL; - menu_add_item(menu, &item, NULL, c, NULL); - } - - status_prompt_area(c, &ax, &aw); - if (options_get_number(c->session->options, "status-position") == 0) - py = lines; - else - py = c->tty.sy - 3 - height; - offset += utf8_cstrwidth(c->prompt_string); - offset += ax; - if (offset > 2) - offset -= 2; - else - offset = 0; - - if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c, - BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, - status_prompt_menu_callback, spm) != 0) { - menu_free(menu); - free(spm); - return (0); - } - return (1); -} - -/* Sort complete list. */ -static int -status_prompt_complete_sort(const void *a, const void *b) -{ - const char **aa = (const char **)a, **bb = (const char **)b; - - return (strcmp(*aa, *bb)); -} - -/* Complete word. */ -static char * -status_prompt_complete(struct client *c, const char *word, u_int offset) -{ - char **list = NULL, *out = NULL; - u_int size = 0, i; - - if (c->prompt_type != PROMPT_TYPE_COMMAND || offset != 0 || - *word == '\0') - return (NULL); - - list = status_prompt_complete_list(&size, word); - if (size == 0) - out = NULL; - else if (size == 1) - xasprintf(&out, "%s ", list[0]); - else - out = status_prompt_complete_prefix(list, size); - - if (size != 0) { - qsort(list, size, sizeof *list, status_prompt_complete_sort); - for (i = 0; i < size; i++) - log_debug("complete %u: %s", i, list[i]); - } - - if (out != NULL && strcmp(word, out) == 0) { - free(out); - out = NULL; - } - if (out != NULL || - !status_prompt_complete_list_menu(c, list, size, offset)) { - for (i = 0; i < size; i++) - free(list[i]); - free(list); - } - return (out); -} - -/* Return the type of the prompt as an enum. */ -enum prompt_type -status_prompt_type(const char *type) -{ - u_int i; - - for (i = 0; i < PROMPT_NTYPES; i++) { - if (strcmp(type, status_prompt_type_string(i)) == 0) - return (i); - } - return (PROMPT_TYPE_INVALID); -} - -/* Accessor for prompt_type_strings. */ -const char * -status_prompt_type_string(u_int type) -{ - if (type >= PROMPT_NTYPES) - return ("invalid"); - return (prompt_type_strings[type]); -} diff --git a/tmux.1 b/tmux.1 index c9457cc47..feeb84393 100644 --- a/tmux.1 +++ b/tmux.1 @@ -7359,7 +7359,7 @@ See for possible values for .Ar prompt\-type . .It Xo Ic command\-prompt -.Op Fl 1bCeFiklN +.Op Fl 1bCeFiklNP .Op Fl I Ar inputs .Op Fl p Ar prompts .Op Fl t Ar target\-client @@ -7439,6 +7439,8 @@ user exits the command prompt. makes .Em BSpace cancel an empty prompt. +.Fl P +opens a prompt inside a pane instead of on the status line. .Pp .Fl T tells diff --git a/tmux.h b/tmux.h index cce45b652..07d0922dc 100644 --- a/tmux.h +++ b/tmux.h @@ -62,6 +62,8 @@ struct mouse_event; struct options; struct options_array_item; struct options_entry; +struct prompt; +struct window_pane_prompt; struct redraw_scene; struct redraw_span; struct screen_write_citem; @@ -1296,6 +1298,10 @@ struct window_pane { char *searchstr; int searchregex; + struct prompt *prompt; + struct window_pane_prompt *prompt_data; + u_int prompt_cx; + int border_gc_set; struct grid_cell border_gc; int active_border_gc_set; @@ -1936,20 +1942,12 @@ struct status_line { struct screen *active; int references; + u_int prompt_cx; + struct grid_cell style; struct style_line_entry entries[STATUS_LINES_LIMIT]; }; -/* Prompt type. */ -#define PROMPT_NTYPES 4 -enum prompt_type { - PROMPT_TYPE_COMMAND, - PROMPT_TYPE_SEARCH, - PROMPT_TYPE_TARGET, - PROMPT_TYPE_WINDOW_TARGET, - PROMPT_TYPE_INVALID = 0xff -}; - /* File in client. */ typedef void (*client_file_cb) (struct client *, const char *, int, int, struct evbuffer *, void *); @@ -1990,6 +1988,14 @@ RB_HEAD(client_windows, client_window); /* Maximum time to be pasting. */ #define CLIENT_PASTE_TIME_LIMIT 5 +/* Prompt type. */ +#define PROMPT_NTYPES 2 +enum prompt_type { + PROMPT_TYPE_COMMAND, + PROMPT_TYPE_SEARCH, + PROMPT_TYPE_INVALID = 0xff +}; + /* Prompt result. */ enum prompt_result { PROMPT_CONTINUE, @@ -2005,11 +2011,63 @@ enum prompt_key_result { }; /* Prompt callbacks. */ -typedef enum prompt_result (*prompt_input_cb)(struct client *, void *, +typedef enum prompt_result (*prompt_input_cb)(void *, const char *, + enum prompt_key_result); +typedef enum prompt_result (*status_prompt_input_cb)(struct client *, void *, + const char *, enum prompt_key_result); +typedef enum prompt_result (*mode_tree_prompt_input_cb)(struct client *, void *, const char *, enum prompt_key_result); typedef void (*prompt_free_cb)(void *); -/* Overlay callbacks. */ +/* Prompt flags. */ +#define PROMPT_SINGLE 0x1 +#define PROMPT_NUMERIC 0x2 +#define PROMPT_INCREMENTAL 0x4 +#define PROMPT_NOFORMAT 0x8 +#define PROMPT_KEY 0x10 +#define PROMPT_ACCEPT 0x20 +#define PROMPT_QUOTENEXT 0x40 +#define PROMPT_BSPACE_EXIT 0x80 +#define PROMPT_NOFREEZE 0x100 +#define PROMPT_COMMANDMODE 0x200 +#define PROMPT_ISPANE 0x400 +#define PROMPT_ISMODE 0x800 + +/* Prompt create data. */ +struct prompt_create_data { + struct cmd_find_state *fs; + const char *prompt; + const char *input; + enum prompt_type type; + int flags; + + struct grid_cell style; + struct grid_cell command_style; + enum screen_cursor_style cstyle; + enum screen_cursor_style command_cstyle; + int ccolour; + int cmode; + int command_cmode; + const char *message_format; + int keys; + const char *word_separators; + + prompt_input_cb inputcb; + prompt_free_cb freecb; + void *data; +}; + +/* Prompt draw data. */ +struct prompt_draw_data { + struct screen_write_ctx *ctx; + u_int *cursor_x; + + u_int area_x; + u_int area_width; + u_int prompt_line; +}; + +/* Overlay callbacks */ typedef struct visible_ranges *(*overlay_check_cb)(struct client *, void *, u_int, u_int, u_int); typedef struct screen *(*overlay_mode_cb)(struct client *, void *, u_int *, @@ -2154,29 +2212,7 @@ struct client { char *message_string; struct event message_timer; - char *prompt_string; - struct utf8_data *prompt_buffer; - struct cmd_find_state prompt_state; - char *prompt_last; - size_t prompt_index; - prompt_input_cb prompt_inputcb; - prompt_free_cb prompt_freecb; - void *prompt_data; - u_int prompt_hindex[PROMPT_NTYPES]; - struct utf8_data *prompt_saved; -#define PROMPT_SINGLE 0x1 -#define PROMPT_NUMERIC 0x2 -#define PROMPT_INCREMENTAL 0x4 -#define PROMPT_NOFORMAT 0x8 -#define PROMPT_KEY 0x10 -#define PROMPT_ACCEPT 0x20 -#define PROMPT_QUOTENEXT 0x40 -#define PROMPT_BSPACE_EXIT 0x80 -#define PROMPT_NOFREEZE 0x100 -#define PROMPT_COMMANDMODE 0x200 - int prompt_flags; - enum prompt_type prompt_type; - int prompt_cursor; + struct prompt *prompt; struct session *session; struct session *last_session; @@ -3100,8 +3136,6 @@ void server_check_unattached(void); void server_unzoom_window(struct window *); /* status.c */ -extern char **status_prompt_hlist[]; -extern u_int status_prompt_hsize[]; void status_timer_start(struct client *); void status_timer_start_all(void); void status_update_cache(struct session *); @@ -3117,16 +3151,38 @@ void printflike(6, 7) status_message_set(struct client *, int, int, int, int, void status_message_clear(struct client *); int status_message_redraw(struct client *); void status_prompt_set(struct client *, struct cmd_find_state *, - const char *, const char *, prompt_input_cb, prompt_free_cb, + const char *, const char *, status_prompt_input_cb, prompt_free_cb, void *, int, enum prompt_type); void status_prompt_clear(struct client *); int status_prompt_redraw(struct client *); -enum prompt_key_result status_prompt_key(struct client *, key_code); +void status_prompt_cursor(struct client *, u_int *, u_int *); +enum prompt_key_result status_prompt_key(struct client *, key_code, + struct mouse_event *); void status_prompt_update(struct client *, const char *, const char *); -void status_prompt_load_history(void); -void status_prompt_save_history(void); -const char *status_prompt_type_string(u_int); -enum prompt_type status_prompt_type(const char *type); + +/* prompt.c */ +void prompt_set_options(struct prompt_create_data *, struct session *); +struct prompt *prompt_create(const struct prompt_create_data *); +void prompt_free(struct prompt *); +void prompt_incremental_start(struct prompt *); +void prompt_draw(struct prompt *, struct prompt_draw_data *); +enum prompt_key_result prompt_key(struct prompt *, key_code, int *); +enum prompt_key_result prompt_mouse(struct prompt *, u_int, u_int, u_int, + int *); +void prompt_update(struct prompt *, const char *, const char *); +int prompt_closed(struct prompt *); +enum prompt_type prompt_type(const char *); +const char *prompt_type_string(enum prompt_type); + +/* prompt-history.c */ +const char *prompt_up_history(u_int *, u_int); +const char *prompt_down_history(u_int *, u_int); +void prompt_add_history(const char *, u_int); +u_int prompt_history_size(enum prompt_type); +const char *prompt_history_get(enum prompt_type, u_int); +void prompt_history_clear(enum prompt_type); +void prompt_load_history(void); +void prompt_save_history(void); /* resize.c */ void resize_window(struct window *, u_int, u_int, int, int); @@ -3452,6 +3508,16 @@ int window_pane_key(struct window_pane *, struct client *, struct mouse_event *); void window_pane_paste(struct window_pane *, key_code, char *, size_t); +void window_pane_set_prompt(struct window_pane *, struct client *, + struct cmd_find_state *, const char *, const char *, + status_prompt_input_cb, prompt_free_cb, void *, int, + enum prompt_type); +void window_pane_clear_prompt(struct window_pane *); +int window_pane_has_prompt(struct window_pane *); +void window_pane_update_prompt(struct window_pane *, const char *, + const char *); +enum prompt_key_result window_pane_prompt_key(struct window_pane *, + struct client *, key_code, struct mouse_event *); int window_pane_is_visible(struct window_pane *); int window_pane_exited(struct window_pane *); u_int window_pane_search(struct window_pane *, const char *, int, @@ -3612,6 +3678,11 @@ void mode_tree_remove(struct mode_tree_data *, struct mode_tree_item *); void mode_tree_draw(struct mode_tree_data *); int mode_tree_key(struct mode_tree_data *, struct client *, key_code *, struct mouse_event *, u_int *, u_int *); +void mode_tree_set_prompt(struct mode_tree_data *, struct client *, + const char *, const char *, enum prompt_type, int, + mode_tree_prompt_input_cb, prompt_free_cb, void *); +void mode_tree_clear_prompt(struct mode_tree_data *); +int mode_tree_has_prompt(struct mode_tree_data *); void mode_tree_run_command(struct client *, struct cmd_find_state *, const char *, const char *); diff --git a/window-customize.c b/window-customize.c index 1e954fca9..559593873 100644 --- a/window-customize.c +++ b/window-customize.c @@ -1145,10 +1145,10 @@ window_customize_set_option(struct client *c, new_item->idx = idx; data->references++; - status_prompt_set(c, NULL, prompt, value, + mode_tree_set_prompt(data->data, c, prompt, value, + PROMPT_TYPE_COMMAND, PROMPT_NOFORMAT, window_customize_set_option_callback, - window_customize_free_item_callback, new_item, - PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + window_customize_free_item_callback, new_item); free(prompt); free(value); @@ -1283,10 +1283,10 @@ window_customize_set_key(struct client *c, new_item->key = key; data->references++; - status_prompt_set(c, NULL, prompt, value, + mode_tree_set_prompt(data->data, c, prompt, value, + PROMPT_TYPE_COMMAND, PROMPT_NOFORMAT, window_customize_set_command_callback, - window_customize_free_item_callback, new_item, - PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + window_customize_free_item_callback, new_item); free(prompt); free(value); } else if (strcmp(s, "Note") == 0) { @@ -1299,11 +1299,11 @@ window_customize_set_key(struct client *c, new_item->key = key; data->references++; - status_prompt_set(c, NULL, prompt, + mode_tree_set_prompt(data->data, c, prompt, (bd->note == NULL ? "" : bd->note), + PROMPT_TYPE_COMMAND, PROMPT_NOFORMAT, window_customize_set_note_callback, - window_customize_free_item_callback, new_item, - PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + window_customize_free_item_callback, new_item); free(prompt); } } @@ -1477,11 +1477,11 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, xasprintf(&prompt, "Reset %s to default? ", item->name); data->references++; data->change = WINDOW_CUSTOMIZE_RESET; - status_prompt_set(c, NULL, prompt, "", - window_customize_change_current_callback, - window_customize_free_callback, data, + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + window_customize_change_current_callback, + window_customize_free_callback, data); free(prompt); break; case 'D': @@ -1491,11 +1491,11 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, xasprintf(&prompt, "Reset %u tagged to default? ", tagged); data->references++; data->change = WINDOW_CUSTOMIZE_RESET; - status_prompt_set(c, NULL, prompt, "", - window_customize_change_tagged_callback, - window_customize_free_callback, data, + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + window_customize_change_tagged_callback, + window_customize_free_callback, data); free(prompt); break; case 'u': @@ -1508,11 +1508,11 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, xasprintf(&prompt, "Unset %s? ", item->name); data->references++; data->change = WINDOW_CUSTOMIZE_UNSET; - status_prompt_set(c, NULL, prompt, "", - window_customize_change_current_callback, - window_customize_free_callback, data, + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + window_customize_change_current_callback, + window_customize_free_callback, data); free(prompt); break; case 'U': @@ -1522,11 +1522,11 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, xasprintf(&prompt, "Unset %u tagged? ", tagged); data->references++; data->change = WINDOW_CUSTOMIZE_UNSET; - status_prompt_set(c, NULL, prompt, "", - window_customize_change_tagged_callback, - window_customize_free_callback, data, + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + window_customize_change_tagged_callback, + window_customize_free_callback, data); free(prompt); break; case 'H': diff --git a/window-tree.c b/window-tree.c index 30c564dfd..58a8bbb9b 100644 --- a/window-tree.c +++ b/window-tree.c @@ -1317,10 +1317,11 @@ again: if (prompt == NULL) break; data->references++; - status_prompt_set(c, NULL, prompt, "", + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, + PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, window_tree_kill_current_callback, window_tree_command_free, - data, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + data); free(prompt); break; case 'X': @@ -1329,10 +1330,11 @@ again: break; xasprintf(&prompt, "Kill %u tagged? ", tagged); data->references++; - status_prompt_set(c, NULL, prompt, "", + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, + PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, window_tree_kill_tagged_callback, window_tree_command_free, - data, PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags, - PROMPT_TYPE_COMMAND); + data); free(prompt); break; case ':': @@ -1342,9 +1344,10 @@ again: else xasprintf(&prompt, "(current) "); data->references++; - status_prompt_set(c, NULL, prompt, "", + mode_tree_set_prompt(data->data, c, prompt, "", + PROMPT_TYPE_COMMAND, PROMPT_NOFORMAT, window_tree_command_callback, window_tree_command_free, - data, PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + data); free(prompt); break; case '\r': diff --git a/window.c b/window.c index 8a388001a..9d3106a22 100644 --- a/window.c +++ b/window.c @@ -80,6 +80,14 @@ RB_GENERATE(windows, window, entry, window_cmp); RB_GENERATE(winlinks, winlink, entry, winlink_cmp); RB_GENERATE(window_pane_tree, window_pane, tree_entry, window_pane_cmp); +struct window_pane_prompt { + u_int wp_id; + struct client *c; + status_prompt_input_cb inputcb; + prompt_free_cb freecb; + void *data; +}; + int window_cmp(struct window *w1, struct window *w2) { @@ -1148,6 +1156,8 @@ window_pane_destroy(struct window_pane *wp) window_pane_wait_finish(wp); spawn_editor_finish(wp); + window_pane_clear_prompt(wp); + window_pane_free_modes(wp); free(wp->searchstr); @@ -1360,6 +1370,7 @@ window_pane_reset_mode(struct window_pane *wp) server_kill_pane(wp); } +/* Reset all modes. */ void window_pane_reset_mode_all(struct window_pane *wp) { @@ -1367,6 +1378,165 @@ window_pane_reset_mode_all(struct window_pane *wp) window_pane_reset_mode(wp); } +/* Prompt input callback. */ +static enum prompt_result +window_pane_prompt_input_callback(void *data, const char *s, + enum prompt_key_result key) +{ + struct window_pane_prompt *wpp = data; + + if (wpp->inputcb != NULL) + return (wpp->inputcb(wpp->c, wpp->data, s, key)); + return (PROMPT_CLOSE); +} + +/* Prompt free callback. */ +static void +window_pane_prompt_free_callback(void *data) +{ + struct window_pane_prompt *wpp = data; + struct window_pane *wp; + + wp = window_pane_find_by_id(wpp->wp_id); + if (wp != NULL && wp->prompt_data == wpp) + wp->prompt_data = NULL; + if (wpp->freecb != NULL) + wpp->freecb(wpp->data); + free(wpp); +} + +/* Open a prompt owned by a pane, drawn over the pane instead of the status. */ +void +window_pane_set_prompt(struct window_pane *wp, struct client *c, + struct cmd_find_state *fs, const char *msg, const char *input, + status_prompt_input_cb inputcb, prompt_free_cb freecb, void *data, + int flags, enum prompt_type type) +{ + struct session *s = NULL; + struct prompt_create_data pd; + struct window_pane_prompt *wpp; + + if (c != NULL) + s = c->session; + + window_pane_clear_prompt(wp); + + wpp = xcalloc(1, sizeof *wpp); + wpp->wp_id = wp->id; + wpp->c = c; + wpp->inputcb = inputcb; + wpp->freecb = freecb; + wpp->data = data; + + memset(&pd, 0, sizeof pd); + prompt_set_options(&pd, s); + pd.fs = fs; + pd.prompt = msg; + pd.input = input; + pd.type = type; + pd.flags = flags; + pd.inputcb = window_pane_prompt_input_callback; + pd.freecb = window_pane_prompt_free_callback; + pd.data = wpp; + + wp->prompt = prompt_create(&pd); + wp->prompt_data = wpp; + wp->flags |= PANE_REDRAW; + + prompt_incremental_start(wp->prompt); +} + +/* Close a pane prompt. */ +void +window_pane_clear_prompt(struct window_pane *wp) +{ + struct prompt *prompt = wp->prompt; + + if (prompt == NULL) + return; + wp->prompt = NULL; + prompt_free(prompt); + wp->flags |= PANE_REDRAW; +} + +/* Does this pane have an open prompt? */ +int +window_pane_has_prompt(struct window_pane *wp) +{ + return (wp->prompt != NULL); +} + +/* Replace the message and input of an open pane prompt. */ +void +window_pane_update_prompt(struct window_pane *wp, const char *msg, + const char *input) +{ + if (wp->prompt != NULL) { + prompt_update(wp->prompt, msg, input); + wp->flags |= PANE_REDRAW; + } +} + +/* + * Pass a key to a pane prompt. The client is set transiently for the duration + * of the key in case the prompt or pane is destroyed by the callback. + */ +enum prompt_key_result +window_pane_prompt_key(struct window_pane *wp, struct client *c, key_code key, + struct mouse_event *m) +{ + struct prompt *prompt = wp->prompt; + struct window_pane_prompt *wpp = wp->prompt_data; + enum prompt_key_result result; + u_int wp_id = wp->id, x, y, py; + int redraw = 0; + + if (prompt == NULL) + return (PROMPT_KEY_NOT_HANDLED); + + if (wpp != NULL) + wpp->c = c; + if (KEYC_IS_MOUSE(key)) { + if (m == NULL || + MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1 || + MOUSE_DRAG(m->b) || + MOUSE_RELEASE(m->b) || + cmd_mouse_at(wp, m, &x, &y, 0) != 0) + result = PROMPT_KEY_NOT_HANDLED; + else { + if (c != NULL && status_at_line(c) == 0) + py = 0; + else + py = wp->sy - 1; + if (y == py) { + result = prompt_mouse(prompt, x, 0, wp->sx, + &redraw); + } else + result = PROMPT_KEY_NOT_HANDLED; + } + } else + result = prompt_key(prompt, key, &redraw); + + wp = window_pane_find_by_id(wp_id); + if (wp == NULL) + return (result); + if (wpp != NULL && wp->prompt_data == wpp) + wpp->c = NULL; + + /* + * Only an explicit close or the prompt marking itself closed ends it; + * cursor movement and editing keep it open. + */ + if (wp->prompt == prompt && + (result == PROMPT_KEY_CLOSE || prompt_closed(prompt))) + window_pane_clear_prompt(wp); + + if (redraw || wp->prompt != prompt) + wp->flags |= PANE_REDRAW; + + return (result); +} + static void window_pane_copy_paste(struct window_pane *wp, char *buf, size_t len) {