From e5106bfb9660d5c9470acf9defa1084c61a070d4 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:21:09 +0000 Subject: [PATCH 01/29] Add another couple of keys needed for extended keys, GitHub issue 2658. Handle modifier 9 as Meta, GitHub issue 2647. --- key-bindings.c | 6 ++++++ key-string.c | 10 +++++++--- tty-keys.c | 11 +++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/key-bindings.c b/key-bindings.c index b47f6ff7..467c7f93 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -215,6 +215,9 @@ key_bindings_add(const char *name, key_code key, const char *note, int repeat, if (repeat) bd->flags |= KEY_BINDING_REPEAT; bd->cmdlist = cmdlist; + + log_debug("%s: %#llx %s = %s", __func__, bd->key, + key_string_lookup_key(bd->key, 1), cmd_list_print(bd->cmdlist, 0)); } void @@ -231,6 +234,9 @@ key_bindings_remove(const char *name, key_code key) if (bd == NULL) return; + log_debug("%s: %#llx %s", __func__, bd->key, + key_string_lookup_key(bd->key, 1)); + RB_REMOVE(key_bindings, &table->key_bindings, bd); key_bindings_free(bd); diff --git a/key-string.c b/key-string.c index 8d60f132..c24a33fc 100644 --- a/key-string.c +++ b/key-string.c @@ -164,7 +164,7 @@ key_string_get_modifiers(const char **string) key_code key_string_lookup_string(const char *string) { - static const char *other = "!#()+,-.0123456789:;<=>'\r\t\177"; + static const char *other = "!#()+,-.0123456789:;<=>'\r\t\177`/"; key_code key, modifiers; u_int u, i; struct utf8_data ud, *udp; @@ -238,8 +238,12 @@ key_string_lookup_string(const char *string) } /* Convert the standard control keys. */ - if (key < KEYC_BASE && (modifiers & KEYC_CTRL) && - strchr(other, key) == NULL) { + if (key < KEYC_BASE && + (modifiers & KEYC_CTRL) && + strchr(other, key) == NULL && + key != 9 && + key != 13 && + key != 27) { if (key >= 97 && key <= 122) key -= 96; else if (key >= 64 && key <= 95) diff --git a/tty-keys.c b/tty-keys.c index 040e7005..3012be3d 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -252,7 +252,8 @@ static const key_code tty_default_xterm_modifiers[] = { KEYC_CTRL, KEYC_SHIFT|KEYC_CTRL, KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL, - KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL + KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL, + KEYC_META|KEYC_IMPLIED_META }; /* @@ -944,6 +945,9 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len, case 8: nkey |= (KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL); break; + case 9: + nkey |= (KEYC_META|KEYC_IMPLIED_META); + break; default: *key = KEYC_NONE; break; @@ -955,7 +959,10 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len, */ if (nkey & KEYC_CTRL) { onlykey = (nkey & KEYC_MASK_KEY); - if (onlykey < 32 && onlykey != 9) + if (onlykey < 32 && + onlykey != 9 && + onlykey != 13 && + onlykey != 27) /* nothing */; else if (onlykey >= 97 && onlykey <= 122) onlykey -= 96; From c46a607dc10606047f685f29a71213501de819d1 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:21:46 +0000 Subject: [PATCH 02/29] Adjust latest client when a client detaches, GitHub issue 2657. --- server-client.c | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/server-client.c b/server-client.c index 25d61376..1114396d 100644 --- a/server-client.c +++ b/server-client.c @@ -46,6 +46,7 @@ static void server_client_check_modes(struct client *); static void server_client_set_title(struct client *); static void server_client_reset_state(struct client *); static int server_client_assume_paste(struct session *); +static void server_client_update_latest(struct client *); static void server_client_dispatch(struct imsg *, void *); static void server_client_dispatch_command(struct client *, struct imsg *); @@ -274,6 +275,40 @@ server_client_open(struct client *c, char **cause) return (0); } +/* Lost an attached client. */ +static void +server_client_attached_lost(struct client *c) +{ + struct session *s = c->session; + struct window *w; + struct client *loop; + struct client *found; + + log_debug("lost attached client %p", c); + + /* + * By this point the session in the client has been cleared so walk all + * windows to find any with this client as the latest. + */ + RB_FOREACH(w, windows, &windows) { + if (w->latest != c) + continue; + + found = NULL; + TAILQ_FOREACH(loop, &clients, entry) { + s = loop->session; + if (loop == c || s == NULL || s->curw->window != w) + continue; + if (found == NULL || + timercmp(&loop->activity_time, &found->activity_time, + >)) + found = loop; + } + if (found != NULL) + server_client_update_latest(found); + } +} + /* Lost a client. */ void server_client_lost(struct client *c) @@ -299,8 +334,10 @@ server_client_lost(struct client *c) TAILQ_REMOVE(&clients, c, entry); log_debug("lost client %p", c); - if (c->flags & CLIENT_ATTACHED) + if (c->flags & CLIENT_ATTACHED) { + server_client_attached_lost(c); notify_client("client-detached", c); + } if (c->flags & CLIENT_CONTROL) control_stop(c); From 788f56b40a695b21d5ee861d2789f354608cda81 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:22:06 +0000 Subject: [PATCH 03/29] Fix display-menu -xR, from Alexis Hildebrandt. --- cmd-display-menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-display-menu.c b/cmd-display-menu.c index 27a4c1d7..f2100bdb 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -206,7 +206,7 @@ cmd_display_menu_get_position(struct client *tc, struct cmdq_item *item, if (xp == NULL || strcmp(xp, "C") == 0) xp = "#{popup_centre_x}"; else if (strcmp(xp, "R") == 0) - xp = "#{popup_right}"; + xp = "#{popup_pane_right}"; else if (strcmp(xp, "P") == 0) xp = "#{popup_pane_left}"; else if (strcmp(xp, "M") == 0) From 1ee231956ca9d1436e101350f4246abc1f157224 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:22:37 +0000 Subject: [PATCH 04/29] back-to-indentation fixes, from Anindya Mukherjee. --- grid-reader.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/grid-reader.c b/grid-reader.c index 89fe90fb..df0dd450 100644 --- a/grid-reader.c +++ b/grid-reader.c @@ -371,19 +371,26 @@ void grid_reader_cursor_back_to_indentation(struct grid_reader *gr) { struct grid_cell gc; - u_int px, py, xx, yy; + u_int px, py, xx, yy, oldx, oldy; yy = gr->gd->hsize + gr->gd->sy - 1; + oldx = gr->cx; + oldy = gr->cy; grid_reader_cursor_start_of_line(gr, 1); for (py = gr->cy; py <= yy; py++) { xx = grid_line_length(gr->gd, py); for (px = 0; px < xx; px++) { grid_get_cell(gr->gd, px, py, &gc); - if (gc.data.size != 1 || *gc.data.data != ' ') - break; + if (gc.data.size != 1 || *gc.data.data != ' ') { + gr->cx = px; + gr->cy = py; + return; + } } if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED) break; } + gr->cx = oldx; + gr->cy = oldy; } From 64c276c23bd729c03c2a3ff00c6f9e6ac4c644c6 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:24:10 +0000 Subject: [PATCH 05/29] Add an "always" value to the extended-keys option to always forward these keys to applications inside tmux. --- input.c | 2 ++ options-table.c | 6 +++++- screen.c | 4 +++- tmux.1 | 25 +++++++++++++++++++------ 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/input.c b/input.c index f6aeb027..b599f27d 100644 --- a/input.c +++ b/input.c @@ -1390,6 +1390,8 @@ input_csi_dispatch(struct input_ctx *ictx) case INPUT_CSI_MODSET: n = input_get(ictx, 0, 0, 0); m = input_get(ictx, 1, 0, 0); + if (options_get_number(global_options, "extended-keys") == 2) + break; if (n == 0 || (n == 4 && m == 0)) screen_write_mode_clear(sctx, MODE_KEXTENDED); else if (n == 4 && (m == 1 || m == 2)) diff --git a/options-table.c b/options-table.c index 8ea56918..4f4d9ff6 100644 --- a/options-table.c +++ b/options-table.c @@ -75,6 +75,9 @@ static const char *options_table_remain_on_exit_list[] = { static const char *options_table_detach_on_destroy_list[] = { "off", "on", "no-detached", NULL }; +static const char *options_table_extended_keys_list[] = { + "off", "on", "always", NULL +}; /* Status line format. */ #define OPTIONS_TABLE_STATUS_FORMAT1 \ @@ -267,8 +270,9 @@ const struct options_table_entry options_table[] = { }, { .name = "extended-keys", - .type = OPTIONS_TABLE_FLAG, + .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SERVER, + .choices = options_table_extended_keys_list, .default_num = 0, .text = "Whether to request extended key sequences from terminals " "that support it." diff --git a/screen.c b/screen.c index 97ccdeec..0b1047ab 100644 --- a/screen.c +++ b/screen.c @@ -101,7 +101,9 @@ screen_reinit(struct screen *s) s->rupper = 0; s->rlower = screen_size_y(s) - 1; - s->mode = MODE_CURSOR | MODE_WRAP; + s->mode = MODE_CURSOR|MODE_WRAP; + if (options_get_number(global_options, "extended-keys") == 2) + s->mode |= MODE_KEXTENDED; if (s->saved_grid != NULL) screen_alternate_off(s, NULL, 0); diff --git a/tmux.1 b/tmux.1 index f5689bfa..146c998f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3418,11 +3418,24 @@ sessions. .Xc If enabled, the server will exit when there are no attached clients. .It Xo Ic extended-keys -.Op Ic on | off +.Op Ic on | off | always .Xc -When enabled, extended keys are requested from the terminal and if supported -are recognised by -.Nm . +When +.Ic on +or +.Ic always , +the escape sequence to enable extended keys is sent to the terminal, if +.Nm +knows that it is supported. +.Nm +always recognises extended keys itself. +If this option is +.Ic on , +.Nm +will only forward extended keys to applications when they request them; if +.Ic always , +.Nm +will always forward the keys. .It Xo Ic focus-events .Op Ic on | off .Xc @@ -3501,8 +3514,8 @@ capabilities to be set instead, is intended for classes of functionality supported in a standard way but not reported by .Xr terminfo 5 . -Care must be taken only to configure this with features the terminal actually -support. +Care must be taken to configure this only with features the terminal actually +supports. .Pp This is an array option where each entry is a colon-separated string made up of a terminal type pattern (matched using From 9f38a8807c6c6b3b7596f06c2bb662242337ac48 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:24:45 +0000 Subject: [PATCH 06/29] Include current client in size calculation for new sessions, GitHub issue 2662. --- cmd-new-session.c | 1 + resize.c | 47 +++++++++++++++++++++++++++++++++-------------- status.c | 2 ++ window.c | 2 ++ 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/cmd-new-session.c b/cmd-new-session.c index cc3494de..c47d66cd 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -269,6 +269,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) memset(&sc, 0, sizeof sc); sc.item = item; sc.s = s; + sc.tc = c; sc.name = args_get(args, 'n'); sc.argc = args->argc; diff --git a/resize.c b/resize.c index 172cbcb5..66cb9430 100644 --- a/resize.c +++ b/resize.c @@ -108,17 +108,19 @@ clients_with_window(struct window *w) } static int -clients_calculate_size(int type, int current, struct session *s, - struct window *w, int (*skip_client)(struct client *, int, int, - struct session *, struct window *), u_int *sx, u_int *sy, u_int *xpixel, - u_int *ypixel) +clients_calculate_size(int type, int current, struct client *c, + struct session *s, struct window *w, int (*skip_client)(struct client *, + int, int, struct session *, struct window *), u_int *sx, u_int *sy, + u_int *xpixel, u_int *ypixel) { struct client *loop; u_int cx, cy, n = 0; /* Manual windows do not have their size changed based on a client. */ - if (type == WINDOW_SIZE_MANUAL) + if (type == WINDOW_SIZE_MANUAL) { + log_debug("%s: type is manual", __func__); return (0); + } /* * Start comparing with 0 for largest and UINT_MAX for smallest or @@ -139,18 +141,24 @@ clients_calculate_size(int type, int current, struct session *s, /* Loop over the clients and work out the size. */ TAILQ_FOREACH(loop, &clients, entry) { - if (ignore_client_size(loop)) + if (loop != c && ignore_client_size(loop)) { + log_debug("%s: ignoring %s", __func__, loop->name); continue; - if (skip_client(loop, type, current, s, w)) + } + if (loop != c && skip_client(loop, type, current, s, w)) { + log_debug("%s: skipping %s", __func__, loop->name); continue; + } /* * If there are multiple clients attached, only accept the * latest client; otherwise let the only client be chosen as * for smallest. */ - if (type == WINDOW_SIZE_LATEST && n > 1 && loop != w->latest) + if (type == WINDOW_SIZE_LATEST && n > 1 && loop != w->latest) { + log_debug("%s: %s is not latest", __func__, loop->name); continue; + } /* Work out this client's size. */ cx = loop->tty.sx; @@ -175,16 +183,24 @@ clients_calculate_size(int type, int current, struct session *s, *xpixel = loop->tty.xpixel; *ypixel = loop->tty.ypixel; } + log_debug("%s: after %s (%ux%u), size is %ux%u", __func__, + loop->name, cx, cy, *sx, *sy); } /* Return whether a suitable size was found. */ - if (type == WINDOW_SIZE_LARGEST) + if (type == WINDOW_SIZE_LARGEST) { + log_debug("%s: type is largest", __func__); return (*sx != 0 && *sy != 0); + } + if (type == WINDOW_SIZE_LATEST) + log_debug("%s: type is latest", __func__); + else + log_debug("%s: type is smallest", __func__); return (*sx != UINT_MAX && *sy != UINT_MAX); } static int -default_window_size_skip_client (struct client *loop, int type, +default_window_size_skip_client(struct client *loop, int type, __unused int current, struct session *s, struct window *w) { /* @@ -221,23 +237,25 @@ default_window_size(struct client *c, struct session *s, struct window *w, *sy = c->tty.sy - status_line_size(c); *xpixel = c->tty.xpixel; *ypixel = c->tty.ypixel; + log_debug("%s: using %ux%u from %s", __func__, *sx, *sy, + c->name); goto done; } - if (w == NULL) - type = WINDOW_SIZE_MANUAL; } /* * Look for a client to base the size on. If none exists (or the type * is manual), use the default-size option. */ - if (!clients_calculate_size(type, 0, s, w, + if (!clients_calculate_size(type, 0, c, s, w, default_window_size_skip_client, sx, sy, xpixel, ypixel)) { value = options_get_string(s->options, "default-size"); if (sscanf(value, "%ux%u", sx, sy) != 2) { *sx = 80; *sy = 24; } + log_debug("%s: using %ux%u from default-size", __func__, *sx, + *sy); } done: @@ -250,6 +268,7 @@ done: *sy = WINDOW_MINIMUM; if (*sy > WINDOW_MAXIMUM) *sy = WINDOW_MAXIMUM; + log_debug("%s: resulting size is %ux%u", __func__, *sx, *sy); } static int @@ -289,7 +308,7 @@ recalculate_size(struct window *w, int now) current = options_get_number(w->options, "aggressive-resize"); /* Look for a suitable client and get the new size. */ - changed = clients_calculate_size(type, current, NULL, w, + changed = clients_calculate_size(type, current, NULL, NULL, w, recalculate_size_skip_client, &sx, &sy, &xpixel, &ypixel); /* diff --git a/status.c b/status.c index f9786f4b..271e1afa 100644 --- a/status.c +++ b/status.c @@ -226,6 +226,8 @@ status_line_size(struct client *c) if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) return (0); + if (s == NULL) + return (options_get_number(global_s_options, "status")); return (s->statuslines); } diff --git a/window.c b/window.c index 36cf12d9..519f91b1 100644 --- a/window.c +++ b/window.c @@ -331,6 +331,8 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel) window_update_activity(w); + log_debug("%s: @%u create %ux%u (%ux%u)", __func__, w->id, sx, sy, + w->xpixel, w->ypixel); return (w); } From 0c5cbbbf5cd09598ff2f3d6d1ae99fa353ec5b40 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:28:45 +0000 Subject: [PATCH 07/29] Three changes to fix problems with xterm in VT340 mode, reported by Thomas Sattler. 1) Do not include the DECSLRM or DECFRA features for xterm; they will be added instead if secondary DA responds as VT420 (this happens already). 2) Set or reset the individual flags after terminal-overrides is applied, so the user can properly disable them. 3) Add a capability for DECFRA ("Rect"). --- tmux.1 | 4 +++ tmux.h | 1 + tty-features.c | 13 +++++-- tty-term.c | 98 ++++++++++++++++++++++++++++++++------------------ 4 files changed, 80 insertions(+), 36 deletions(-) diff --git a/tmux.1 b/tmux.1 index 146c998f..d2ad593f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6049,6 +6049,10 @@ Disable and enable focus reporting. These are set automatically if the .Em XT capability is present. +.It Em \&Rect +Tell +.Nm +that the terminal supports rectangle operations. .It Em \&Smol Enable the overline attribute. .It Em \&Smulx diff --git a/tmux.h b/tmux.h index fb2f35f9..1947009f 100644 --- a/tmux.h +++ b/tmux.h @@ -451,6 +451,7 @@ enum tty_code_code { TTYC_MS, TTYC_OL, TTYC_OP, + TTYC_RECT, TTYC_REV, TTYC_RGB, TTYC_RI, diff --git a/tty-features.c b/tty-features.c index f167a2d3..b42cf74a 100644 --- a/tty-features.c +++ b/tty-features.c @@ -218,9 +218,13 @@ static const struct tty_feature tty_feature_margins = { }; /* Terminal supports DECFRA rectangle fill. */ +static const char *tty_feature_rectfill_capabilities[] = { + "Rect", + NULL +}; static const struct tty_feature tty_feature_rectfill = { "rectfill", - NULL, + tty_feature_rectfill_capabilities, TERM_DECFRA }; @@ -351,8 +355,13 @@ tty_default_features(int *feat, const char *name, u_int version) ",cstyle,extkeys,margins,sync" }, { .name = "XTerm", + /* + * xterm also supports DECSLRM and DECFRA, but they can be + * disabled so not set it here - they will be added if + * secondary DA shows VT420. + */ .features = TTY_FEATURES_BASE_MODERN_XTERM - ",ccolour,cstyle,extkeys,focus,margins,rectfill" + ",ccolour,cstyle,extkeys,focus" } }; u_int i; diff --git a/tty-term.c b/tty-term.c index 9df50948..275efe2f 100644 --- a/tty-term.c +++ b/tty-term.c @@ -248,6 +248,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_MS] = { TTYCODE_STRING, "Ms" }, [TTYC_OL] = { TTYCODE_STRING, "ol" }, [TTYC_OP] = { TTYCODE_STRING, "op" }, + [TTYC_RECT] = { TTYCODE_STRING, "Rect" }, [TTYC_REV] = { TTYCODE_STRING, "rev" }, [TTYC_RGB] = { TTYCODE_FLAG, "RGB" }, [TTYC_RIN] = { TTYCODE_STRING, "rin" }, @@ -431,10 +432,11 @@ tty_term_apply_overrides(struct tty_term *term) struct options_entry *o; struct options_array_item *a; union options_value *ov; - const char *s; + const char *s, *acs; size_t offset; char *first; + /* Update capabilities from the option. */ o = options_get_only(global_options, "terminal-overrides"); a = options_array_first(o); while (a != NULL) { @@ -447,6 +449,64 @@ tty_term_apply_overrides(struct tty_term *term) tty_term_apply(term, s + offset, 0); a = options_array_next(a); } + + /* Update the RGB flag if the terminal has RGB colours. */ + if (tty_term_has(term, TTYC_SETRGBF) && + tty_term_has(term, TTYC_SETRGBB)) + term->flags |= TERM_RGBCOLOURS; + else + term->flags &= ~TERM_RGBCOLOURS; + log_debug("RGBCOLOURS flag is %d", !!(term->flags & TERM_RGBCOLOURS)); + + /* + * Set or clear the DECSLRM flag if the terminal has the margin + * capabilities. + */ + if (tty_term_has(term, TTYC_CMG) && tty_term_has(term, TTYC_CLMG)) + term->flags |= TERM_DECSLRM; + else + term->flags &= ~TERM_DECSLRM; + log_debug("DECSLRM flag is %d", !!(term->flags & TERM_DECSLRM)); + + /* + * Set or clear the DECFRA flag if the terminal has the rectangle + * capability. + */ + if (tty_term_has(term, TTYC_RECT)) + term->flags |= TERM_DECFRA; + else + term->flags &= ~TERM_DECFRA; + log_debug("DECFRA flag is %d", !!(term->flags & TERM_DECFRA)); + + /* + * Terminals without am (auto right margin) wrap at at $COLUMNS - 1 + * rather than $COLUMNS (the cursor can never be beyond $COLUMNS - 1). + * + * Terminals without xenl (eat newline glitch) ignore a newline beyond + * the right edge of the terminal, but tmux doesn't care about this - + * it always uses absolute only moves the cursor with a newline when + * also sending a linefeed. + * + * This is irritating, most notably because it is painful to write to + * the very bottom-right of the screen without scrolling. + * + * Flag the terminal here and apply some workarounds in other places to + * do the best possible. + */ + if (!tty_term_flag(term, TTYC_AM)) + term->flags |= TERM_NOAM; + else + term->flags &= ~TERM_NOAM; + log_debug("NOAM flag is %d", !!(term->flags & TERM_NOAM)); + + /* Generate ACS table. If none is present, use nearest ASCII. */ + memset(term->acs, 0, sizeof term->acs); + if (tty_term_has(term, TTYC_ACSC)) + acs = tty_term_string(term, TTYC_ACSC); + else + acs = "a#j+k+l+m+n+o-p-q-r-s-t+u+v+w+x|y~."; + for (; acs[0] != '\0' && acs[1] != '\0'; acs += 2) + term->acs[(u_char) acs[0]][0] = acs[1]; } struct tty_term * @@ -460,7 +520,7 @@ tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps, struct options_array_item *a; union options_value *ov; u_int i, j; - const char *s, *acs, *value; + const char *s, *value; size_t offset, namelen; char *first; @@ -557,40 +617,10 @@ tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps, (!tty_term_has(term, TTYC_SETRGBF) || !tty_term_has(term, TTYC_SETRGBB))) tty_add_features(feat, "RGB", ","); - if (tty_term_has(term, TTYC_SETRGBF) && - tty_term_has(term, TTYC_SETRGBB)) - term->flags |= TERM_RGBCOLOURS; /* Apply the features and overrides again. */ - tty_apply_features(term, *feat); - tty_term_apply_overrides(term); - - /* - * Terminals without am (auto right margin) wrap at at $COLUMNS - 1 - * rather than $COLUMNS (the cursor can never be beyond $COLUMNS - 1). - * - * Terminals without xenl (eat newline glitch) ignore a newline beyond - * the right edge of the terminal, but tmux doesn't care about this - - * it always uses absolute only moves the cursor with a newline when - * also sending a linefeed. - * - * This is irritating, most notably because it is painful to write to - * the very bottom-right of the screen without scrolling. - * - * Flag the terminal here and apply some workarounds in other places to - * do the best possible. - */ - if (!tty_term_flag(term, TTYC_AM)) - term->flags |= TERM_NOAM; - - /* Generate ACS table. If none is present, use nearest ASCII. */ - memset(term->acs, 0, sizeof term->acs); - if (tty_term_has(term, TTYC_ACSC)) - acs = tty_term_string(term, TTYC_ACSC); - else - acs = "a#j+k+l+m+n+o-p-q-r-s-t+u+v+w+x|y~."; - for (; acs[0] != '\0' && acs[1] != '\0'; acs += 2) - term->acs[(u_char) acs[0]][0] = acs[1]; + if (tty_apply_features(term, *feat)) + tty_term_apply_overrides(term); /* Log the capabilities. */ for (i = 0; i < tty_term_ncodes(); i++) From b573dbba9062fa9db3b60310cdb4d8776a0be3fd Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:29:45 +0000 Subject: [PATCH 08/29] Do not count client (and crash) if no window. --- resize.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resize.c b/resize.c index 66cb9430..38cda23d 100644 --- a/resize.c +++ b/resize.c @@ -136,7 +136,7 @@ clients_calculate_size(int type, int current, struct client *c, * For latest, count the number of clients with this window. We only * care if there is more than one. */ - if (type == WINDOW_SIZE_LATEST) + if (type == WINDOW_SIZE_LATEST && w != NULL) n = clients_with_window(w); /* Loop over the clients and work out the size. */ From 84e22168a52f8fe0fa0ef0431d1752273f53e8bd Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:33:41 +0000 Subject: [PATCH 09/29] Change resize timers and flags into one timer and a queue which is simpler and fixes problems with vim when resized multiple times. GitHub issue 2677. --- server-client.c | 117 +++++++++++++++++++++++------------------------- tmux.1 | 2 + tmux.h | 25 ++++++++--- window.c | 51 +++++++++------------ 4 files changed, 96 insertions(+), 99 deletions(-) diff --git a/server-client.c b/server-client.c index 1114396d..9bd2ad19 100644 --- a/server-client.c +++ b/server-client.c @@ -1450,84 +1450,79 @@ server_client_resize_timer(__unused int fd, __unused short events, void *data) evtimer_del(&wp->resize_timer); } -/* Start the resize timer. */ -static void -server_client_start_resize_timer(struct window_pane *wp) -{ - struct timeval tv = { .tv_usec = 250000 }; - - log_debug("%s: %%%u resize timer started", __func__, wp->id); - evtimer_add(&wp->resize_timer, &tv); -} - -/* Force timer event. */ -static void -server_client_force_timer(__unused int fd, __unused short events, void *data) -{ - struct window_pane *wp = data; - - log_debug("%s: %%%u force timer expired", __func__, wp->id); - evtimer_del(&wp->force_timer); - wp->flags |= PANE_RESIZENOW; -} - -/* Start the force timer. */ -static void -server_client_start_force_timer(struct window_pane *wp) -{ - struct timeval tv = { .tv_usec = 10000 }; - - log_debug("%s: %%%u force timer started", __func__, wp->id); - evtimer_add(&wp->force_timer, &tv); -} - /* Check if pane should be resized. */ static void server_client_check_pane_resize(struct window_pane *wp) { + struct window_pane_resize *r; + struct window_pane_resize *r1; + struct window_pane_resize *first; + struct window_pane_resize *last; + struct timeval tv = { .tv_usec = 250000 }; + + if (TAILQ_EMPTY(&wp->resize_queue)) + return; + if (!event_initialized(&wp->resize_timer)) evtimer_set(&wp->resize_timer, server_client_resize_timer, wp); - if (!event_initialized(&wp->force_timer)) - evtimer_set(&wp->force_timer, server_client_force_timer, wp); - - if (~wp->flags & PANE_RESIZE) + if (evtimer_pending(&wp->resize_timer, NULL)) return; + log_debug("%s: %%%u needs to be resized", __func__, wp->id); - - if (evtimer_pending(&wp->resize_timer, NULL)) { - log_debug("%s: %%%u resize timer is running", __func__, wp->id); - return; + TAILQ_FOREACH(r, &wp->resize_queue, entry) { + log_debug("queued resize: %ux%u -> %ux%u", r->osx, r->osy, + r->sx, r->sy); } - server_client_start_resize_timer(wp); - if (~wp->flags & PANE_RESIZEFORCE) { - /* - * The timer is not running and we don't need to force a - * resize, so just resize immediately. - */ - log_debug("%s: resizing %%%u now", __func__, wp->id); - window_pane_send_resize(wp, 0); - wp->flags &= ~PANE_RESIZE; + /* + * There are three cases that matter: + * + * - Only one resize. It can just be applied. + * + * - Multiple resizes and the ending size is different from the + * starting size. We can discard all resizes except the most recent. + * + * - Multiple resizes and the ending size is the same as the starting + * size. We must resize at least twice to force the application to + * redraw. So apply the first and leave the last on the queue for + * next time. + */ + first = TAILQ_FIRST(&wp->resize_queue); + last = TAILQ_LAST(&wp->resize_queue, window_pane_resizes); + if (first == last) { + /* Only one resize. */ + window_pane_send_resize(wp, first->sx, first->sy); + TAILQ_REMOVE(&wp->resize_queue, first, entry); + free(first); + } else if (last->sx != first->osx || last->sy != first->osy) { + /* Multiple resizes ending up with a different size. */ + window_pane_send_resize(wp, last->sx, last->sy); + TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) { + TAILQ_REMOVE(&wp->resize_queue, r, entry); + free(r); + } } else { /* - * The timer is not running, but we need to force a resize. If - * the force timer has expired, resize to the real size now. - * Otherwise resize to the force size and start the timer. + * Multiple resizes ending up with the same size. There will + * not be more than one to the same size in succession so we + * can just use the last-but-one on the list and leave the last + * for later. We reduce the time until the next check to avoid + * a long delay between the resizes. */ - if (wp->flags & PANE_RESIZENOW) { - log_debug("%s: resizing %%%u after forced resize", - __func__, wp->id); - window_pane_send_resize(wp, 0); - wp->flags &= ~(PANE_RESIZE|PANE_RESIZEFORCE|PANE_RESIZENOW); - } else if (!evtimer_pending(&wp->force_timer, NULL)) { - log_debug("%s: forcing resize of %%%u", __func__, - wp->id); - window_pane_send_resize(wp, 1); - server_client_start_force_timer(wp); + r = TAILQ_PREV(last, window_pane_resizes, entry); + window_pane_send_resize(wp, r->sx, r->sy); + TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) { + if (r == last) + break; + TAILQ_REMOVE(&wp->resize_queue, r, entry); + free(r); } + tv.tv_usec = 10000; } + evtimer_add(&wp->resize_timer, &tv); } + /* Check pane buffer size. */ static void server_client_check_pane_buffer(struct window_pane *wp) diff --git a/tmux.1 b/tmux.1 index d2ad593f..47cf4f2c 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2953,6 +2953,8 @@ Ctrl keys may be prefixed with .Ql C- or .Ql ^ , +Shift keys with +.Ql S- and Alt (meta) with .Ql M- . In addition, the following special key names are accepted: diff --git a/tmux.h b/tmux.h index 1947009f..508fddd3 100644 --- a/tmux.h +++ b/tmux.h @@ -918,7 +918,7 @@ struct window_mode_entry { struct screen *screen; u_int prefix; - TAILQ_ENTRY (window_mode_entry) entry; + TAILQ_ENTRY(window_mode_entry) entry; }; /* Offsets into pane buffer. */ @@ -926,6 +926,18 @@ struct window_pane_offset { size_t used; }; +/* Queued pane resize. */ +struct window_pane_resize { + u_int sx; + u_int sy; + + u_int osx; + u_int osy; + + TAILQ_ENTRY(window_pane_resize) entry; +}; +TAILQ_HEAD(window_pane_resizes, window_pane_resize); + /* Child window structure. */ struct window_pane { u_int id; @@ -950,8 +962,8 @@ struct window_pane { #define PANE_REDRAW 0x1 #define PANE_DROP 0x2 #define PANE_FOCUSED 0x4 -#define PANE_RESIZE 0x8 -#define PANE_RESIZEFORCE 0x10 +/* 0x8 unused */ +/* 0x10 unused */ #define PANE_FOCUSPUSH 0x20 #define PANE_INPUTOFF 0x40 #define PANE_CHANGED 0x80 @@ -960,7 +972,6 @@ struct window_pane { #define PANE_STATUSDRAWN 0x400 #define PANE_EMPTY 0x800 #define PANE_STYLECHANGED 0x1000 -#define PANE_RESIZENOW 0x2000 int argc; char **argv; @@ -977,8 +988,8 @@ struct window_pane { struct window_pane_offset offset; size_t base_offset; + struct window_pane_resizes resize_queue; struct event resize_timer; - struct event force_timer; struct input_ctx *ictx; @@ -996,7 +1007,7 @@ struct window_pane { struct screen status_screen; size_t status_size; - TAILQ_HEAD (, window_mode_entry) modes; + TAILQ_HEAD(, window_mode_entry) modes; char *searchstr; int searchregex; @@ -2755,7 +2766,7 @@ void window_redraw_active_switch(struct window *, struct window_pane *window_add_pane(struct window *, struct window_pane *, u_int, int); void window_resize(struct window *, u_int, u_int, int, int); -void window_pane_send_resize(struct window_pane *, int); +void window_pane_send_resize(struct window_pane *, u_int, u_int); int window_zoom(struct window_pane *); int window_unzoom(struct window *); int window_push_zoom(struct window *, int, int); diff --git a/window.c b/window.c index 519f91b1..aa448e1f 100644 --- a/window.c +++ b/window.c @@ -427,25 +427,18 @@ window_resize(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) } void -window_pane_send_resize(struct window_pane *wp, int force) +window_pane_send_resize(struct window_pane *wp, u_int sx, u_int sy) { struct window *w = wp->window; struct winsize ws; - u_int sy; if (wp->fd == -1) return; - if (!force) - sy = wp->sy; - else if (wp->sy <= 1) - sy = wp->sy + 1; - else - sy = wp->sy - 1; - log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, wp->sx, sy); + log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, sx, sy); memset(&ws, 0, sizeof ws); - ws.ws_col = wp->sx; + ws.ws_col = sx; ws.ws_row = sy; ws.ws_xpixel = w->xpixel * ws.ws_col; ws.ws_ypixel = w->ypixel * ws.ws_row; @@ -860,29 +853,19 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) wp->id = next_window_pane_id++; RB_INSERT(window_pane_tree, &all_window_panes, wp); - wp->argc = 0; - wp->argv = NULL; - wp->shell = NULL; - wp->cwd = NULL; - wp->fd = -1; - wp->event = NULL; wp->fg = 8; wp->bg = 8; TAILQ_INIT(&wp->modes); - wp->layout_cell = NULL; - - wp->xoff = 0; - wp->yoff = 0; + TAILQ_INIT (&wp->resize_queue); wp->sx = sx; wp->sy = sy; wp->pipe_fd = -1; - wp->pipe_event = NULL; screen_init(&wp->base, sx, sy, hlimit); wp->screen = &wp->base; @@ -898,6 +881,9 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) static void window_pane_destroy(struct window_pane *wp) { + struct window_pane_resize *r; + struct window_pane_resize *r1; + window_pane_reset_mode_all(wp); free(wp->searchstr); @@ -919,8 +905,10 @@ window_pane_destroy(struct window_pane *wp) if (event_initialized(&wp->resize_timer)) event_del(&wp->resize_timer); - if (event_initialized(&wp->force_timer)) - event_del(&wp->force_timer); + TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) { + TAILQ_REMOVE(&wp->resize_queue, r, entry); + free(r); + } RB_REMOVE(window_pane_tree, &all_window_panes, wp); @@ -989,9 +977,18 @@ void window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) { struct window_mode_entry *wme; + struct window_pane_resize *r; if (sx == wp->sx && sy == wp->sy) return; + + r = xmalloc (sizeof *r); + r->sx = sx; + r->sy = sy; + r->osx = wp->sx; + r->osy = wp->sy; + TAILQ_INSERT_TAIL (&wp->resize_queue, r, entry); + wp->sx = sx; wp->sy = sy; @@ -1001,14 +998,6 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) wme = TAILQ_FIRST(&wp->modes); if (wme != NULL && wme->mode->resize != NULL) wme->mode->resize(wme, sx, sy); - - /* - * If the pane has already been resized, set the force flag and make - * the application resize twice to force it to redraw. - */ - if (wp->flags & PANE_RESIZE) - wp->flags |= PANE_RESIZEFORCE; - wp->flags |= PANE_RESIZE; } void From f9f97c8145e97b0295f0792a643286126fe2d3f8 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:36:47 +0000 Subject: [PATCH 10/29] Change cursor style handling so tmux understands which sequences contain blinking and sets the flag appropriately, means that it works whether cnorm disables blinking or not. GitHub issue 2682. --- screen.c | 32 +++++++++++++-- tmux.h | 14 +++++-- tty.c | 117 +++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 120 insertions(+), 43 deletions(-) diff --git a/screen.c b/screen.c index 0b1047ab..2d770abb 100644 --- a/screen.c +++ b/screen.c @@ -81,7 +81,7 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) s->titles = NULL; s->path = NULL; - s->cstyle = 0; + s->cstyle = SCREEN_CURSOR_DEFAULT; s->ccolour = xstrdup(""); s->tabs = NULL; s->sel = NULL; @@ -156,9 +156,35 @@ screen_reset_tabs(struct screen *s) void screen_set_cursor_style(struct screen *s, u_int style) { - if (style <= 6) { - s->cstyle = style; + switch (style) + { + case 0: + s->cstyle = SCREEN_CURSOR_DEFAULT; + break; + case 1: + s->cstyle = SCREEN_CURSOR_BLOCK; + s->mode |= MODE_BLINKING; + break; + case 2: + s->cstyle = SCREEN_CURSOR_BLOCK; s->mode &= ~MODE_BLINKING; + break; + case 3: + s->cstyle = SCREEN_CURSOR_UNDERLINE; + s->mode |= MODE_BLINKING; + break; + case 4: + s->cstyle = SCREEN_CURSOR_UNDERLINE; + s->mode &= ~MODE_BLINKING; + break; + case 5: + s->cstyle = SCREEN_CURSOR_BAR; + s->mode |= MODE_BLINKING; + break; + case 6: + s->cstyle = SCREEN_CURSOR_BAR; + s->mode &= ~MODE_BLINKING; + break; } } diff --git a/tmux.h b/tmux.h index 508fddd3..96d829fb 100644 --- a/tmux.h +++ b/tmux.h @@ -794,6 +794,14 @@ struct style { enum style_default_type default_type; }; +/* Cursor style. */ +enum screen_cursor_style { + SCREEN_CURSOR_DEFAULT, + SCREEN_CURSOR_BLOCK, + SCREEN_CURSOR_UNDERLINE, + SCREEN_CURSOR_BAR +}; + /* Virtual screen. */ struct screen_sel; struct screen_titles; @@ -807,8 +815,8 @@ struct screen { u_int cx; /* cursor x */ u_int cy; /* cursor y */ - u_int cstyle; /* cursor style */ - char *ccolour; /* cursor colour string */ + enum screen_cursor_style cstyle; /* cursor style */ + char *ccolour; /* cursor colour */ u_int rupper; /* scroll region top */ u_int rlower; /* scroll region bottom */ @@ -1296,7 +1304,7 @@ struct tty { u_int cx; u_int cy; - u_int cstyle; + enum screen_cursor_style cstyle; char *ccolour; int oflag; diff --git a/tty.c b/tty.c index e8a8cbaa..367f54d5 100644 --- a/tty.c +++ b/tty.c @@ -98,7 +98,7 @@ tty_init(struct tty *tty, struct client *c) memset(tty, 0, sizeof *tty); tty->client = c; - tty->cstyle = 0; + tty->cstyle = SCREEN_CURSOR_DEFAULT; tty->ccolour = xstrdup(""); if (tcgetattr(c->fd, &tty->tio) != 0) @@ -392,10 +392,10 @@ tty_stop_tty(struct tty *tty) tty_raw(tty, tty_term_string(tty->term, TTYC_SGR0)); tty_raw(tty, tty_term_string(tty->term, TTYC_RMKX)); tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR)); - if (tty_term_has(tty->term, TTYC_SS) && tty->cstyle != 0) { + if (tty->cstyle != SCREEN_CURSOR_DEFAULT) { if (tty_term_has(tty->term, TTYC_SE)) tty_raw(tty, tty_term_string(tty->term, TTYC_SE)); - else + else if (tty_term_has(tty->term, TTYC_SS)) tty_raw(tty, tty_term_string1(tty->term, TTYC_SS, 0)); } if (tty->mode & MODE_BRACKETPASTE) @@ -657,11 +657,9 @@ tty_force_cursor_colour(struct tty *tty, const char *ccolour) void tty_update_mode(struct tty *tty, int mode, struct screen *s) { - struct client *c = tty->client; - int changed; - - if (s != NULL && strcmp(s->ccolour, tty->ccolour) != 0) - tty_force_cursor_colour(tty, s->ccolour); + struct client *c = tty->client; + int changed; + enum screen_cursor_style cstyle = tty->cstyle; if (tty->flags & TTY_NOCURSOR) mode &= ~MODE_CURSOR; @@ -670,38 +668,83 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) if (changed != 0) log_debug("%s: update mode %x to %x", c->name, tty->mode, mode); - /* - * The cursor blinking flag can be reset by setting the cursor style, so - * set the style first. - */ - if (s != NULL && tty->cstyle != s->cstyle) { - if (tty_term_has(tty->term, TTYC_SS)) { - if (s->cstyle == 0 && tty_term_has(tty->term, TTYC_SE)) - tty_putcode(tty, TTYC_SE); - else - tty_putcode1(tty, TTYC_SS, s->cstyle); - } - tty->cstyle = s->cstyle; - changed |= (MODE_CURSOR|MODE_BLINKING); + if (s != NULL) { + if (strcmp(s->ccolour, tty->ccolour) != 0) + tty_force_cursor_colour(tty, s->ccolour); + cstyle = s->cstyle; } - - /* - * Cursor invisible (RM ?25) overrides cursor blinking (SM ?12 or RM - * 34), and we need to be careful not send cnorm after cvvis since it - * can undo it. - */ - if (changed & (MODE_CURSOR|MODE_BLINKING)) { - log_debug("%s: cursor %s, %sblinking", __func__, - (mode & MODE_CURSOR) ? "on" : "off", - (mode & MODE_BLINKING) ? "" : "not "); - if (~mode & MODE_CURSOR) + if (~mode & MODE_CURSOR) { + /* Cursor now off - set as invisible. */ + if (changed & MODE_CURSOR) tty_putcode(tty, TTYC_CIVIS); - else if (mode & MODE_BLINKING) { - tty_putcode(tty, TTYC_CNORM); - if (tty_term_has(tty->term, TTYC_CVVIS)) + } else if ((changed & (MODE_CURSOR|MODE_BLINKING)) || + cstyle != tty->cstyle) { + /* + * Cursor now on, blinking flag changed or style changed. Start + * by setting the cursor to normal. + */ + tty_putcode(tty, TTYC_CNORM); + switch (cstyle) { + case SCREEN_CURSOR_DEFAULT: + /* + * If the old style wasn't default, then reset it to + * default. + */ + if (tty->cstyle != SCREEN_CURSOR_DEFAULT) { + if (tty_term_has(tty->term, TTYC_SE)) + tty_putcode(tty, TTYC_SE); + else + tty_putcode1(tty, TTYC_SS, 0); + } + + /* Set the cursor as very visible if necessary. */ + if (mode & MODE_BLINKING) tty_putcode(tty, TTYC_CVVIS); - } else - tty_putcode(tty, TTYC_CNORM); + break; + case SCREEN_CURSOR_BLOCK: + /* + * Set style to either block blinking (1) or steady (2) + * if supported, otherwise just check the blinking + * flag. + */ + if (tty_term_has(tty->term, TTYC_SS)) { + if (mode & MODE_BLINKING) + tty_putcode1(tty, TTYC_SS, 1); + else + tty_putcode1(tty, TTYC_SS, 2); + } else if (mode & MODE_BLINKING) + tty_putcode(tty, TTYC_CVVIS); + break; + case SCREEN_CURSOR_UNDERLINE: + /* + * Set style to either underline blinking (3) or steady + * (4) if supported, otherwise just check the blinking + * flag. + */ + if (tty_term_has(tty->term, TTYC_SS)) { + if (mode & MODE_BLINKING) + tty_putcode1(tty, TTYC_SS, 3); + else + tty_putcode1(tty, TTYC_SS, 4); + } else if (mode & MODE_BLINKING) + tty_putcode(tty, TTYC_CVVIS); + break; + case SCREEN_CURSOR_BAR: + /* + * Set style to either bar blinking (5) or steady (6) + * if supported, otherwise just check the blinking + * flag. + */ + if (tty_term_has(tty->term, TTYC_SS)) { + if (mode & MODE_BLINKING) + tty_putcode1(tty, TTYC_SS, 5); + else + tty_putcode1(tty, TTYC_SS, 6); + } else if (mode & MODE_BLINKING) + tty_putcode(tty, TTYC_CVVIS); + break; + } + tty->cstyle = cstyle; } if ((changed & ALL_MOUSE_MODES) && From f02a6c34e0e8bf2c50ee1dfe9cfacf54370a1405 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:38:28 +0000 Subject: [PATCH 11/29] Move "special" keys into the Unicode PUA rather than making them top bit set, some compilers do not allow enums that are larger than int. GitHub issue 2673. --- input-keys.c | 2 +- key-string.c | 6 +++--- status.c | 2 +- tmux.h | 33 ++++++++++++++++++++++++--------- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/input-keys.c b/input-keys.c index ab7d2212..b975c1ed 100644 --- a/input-keys.c +++ b/input-keys.c @@ -477,7 +477,7 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) input_key_write(__func__, bev, &ud.data[0], 1); return (0); } - if (justkey > 0x7f && justkey < KEYC_BASE) { + if (KEYC_IS_UNICODE(justkey)) { if (key & KEYC_META) input_key_write(__func__, bev, "\033", 1); utf8_to_data(justkey, &ud); diff --git a/key-string.c b/key-string.c index c24a33fc..406d26dd 100644 --- a/key-string.c +++ b/key-string.c @@ -238,7 +238,7 @@ key_string_lookup_string(const char *string) } /* Convert the standard control keys. */ - if (key < KEYC_BASE && + if (key <= 127 && (modifiers & KEYC_CTRL) && strchr(other, key) == NULL && key != 9 && @@ -368,8 +368,8 @@ key_string_lookup_key(key_code key, int with_flags) goto out; } - /* Is this a UTF-8 key? */ - if (key > 127 && key < KEYC_BASE) { + /* Is this a Unicode key? */ + if (KEYC_IS_UNICODE(key)) { utf8_to_data(key, &ud); off = strlen(out); memcpy(out + off, ud.data, ud.size); diff --git a/status.c b/status.c index 271e1afa..f4418500 100644 --- a/status.c +++ b/status.c @@ -1300,7 +1300,7 @@ process_key: return (0); append_key: - if (key <= 0x1f || key >= KEYC_BASE) + if (key <= 0x1f || (key >= KEYC_BASE && key < KEYC_BASE_END)) return (0); if (key <= 0x7f) utf8_set(&tmp, key); diff --git a/tmux.h b/tmux.h index 96d829fb..02dcdad0 100644 --- a/tmux.h +++ b/tmux.h @@ -108,11 +108,16 @@ struct winlink; #define VISUAL_ON 1 #define VISUAL_BOTH 2 -/* Special key codes. */ -#define KEYC_NONE 0x00ff000000000ULL -#define KEYC_UNKNOWN 0x00fe000000000ULL -#define KEYC_BASE 0x0001000000000ULL -#define KEYC_USER 0x0002000000000ULL +/* No key or unknown key. */ +#define KEYC_NONE 0x000ff000000000ULL +#define KEYC_UNKNOWN 0x000fe000000000ULL + +/* + * Base for special (that is, not Unicode) keys. An enum must be at most a + * signed int, so these are based in the highest Unicode PUA. + */ +#define KEYC_BASE 0x0000000010e000ULL +#define KEYC_USER 0x0000000010f000ULL /* Key modifier bits. */ #define KEYC_META 0x00100000000000ULL @@ -135,8 +140,15 @@ struct winlink; #define KEYC_NUSER 1000 /* Is this a mouse key? */ -#define KEYC_IS_MOUSE(key) (((key) & KEYC_MASK_KEY) >= KEYC_MOUSE && \ - ((key) & KEYC_MASK_KEY) < KEYC_BSPACE) +#define KEYC_IS_MOUSE(key) \ + (((key) & KEYC_MASK_KEY) >= KEYC_MOUSE && \ + ((key) & KEYC_MASK_KEY) < KEYC_BSPACE) + +/* Is this a Unicode key? */ +#define KEYC_IS_UNICODE(key) \ + (((key) & KEYC_MASK_KEY) > 0x7f && \ + (((key) & KEYC_MASK_KEY) < KEYC_BASE || \ + ((key) & KEYC_MASK_KEY) >= KEYC_BASE_END)) /* Multiple click timeout. */ #define KEYC_CLICK_TIMEOUT 300 @@ -158,8 +170,8 @@ struct winlink; { #s "Border", KEYC_ ## name ## _BORDER } /* - * A single key. This can be ASCII or Unicode or one of the keys starting at - * KEYC_BASE. + * A single key. This can be ASCII or Unicode or one of the keys between + * KEYC_BASE and KEYC_BASE_END. */ typedef unsigned long long key_code; @@ -252,6 +264,9 @@ enum { KEYC_KP_ENTER, KEYC_KP_ZERO, KEYC_KP_PERIOD, + + /* End of special keys. */ + KEYC_BASE_END }; /* Termcap codes. */ From 02a6b39db73e55f9f30d46106c378ed4c70cb02b Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:43:44 +0000 Subject: [PATCH 12/29] Improve logging of screen mode changes. --- screen-write.c | 6 ++++++ screen.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++-- server-client.c | 5 ++++- tmux.h | 1 + tty.c | 8 ++++++-- 5 files changed, 67 insertions(+), 5 deletions(-) diff --git a/screen-write.c b/screen-write.c index 8e9ec582..e351a5e5 100644 --- a/screen-write.c +++ b/screen-write.c @@ -769,6 +769,9 @@ screen_write_mode_set(struct screen_write_ctx *ctx, int mode) struct screen *s = ctx->s; s->mode |= mode; + + if (log_get_level() != 0) + log_debug("%s: %s", __func__, screen_mode_to_string(mode)); } /* Clear a mode. */ @@ -778,6 +781,9 @@ screen_write_mode_clear(struct screen_write_ctx *ctx, int mode) struct screen *s = ctx->s; s->mode &= ~mode; + + if (log_get_level() != 0) + log_debug("%s: %s", __func__, screen_mode_to_string(mode)); } /* Cursor up by ny. */ diff --git a/screen.c b/screen.c index 2d770abb..e7ee1df9 100644 --- a/screen.c +++ b/screen.c @@ -156,8 +156,8 @@ screen_reset_tabs(struct screen *s) void screen_set_cursor_style(struct screen *s, u_int style) { - switch (style) - { + log_debug("%s: new %u, was %u", __func__, style, s->cstyle); + switch (style) { case 0: s->cstyle = SCREEN_CURSOR_DEFAULT; break; @@ -653,3 +653,51 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) if (s->cy > screen_size_y(s) - 1) s->cy = screen_size_y(s) - 1; } + +/* Get mode as a string. */ +const char * +screen_mode_to_string(int mode) +{ + static char tmp[1024]; + + if (mode == 0) + return "NONE"; + if (mode == ALL_MODES) + return "ALL"; + + *tmp = '\0'; + if (mode & MODE_CURSOR) + strlcat(tmp, "CURSOR,", sizeof tmp); + if (mode & MODE_INSERT) + strlcat(tmp, "INSERT,", sizeof tmp); + if (mode & MODE_KCURSOR) + strlcat(tmp, "KCURSOR,", sizeof tmp); + if (mode & MODE_KKEYPAD) + strlcat(tmp, "KKEYPAD,", sizeof tmp); + if (mode & MODE_WRAP) + strlcat(tmp, "WRAP,", sizeof tmp); + if (mode & MODE_MOUSE_STANDARD) + strlcat(tmp, "STANDARD,", sizeof tmp); + if (mode & MODE_MOUSE_BUTTON) + strlcat(tmp, "BUTTON,", sizeof tmp); + if (mode & MODE_BLINKING) + strlcat(tmp, "BLINKING,", sizeof tmp); + if (mode & MODE_MOUSE_UTF8) + strlcat(tmp, "UTF8,", sizeof tmp); + if (mode & MODE_MOUSE_SGR) + strlcat(tmp, "SGR,", sizeof tmp); + if (mode & MODE_BRACKETPASTE) + strlcat(tmp, "BRACKETPASTE,", sizeof tmp); + if (mode & MODE_FOCUSON) + strlcat(tmp, "FOCUSON,", sizeof tmp); + if (mode & MODE_MOUSE_ALL) + strlcat(tmp, "ALL,", sizeof tmp); + if (mode & MODE_ORIGIN) + strlcat(tmp, "ORIGIN,", sizeof tmp); + if (mode & MODE_CRLF) + strlcat(tmp, "CRLF,", sizeof tmp); + if (mode & MODE_KEXTENDED) + strlcat(tmp, "KEXTENDED,", sizeof tmp); + tmp[strlen (tmp) - 1] = '\0'; + return (tmp); +} diff --git a/server-client.c b/server-client.c index 9bd2ad19..27b264a0 100644 --- a/server-client.c +++ b/server-client.c @@ -1694,7 +1694,10 @@ server_client_reset_state(struct client *c) s = wp->screen; if (s != NULL) mode = s->mode; - log_debug("%s: client %s mode %x", __func__, c->name, mode); + if (log_get_level() != 0) { + log_debug("%s: client %s mode %s", __func__, c->name, + screen_mode_to_string(mode)); + } /* Reset region and margin. */ tty_region_off(tty); diff --git a/tmux.h b/tmux.h index 02dcdad0..fbe19136 100644 --- a/tmux.h +++ b/tmux.h @@ -2749,6 +2749,7 @@ void screen_select_cell(struct screen *, struct grid_cell *, const struct grid_cell *); void screen_alternate_on(struct screen *, struct grid_cell *, int); void screen_alternate_off(struct screen *, struct grid_cell *, int); +const char *screen_mode_to_string(int); /* window.c */ extern struct windows windows; diff --git a/tty.c b/tty.c index 367f54d5..45868fc1 100644 --- a/tty.c +++ b/tty.c @@ -665,8 +665,12 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) mode &= ~MODE_CURSOR; changed = mode ^ tty->mode; - if (changed != 0) - log_debug("%s: update mode %x to %x", c->name, tty->mode, mode); + if (log_get_level() != 0 && changed != 0) { + log_debug("%s: current mode %s", c->name, + screen_mode_to_string(tty->mode)); + log_debug("%s: setting mode %s", c->name, + screen_mode_to_string(mode)); + } if (s != NULL) { if (strcmp(s->ccolour, tty->ccolour) != 0) From 6c659494f5591ebba2ef91e6cfa6a24761c14915 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:45:43 +0000 Subject: [PATCH 13/29] Fix warnings, from Jan Tache in GitHub issue 2692. --- format.c | 2 +- server.c | 3 ++- tty-term.c | 2 +- window-buffer.c | 6 +++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/format.c b/format.c index 3a1385b2..03c01dc4 100644 --- a/format.c +++ b/format.c @@ -4199,7 +4199,7 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, value = xstrdup("0"); } else { format_log(es, "search '%s' pane %%%u", new, wp->id); - value = format_search(fm, wp, new); + value = format_search(search, wp, new); } free(new); } else if (cmp != NULL) { diff --git a/server.c b/server.c index 6ac08a4d..eb6e9610 100644 --- a/server.c +++ b/server.c @@ -161,7 +161,8 @@ server_tidy_event(__unused int fd, __unused short events, __unused void *data) format_tidy_jobs(); - log_debug("%s: took %llu milliseconds", __func__, get_timer() - t); + log_debug("%s: took %llu milliseconds", __func__, + (unsigned long long)(get_timer() - t)); evtimer_add(&server_ev_tidy, &tv); } diff --git a/tty-term.c b/tty-term.c index 275efe2f..ae103fd7 100644 --- a/tty-term.c +++ b/tty-term.c @@ -688,7 +688,7 @@ tty_term_read_list(const char *name, int fd, char ***caps, u_int *ncaps, ent = &tty_term_codes[i]; switch (ent->type) { case TTYCODE_NONE: - break; + continue; case TTYCODE_STRING: s = tigetstr((char *)ent->name); if (s == NULL || s == (char *)-1) diff --git a/window-buffer.c b/window-buffer.c index 5e4dd699..30bd5092 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -294,9 +294,9 @@ window_buffer_get_key(void *modedata, void *itemdata, u_int line) struct window_buffer_modedata *data = modedata; struct window_buffer_itemdata *item = itemdata; struct format_tree *ft; - struct session *s; - struct winlink *wl; - struct window_pane *wp; + struct session *s = NULL; + struct winlink *wl = NULL; + struct window_pane *wp = NULL; struct paste_buffer *pb; char *expanded; key_code key; From 866117636e47c9a5d961df2ac40456b7c910a932 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:50:03 +0000 Subject: [PATCH 14/29] Add different command historys for different types of prompts ("command", "search" etc). From Anindya Mukherjee. --- Makefile | 1 + cmd-command-prompt.c | 39 ++++---- cmd-confirm-before.c | 2 +- cmd-show-prompt-history.c | 108 ++++++++++++++++++++++ cmd.c | 4 + key-bindings.c | 12 +-- mode-tree.c | 4 +- options-table.c | 9 ++ status.c | 184 +++++++++++++++++++++++++++----------- tmux.1 | 47 ++++++++-- tmux.h | 22 +++-- window-customize.c | 14 +-- window-tree.c | 6 +- 13 files changed, 356 insertions(+), 96 deletions(-) create mode 100644 cmd-show-prompt-history.c diff --git a/Makefile b/Makefile index 21141317..4cbbd34a 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,7 @@ SRCS= alerts.c \ cmd-show-environment.c \ cmd-show-messages.c \ cmd-show-options.c \ + cmd-show-prompt-history.c \ cmd-source-file.c \ cmd-split-window.c \ cmd-swap-pane.c \ diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index c82235c5..c2a2dec7 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -40,25 +40,26 @@ const struct cmd_entry cmd_command_prompt_entry = { .name = "command-prompt", .alias = NULL, - .args = { "1kiI:Np:Tt:W", 0, 1 }, - .usage = "[-1kiNTW] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " " - "[template]", + .args = { "1kiI:Np:t:T:", 0, 1 }, + .usage = "[-1kiN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE + " [-T type] [template]", .flags = CMD_CLIENT_TFLAG, .exec = cmd_command_prompt_exec }; struct cmd_command_prompt_cdata { - int flags; + int flags; + enum prompt_type prompt_type; - char *inputs; - char *next_input; + char *inputs; + char *next_input; - char *prompts; - char *next_prompt; + char *prompts; + char *next_prompt; - char *template; - int idx; + char *template; + int idx; }; static enum cmd_retval @@ -67,7 +68,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); struct client *tc = cmdq_get_target_client(item); struct cmd_find_state *target = cmdq_get_target(item); - const char *inputs, *prompts; + const char *inputs, *prompts, *type; struct cmd_command_prompt_cdata *cdata; char *prompt, *ptr, *input = NULL; size_t n; @@ -114,6 +115,16 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) input = strsep(&cdata->next_input, ","); } + /* Get prompt type. */ + if ((type = args_get(args, 'T')) != NULL) { + cdata->prompt_type = status_prompt_type(type); + if (cdata->prompt_type == PROMPT_TYPE_INVALID) { + cmdq_error(item, "unknown type: %s", type); + return (CMD_RETURN_ERROR); + } + } else + cdata->prompt_type = PROMPT_TYPE_COMMAND; + if (args_has(args, '1')) cdata->flags |= PROMPT_SINGLE; else if (args_has(args, 'N')) @@ -122,13 +133,9 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) cdata->flags |= PROMPT_INCREMENTAL; else if (args_has(args, 'k')) cdata->flags |= PROMPT_KEY; - else if (args_has(args, 'W')) - cdata->flags |= PROMPT_WINDOW; - else if (args_has(args, 'T')) - cdata->flags |= PROMPT_TARGET; status_prompt_set(tc, target, prompt, input, cmd_command_prompt_callback, cmd_command_prompt_free, cdata, - cdata->flags); + cdata->flags, cdata->prompt_type); free(prompt); return (CMD_RETURN_NORMAL); diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c index 0c562836..0b490b17 100644 --- a/cmd-confirm-before.c +++ b/cmd-confirm-before.c @@ -74,7 +74,7 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) status_prompt_set(tc, target, new_prompt, NULL, cmd_confirm_before_callback, cmd_confirm_before_free, cdata, - PROMPT_SINGLE); + PROMPT_SINGLE, PROMPT_TYPE_COMMAND); free(new_prompt); return (CMD_RETURN_NORMAL); diff --git a/cmd-show-prompt-history.c b/cmd-show-prompt-history.c new file mode 100644 index 00000000..2091ac9d --- /dev/null +++ b/cmd-show-prompt-history.c @@ -0,0 +1,108 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2021 Anindya Mukherjee + * + * 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 "tmux.h" + +#include + +/* + * Show or clear prompt history. + */ + +static enum cmd_retval cmd_show_prompt_history_exec(struct cmd *, + struct cmdq_item *); + +const struct cmd_entry cmd_show_prompt_history_entry = { + .name = "show-prompt-history", + .alias = "showphist", + + .args = { "T:", 0, 0 }, + .usage = "[-T type]", + + .flags = CMD_AFTERHOOK, + .exec = cmd_show_prompt_history_exec +}; + +const struct cmd_entry cmd_clear_prompt_history_entry = { + .name = "clear-prompt-history", + .alias = "clearphist", + + .args = { "T:", 0, 0 }, + .usage = "[-T type]", + + .flags = CMD_AFTERHOOK, + .exec = cmd_show_prompt_history_exec +}; + +static enum cmd_retval +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; + + if (cmd_get_entry(self) == &cmd_clear_prompt_history_entry) { + if (typestr == NULL) { + for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { + free(status_prompt_hlist[tidx]); + status_prompt_hlist[tidx] = NULL; + status_prompt_hsize[tidx] = 0; + } + } else { + type = status_prompt_type(typestr); + if (type == PROMPT_TYPE_INVALID) { + cmdq_error(item, "invalid type: %s", typestr); + return (CMD_RETURN_ERROR); + } + free(status_prompt_hlist[type]); + status_prompt_hlist[type] = NULL; + status_prompt_hsize[type] = 0; + } + + 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]); + } + cmdq_print(item, "%s", ""); + } + } else { + type = status_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, "%s", ""); + } + + return (CMD_RETURN_NORMAL); +} diff --git a/cmd.c b/cmd.c index 6b443fa2..90fa221a 100644 --- a/cmd.c +++ b/cmd.c @@ -36,6 +36,7 @@ extern const struct cmd_entry cmd_choose_buffer_entry; extern const struct cmd_entry cmd_choose_client_entry; extern const struct cmd_entry cmd_choose_tree_entry; extern const struct cmd_entry cmd_clear_history_entry; +extern const struct cmd_entry cmd_clear_prompt_history_entry; extern const struct cmd_entry cmd_clock_mode_entry; extern const struct cmd_entry cmd_command_prompt_entry; extern const struct cmd_entry cmd_confirm_before_entry; @@ -105,6 +106,7 @@ extern const struct cmd_entry cmd_show_environment_entry; extern const struct cmd_entry cmd_show_hooks_entry; extern const struct cmd_entry cmd_show_messages_entry; extern const struct cmd_entry cmd_show_options_entry; +extern const struct cmd_entry cmd_show_prompt_history_entry; extern const struct cmd_entry cmd_show_window_options_entry; extern const struct cmd_entry cmd_source_file_entry; extern const struct cmd_entry cmd_split_window_entry; @@ -127,6 +129,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_choose_client_entry, &cmd_choose_tree_entry, &cmd_clear_history_entry, + &cmd_clear_prompt_history_entry, &cmd_clock_mode_entry, &cmd_command_prompt_entry, &cmd_confirm_before_entry, @@ -195,6 +198,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_show_hooks_entry, &cmd_show_messages_entry, &cmd_show_options_entry, + &cmd_show_prompt_history_entry, &cmd_show_window_options_entry, &cmd_source_file_entry, &cmd_split_window_entry, diff --git a/key-bindings.c b/key-bindings.c index 467c7f93..b380f2cd 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -350,12 +350,12 @@ key_bindings_init(void) "bind -N 'Rename current session' '$' command-prompt -I'#S' \"rename-session -- '%%'\"", "bind -N 'Split window horizontally' % split-window -h", "bind -N 'Kill current window' & confirm-before -p\"kill-window #W? (y/n)\" kill-window", - "bind -N 'Prompt for window index to select' \"'\" command-prompt -Wpindex \"select-window -t ':%%'\"", + "bind -N 'Prompt for window index to select' \"'\" command-prompt -T window-target -pindex \"select-window -t ':%%'\"", "bind -N 'Switch to previous client' ( switch-client -p", "bind -N 'Switch to next client' ) switch-client -n", "bind -N 'Rename current window' , command-prompt -I'#W' \"rename-window -- '%%'\"", "bind -N 'Delete the most recent paste buffer' - delete-buffer", - "bind -N 'Move the current window' . command-prompt -T \"move-window -t '%%'\"", + "bind -N 'Move the current window' . command-prompt -T target \"move-window -t '%%'\"", "bind -N 'Describe key binding' '/' command-prompt -kpkey 'list-keys -1N \"%%%\"'", "bind -N 'Select window 0' 0 select-window -t:=0", "bind -N 'Select window 1' 1 select-window -t:=1", @@ -479,8 +479,8 @@ key_bindings_init(void) "bind -Tcopy-mode C-k send -X copy-end-of-line", "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 -ip'(search up)' -I'#{pane_search_string}' 'send -X search-backward-incremental \"%%%\"'", - "bind -Tcopy-mode C-s command-prompt -ip'(search down)' -I'#{pane_search_string}' 'send -X search-forward-incremental \"%%%\"'", + "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-v send -X page-down", "bind -Tcopy-mode C-w send -X copy-pipe-and-cancel", "bind -Tcopy-mode Escape send -X cancel", @@ -559,7 +559,7 @@ 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 -p'(search down)' 'send -X search-forward \"%%%\"'", + "bind -Tcopy-mode-vi / command-prompt -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 \"%%%\"'", @@ -572,7 +572,7 @@ key_bindings_init(void) "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 \\; send -X jump-again", - "bind -Tcopy-mode-vi ? command-prompt -p'(search up)' 'send -X search-backward \"%%%\"'", + "bind -Tcopy-mode-vi ? command-prompt -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-end-of-line", diff --git a/mode-tree.c b/mode-tree.c index ca7f33a4..807c1dcb 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -1168,7 +1168,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mtd->references++; status_prompt_set(c, NULL, "(search) ", "", mode_tree_search_callback, mode_tree_search_free, mtd, - PROMPT_NOFORMAT); + PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); break; case 'n': mode_tree_search_set(mtd); @@ -1177,7 +1177,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mtd->references++; status_prompt_set(c, NULL, "(filter) ", mtd->filter, mode_tree_filter_callback, mode_tree_filter_free, mtd, - PROMPT_NOFORMAT); + PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); break; case 'v': mtd->preview = !mtd->preview; diff --git a/options-table.c b/options-table.c index 4f4d9ff6..73c7fd63 100644 --- a/options-table.c +++ b/options-table.c @@ -302,6 +302,15 @@ const struct options_table_entry options_table[] = { .text = "Maximum number of server messages to keep." }, + { .name = "prompt-history-limit", + .type = OPTIONS_TABLE_NUMBER, + .scope = OPTIONS_TABLE_SERVER, + .minimum = 0, + .maximum = INT_MAX, + .default_num = 100, + .text = "Maximum number of commands to keep in history." + }, + { .name = "set-clipboard", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SERVER, diff --git a/status.c b/status.c index f4418500..7bcd2f38 100644 --- a/status.c +++ b/status.c @@ -33,9 +33,9 @@ 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 *); -static const char *status_prompt_down_history(u_int *); -static void status_prompt_add_history(const char *); +static const char *status_prompt_up_history(u_int *, u_int); +static const char *status_prompt_down_history(u_int *, u_int); +static void status_prompt_add_history(const char *, u_int); static char *status_prompt_complete(struct client *, const char *, u_int); static char *status_prompt_complete_window_menu(struct client *, @@ -49,10 +49,16 @@ struct status_prompt_menu { char flag; }; +static const char *prompt_type_strings[] = { + "command", + "search", + "target", + "window-target" +}; + /* Status prompt history. */ -#define PROMPT_HISTORY 100 -static char **status_prompt_hlist; -static u_int status_prompt_hsize; +char **status_prompt_hlist[PROMPT_NTYPES]; +u_int status_prompt_hsize[PROMPT_NTYPES]; /* Find the history file to load/save from/to. */ static char * @@ -75,6 +81,28 @@ status_prompt_find_history_file(void) 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) @@ -102,12 +130,12 @@ status_prompt_load_history(void) if (length > 0) { if (line[length - 1] == '\n') { line[length - 1] = '\0'; - status_prompt_add_history(line); + status_prompt_add_typed_history(line); } else { tmp = xmalloc(length + 1); memcpy(tmp, line, length); tmp[length] = '\0'; - status_prompt_add_history(tmp); + status_prompt_add_typed_history(tmp); free(tmp); } } @@ -120,7 +148,7 @@ void status_prompt_save_history(void) { FILE *f; - u_int i; + u_int i, type; char *history_file; if ((history_file = status_prompt_find_history_file()) == NULL) @@ -135,9 +163,13 @@ status_prompt_save_history(void) } free(history_file); - for (i = 0; i < status_prompt_hsize; i++) { - fputs(status_prompt_hlist[i], f); - fputc('\n', f); + 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); @@ -545,7 +577,7 @@ status_message_redraw(struct client *c) void status_prompt_set(struct client *c, struct cmd_find_state *fs, const char *msg, const char *input, prompt_input_cb inputcb, - prompt_free_cb freecb, void *data, int flags) + prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type) { struct format_tree *ft; char *tmp; @@ -581,9 +613,10 @@ status_prompt_set(struct client *c, struct cmd_find_state *fs, c->prompt_freecb = freecb; c->prompt_data = data; - c->prompt_hindex = 0; + memset(c->prompt_hindex, 0, sizeof c->prompt_hindex); c->prompt_flags = flags; + c->prompt_type = prompt_type; c->prompt_mode = PROMPT_ENTRY; if (~flags & PROMPT_INCREMENTAL) @@ -644,7 +677,7 @@ status_prompt_update(struct client *c, const char *msg, const char *input) c->prompt_buffer = utf8_fromcstr(tmp); c->prompt_index = utf8_strlen(c->prompt_buffer); - c->prompt_hindex = 0; + memset(c->prompt_hindex, 0, sizeof c->prompt_hindex); c->flags |= CLIENT_REDRAWSTATUS; @@ -768,7 +801,7 @@ status_prompt_space(const struct utf8_data *ud) } /* - * Translate key from emacs to vi. Return 0 to drop key, 1 to process the 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 @@ -1222,7 +1255,8 @@ process_key: goto changed; case KEYC_UP: case '\020': /* C-p */ - histstr = status_prompt_up_history(&c->prompt_hindex); + histstr = status_prompt_up_history(c->prompt_hindex, + c->prompt_type); if (histstr == NULL) break; free(c->prompt_buffer); @@ -1231,7 +1265,8 @@ process_key: goto changed; case KEYC_DOWN: case '\016': /* C-n */ - histstr = status_prompt_down_history(&c->prompt_hindex); + histstr = status_prompt_down_history(c->prompt_hindex, + c->prompt_type); if (histstr == NULL) break; free(c->prompt_buffer); @@ -1259,7 +1294,7 @@ process_key: case '\n': s = utf8_tocstr(c->prompt_buffer); if (*s != '\0') - status_prompt_add_history(s); + status_prompt_add_history(s, c->prompt_type); if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) status_prompt_clear(c); free(s); @@ -1348,54 +1383,78 @@ changed: /* Get previous line from the history. */ static const char * -status_prompt_up_history(u_int *idx) +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 == 0 || *idx == status_prompt_hsize) + if (status_prompt_hsize[type] == 0 || + idx[type] == status_prompt_hsize[type]) return (NULL); - (*idx)++; - return (status_prompt_hlist[status_prompt_hsize - *idx]); + 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) +status_prompt_down_history(u_int *idx, u_int type) { - if (status_prompt_hsize == 0 || *idx == 0) + if (status_prompt_hsize[type] == 0 || idx[type] == 0) return (""); - (*idx)--; - if (*idx == 0) + idx[type]--; + if (idx[type] == 0) return (""); - return (status_prompt_hlist[status_prompt_hsize - *idx]); + 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) +status_prompt_add_history(const char *line, u_int type) { - size_t size; + u_int i, oldsize, newsize, freecount, hlimit, new = 1; + size_t movesize; - if (status_prompt_hsize > 0 && - strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) - return; + oldsize = status_prompt_hsize[type]; + if (oldsize > 0 && + strcmp(status_prompt_hlist[type][oldsize - 1], line) == 0) + new = 0; - if (status_prompt_hsize == PROMPT_HISTORY) { - free(status_prompt_hlist[0]); - - size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; - memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); - - status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); - return; + 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); + } } - status_prompt_hlist = xreallocarray(status_prompt_hlist, - status_prompt_hsize + 1, sizeof *status_prompt_hlist); - status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); + 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; } /* Build completion list. */ @@ -1501,7 +1560,7 @@ status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key, s = xstrdup(spm->list[idx]); else xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]); - if (c->prompt_flags & PROMPT_WINDOW) { + if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { free(c->prompt_buffer); c->prompt_buffer = utf8_fromcstr(s); c->prompt_index = utf8_strlen(c->prompt_buffer); @@ -1609,7 +1668,7 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, } list = xreallocarray(list, size + 1, sizeof *list); - if (c->prompt_flags & PROMPT_WINDOW) { + if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name); xasprintf(&list[size++], "%d", wl->idx); } else { @@ -1717,10 +1776,12 @@ status_prompt_complete(struct client *c, const char *word, u_int offset) u_int size = 0, i; if (*word == '\0' && - ((c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) == 0)) + c->prompt_type != PROMPT_TYPE_TARGET && + c->prompt_type != PROMPT_TYPE_WINDOW_TARGET) return (NULL); - if (((c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) == 0) && + if (c->prompt_type != PROMPT_TYPE_TARGET && + c->prompt_type != PROMPT_TYPE_WINDOW_TARGET && strncmp(word, "-t", 2) != 0 && strncmp(word, "-s", 2) != 0) { list = status_prompt_complete_list(&size, word, offset == 0); @@ -1733,7 +1794,8 @@ status_prompt_complete(struct client *c, const char *word, u_int offset) goto found; } - if (c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) { + if (c->prompt_type == PROMPT_TYPE_TARGET || + c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { s = word; flag = '\0'; } else { @@ -1743,7 +1805,7 @@ status_prompt_complete(struct client *c, const char *word, u_int offset) } /* If this is a window completion, open the window menu. */ - if (c->prompt_flags & PROMPT_WINDOW) { + if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { out = status_prompt_complete_window_menu(c, c->session, s, offset, '\0'); goto found; @@ -1793,3 +1855,25 @@ found: } 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 47cf4f2c..50b3b6b7 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3453,7 +3453,9 @@ will write command prompt history on exit and load it from on start. .It Ic message-limit Ar number Set the number of error or information messages to save in the message log for each client. -The default is 100. +.It Ic prompt-history-limit Ar number +Set the number of history items to save in the history file for each type of +command prompt. .It Xo Ic set-clipboard .Op Ic on | external | off .Xc @@ -5376,11 +5378,25 @@ session option. .Pp Commands related to the status line are as follows: .Bl -tag -width Ds +.It Xo Ic clear-prompt-history +.Op Fl T Ar prompt-type +.Xc +.D1 (alias: Ic clrphist) +Clear status prompt history for prompt type +.Ar prompt-type . +If +.Fl T +is omitted, then clear history for all types. +See +.Ic command-prompt +for possible values for +.Ar prompt-type . .It Xo Ic command-prompt -.Op Fl 1ikNTW +.Op Fl 1ikN .Op Fl I Ar inputs .Op Fl p Ar prompts .Op Fl t Ar target-client +.Op Fl T Ar prompt-type .Op Ar template .Xc Open the command prompt in a client. @@ -5436,14 +5452,20 @@ makes the prompt only accept numeric key presses. .Fl i executes the command every time the prompt input changes instead of when the user exits the command prompt. +.Pp .Fl T tells .Nm -that the prompt is for a target which affects what completions are offered when +the prompt type. +This affects what completions are offered when .Em Tab -is pressed; -.Fl W -is similar but indicates the prompt is for a window. +is pressed. +Available types are: +.Ql command , +.Ql search , +.Ql target +and +.Ql window-target . .Pp The following keys have a special meaning in the command prompt, depending on the value of the @@ -5665,6 +5687,19 @@ If omitted, half of the terminal size is used. The .Fl C flag closes any popup on the client. +.It Xo Ic show-prompt-history +.Op Fl T Ar prompt-type +.Xc +.D1 (alias: Ic showphist) +Display status prompt history for prompt type +.Ar prompt-type . +If +.Fl T +is omitted, then show history for all types. +See +.Ic command-prompt +for possible values for +.Ar prompt-type . .El .Sh BUFFERS .Nm diff --git a/tmux.h b/tmux.h index fbe19136..5fefcef5 100644 --- a/tmux.h +++ b/tmux.h @@ -1571,6 +1571,16 @@ struct status_line { struct status_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 *); @@ -1734,18 +1744,16 @@ struct client { prompt_input_cb prompt_inputcb; prompt_free_cb prompt_freecb; void *prompt_data; - u_int prompt_hindex; + u_int prompt_hindex[PROMPT_NTYPES]; enum { PROMPT_ENTRY, PROMPT_COMMAND } prompt_mode; struct utf8_data *prompt_saved; - #define PROMPT_SINGLE 0x1 #define PROMPT_NUMERIC 0x2 #define PROMPT_INCREMENTAL 0x4 #define PROMPT_NOFORMAT 0x8 #define PROMPT_KEY 0x10 -#define PROMPT_WINDOW 0x20 -#define PROMPT_TARGET 0x40 int prompt_flags; + enum prompt_type prompt_type; struct session *session; struct session *last_session; @@ -2517,6 +2525,8 @@ 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 *); @@ -2531,13 +2541,15 @@ 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, - void *, int); + void *, int, enum prompt_type); void status_prompt_clear(struct client *); int status_prompt_redraw(struct client *); int status_prompt_key(struct client *, key_code); void status_prompt_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); /* resize.c */ void resize_window(struct window *, u_int, u_int, int, int); diff --git a/window-customize.c b/window-customize.c index 34a13f73..782542f7 100644 --- a/window-customize.c +++ b/window-customize.c @@ -1123,7 +1123,7 @@ window_customize_set_option(struct client *c, status_prompt_set(c, NULL, prompt, value, window_customize_set_option_callback, window_customize_free_item_callback, new_item, - PROMPT_NOFORMAT); + PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); free(value); @@ -1264,7 +1264,7 @@ window_customize_set_key(struct client *c, status_prompt_set(c, NULL, prompt, value, window_customize_set_command_callback, window_customize_free_item_callback, new_item, - PROMPT_NOFORMAT); + PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); free(value); } else if (strcmp(s, "Note") == 0) { @@ -1281,7 +1281,7 @@ window_customize_set_key(struct client *c, (bd->note == NULL ? "" : bd->note), window_customize_set_note_callback, window_customize_free_item_callback, new_item, - PROMPT_NOFORMAT); + PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); } } @@ -1458,7 +1458,7 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, status_prompt_set(c, NULL, prompt, "", window_customize_change_current_callback, window_customize_free_callback, data, - PROMPT_SINGLE|PROMPT_NOFORMAT); + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); break; case 'D': @@ -1471,7 +1471,7 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, status_prompt_set(c, NULL, prompt, "", window_customize_change_tagged_callback, window_customize_free_callback, data, - PROMPT_SINGLE|PROMPT_NOFORMAT); + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); break; case 'u': @@ -1487,7 +1487,7 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, status_prompt_set(c, NULL, prompt, "", window_customize_change_current_callback, window_customize_free_callback, data, - PROMPT_SINGLE|PROMPT_NOFORMAT); + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); break; case 'U': @@ -1500,7 +1500,7 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, status_prompt_set(c, NULL, prompt, "", window_customize_change_tagged_callback, window_customize_free_callback, data, - PROMPT_SINGLE|PROMPT_NOFORMAT); + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); break; case 'H': diff --git a/window-tree.c b/window-tree.c index 881896e7..f8e38399 100644 --- a/window-tree.c +++ b/window-tree.c @@ -1296,7 +1296,7 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, data->references++; status_prompt_set(c, NULL, prompt, "", window_tree_kill_current_callback, window_tree_command_free, - data, PROMPT_SINGLE|PROMPT_NOFORMAT); + data, PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); break; case 'X': @@ -1307,7 +1307,7 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, data->references++; status_prompt_set(c, NULL, prompt, "", window_tree_kill_tagged_callback, window_tree_command_free, - data, PROMPT_SINGLE|PROMPT_NOFORMAT); + data, PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); break; case ':': @@ -1319,7 +1319,7 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, data->references++; status_prompt_set(c, NULL, prompt, "", window_tree_command_callback, window_tree_command_free, - data, PROMPT_NOFORMAT); + data, PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); break; case '\r': From cfc7c9cf2403f9975421c634d09ed2dd1e04661d Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:51:43 +0000 Subject: [PATCH 15/29] Fire check callback after cleaning up event so it does not get stuck, from Jeongho Jang in GitHub issue 2695. --- file.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/file.c b/file.c index 8f497b38..e527f121 100644 --- a/file.c +++ b/file.c @@ -506,14 +506,14 @@ file_write_error_callback(__unused struct bufferevent *bev, __unused short what, log_debug("write error file %d", cf->stream); - if (cf->cb != NULL) - cf->cb(NULL, NULL, 0, -1, NULL, cf->data); - bufferevent_free(cf->event); cf->event = NULL; close(cf->fd); cf->fd = -1; + + if (cf->cb != NULL) + cf->cb(NULL, NULL, 0, -1, NULL, cf->data); } /* Client file write callback. */ From f706a7e2366ba19bb06265a331fa22a3c7546d90 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:52:07 +0000 Subject: [PATCH 16/29] Remove old shift function keys which interfere with xterm keys now. GitHub issue 2696. --- input-keys.c | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/input-keys.c b/input-keys.c index b975c1ed..d6f48e6c 100644 --- a/input-keys.c +++ b/input-keys.c @@ -95,30 +95,6 @@ static struct input_key_entry input_key_defaults[] = { { .key = KEYC_F12, .data = "\033[24~" }, - { .key = KEYC_F1|KEYC_SHIFT, - .data = "\033[25~" - }, - { .key = KEYC_F2|KEYC_SHIFT, - .data = "\033[26~" - }, - { .key = KEYC_F3|KEYC_SHIFT, - .data = "\033[28~" - }, - { .key = KEYC_F4|KEYC_SHIFT, - .data = "\033[29~" - }, - { .key = KEYC_F5|KEYC_SHIFT, - .data = "\033[31~" - }, - { .key = KEYC_F6|KEYC_SHIFT, - .data = "\033[32~" - }, - { .key = KEYC_F7|KEYC_SHIFT, - .data = "\033[33~" - }, - { .key = KEYC_F8|KEYC_SHIFT, - .data = "\033[34~" - }, { .key = KEYC_IC, .data = "\033[2~" }, From 73bf358f6da9ce8b6c2dd51849ab88a22bb883ad Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:52:29 +0000 Subject: [PATCH 17/29] Do not expand the file given with -f so it can contain :s. --- tmux.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tmux.c b/tmux.c index 71cbccba..16877407 100644 --- a/tmux.c +++ b/tmux.c @@ -338,7 +338,7 @@ main(int argc, char **argv) char *path = NULL, *label = NULL; char *cause, **var; const char *s, *cwd; - int opt, keys, feat = 0; + int opt, keys, feat = 0, fflag = 0; uint64_t flags = 0; const struct options_table_entry *oe; u_int i; @@ -383,10 +383,15 @@ main(int argc, char **argv) flags |= CLIENT_CONTROL; break; case 'f': - for (i = 0; i < cfg_nfiles; i++) - free(cfg_files[i]); - free(cfg_files); - expand_paths(optarg, &cfg_files, &cfg_nfiles, 0); + if (!fflag) { + fflag = 1; + for (i = 0; i < cfg_nfiles; i++) + free(cfg_files[i]); + cfg_nfiles = 0; + } + cfg_files = xreallocarray(cfg_files, cfg_nfiles + 1, + sizeof *cfg_files); + cfg_files[cfg_nfiles++] = xstrdup(optarg); cfg_quiet = 0; break; case 'V': From 1bbdd2aba27057363134fa8c20c10e56ce095dea Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:52:56 +0000 Subject: [PATCH 18/29] Add -F for command-prompt and use it to fix "Rename" on the window menu, GitHub issue 2699. --- cmd-command-prompt.c | 10 ++++++---- key-bindings.c | 2 +- tmux.1 | 7 ++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index c2a2dec7..a955ac32 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -40,8 +40,8 @@ const struct cmd_entry cmd_command_prompt_entry = { .name = "command-prompt", .alias = NULL, - .args = { "1kiI:Np:t:T:", 0, 1 }, - .usage = "[-1kiN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE + .args = { "1FkiI:Np:t:T:", 0, 1 }, + .usage = "[-1FkiN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " [-T type] [template]", .flags = CMD_CLIENT_TFLAG, @@ -59,7 +59,7 @@ struct cmd_command_prompt_cdata { char *next_prompt; char *template; - int idx; + int idx; }; static enum cmd_retval @@ -87,7 +87,9 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) cdata->template = NULL; cdata->idx = 1; - if (args->argc != 0) + if (args->argc != 0 && args_has(args, 'F')) + cdata->template = format_single_from_target(item, args->argv[0]); + else if (args->argc != 0) cdata->template = xstrdup(args->argv[0]); else cdata->template = xstrdup("%1"); diff --git a/key-bindings.c b/key-bindings.c index b380f2cd..de5c20ee 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -41,7 +41,7 @@ " 'Kill' 'X' {kill-window}" \ " 'Respawn' 'R' {respawn-window -k}" \ " '#{?pane_marked,Unmark,Mark}' 'm' {select-pane -m}" \ - " 'Rename' 'n' {command-prompt -I \"#W\" \"rename-window -- '%%'\"}" \ + " 'Rename' 'n' {command-prompt -FI \"#W\" \"rename-window -t#{window_id} -- '%%'\"}" \ " ''" \ " 'New After' 'w' {new-window -a}" \ " 'New At End' 'W' {new-window}" diff --git a/tmux.1 b/tmux.1 index 50b3b6b7..d44dd591 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5392,7 +5392,7 @@ See for possible values for .Ar prompt-type . .It Xo Ic command-prompt -.Op Fl 1ikN +.Op Fl 1FikN .Op Fl I Ar inputs .Op Fl p Ar prompts .Op Fl t Ar target-client @@ -5407,6 +5407,11 @@ to execute commands interactively. If .Ar template is specified, it is used as the command. +With +.Fl F , +.Ar template +is expanded as a format. +.Pp If present, .Fl I is a comma-separated list of the initial text for each prompt. From 77bd6b9ec32bfbc7fbf4de09cae1ce7ea7f3ac35 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:53:19 +0000 Subject: [PATCH 19/29] Do not use NULL client when source-file finishes, GitHub issue 2707. --- cmd-source-file.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd-source-file.c b/cmd-source-file.c index b7a7abee..5509259f 100644 --- a/cmd-source-file.c +++ b/cmd-source-file.c @@ -68,7 +68,9 @@ cmd_source_file_complete(struct client *c, struct cmd_source_file_data *cdata) struct cmdq_item *new_item; if (cfg_finished) { - if (cdata->retval == CMD_RETURN_ERROR && c->session == NULL) + if (cdata->retval == CMD_RETURN_ERROR && + c != NULL && + c->session == NULL) c->retval = 1; new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL); cmdq_insert_after(cdata->after, new_item); From 77b1290698e3bc3ea8edeadb96fd483af26efdfe Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:56:47 +0000 Subject: [PATCH 20/29] More accurate vi(1) word navigation in copy mode and on the status line. This changes the meaning of the word-separators option - setting it to the empty string is equivalent to the previous behavior. From Will Noble in GitHub issue 2693. --- grid-reader.c | 161 ++++++++++++++++++++++--------------- options-table.c | 6 +- status.c | 207 ++++++++++++++++++++++++++++++++++++------------ tmux.1 | 16 ++-- tmux.h | 6 +- utf8.c | 2 +- window-copy.c | 102 +++++++++++++----------- 7 files changed, 325 insertions(+), 175 deletions(-) diff --git a/grid-reader.c b/grid-reader.c index df0dd450..c14e3d33 100644 --- a/grid-reader.c +++ b/grid-reader.c @@ -153,6 +153,29 @@ grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all) gr->cx = grid_reader_line_length(gr); } +/* Handle line wrapping while moving the cursor. */ +static int +grid_reader_handle_wrap(struct grid_reader *gr, u_int *xx, u_int *yy) +{ + /* + * Make sure the cursor lies within the grid reader's bounding area, + * wrapping to the next line as necessary. Return zero if the cursor + * would wrap past the bottom of the grid. + */ + while (gr->cx > *xx) { + if (gr->cy == *yy) + return (0); + grid_reader_cursor_start_of_line(gr, 0); + grid_reader_cursor_down(gr); + + if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) + *xx = gr->gd->sx - 1; + else + *xx = grid_reader_line_length(gr); + } + return (1); +} + /* Check if character under cursor is in set. */ int grid_reader_in_set(struct grid_reader *gr, const char *set) @@ -170,7 +193,6 @@ void grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators) { u_int xx, yy; - int expected = 0; /* Do not break up wrapped words. */ if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) @@ -180,33 +202,35 @@ grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators) yy = gr->gd->hsize + gr->gd->sy - 1; /* - * If we started inside a word, skip over word characters. Then skip - * over separators till the next word. + * When navigating via spaces (for example with next-space) separators + * should be empty. * - * expected is initially set to 0 for the former and then 1 for the - * latter. It is finally set to 0 when the beginning of the next word is - * found. + * If we started on a separator that is not whitespace, skip over + * subsequent separators that are not whitespace. Otherwise, if we + * started on a non-whitespace character, skip over subsequent + * characters that are neither whitespace nor separators. Then, skip + * over whitespace (if any) until the next non-whitespace character. */ - do { - while (gr->cx > xx || - grid_reader_in_set(gr, separators) == expected) { - /* Move down if we are past the end of the line. */ - if (gr->cx > xx) { - if (gr->cy == yy) - return; - grid_reader_cursor_start_of_line(gr, 0); - grid_reader_cursor_down(gr); - - if (grid_get_line(gr->gd, gr->cy)->flags & - GRID_LINE_WRAPPED) - xx = gr->gd->sx - 1; - else - xx = grid_reader_line_length(gr); - } else + if (!grid_reader_handle_wrap(gr, &xx, &yy)) + return; + if (!grid_reader_in_set(gr, WHITESPACE)) { + if (grid_reader_in_set(gr, separators)) { + do gr->cx++; + while (grid_reader_handle_wrap(gr, &xx, &yy) && + grid_reader_in_set(gr, separators) && + !grid_reader_in_set(gr, WHITESPACE)); + } else { + do + gr->cx++; + while (grid_reader_handle_wrap(gr, &xx, &yy) && + !(grid_reader_in_set(gr, separators) || + grid_reader_in_set(gr, WHITESPACE))); } - expected = !expected; - } while (expected == 1); + } + while (grid_reader_handle_wrap(gr, &xx, &yy) && + grid_reader_in_set(gr, WHITESPACE)) + gr->cx++; } /* Move cursor to the end of the next word. */ @@ -214,7 +238,6 @@ void grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators) { u_int xx, yy; - int expected = 1; /* Do not break up wrapped words. */ if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) @@ -224,49 +247,54 @@ grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators) yy = gr->gd->hsize + gr->gd->sy - 1; /* - * If we started on a separator, skip over separators. Then skip over - * word characters till the next separator. + * When navigating via spaces (for example with next-space), separators + * should be empty in both modes. * - * expected is initially set to 1 for the former and then 1 for the - * latter. It is finally set to 1 when the end of the next word is - * found. + * If we started on a whitespace, move until reaching the first + * non-whitespace character. If that character is a separator, treat + * subsequent separators as a word, and continue moving until the first + * non-separator. Otherwise, continue moving until the first separator + * or whitespace. */ - do { - while (gr->cx > xx || - grid_reader_in_set(gr, separators) == expected) { - /* Move down if we are past the end of the line. */ - if (gr->cx > xx) { - if (gr->cy == yy) - return; - grid_reader_cursor_start_of_line(gr, 0); - grid_reader_cursor_down(gr); - if (grid_get_line(gr->gd, gr->cy)->flags & - GRID_LINE_WRAPPED) - xx = gr->gd->sx - 1; - else - xx = grid_reader_line_length(gr); - } else + while (grid_reader_handle_wrap(gr, &xx, &yy)) { + if (grid_reader_in_set(gr, WHITESPACE)) + gr->cx++; + else if (grid_reader_in_set(gr, separators)) { + do gr->cx++; + while (grid_reader_handle_wrap(gr, &xx, &yy) && + grid_reader_in_set(gr, separators) && + !grid_reader_in_set(gr, WHITESPACE)); + return; + } else { + do + gr->cx++; + while (grid_reader_handle_wrap(gr, &xx, &yy) && + !(grid_reader_in_set(gr, WHITESPACE) || + grid_reader_in_set(gr, separators))); + return; } - expected = !expected; - } while (expected == 0); + } } /* Move to the previous place where a word begins. */ void grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators, - int already) + int already, int stop_at_eol) { - int oldx, oldy, r; + int oldx, oldy, at_eol, word_is_letters; /* Move back to the previous word character. */ - if (already || grid_reader_in_set(gr, separators)) { + if (already || grid_reader_in_set(gr, WHITESPACE)) { for (;;) { if (gr->cx > 0) { gr->cx--; - if (!grid_reader_in_set(gr, separators)) + if (!grid_reader_in_set(gr, WHITESPACE)) { + word_is_letters = + !grid_reader_in_set(gr, separators); break; + } } else { if (gr->cy == 0) return; @@ -274,17 +302,21 @@ grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators, grid_reader_cursor_end_of_line(gr, 0, 0); /* Stop if separator at EOL. */ - if (gr->cx > 0) { + if (stop_at_eol && gr->cx > 0) { oldx = gr->cx; gr->cx--; - r = grid_reader_in_set(gr, separators); + at_eol = grid_reader_in_set(gr, + WHITESPACE); gr->cx = oldx; - if (r) + if (at_eol) { + word_is_letters = 0; break; + } } } } - } + } else + word_is_letters = !grid_reader_in_set(gr, separators); /* Move back to the beginning of this word. */ do { @@ -292,15 +324,16 @@ grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators, oldy = gr->cy; if (gr->cx == 0) { if (gr->cy == 0 || - ~grid_get_line(gr->gd, gr->cy - 1)->flags & - GRID_LINE_WRAPPED) + (~grid_get_line(gr->gd, gr->cy - 1)->flags & + GRID_LINE_WRAPPED)) break; grid_reader_cursor_up(gr); grid_reader_cursor_end_of_line(gr, 0, 1); } if (gr->cx > 0) gr->cx--; - } while (!grid_reader_in_set(gr, separators)); + } while (!grid_reader_in_set(gr, WHITESPACE) && + word_is_letters != grid_reader_in_set(gr, separators)); gr->cx = oldx; gr->cy = oldy; } @@ -324,17 +357,17 @@ grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc) memcmp(gc.data.data, jc->data, gc.data.size) == 0) { gr->cx = px; gr->cy = py; - return 1; + return (1); } px++; } if (py == yy || !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)) - return 0; + return (0); px = 0; } - return 0; + return (0); } /* Jump back to character. */ @@ -354,16 +387,16 @@ grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc) memcmp(gc.data.data, jc->data, gc.data.size) == 0) { gr->cx = px - 1; gr->cy = py - 1; - return 1; + return (1); } } if (py == 1 || !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED)) - return 0; + return (0); xx = grid_line_length(gr->gd, py - 2); } - return 0; + return (0); } /* Jump back to the first non-blank character of the line. */ diff --git a/options-table.c b/options-table.c index 73c7fd63..01880d62 100644 --- a/options-table.c +++ b/options-table.c @@ -755,7 +755,11 @@ const struct options_table_entry options_table[] = { { .name = "word-separators", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = " ", + /* + * The set of non-alphanumeric printable ASCII characters minus the + * underscore. + */ + .default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", .text = "Characters considered to separate words." }, diff --git a/status.c b/status.c index 7bcd2f38..7435b31a 100644 --- a/status.c +++ b/status.c @@ -876,17 +876,25 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) *new_key = KEYC_BSPACE; return (1); case 'b': - case 'B': *new_key = 'b'|KEYC_META; return (1); + case 'B': + *new_key = 'B'|KEYC_VI; + return (1); case 'd': *new_key = '\025'; 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 = 'f'|KEYC_META; + *new_key = 'W'|KEYC_VI; return (1); case 'p': *new_key = '\031'; /* C-y */ @@ -1061,16 +1069,125 @@ status_prompt_replace_complete(struct client *c, const char *s) 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; +} + /* Handle keys in prompt. */ int status_prompt_key(struct client *c, key_code key) { struct options *oo = c->session->options; char *s, *cp, prefix = '='; - const char *histstr, *ws = NULL, *keystring; + const char *histstr, *separators = NULL, *keystring; size_t size, idx; struct utf8_data tmp; - int keys; + int keys, word_is_separators; if (c->prompt_flags & PROMPT_KEY) { keystring = key_string_lookup_key(key, 0); @@ -1173,20 +1290,24 @@ process_key: } break; case '\027': /* C-w */ - ws = options_get_string(oo, "word-separators"); + separators = options_get_string(oo, "word-separators"); idx = c->prompt_index; - /* Find a non-separator. */ + /* Find non-whitespace. */ while (idx != 0) { idx--; - if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) + if (!status_prompt_space(&c->prompt_buffer[idx])) break; } + word_is_separators = status_prompt_in_list(separators, + &c->prompt_buffer[idx]); - /* Find the separator at the beginning of the word. */ + /* Find the character before the beginning of the word. */ while (idx != 0) { idx--; - if (status_prompt_in_list(ws, &c->prompt_buffer[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; @@ -1208,50 +1329,32 @@ process_key: c->prompt_index = idx; goto changed; - case 'f'|KEYC_META: case KEYC_RIGHT|KEYC_CTRL: - ws = options_get_string(oo, "word-separators"); - - /* Find a word. */ - while (c->prompt_index != size) { - idx = ++c->prompt_index; - if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) - break; - } - - /* Find the separator at the end of the word. */ - while (c->prompt_index != size) { - idx = ++c->prompt_index; - if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) - break; - } - - /* Back up to the end-of-word like vi. */ - if (options_get_number(oo, "status-keys") == MODEKEY_VI && - c->prompt_index != 0) - c->prompt_index--; - + 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 'b'|KEYC_META: case KEYC_LEFT|KEYC_CTRL: - ws = options_get_string(oo, "word-separators"); - - /* Find a non-separator. */ - while (c->prompt_index != 0) { - idx = --c->prompt_index; - if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) - break; - } - - /* Find the separator at the beginning of the word. */ - while (c->prompt_index != 0) { - idx = --c->prompt_index; - if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { - /* Go back to the word. */ - c->prompt_index++; - break; - } - } + case 'b'|KEYC_META: + separators = options_get_string(oo, "word-separators"); + status_prompt_backward_word(c, separators); goto changed; case KEYC_UP: case '\020': /* C-p */ @@ -1339,8 +1442,10 @@ append_key: return (0); if (key <= 0x7f) utf8_set(&tmp, key); - else + else if (KEYC_IS_UNICODE(key)) utf8_to_data(key, &tmp); + else + return (0); c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, sizeof *c->prompt_buffer); diff --git a/tmux.1 b/tmux.1 index d44dd591..2a77ba21 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1782,19 +1782,18 @@ commands) or when the cursor reaches the bottom (for scrolling commands). .Ql -no-clear variants do not clear the selection. .Pp -The next and previous word keys use space and the -.Ql - , -.Ql _ -and -.Ql @ -characters as word delimiters by default, but this can be adjusted by -setting the +The next and previous word keys skip over whitespace and treat consecutive +runs of either word separators or other letters as words. +Word separators can be customized with the .Em word-separators session option. Next word moves to the start of the next word, next word end to the end of the next word and previous word to the start of the previous word. The three next and previous space keys work similarly but use a space alone as the word separator. +Setting +.Em word-separators +to the empty string makes next/previous word equivalent to next/previous space. .Pp The jump commands enable quick movement within a line. For instance, typing @@ -3974,9 +3973,6 @@ If set to both, a bell and a message are produced. Sets the session's conception of what characters are considered word separators, for the purposes of the next and previous word commands in copy mode. -The default is -.Ql \ -_@ . -.El .Pp Available window options are: .Pp diff --git a/tmux.h b/tmux.h index 5fefcef5..917f4528 100644 --- a/tmux.h +++ b/tmux.h @@ -130,6 +130,7 @@ struct winlink; #define KEYC_CURSOR 0x04000000000000ULL #define KEYC_IMPLIED_META 0x08000000000000ULL #define KEYC_BUILD_MODIFIERS 0x10000000000000ULL +#define KEYC_VI 0x20000000000000ULL /* Masks for key bits. */ #define KEYC_MASK_MODIFIERS 0x00f00000000000ULL @@ -589,6 +590,9 @@ struct msg_write_close { int stream; }; +/* Character classes. */ +#define WHITESPACE " " + /* Mode keys. */ #define MODEKEY_EMACS 0 #define MODEKEY_VI 1 @@ -2639,7 +2643,7 @@ void grid_reader_cursor_end_of_line(struct grid_reader *, int, int); void grid_reader_cursor_next_word(struct grid_reader *, const char *); void grid_reader_cursor_next_word_end(struct grid_reader *, const char *); void grid_reader_cursor_previous_word(struct grid_reader *, const char *, - int); + int, int); int grid_reader_cursor_jump(struct grid_reader *, const struct utf8_data *); int grid_reader_cursor_jump_back(struct grid_reader *, diff --git a/utf8.c b/utf8.c index 88dbdb7a..55a68110 100644 --- a/utf8.c +++ b/utf8.c @@ -65,7 +65,7 @@ static struct utf8_index_tree utf8_index_tree = RB_INITIALIZER(utf8_index_tree); static u_int utf8_next_index; #define UTF8_GET_SIZE(uc) (((uc) >> 24) & 0x1f) -#define UTF8_GET_WIDTH(flags) (((uc) >> 29) - 1) +#define UTF8_GET_WIDTH(uc) (((uc) >> 29) - 1) #define UTF8_SET_SIZE(size) (((utf8_char)(size)) << 24) #define UTF8_SET_WIDTH(width) ((((utf8_char)(width)) + 1) << 29) diff --git a/window-copy.c b/window-copy.c index 423cce8f..2f33a23a 100644 --- a/window-copy.c +++ b/window-copy.c @@ -128,7 +128,7 @@ static void window_copy_cursor_next_word_end_pos(struct window_mode_entry *, static void window_copy_cursor_next_word_end(struct window_mode_entry *, const char *, int); static void window_copy_cursor_previous_word_pos(struct window_mode_entry *, - const char *, int, u_int *, u_int *); + const char *, u_int *, u_int *); static void window_copy_cursor_previous_word(struct window_mode_entry *, const char *, int); static void window_copy_scroll_up(struct window_mode_entry *, u_int); @@ -255,7 +255,7 @@ struct window_copy_mode_data { SEL_LINE, /* select one line at a time */ } selflag; - const char *ws; /* word separators */ + const char *separators; /* word separators */ u_int dx; /* drag start position */ u_int dy; @@ -1321,7 +1321,7 @@ window_copy_cmd_previous_matching_bracket(struct window_copy_cmd_state *cs) tried = 1; goto retry; } - window_copy_cursor_previous_word(wme, "}]) ", 1); + window_copy_cursor_previous_word(wme, close, 1); } continue; } @@ -1433,8 +1433,7 @@ window_copy_cmd_next_matching_bracket(struct window_copy_cmd_state *cs) tried = 1; goto retry; } - window_copy_cursor_next_word_end(wme, "{[( ", - 0); + window_copy_cursor_next_word_end(wme, open, 0); continue; } /* For vi, continue searching for bracket until EOL. */ @@ -1506,7 +1505,7 @@ window_copy_cmd_next_space(struct window_copy_cmd_state *cs) u_int np = wme->prefix; for (; np != 0; np--) - window_copy_cursor_next_word(wme, " "); + window_copy_cursor_next_word(wme, ""); return (WINDOW_COPY_CMD_NOTHING); } @@ -1517,7 +1516,7 @@ window_copy_cmd_next_space_end(struct window_copy_cmd_state *cs) u_int np = wme->prefix; for (; np != 0; np--) - window_copy_cursor_next_word_end(wme, " ", 0); + window_copy_cursor_next_word_end(wme, "", 0); return (WINDOW_COPY_CMD_NOTHING); } @@ -1525,13 +1524,13 @@ static enum window_copy_cmd_action window_copy_cmd_next_word(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; - struct session *s = cs->s; u_int np = wme->prefix; - const char *ws; + const char *separators; + + separators = options_get_string(cs->s->options, "word-separators"); - ws = options_get_string(s->options, "word-separators"); for (; np != 0; np--) - window_copy_cursor_next_word(wme, ws); + window_copy_cursor_next_word(wme, separators); return (WINDOW_COPY_CMD_NOTHING); } @@ -1539,13 +1538,13 @@ static enum window_copy_cmd_action window_copy_cmd_next_word_end(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; - struct session *s = cs->s; u_int np = wme->prefix; - const char *ws; + const char *separators; + + separators = options_get_string(cs->s->options, "word-separators"); - ws = options_get_string(s->options, "word-separators"); for (; np != 0; np--) - window_copy_cursor_next_word_end(wme, ws, 0); + window_copy_cursor_next_word_end(wme, separators, 0); return (WINDOW_COPY_CMD_NOTHING); } @@ -1618,7 +1617,7 @@ window_copy_cmd_previous_space(struct window_copy_cmd_state *cs) u_int np = wme->prefix; for (; np != 0; np--) - window_copy_cursor_previous_word(wme, " ", 1); + window_copy_cursor_previous_word(wme, "", 1); return (WINDOW_COPY_CMD_NOTHING); } @@ -1626,13 +1625,13 @@ static enum window_copy_cmd_action window_copy_cmd_previous_word(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; - struct session *s = cs->s; u_int np = wme->prefix; - const char *ws; + const char *separators; + + separators = options_get_string(cs->s->options, "word-separators"); - ws = options_get_string(s->options, "word-separators"); for (; np != 0; np--) - window_copy_cursor_previous_word(wme, ws, 1); + window_copy_cursor_previous_word(wme, separators, 1); return (WINDOW_COPY_CMD_NOTHING); } @@ -1778,18 +1777,20 @@ static enum window_copy_cmd_action window_copy_cmd_select_word(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; - struct session *s = cs->s; + struct options *session_options = cs->s->options; struct window_copy_mode_data *data = wme->data; u_int px, py, nextx, nexty; + data->lineflag = LINE_SEL_LEFT_RIGHT; data->rectflag = 0; data->selflag = SEL_WORD; data->dx = data->cx; data->dy = screen_hsize(data->backing) + data->cy - data->oy; - data->ws = options_get_string(s->options, "word-separators"); - window_copy_cursor_previous_word(wme, data->ws, 0); + data->separators = options_get_string(session_options, + "word-separators"); + window_copy_cursor_previous_word(wme, data->separators, 0); px = data->cx; py = screen_hsize(data->backing) + data->cy - data->oy; data->selrx = px; @@ -1805,8 +1806,8 @@ window_copy_cmd_select_word(struct window_copy_cmd_state *cs) nexty++; } if (px >= window_copy_find_length(wme, py) || - !window_copy_in_set(wme, nextx, nexty, data->ws)) - window_copy_cursor_next_word_end(wme, data->ws, 1); + !window_copy_in_set(wme, nextx, nexty, WHITESPACE)) + window_copy_cursor_next_word_end(wme, data->separators, 1); else { window_copy_update_cursor(wme, px, data->cy); if (window_copy_update_selection(wme, 1, 1)) @@ -3730,8 +3731,8 @@ window_copy_synchronize_cursor_end(struct window_mode_entry *wme, int begin, begin = 0; if (data->dy > yy || (data->dy == yy && data->dx > xx)) { /* Right to left selection. */ - window_copy_cursor_previous_word_pos(wme, data->ws, 0, - &xx, &yy); + window_copy_cursor_previous_word_pos(wme, + data->separators, &xx, &yy); begin = 1; /* Reset the end. */ @@ -3740,9 +3741,10 @@ window_copy_synchronize_cursor_end(struct window_mode_entry *wme, int begin, } else { /* Left to right selection. */ if (xx >= window_copy_find_length(wme, yy) || - !window_copy_in_set(wme, xx + 1, yy, data->ws)) + !window_copy_in_set(wme, xx + 1, yy, WHITESPACE)) { window_copy_cursor_next_word_end_pos(wme, - data->ws, &xx, &yy); + data->separators, &xx, &yy); + } /* Reset the start. */ data->selx = data->selrx; @@ -4666,19 +4668,19 @@ window_copy_cursor_next_word_end_pos(struct window_mode_entry *wme, struct screen *back_s = data->backing; struct grid_reader gr; u_int px, py, hsize; - int keys; px = data->cx; hsize = screen_hsize(back_s); py = hsize + data->cy - data->oy; grid_reader_start(&gr, back_s->grid, px, py); - keys = options_get_number(oo, "mode-keys"); - if (keys == MODEKEY_VI && !grid_reader_in_set(&gr, separators)) - grid_reader_cursor_right(&gr, 0, 0); - grid_reader_cursor_next_word_end(&gr, separators); - if (keys == MODEKEY_VI) + if (options_get_number(oo, "mode-keys") == MODEKEY_VI) { + if (!grid_reader_in_set(&gr, WHITESPACE)) + grid_reader_cursor_right(&gr, 0, 0); + grid_reader_cursor_next_word_end(&gr, separators); grid_reader_cursor_left(&gr, 1); + } else + grid_reader_cursor_next_word_end(&gr, separators); grid_reader_get_cursor(&gr, &px, &py); *ppx = px; *ppy = py; @@ -4695,7 +4697,6 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme, struct screen *back_s = data->backing; struct grid_reader gr; u_int px, py, oldy, hsize; - int keys; px = data->cx; hsize = screen_hsize(back_s); @@ -4703,12 +4704,13 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme, oldy = data->cy; grid_reader_start(&gr, back_s->grid, px, py); - keys = options_get_number(oo, "mode-keys"); - if (keys == MODEKEY_VI && !grid_reader_in_set(&gr, separators)) - grid_reader_cursor_right(&gr, 0, 0); - grid_reader_cursor_next_word_end(&gr, separators); - if (keys == MODEKEY_VI) + if (options_get_number(oo, "mode-keys") == MODEKEY_VI) { + if (!grid_reader_in_set(&gr, WHITESPACE)) + grid_reader_cursor_right(&gr, 0, 0); + grid_reader_cursor_next_word_end(&gr, separators); grid_reader_cursor_left(&gr, 1); + } else + grid_reader_cursor_next_word_end(&gr, separators); grid_reader_get_cursor(&gr, &px, &py); window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s), data->oy, oldy, px, py, no_reset); @@ -4717,7 +4719,7 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme, /* Compute the previous place where a word begins. */ static void window_copy_cursor_previous_word_pos(struct window_mode_entry *wme, - const char *separators, int already, u_int *ppx, u_int *ppy) + const char *separators, u_int *ppx, u_int *ppy) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; @@ -4729,7 +4731,8 @@ window_copy_cursor_previous_word_pos(struct window_mode_entry *wme, py = hsize + data->cy - data->oy; grid_reader_start(&gr, back_s->grid, px, py); - grid_reader_cursor_previous_word(&gr, separators, already); + grid_reader_cursor_previous_word(&gr, separators, /* already= */ 0, + /* stop_at_eol= */ 1); grid_reader_get_cursor(&gr, &px, &py); *ppx = px; *ppy = py; @@ -4744,6 +4747,11 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, struct screen *back_s = data->backing; struct grid_reader gr; u_int px, py, oldy, hsize; + int stop_at_eol; + + stop_at_eol = + options_get_number(wme->wp->window->options, "mode-keys") + == MODEKEY_EMACS; px = data->cx; hsize = screen_hsize(back_s); @@ -4751,7 +4759,7 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, oldy = data->cy; grid_reader_start(&gr, back_s->grid, px, py); - grid_reader_cursor_previous_word(&gr, separators, already); + grid_reader_cursor_previous_word(&gr, separators, already, stop_at_eol); grid_reader_get_cursor(&gr, &px, &py); window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py); } @@ -4893,10 +4901,10 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) data->selflag = SEL_CHAR; switch (data->selflag) { case SEL_WORD: - if (data->ws != NULL) { + if (data->separators != NULL) { window_copy_update_cursor(wme, x, y); - window_copy_cursor_previous_word_pos(wme, data->ws, 0, - &x, &y); + window_copy_cursor_previous_word_pos(wme, + data->separators, &x, &y); y -= screen_hsize(data->backing) - data->oy; } window_copy_update_cursor(wme, x, y); From 3a5b576399e5f4dff4a975cadb9ef741de8e2011 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:57:06 +0000 Subject: [PATCH 21/29] Fix <= operator. --- format.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/format.c b/format.c index 03c01dc4..17ce998d 100644 --- a/format.c +++ b/format.c @@ -3991,7 +3991,7 @@ format_replace_expression(struct format_modifier *mexp, result = (mleft < mright); break; case LESS_THAN_EQUAL: - result = (mleft > mright); + result = (mleft >= mright); break; } if (use_fp) From 8d75542986a54fdedda9d8ad1fc37dd83f7f05c9 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:58:08 +0000 Subject: [PATCH 22/29] Bump FORMAT_LOOOP_LIMIT and add a log message when hit, GitHub issue 2715. --- format.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/format.c b/format.c index 17ce998d..a10af458 100644 --- a/format.c +++ b/format.c @@ -103,7 +103,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_CHARACTER 0x10000 /* Limit on recursion. */ -#define FORMAT_LOOP_LIMIT 10 +#define FORMAT_LOOP_LIMIT 100 /* Format expand flags. */ #define FORMAT_EXPAND_TIME 0x1 @@ -3991,7 +3991,7 @@ format_replace_expression(struct format_modifier *mexp, result = (mleft < mright); break; case LESS_THAN_EQUAL: - result = (mleft >= mright); + result = (mleft <= mright); break; } if (use_fp) @@ -4441,8 +4441,10 @@ format_expand1(struct format_expand_state *es, const char *fmt) if (fmt == NULL || *fmt == '\0') return (xstrdup("")); - if (es->loop == FORMAT_LOOP_LIMIT) + if (es->loop == FORMAT_LOOP_LIMIT) { + format_log(es, "reached loop limit (%u)", FORMAT_LOOP_LIMIT); return (xstrdup("")); + } es->loop++; format_log(es, "expanding format: %s", fmt); From 43514f4af612405c0dc07abfdee54bae0889989e Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:58:42 +0000 Subject: [PATCH 23/29] Fix rectangle selection, from Anindya Mukherjee, GitHub issue 2709. --- window-copy.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/window-copy.c b/window-copy.c index 2f33a23a..31875739 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1102,10 +1102,13 @@ static enum window_copy_cmd_action window_copy_cmd_cursor_right(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; u_int np = wme->prefix; - for (; np != 0; np--) - window_copy_cursor_right(wme, 0); + for (; np != 0; np--) { + window_copy_cursor_right(wme, data->screen.sel != NULL && + data->rectflag); + } return (WINDOW_COPY_CMD_NOTHING); } @@ -4427,10 +4430,12 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; u_int ox, oy, px, py; + int norectsel; + norectsel = data->screen.sel == NULL || !data->rectflag; oy = screen_hsize(data->backing) + data->cy - data->oy; ox = window_copy_find_length(wme, oy); - if (data->cx != ox) { + if (norectsel && data->cx != ox) { data->lastcx = data->cx; data->lastsx = ox; } @@ -4439,7 +4444,8 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) window_copy_other_end(wme); if (scroll_only || data->cy == 0) { - data->cx = data->lastcx; + if (norectsel) + data->cx = data->lastcx; window_copy_scroll_down(wme, 1); if (scroll_only) { if (data->cy == screen_size_y(s) - 1) @@ -4448,7 +4454,11 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) window_copy_redraw_lines(wme, data->cy, 2); } } else { - window_copy_update_cursor(wme, data->lastcx, data->cy - 1); + if (norectsel) { + window_copy_update_cursor(wme, data->lastcx, + data->cy - 1); + } else + window_copy_update_cursor(wme, data->cx, data->cy - 1); if (window_copy_update_selection(wme, 1, 0)) { if (data->cy == screen_size_y(s) - 1) window_copy_redraw_lines(wme, data->cy, 1); @@ -4457,7 +4467,7 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) } } - if (data->screen.sel == NULL || !data->rectflag) { + if (norectsel) { py = screen_hsize(data->backing) + data->cy - data->oy; px = window_copy_find_length(wme, py); if ((data->cx >= data->lastsx && data->cx != px) || @@ -4494,10 +4504,12 @@ window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only) struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; u_int ox, oy, px, py; + int norectsel; + norectsel = data->screen.sel == NULL || !data->rectflag; oy = screen_hsize(data->backing) + data->cy - data->oy; ox = window_copy_find_length(wme, oy); - if (data->cx != ox) { + if (norectsel && data->cx != ox) { data->lastcx = data->cx; data->lastsx = ox; } @@ -4506,17 +4518,22 @@ window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only) window_copy_other_end(wme); if (scroll_only || data->cy == screen_size_y(s) - 1) { - data->cx = data->lastcx; + if (norectsel) + data->cx = data->lastcx; window_copy_scroll_up(wme, 1); if (scroll_only && data->cy > 0) window_copy_redraw_lines(wme, data->cy - 1, 2); } else { - window_copy_update_cursor(wme, data->lastcx, data->cy + 1); + if (norectsel) { + window_copy_update_cursor(wme, data->lastcx, + data->cy + 1); + } else + window_copy_update_cursor(wme, data->cx, data->cy + 1); if (window_copy_update_selection(wme, 1, 0)) window_copy_redraw_lines(wme, data->cy - 1, 2); } - if (data->screen.sel == NULL || !data->rectflag) { + if (norectsel) { py = screen_hsize(data->backing) + data->cy - data->oy; px = window_copy_find_length(wme, py); if ((data->cx >= data->lastsx && data->cx != px) || From 1e879ef458a21ea4dd266761757537fbbf567eee Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:59:08 +0000 Subject: [PATCH 24/29] Feature for the mouse since FreeBSD termcap does not have kmous. --- tmux.1 | 4 ++++ tty-features.c | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/tmux.1 b/tmux.1 index 2a77ba21..d6a7e35f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3540,6 +3540,10 @@ Supports extended keys. Supports focus reporting. .It margins Supports DECSLRM margins. +.It mouse +Supports +.Xr xterm 1 +mouse sequences. .It overline Supports the overline SGR attribute. .It rectfill diff --git a/tty-features.c b/tty-features.c index b42cf74a..48ac51be 100644 --- a/tty-features.c +++ b/tty-features.c @@ -25,7 +25,6 @@ /* * Still hardcoded: - * - mouse (under kmous capability); * - default colours (under AX or op capabilities); * - AIX colours (under colors >= 16); * - alternate escape (if terminal is VT100-like). @@ -54,6 +53,17 @@ static const struct tty_feature tty_feature_title = { 0 }; +/* Terminal has mouse support. */ +static const char *tty_feature_mouse_capabilities[] = { + "kmous=\\E[M", + NULL +}; +static const struct tty_feature tty_feature_mouse = { + "mouse", + tty_feature_mouse_capabilities, + 0 +}; + /* Terminal can set the clipboard with OSC 52. */ static const char *tty_feature_clipboard_capabilities[] = { "Ms=\\E]52;%p1%s;%p2%s\\a", @@ -238,6 +248,7 @@ static const struct tty_feature *tty_features[] = { &tty_feature_extkeys, &tty_feature_focus, &tty_feature_margins, + &tty_feature_mouse, &tty_feature_overline, &tty_feature_rectfill, &tty_feature_rgb, @@ -338,7 +349,7 @@ tty_default_features(int *feat, const char *name, u_int version) const char *features; } table[] = { #define TTY_FEATURES_BASE_MODERN_XTERM \ - "256,RGB,bpaste,clipboard,strikethrough,title" + "256,RGB,bpaste,clipboard,mouse,strikethrough,title" { .name = "mintty", .features = TTY_FEATURES_BASE_MODERN_XTERM ",ccolour,cstyle,extkeys,margins,overline,usstyle" @@ -348,7 +359,7 @@ tty_default_features(int *feat, const char *name, u_int version) ",ccolour,cstyle,focus,overline,usstyle" }, { .name = "rxvt-unicode", - .features = "256,bpaste,ccolour,cstyle,title" + .features = "256,bpaste,ccolour,cstyle,mouse,title" }, { .name = "iTerm2", .features = TTY_FEATURES_BASE_MODERN_XTERM From a35c897f0f16a9dc578c9ebfe4c4c4b0d73002aa Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 10 Jun 2021 07:59:31 +0000 Subject: [PATCH 25/29] Do not clear region based on current cursor position, this is not necessary anymore and causes problems, GitHub issue 2735. --- tty.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tty.c b/tty.c index 45868fc1..4b1b6777 100644 --- a/tty.c +++ b/tty.c @@ -1005,13 +1005,8 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) return; } - if (ctx->ocy < ctx->orupper || ctx->ocy > ctx->orlower) { - for (i = ctx->ocy; i < ctx->sy; i++) - tty_draw_pane(tty, ctx, i); - } else { - for (i = ctx->orupper; i <= ctx->orlower; i++) - tty_draw_pane(tty, ctx, i); - } + for (i = ctx->orupper; i <= ctx->orlower; i++) + tty_draw_pane(tty, ctx, i); } /* Is this position visible in the pane? */ From 0d0683c28aa467d30c50ac7cc99b95ae829f61a5 Mon Sep 17 00:00:00 2001 From: jmc Date: Thu, 10 Jun 2021 13:12:31 +0000 Subject: [PATCH 26/29] fix some formatting errors; --- tmux.1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tmux.1 b/tmux.1 index d6a7e35f..c06b6456 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4331,6 +4331,7 @@ see the .Sx STYLES section. .El +.El .Sh HOOKS .Nm allows commands to run on various triggers, called @@ -5381,7 +5382,7 @@ Commands related to the status line are as follows: .It Xo Ic clear-prompt-history .Op Fl T Ar prompt-type .Xc -.D1 (alias: Ic clrphist) +.D1 (alias: Ic clrphist ) Clear status prompt history for prompt type .Ar prompt-type . If @@ -5695,7 +5696,7 @@ flag closes any popup on the client. .It Xo Ic show-prompt-history .Op Fl T Ar prompt-type .Xc -.D1 (alias: Ic showphist) +.D1 (alias: Ic showphist ) Display status prompt history for prompt type .Ar prompt-type . If From 9f3874e5c74f5d2c990c44ab0e1369d3876eb57b Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 16 Jun 2021 08:37:58 +0000 Subject: [PATCH 27/29] Pass Ctrl keys through as is when given as hex, GitHub issue 2724. --- key-string.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/key-string.c b/key-string.c index 406d26dd..4f7be858 100644 --- a/key-string.c +++ b/key-string.c @@ -183,6 +183,8 @@ key_string_lookup_string(const char *string) if (string[0] == '0' && string[1] == 'x') { if (sscanf(string + 2, "%x", &u) != 1) return (KEYC_UNKNOWN); + if (u < 32) + return (u); mlen = wctomb(m, u); if (mlen <= 0 || mlen > MB_LEN_MAX) return (KEYC_UNKNOWN); From a83fb8127a6995a92b1f4954e397be9e5ee3c9a0 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 16 Jun 2021 11:57:04 +0000 Subject: [PATCH 28/29] Minor fixes to option descriptions. --- options-table.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/options-table.c b/options-table.c index 01880d62..da1d8776 100644 --- a/options-table.c +++ b/options-table.c @@ -368,7 +368,7 @@ const struct options_table_entry options_table[] = { .maximum = INT_MAX, .default_num = 1, .unit = "milliseconds", - .text = "Maximum time between input to assume it pasting rather " + .text = "Maximum time between input to assume it is pasting rather " "than typing." }, @@ -615,7 +615,7 @@ const struct options_table_entry options_table[] = { .text = "Formats for the status lines. " "Each array member is the format for one status line. " "The default status line is made up of several components " - "which may be configured individually with other option such " + "which may be configured individually with other options such " "as 'status-left'." }, @@ -952,7 +952,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_pane_lines_list, .default_num = PANE_LINES_SINGLE, - .text = "Type of the pane type lines." + .text = "Type of characters used to draw pane border lines. Some of " + "these are only supported on terminals with UTF-8 support." }, { .name = "pane-border-status", From 1d4296f17fde47186e2d09752a912559af034e37 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 18 Jun 2021 07:46:54 +0000 Subject: [PATCH 29/29] Mention %1 under choose-tree also. --- tmux.1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tmux.1 b/tmux.1 index c06b6456..dd25edfc 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2102,9 +2102,11 @@ The following keys may be used in tree mode: .It Li "q" Ta "Exit mode" .El .Pp -After a session, window or pane is chosen, +After a session, window or pane is chosen, the first instance of .Ql %% -is replaced by the target in +and all instances of +.Ql %1 +are replaced by the target in .Ar template and the result executed as a command. If