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) {