From eece41547e5404573aa40a89cf00d3ec7104754a Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 20 Dec 2024 07:10:51 +0000 Subject: [PATCH 01/19] Only map S-Tag in mode 2, not mode 1. GitHub issue 4304. --- input-keys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input-keys.c b/input-keys.c index 3fa38049..69b62edc 100644 --- a/input-keys.c +++ b/input-keys.c @@ -596,7 +596,7 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) /* Is this backtab? */ if ((key & KEYC_MASK_KEY) == KEYC_BTAB) { - if ((s->mode & EXTENDED_KEY_MODES) != 0) { + if (s->mode & MODE_KEYS_EXTENDED_2) { /* When in xterm extended mode, remap into S-Tab. */ key = '\011' | (key & ~KEYC_MASK_KEY) | KEYC_SHIFT; } else { From e00853ee829024637d7ea394169674a90ae60217 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 1 Jan 2025 15:17:36 +0000 Subject: [PATCH 02/19] Add an option allowing users to override the width of individual Unicode codepoints (overriding tmux's default list). --- options-table.c | 9 + options.c | 3 +- server.c | 1 + tmux.1 | 8 + tmux.h | 1 + utf8-combined.c | 73 ++++--- utf8.c | 509 ++++++++++++++++++++++++++++++------------------ 7 files changed, 371 insertions(+), 233 deletions(-) diff --git a/options-table.c b/options-table.c index b09dcc3d..bea0bc4c 100644 --- a/options-table.c +++ b/options-table.c @@ -254,6 +254,15 @@ const struct options_table_entry options_table[] = { "Each entry is an alias and a command separated by '='." }, + { .name = "codepoint-widths", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_ARRAY, + .default_str = "", + .separator = ",", + .text = "Array of override widths for Unicode codepoints." + }, + { .name = "copy-command", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, diff --git a/options.c b/options.c index 528de504..4beb9898 100644 --- a/options.c +++ b/options.c @@ -1185,7 +1185,8 @@ options_push_changes(const char *name) RB_FOREACH(w, windows, &windows) layout_fix_panes(w, NULL); } - + if (strcmp(name, "codepoint-widths") == 0) + utf8_update_width_cache(); if (strcmp(name, "input-buffer-size") == 0) input_set_buffer_size(options_get_number(global_options, name)); RB_FOREACH(s, sessions, &sessions) diff --git a/server.c b/server.c index 1bd18c8f..c941b25a 100644 --- a/server.c +++ b/server.c @@ -205,6 +205,7 @@ server_start(struct tmuxproc *client, uint64_t flags, struct event_base *base, fatal("pledge failed"); input_key_build(); + utf8_update_width_cache(); RB_INIT(&windows); RB_INIT(&all_window_panes); TAILQ_INIT(&clients); diff --git a/tmux.1 b/tmux.1 index f1502ea4..2cb3b3c6 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4057,6 +4057,14 @@ Note that aliases are expanded when a command is parsed rather than when it is executed, so binding an alias with .Ic bind-key will bind the expanded form. +.It Ic codepoint-widths[] Ar string +An array option allowing widths of Unicode codepoints to be overridden. +Note the new width applies to all clients. +Each entry is of the form +.Em codepoint=width , +where codepoint may be a UTF-8 character or an identifier of the form +.Ql U+number +where the number is a hexadecimal number. .It Ic copy-command Ar shell-command Give the command to pipe to if the .Ic copy-pipe diff --git a/tmux.h b/tmux.h index 7077f9b1..558bc009 100644 --- a/tmux.h +++ b/tmux.h @@ -3397,6 +3397,7 @@ void session_renumber_windows(struct session *); enum utf8_state utf8_towc (const struct utf8_data *, wchar_t *); enum utf8_state utf8_fromwc(wchar_t wc, struct utf8_data *); int utf8_in_table(wchar_t, const wchar_t *, u_int); +void utf8_update_width_cache(void); utf8_char utf8_build_one(u_char); enum utf8_state utf8_from_data(const struct utf8_data *, utf8_char *); void utf8_to_data(utf8_char, struct utf8_data *); diff --git a/utf8-combined.c b/utf8-combined.c index 05958d47..16ecb453 100644 --- a/utf8-combined.c +++ b/utf8-combined.c @@ -24,40 +24,6 @@ #include "tmux.h" -static const wchar_t utf8_modifier_table[] = { - 0x1F1E6, - 0x1F1E7, - 0x1F1E8, - 0x1F1E9, - 0x1F1EA, - 0x1F1EB, - 0x1F1EC, - 0x1F1ED, - 0x1F1EE, - 0x1F1EF, - 0x1F1F0, - 0x1F1F1, - 0x1F1F2, - 0x1F1F3, - 0x1F1F4, - 0x1F1F5, - 0x1F1F6, - 0x1F1F7, - 0x1F1F8, - 0x1F1F9, - 0x1F1FA, - 0x1F1FB, - 0x1F1FC, - 0x1F1FD, - 0x1F1FE, - 0x1F1FF, - 0x1F3FB, - 0x1F3FC, - 0x1F3FD, - 0x1F3FE, - 0x1F3FF -}; - /* Has this got a zero width joiner at the end? */ int utf8_has_zwj(const struct utf8_data *ud) @@ -93,8 +59,39 @@ utf8_is_modifier(const struct utf8_data *ud) if (utf8_towc(ud, &wc) != UTF8_DONE) return (0); - if (!utf8_in_table(wc, utf8_modifier_table, - nitems(utf8_modifier_table))) - return (0); - return (1); + switch (wc) { + case 0x1F1E6: + case 0x1F1E7: + case 0x1F1E8: + case 0x1F1E9: + case 0x1F1EA: + case 0x1F1EB: + case 0x1F1EC: + case 0x1F1ED: + case 0x1F1EE: + case 0x1F1EF: + case 0x1F1F0: + case 0x1F1F1: + case 0x1F1F2: + case 0x1F1F3: + case 0x1F1F4: + case 0x1F1F5: + case 0x1F1F6: + case 0x1F1F7: + case 0x1F1F8: + case 0x1F1F9: + case 0x1F1FA: + case 0x1F1FB: + case 0x1F1FC: + case 0x1F1FD: + case 0x1F1FE: + case 0x1F1FF: + case 0x1F3FB: + case 0x1F3FC: + case 0x1F3FD: + case 0x1F3FE: + case 0x1F3FF: + return (1); + } + return (0); } diff --git a/utf8.c b/utf8.c index 5c805cbd..d9e69df4 100644 --- a/utf8.c +++ b/utf8.c @@ -26,169 +26,192 @@ #include "tmux.h" -static const wchar_t utf8_force_wide[] = { - 0x0261D, - 0x026F9, - 0x0270A, - 0x0270B, - 0x0270C, - 0x0270D, - 0x1F1E6, - 0x1F1E7, - 0x1F1E8, - 0x1F1E9, - 0x1F1EA, - 0x1F1EB, - 0x1F1EC, - 0x1F1ED, - 0x1F1EE, - 0x1F1EF, - 0x1F1F0, - 0x1F1F1, - 0x1F1F2, - 0x1F1F3, - 0x1F1F4, - 0x1F1F5, - 0x1F1F6, - 0x1F1F7, - 0x1F1F8, - 0x1F1F9, - 0x1F1FA, - 0x1F1FB, - 0x1F1FC, - 0x1F1FD, - 0x1F1FE, - 0x1F1FF, - 0x1F385, - 0x1F3C2, - 0x1F3C3, - 0x1F3C4, - 0x1F3C7, - 0x1F3CA, - 0x1F3CB, - 0x1F3CC, - 0x1F3FB, - 0x1F3FC, - 0x1F3FD, - 0x1F3FE, - 0x1F3FF, - 0x1F442, - 0x1F443, - 0x1F446, - 0x1F447, - 0x1F448, - 0x1F449, - 0x1F44A, - 0x1F44B, - 0x1F44C, - 0x1F44D, - 0x1F44E, - 0x1F44F, - 0x1F450, - 0x1F466, - 0x1F467, - 0x1F468, - 0x1F469, - 0x1F46B, - 0x1F46C, - 0x1F46D, - 0x1F46E, - 0x1F470, - 0x1F471, - 0x1F472, - 0x1F473, - 0x1F474, - 0x1F475, - 0x1F476, - 0x1F477, - 0x1F478, - 0x1F47C, - 0x1F481, - 0x1F482, - 0x1F483, - 0x1F485, - 0x1F486, - 0x1F487, - 0x1F48F, - 0x1F491, - 0x1F4AA, - 0x1F574, - 0x1F575, - 0x1F57A, - 0x1F590, - 0x1F595, - 0x1F596, - 0x1F645, - 0x1F646, - 0x1F647, - 0x1F64B, - 0x1F64C, - 0x1F64D, - 0x1F64E, - 0x1F64F, - 0x1F6A3, - 0x1F6B4, - 0x1F6B5, - 0x1F6B6, - 0x1F6C0, - 0x1F6CC, - 0x1F90C, - 0x1F90F, - 0x1F918, - 0x1F919, - 0x1F91A, - 0x1F91B, - 0x1F91C, - 0x1F91D, - 0x1F91E, - 0x1F91F, - 0x1F926, - 0x1F930, - 0x1F931, - 0x1F932, - 0x1F933, - 0x1F934, - 0x1F935, - 0x1F936, - 0x1F937, - 0x1F938, - 0x1F939, - 0x1F93D, - 0x1F93E, - 0x1F977, - 0x1F9B5, - 0x1F9B6, - 0x1F9B8, - 0x1F9B9, - 0x1F9BB, - 0x1F9CD, - 0x1F9CE, - 0x1F9CF, - 0x1F9D1, - 0x1F9D2, - 0x1F9D3, - 0x1F9D4, - 0x1F9D5, - 0x1F9D6, - 0x1F9D7, - 0x1F9D8, - 0x1F9D9, - 0x1F9DA, - 0x1F9DB, - 0x1F9DC, - 0x1F9DD, - 0x1FAC3, - 0x1FAC4, - 0x1FAC5, - 0x1FAF0, - 0x1FAF1, - 0x1FAF2, - 0x1FAF3, - 0x1FAF4, - 0x1FAF5, - 0x1FAF6, - 0x1FAF7, - 0x1FAF8 +struct utf8_width_item { + wchar_t wc; + u_int width; + int allocated; + + RB_ENTRY(utf8_width_item) entry; +}; + +static int +utf8_width_cache_cmp(struct utf8_width_item *uw1, struct utf8_width_item *uw2) +{ + if (uw1->wc < uw2->wc) + return (-1); + if (uw1->wc > uw2->wc) + return (1); + return (0); +} +RB_HEAD(utf8_width_cache, utf8_width_item); +RB_GENERATE_STATIC(utf8_width_cache, utf8_width_item, entry, + utf8_width_cache_cmp); +static struct utf8_width_cache utf8_width_cache = + RB_INITIALIZER(utf8_width_cache); + +static struct utf8_width_item utf8_default_width_cache[] = { + { .wc = 0x0261D, .width = 2 }, + { .wc = 0x026F9, .width = 2 }, + { .wc = 0x0270A, .width = 2 }, + { .wc = 0x0270B, .width = 2 }, + { .wc = 0x0270C, .width = 2 }, + { .wc = 0x0270D, .width = 2 }, + { .wc = 0x1F1E6, .width = 2 }, + { .wc = 0x1F1E7, .width = 2 }, + { .wc = 0x1F1E8, .width = 2 }, + { .wc = 0x1F1E9, .width = 2 }, + { .wc = 0x1F1EA, .width = 2 }, + { .wc = 0x1F1EB, .width = 2 }, + { .wc = 0x1F1EC, .width = 2 }, + { .wc = 0x1F1ED, .width = 2 }, + { .wc = 0x1F1EE, .width = 2 }, + { .wc = 0x1F1EF, .width = 2 }, + { .wc = 0x1F1F0, .width = 2 }, + { .wc = 0x1F1F1, .width = 2 }, + { .wc = 0x1F1F2, .width = 2 }, + { .wc = 0x1F1F3, .width = 2 }, + { .wc = 0x1F1F4, .width = 2 }, + { .wc = 0x1F1F5, .width = 2 }, + { .wc = 0x1F1F6, .width = 2 }, + { .wc = 0x1F1F7, .width = 2 }, + { .wc = 0x1F1F8, .width = 2 }, + { .wc = 0x1F1F9, .width = 2 }, + { .wc = 0x1F1FA, .width = 2 }, + { .wc = 0x1F1FB, .width = 2 }, + { .wc = 0x1F1FC, .width = 2 }, + { .wc = 0x1F1FD, .width = 2 }, + { .wc = 0x1F1FE, .width = 2 }, + { .wc = 0x1F1FF, .width = 2 }, + { .wc = 0x1F385, .width = 2 }, + { .wc = 0x1F3C2, .width = 2 }, + { .wc = 0x1F3C3, .width = 2 }, + { .wc = 0x1F3C4, .width = 2 }, + { .wc = 0x1F3C7, .width = 2 }, + { .wc = 0x1F3CA, .width = 2 }, + { .wc = 0x1F3CB, .width = 2 }, + { .wc = 0x1F3CC, .width = 2 }, + { .wc = 0x1F3FB, .width = 2 }, + { .wc = 0x1F3FC, .width = 2 }, + { .wc = 0x1F3FD, .width = 2 }, + { .wc = 0x1F3FE, .width = 2 }, + { .wc = 0x1F3FF, .width = 2 }, + { .wc = 0x1F442, .width = 2 }, + { .wc = 0x1F443, .width = 2 }, + { .wc = 0x1F446, .width = 2 }, + { .wc = 0x1F447, .width = 2 }, + { .wc = 0x1F448, .width = 2 }, + { .wc = 0x1F449, .width = 2 }, + { .wc = 0x1F44A, .width = 2 }, + { .wc = 0x1F44B, .width = 2 }, + { .wc = 0x1F44C, .width = 2 }, + { .wc = 0x1F44D, .width = 2 }, + { .wc = 0x1F44E, .width = 2 }, + { .wc = 0x1F44F, .width = 2 }, + { .wc = 0x1F450, .width = 2 }, + { .wc = 0x1F466, .width = 2 }, + { .wc = 0x1F467, .width = 2 }, + { .wc = 0x1F468, .width = 2 }, + { .wc = 0x1F469, .width = 2 }, + { .wc = 0x1F46B, .width = 2 }, + { .wc = 0x1F46C, .width = 2 }, + { .wc = 0x1F46D, .width = 2 }, + { .wc = 0x1F46E, .width = 2 }, + { .wc = 0x1F470, .width = 2 }, + { .wc = 0x1F471, .width = 2 }, + { .wc = 0x1F472, .width = 2 }, + { .wc = 0x1F473, .width = 2 }, + { .wc = 0x1F474, .width = 2 }, + { .wc = 0x1F475, .width = 2 }, + { .wc = 0x1F476, .width = 2 }, + { .wc = 0x1F477, .width = 2 }, + { .wc = 0x1F478, .width = 2 }, + { .wc = 0x1F47C, .width = 2 }, + { .wc = 0x1F481, .width = 2 }, + { .wc = 0x1F482, .width = 2 }, + { .wc = 0x1F483, .width = 2 }, + { .wc = 0x1F485, .width = 2 }, + { .wc = 0x1F486, .width = 2 }, + { .wc = 0x1F487, .width = 2 }, + { .wc = 0x1F48F, .width = 2 }, + { .wc = 0x1F491, .width = 2 }, + { .wc = 0x1F4AA, .width = 2 }, + { .wc = 0x1F574, .width = 2 }, + { .wc = 0x1F575, .width = 2 }, + { .wc = 0x1F57A, .width = 2 }, + { .wc = 0x1F590, .width = 2 }, + { .wc = 0x1F595, .width = 2 }, + { .wc = 0x1F596, .width = 2 }, + { .wc = 0x1F645, .width = 2 }, + { .wc = 0x1F646, .width = 2 }, + { .wc = 0x1F647, .width = 2 }, + { .wc = 0x1F64B, .width = 2 }, + { .wc = 0x1F64C, .width = 2 }, + { .wc = 0x1F64D, .width = 2 }, + { .wc = 0x1F64E, .width = 2 }, + { .wc = 0x1F64F, .width = 2 }, + { .wc = 0x1F6A3, .width = 2 }, + { .wc = 0x1F6B4, .width = 2 }, + { .wc = 0x1F6B5, .width = 2 }, + { .wc = 0x1F6B6, .width = 2 }, + { .wc = 0x1F6C0, .width = 2 }, + { .wc = 0x1F6CC, .width = 2 }, + { .wc = 0x1F90C, .width = 2 }, + { .wc = 0x1F90F, .width = 2 }, + { .wc = 0x1F918, .width = 2 }, + { .wc = 0x1F919, .width = 2 }, + { .wc = 0x1F91A, .width = 2 }, + { .wc = 0x1F91B, .width = 2 }, + { .wc = 0x1F91C, .width = 2 }, + { .wc = 0x1F91D, .width = 2 }, + { .wc = 0x1F91E, .width = 2 }, + { .wc = 0x1F91F, .width = 2 }, + { .wc = 0x1F926, .width = 2 }, + { .wc = 0x1F930, .width = 2 }, + { .wc = 0x1F931, .width = 2 }, + { .wc = 0x1F932, .width = 2 }, + { .wc = 0x1F933, .width = 2 }, + { .wc = 0x1F934, .width = 2 }, + { .wc = 0x1F935, .width = 2 }, + { .wc = 0x1F936, .width = 2 }, + { .wc = 0x1F937, .width = 2 }, + { .wc = 0x1F938, .width = 2 }, + { .wc = 0x1F939, .width = 2 }, + { .wc = 0x1F93D, .width = 2 }, + { .wc = 0x1F93E, .width = 2 }, + { .wc = 0x1F977, .width = 2 }, + { .wc = 0x1F9B5, .width = 2 }, + { .wc = 0x1F9B6, .width = 2 }, + { .wc = 0x1F9B8, .width = 2 }, + { .wc = 0x1F9B9, .width = 2 }, + { .wc = 0x1F9BB, .width = 2 }, + { .wc = 0x1F9CD, .width = 2 }, + { .wc = 0x1F9CE, .width = 2 }, + { .wc = 0x1F9CF, .width = 2 }, + { .wc = 0x1F9D1, .width = 2 }, + { .wc = 0x1F9D2, .width = 2 }, + { .wc = 0x1F9D3, .width = 2 }, + { .wc = 0x1F9D4, .width = 2 }, + { .wc = 0x1F9D5, .width = 2 }, + { .wc = 0x1F9D6, .width = 2 }, + { .wc = 0x1F9D7, .width = 2 }, + { .wc = 0x1F9D8, .width = 2 }, + { .wc = 0x1F9D9, .width = 2 }, + { .wc = 0x1F9DA, .width = 2 }, + { .wc = 0x1F9DB, .width = 2 }, + { .wc = 0x1F9DC, .width = 2 }, + { .wc = 0x1F9DD, .width = 2 }, + { .wc = 0x1FAC3, .width = 2 }, + { .wc = 0x1FAC4, .width = 2 }, + { .wc = 0x1FAC5, .width = 2 }, + { .wc = 0x1FAF0, .width = 2 }, + { .wc = 0x1FAF1, .width = 2 }, + { .wc = 0x1FAF2, .width = 2 }, + { .wc = 0x1FAF3, .width = 2 }, + { .wc = 0x1FAF4, .width = 2 }, + { .wc = 0x1FAF5, .width = 2 }, + { .wc = 0x1FAF6, .width = 2 }, + { .wc = 0x1FAF7, .width = 2 }, + { .wc = 0x1FAF8, .width = 2 } }; struct utf8_item { @@ -226,7 +249,8 @@ RB_HEAD(utf8_index_tree, utf8_item); RB_GENERATE_STATIC(utf8_index_tree, utf8_item, index_entry, utf8_index_cmp); static struct utf8_index_tree utf8_index_tree = RB_INITIALIZER(utf8_index_tree); -static u_int utf8_next_index; +static int utf8_no_width; +static u_int utf8_next_index; #define UTF8_GET_SIZE(uc) (((uc) >> 24) & 0x1f) #define UTF8_GET_WIDTH(uc) (((uc) >> 29) - 1) @@ -257,6 +281,120 @@ utf8_item_by_index(u_int index) return (RB_FIND(utf8_index_tree, &utf8_index_tree, &ui)); } +/* Find a codepoint in the cache. */ +static struct utf8_width_item * +utf8_find_in_width_cache(wchar_t wc) +{ + struct utf8_width_item uw; + + uw.wc = wc; + return RB_FIND(utf8_width_cache, &utf8_width_cache, &uw); +} + +/* Parse a single codepoint option. */ +static void +utf8_add_to_width_cache(const char *s) +{ + struct utf8_width_item *uw, *old; + char *copy, *cp, *endptr; + u_int width; + const char *errstr; + struct utf8_data *ud; + wchar_t wc; + unsigned long long n; + + copy = xstrdup(s); + if ((cp = strchr(copy, '=')) == NULL) { + free(copy); + return; + } + *cp++ = '\0'; + + width = strtonum(cp, 0, 2, &errstr); + if (errstr != NULL) { + free(copy); + return; + } + + if (strncmp(copy, "U+", 2) == 0) { + errno = 0; + n = strtoull(copy + 2, &endptr, 16); + if (copy[2] == '\0' || + *endptr != '\0' || + n == 0 || + n > WCHAR_MAX || + (errno == ERANGE && n == ULLONG_MAX)) { + free(copy); + return; + } + wc = n; + } else { + utf8_no_width = 1; + ud = utf8_fromcstr(copy); + utf8_no_width = 0; + if (ud[0].size == 0 || ud[1].size != 0) { + free(ud); + free(copy); + return; + } +#ifdef HAVE_UTF8PROC + if (utf8proc_mbtowc(&wc, ud[0].data, ud[0].size) <= 0) { +#else + if (mbtowc(&wc, ud[0].data, ud[0].size) <= 0) { +#endif + free(ud); + free(copy); + return; + } + free(ud); + } + + log_debug("Unicode width cache: %08X=%u", (u_int)wc, width); + + uw = xcalloc(1, sizeof *uw); + uw->wc = wc; + uw->width = width; + uw->allocated = 1; + + old = RB_INSERT(utf8_width_cache, &utf8_width_cache, uw); + if (old != NULL) { + RB_REMOVE(utf8_width_cache, &utf8_width_cache, old); + if (old->allocated) + free(old); + RB_INSERT(utf8_width_cache, &utf8_width_cache, uw); + } + + free(copy); +} + +/* Rebuild cache of widths. */ +void +utf8_update_width_cache(void) +{ + struct utf8_width_item *uw, *uw1; + struct options_entry *o; + struct options_array_item *a; + u_int i; + + RB_FOREACH_SAFE (uw, utf8_width_cache, &utf8_width_cache, uw1) { + RB_REMOVE(utf8_width_cache, &utf8_width_cache, uw); + if (uw->allocated) + free(uw); + } + + for (i = 0; i < nitems(utf8_default_width_cache); i++) { + RB_INSERT(utf8_width_cache, &utf8_width_cache, + &utf8_default_width_cache[i]); + } + + o = options_get(global_options, "codepoint-widths"); + a = options_array_first(o); + while (a != NULL) { + utf8_add_to_width_cache(options_array_item_value(a)->string); + a = options_array_next(a); + } +} + /* Add a UTF-8 item. */ static int utf8_put_item(const u_char *data, size_t size, u_int *index) @@ -287,28 +425,6 @@ utf8_put_item(const u_char *data, size_t size, u_int *index) return (0); } -static int -utf8_table_cmp(const void *vp1, const void *vp2) -{ - const wchar_t *wc1 = vp1, *wc2 = vp2; - - if (*wc1 < *wc2) - return (-1); - if (*wc1 > *wc2) - return (1); - return (0); -} - -/* Check if character in table. */ -int -utf8_in_table(wchar_t find, const wchar_t *table, u_int count) -{ - wchar_t *found; - - found = bsearch(&find, table, count, sizeof *table, utf8_table_cmp); - return (found != NULL); -} - /* Get UTF-8 character from data. */ enum utf8_state utf8_from_data(const struct utf8_data *ud, utf8_char *uc) @@ -401,12 +517,15 @@ utf8_copy(struct utf8_data *to, const struct utf8_data *from) static enum utf8_state utf8_width(struct utf8_data *ud, int *width) { - wchar_t wc; + struct utf8_width_item *uw; + wchar_t wc; if (utf8_towc(ud, &wc) != UTF8_DONE) return (UTF8_ERROR); - if (utf8_in_table(wc, utf8_force_wide, nitems(utf8_force_wide))) { - *width = 2; + uw = utf8_find_in_width_cache(wc); + if (uw != NULL) { + *width = uw->width; + log_debug("cached width for %08X is %d", (u_int)wc, *width); return (UTF8_DONE); } @@ -504,11 +623,13 @@ utf8_append(struct utf8_data *ud, u_char ch) if (ud->have != ud->size) return (UTF8_MORE); - if (ud->width == 0xff) - return (UTF8_ERROR); - if (utf8_width(ud, &width) != UTF8_DONE) - return (UTF8_ERROR); - ud->width = width; + if (!utf8_no_width) { + if (ud->width == 0xff) + return (UTF8_ERROR); + if (utf8_width(ud, &width) != UTF8_DONE) + return (UTF8_ERROR); + ud->width = width; + } return (UTF8_DONE); } From 2a5eba7899a47d3d2445ff9eabcad466a47b04fa Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 2 Jan 2025 10:34:45 +0000 Subject: [PATCH 03/19] Check backspace against VERASE earlier before it is translated to an internal key and do not go through the mapping on output. Fixes problems reported by Ben Price in GitHub issue 4284 and by tb@. --- input-keys.c | 25 ++++++++++++++++++------- tty-keys.c | 21 +++++++++++---------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/input-keys.c b/input-keys.c index 69b62edc..fa4e3f8e 100644 --- a/input-keys.c +++ b/input-keys.c @@ -586,12 +586,24 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) /* Is this backspace? */ if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) { newkey = options_get_number(global_options, "backspace"); - if (newkey == KEYC_BSPACE) - newkey = '\b'; - newkey |= (key & (KEYC_MASK_FLAGS|KEYC_MASK_MODIFIERS)); - log_debug("%s: key 0x%llx is backspace -> 0x%llx", __func__, key, - newkey); - key = newkey; + log_debug("%s: key 0x%llx is backspace -> 0x%llx", __func__, + key, newkey); + if ((key & KEYC_MASK_MODIFIERS) == 0) { + ud.data[0] = 255; + if ((newkey & KEYC_MASK_MODIFIERS) == 0) + ud.data[0] = newkey; + else if ((newkey & KEYC_MASK_MODIFIERS) == KEYC_CTRL) { + newkey &= KEYC_MASK_KEY; + if (newkey >= 'A' && newkey <= 'Z') + ud.data[0] = newkey - 0x40; + else if (newkey >= 'a' && newkey <= 'z') + ud.data[0] = newkey - 0x60; + } + if (ud.data[0] != 255) + input_key_write(__func__, bev, &ud.data[0], 1); + return (0); + } + key = newkey|(key & (KEYC_MASK_FLAGS|KEYC_MASK_MODIFIERS)); } /* Is this backtab? */ @@ -613,7 +625,6 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) if (!(key & ~KEYC_MASK_KEY)) { if (key == C0_HT || key == C0_CR || - key == C0_BS || key == C0_ESC || (key >= 0x20 && key <= 0x7f)) { ud.data[0] = key; diff --git a/tty-keys.c b/tty-keys.c index e551e726..0de31c5d 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -885,6 +885,17 @@ first_key: if ((key & KEYC_MASK_KEY) == C0_NUL) key = ' ' | KEYC_CTRL | (key & KEYC_META); + /* + * Check for backspace key using termios VERASE - the terminfo + * kbs entry is extremely unreliable, so cannot be safely + * used. termios should have a better idea. + */ + bspace = tty->tio.c_cc[VERASE]; + if (bspace != _POSIX_VDISABLE && key == bspace) { + log_debug("%s: key %#llx is backspace", c->name, key); + key = KEYC_BSPACE; + } + /* * Fix up all C0 control codes that don't have a dedicated key into * corresponding Ctrl keys. Convert characters in the A-Z range into @@ -894,7 +905,6 @@ first_key: if (onlykey < 0x20 && onlykey != C0_HT && onlykey != C0_CR && - onlykey != C0_BS && onlykey != C0_ESC) { onlykey |= 0x40; if (onlykey >= 'A' && onlykey <= 'Z') @@ -936,15 +946,6 @@ partial_key: complete_key: log_debug("%s: complete key %.*s %#llx", c->name, (int)size, buf, key); - /* - * Check for backspace key using termios VERASE - the terminfo - * kbs entry is extremely unreliable, so cannot be safely - * used. termios should have a better idea. - */ - bspace = tty->tio.c_cc[VERASE]; - if (bspace != _POSIX_VDISABLE && (key & KEYC_MASK_KEY) == bspace) - key = (key & KEYC_MASK_MODIFIERS)|KEYC_BSPACE; - /* Remove key timer. */ if (event_initialized(&tty->key_timer)) evtimer_del(&tty->key_timer); From 37ad1e2f6da31ba8bf09865fccddec61addaad24 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 12 Jan 2025 14:20:49 +0000 Subject: [PATCH 04/19] Map bright black (colour 8) to white (7) if the background is black on terminals with only eight colours so the text is not invisible. From Dmytro Bagrii in GitHub issue 4322. --- tty.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tty.c b/tty.c index f4676170..0080b7c6 100644 --- a/tty.c +++ b/tty.c @@ -2758,6 +2758,8 @@ tty_check_fg(struct tty *tty, struct colour_palette *palette, gc->fg &= 7; if (colours >= 16) gc->fg += 90; + else if (gc->fg == 0 && gc->bg == 0) + gc->fg = 7; } } return; From 97fe3563facf319971e86ba8d46ccff96983ef0b Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 12 Jan 2025 14:36:28 +0000 Subject: [PATCH 05/19] Do not crash if moving popup that has exited to a pane, from Michael Grant in GitHub issue 4312. --- popup.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/popup.c b/popup.c index 21be8f05..ebe1fc98 100644 --- a/popup.c +++ b/popup.c @@ -353,9 +353,11 @@ popup_make_pane(struct popup_data *pd, enum layout_type type) new_wp = window_add_pane(wp->window, NULL, hlimit, 0); layout_assign_pane(lc, new_wp, 0); - new_wp->fd = job_transfer(pd->job, &new_wp->pid, new_wp->tty, - sizeof new_wp->tty); - pd->job = NULL; + if (pd->job != NULL) { + new_wp->fd = job_transfer(pd->job, &new_wp->pid, new_wp->tty, + sizeof new_wp->tty); + pd->job = NULL; + } screen_set_title(&pd->s, new_wp->base.title); screen_free(&new_wp->base); From 31e8d4676ac8a358861626a7760ec6412c329057 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 13 Jan 2025 08:58:34 +0000 Subject: [PATCH 06/19] Count line numbers correctly inside strings, reported by Pedro Navarro in GitHub issue 4325. --- cmd-parse.y | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cmd-parse.y b/cmd-parse.y index fbed727b..594e8cb3 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -1627,6 +1627,7 @@ yylex_token_tilde(char **buf, size_t *len) static char * yylex_token(int ch) { + struct cmd_parse_state *ps = &parse_state; char *buf; size_t len; enum { START, @@ -1650,9 +1651,12 @@ yylex_token(int ch) ch = '\r'; } } - if (state == NONE && ch == '\n') { - log_debug("%s: end at EOL", __func__); - break; + if (ch == '\n') { + if (state == NONE) { + log_debug("%s: end at EOL", __func__); + break; + } + ps->input->line++; } /* Whitespace or ; or } ends a token unless inside quotes. */ From 9260f5dc96ce6b0095bda2302f5238f67bf9fe1b Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 17 Jan 2025 15:53:01 +0000 Subject: [PATCH 07/19] Do not update focus on client's without a session. --- server-client.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server-client.c b/server-client.c index a8911224..4581e9bc 100644 --- a/server-client.c +++ b/server-client.c @@ -159,7 +159,8 @@ server_client_clear_overlay(struct client *c) c->overlay_data = NULL; c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR); - window_update_focus(c->session->curw->window); + if (c->session != NULL) + window_update_focus(c->session->curw->window); server_redraw_client(c); } From dd7d04be95f9c08fab3a277e0356eaecd0810fa7 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 20 Jan 2025 13:00:07 +0000 Subject: [PATCH 08/19] Update issue templates --- .github/ISSUE_TEMPLATE/custom.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/custom.md diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 00000000..0aeab3a8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,31 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + +### Issue description + +Please read https://github.com/tmux/tmux/blob/master/.github/CONTRIBUTING.md +before opening an issue. + +If you have upgraded, make sure your issue is not covered in the CHANGES file +for your version: https://raw.githubusercontent.com/tmux/tmux/master/CHANGES + +Describe the problem and the steps to reproduce. Add a minimal tmux config if +necessary. Screenshots can be helpful, but no more than one or two. + +Do not report bugs (crashes, incorrect behaviour) without reproducing on a tmux +built from the latest code in Git. + +### Required information + +Please provide the following information: + +* tmux version (`tmux -V`). +* Platform (`uname -sp`). +* $TERM inside and outside of tmux (`echo $TERM`). +* Logs from tmux (`tmux kill-server; tmux -vv new`). From 0c9f8ff18950a70309411dd61768cb272b55c68e Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 20 Jan 2025 13:02:24 +0000 Subject: [PATCH 09/19] Update issue templates --- .../ISSUE_TEMPLATE/{custom.md => use-this-issue-template.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .github/ISSUE_TEMPLATE/{custom.md => use-this-issue-template.md} (87%) diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/use-this-issue-template.md similarity index 87% rename from .github/ISSUE_TEMPLATE/custom.md rename to .github/ISSUE_TEMPLATE/use-this-issue-template.md index 0aeab3a8..bc95fa2e 100644 --- a/.github/ISSUE_TEMPLATE/custom.md +++ b/.github/ISSUE_TEMPLATE/use-this-issue-template.md @@ -1,6 +1,6 @@ --- -name: Custom issue template -about: Describe this issue template's purpose here. +name: Use this issue template +about: Please read https://github.com/tmux/tmux/blob/master/.github/CONTRIBUTING.md title: '' labels: '' assignees: '' From 6ab268c7bb8b6941796ee6f2ad4b628f54a4f28d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 20 Jan 2025 13:04:08 +0000 Subject: [PATCH 10/19] Remove old issue template. --- .github/ISSUE_TEMPLATE.md | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 8bf1e66a..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,22 +0,0 @@ -### Issue description - -Please read https://github.com/tmux/tmux/blob/master/.github/CONTRIBUTING.md -before opening an issue. - -If you have upgraded, make sure your issue is not covered in the CHANGES file -for your version: https://raw.githubusercontent.com/tmux/tmux/master/CHANGES - -Describe the problem and the steps to reproduce. Add a minimal tmux config if -necessary. Screenshots can be helpful, but no more than one or two. - -Do not report bugs (crashes, incorrect behaviour) without reproducing on a tmux -built from the latest code in Git. - -### Required information - -Please provide the following information: - -* tmux version (`tmux -V`). -* Platform (`uname -sp`). -* $TERM inside and outside of tmux (`echo $TERM`). -* Logs from tmux (`tmux kill-server; tmux -vv new`). From 244bb726e26d7e590b04b7f57dd75fc431b2a492 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 27 Jan 2025 09:05:22 +0000 Subject: [PATCH 11/19] Add some missing spaces, from Ilya Grigoriev. --- cmd-display-menu.c | 4 ++-- cmd-split-window.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd-display-menu.c b/cmd-display-menu.c index 73728b91..ac136766 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -42,7 +42,7 @@ const struct cmd_entry cmd_display_menu_entry = { .args = { "b:c:C:H:s:S:MOt:T:x:y:", 1, -1, cmd_display_menu_args_parse }, .usage = "[-MO] [-b border-lines] [-c target-client] " "[-C starting-choice] [-H selected-style] [-s style] " - "[-S border-style] " CMD_TARGET_PANE_USAGE "[-T title] " + "[-S border-style] " CMD_TARGET_PANE_USAGE " [-T title] " "[-x position] [-y position] name key command ...", .target = { 't', CMD_FIND_PANE, 0 }, @@ -59,7 +59,7 @@ const struct cmd_entry cmd_display_popup_entry = { .usage = "[-BCE] [-b border-lines] [-c target-client] " "[-d start-directory] [-e environment] [-h height] " "[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE - "[-T title] [-w width] [-x position] [-y position] " + " [-T title] [-w width] [-x position] [-y position] " "[shell-command]", .target = { 't', CMD_FIND_PANE, 0 }, diff --git a/cmd-split-window.c b/cmd-split-window.c index 82ff22af..128e9e8b 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -43,7 +43,7 @@ const struct cmd_entry cmd_split_window_entry = { .args = { "bc:de:fF:hIl:p:Pt:vZ", 0, -1, NULL }, .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " "[-F format] [-l size] " CMD_TARGET_PANE_USAGE - "[shell-command]", + " [shell-command]", .target = { 't', CMD_FIND_PANE, 0 }, From 4c12ac9fb80f8f8d2096ba2463b448e9865b70cd Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 27 Jan 2025 09:16:05 +0000 Subject: [PATCH 12/19] Make list-commands command show only one command if an argument is given, from Ilya Grigoriev in GitHub issue 4352. --- cmd-list-keys.c | 68 +++++++++++++++++++++++++++++-------------------- cmd.c | 2 +- tmux.h | 1 + 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 395b147c..b5050f09 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -91,7 +91,7 @@ cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args, struct key_binding *bd; const char *key; char *tmp, *note; - int found = 0; + int found = 0; table = key_bindings_get_table(tablename, 0); if (table == NULL) @@ -321,6 +321,31 @@ out: return (CMD_RETURN_NORMAL); } +static void +cmd_list_single_command(const struct cmd_entry *entry, struct format_tree *ft, + const char *template, struct cmdq_item *item) +{ + const char *s; + char *line; + + format_add(ft, "command_list_name", "%s", entry->name); + if (entry->alias != NULL) + s = entry->alias; + else + s = ""; + format_add(ft, "command_list_alias", "%s", s); + if (entry->usage != NULL) + s = entry->usage; + else + s = ""; + format_add(ft, "command_list_usage", "%s", s); + + line = format_expand(ft, template); + if (*line != '\0') + cmdq_print(item, "%s", line); + free(line); +} + static enum cmd_retval cmd_list_keys_commands(struct cmd *self, struct cmdq_item *item) { @@ -328,8 +353,8 @@ cmd_list_keys_commands(struct cmd *self, struct cmdq_item *item) const struct cmd_entry **entryp; const struct cmd_entry *entry; struct format_tree *ft; - const char *template, *s, *command; - char *line; + const char *template, *command; + char *cause; if ((template = args_get(args, 'F')) == NULL) { template = "#{command_list_name}" @@ -341,30 +366,19 @@ cmd_list_keys_commands(struct cmd *self, struct cmdq_item *item) format_defaults(ft, NULL, NULL, NULL, NULL); command = args_string(args, 0); - for (entryp = cmd_table; *entryp != NULL; entryp++) { - entry = *entryp; - if (command != NULL && - (strcmp(entry->name, command) != 0 && - (entry->alias == NULL || - strcmp(entry->alias, command) != 0))) - continue; - - format_add(ft, "command_list_name", "%s", entry->name); - if (entry->alias != NULL) - s = entry->alias; - else - s = ""; - format_add(ft, "command_list_alias", "%s", s); - if (entry->usage != NULL) - s = entry->usage; - else - s = ""; - format_add(ft, "command_list_usage", "%s", s); - - line = format_expand(ft, template); - if (*line != '\0') - cmdq_print(item, "%s", line); - free(line); + if (command == NULL) { + for (entryp = cmd_table; *entryp != NULL; entryp++) + cmd_list_single_command(*entryp, ft, template, item); + } else { + entry = cmd_find(command, &cause); + if (entry != NULL) + cmd_list_single_command(entry, ft, template, item); + else { + cmdq_error(item, "%s", cause); + free(cause); + format_free(ft); + return (CMD_RETURN_ERROR); + } } format_free(ft); diff --git a/cmd.c b/cmd.c index 9e2ce036..64f63c92 100644 --- a/cmd.c +++ b/cmd.c @@ -445,7 +445,7 @@ cmd_get_alias(const char *name) } /* Look up a command entry by name. */ -static const struct cmd_entry * +const struct cmd_entry * cmd_find(const char *name, char **cause) { const struct cmd_entry **loop, *entry, *found = NULL; diff --git a/tmux.h b/tmux.h index 558bc009..1b6d889a 100644 --- a/tmux.h +++ b/tmux.h @@ -2597,6 +2597,7 @@ int cmd_find_from_nothing(struct cmd_find_state *, int); /* cmd.c */ extern const struct cmd_entry *cmd_table[]; +const struct cmd_entry *cmd_find(const char *, char **); void printflike(3, 4) cmd_log_argv(int, char **, const char *, ...); void cmd_prepend_argv(int *, char ***, const char *); void cmd_append_argv(int *, char ***, const char *); From 80eb460fc919e843c4fc53a08c2df84fa1654b98 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 10 Feb 2025 08:14:32 +0000 Subject: [PATCH 13/19] Add display-message -C flag to update pane while message is displayed, GitHub issue 4363 from Vitaly Ostrosablin. --- alerts.c | 8 ++++---- cmd-display-message.c | 7 ++++--- cmd-if-shell.c | 2 +- cmd-list-keys.c | 8 ++++---- cmd-queue.c | 2 +- cmd-run-shell.c | 2 +- mode-tree.c | 2 +- status.c | 6 ++++-- tmux.1 | 5 ++++- tmux.h | 2 +- window-customize.c | 4 ++-- 11 files changed, 27 insertions(+), 21 deletions(-) diff --git a/alerts.c b/alerts.c index b5ea0cf4..a2b4d654 100644 --- a/alerts.c +++ b/alerts.c @@ -316,11 +316,11 @@ alerts_set_message(struct winlink *wl, const char *type, const char *option) if (visual == VISUAL_OFF) continue; if (c->session->curw == wl) { - status_message_set(c, -1, 1, 0, "%s in current window", - type); + status_message_set(c, -1, 1, 0, 0, + "%s in current window", type); } else { - status_message_set(c, -1, 1, 0, "%s in window %d", type, - wl->idx); + status_message_set(c, -1, 1, 0, 0, + "%s in window %d", type, wl->idx); } } } diff --git a/cmd-display-message.c b/cmd-display-message.c index 512509f0..9ba6d13e 100644 --- a/cmd-display-message.c +++ b/cmd-display-message.c @@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = { .name = "display-message", .alias = "display", - .args = { "ac:d:lINpt:F:v", 0, 1, NULL }, - .usage = "[-aIlNpv] [-c target-client] [-d delay] [-F format] " + .args = { "aCc:d:lINpt:F:v", 0, 1, NULL }, + .usage = "[-aCIlNpv] [-c target-client] [-d delay] [-F format] " CMD_TARGET_PANE_USAGE " [message]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -69,6 +69,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) const char *template; char *msg, *cause; int delay = -1, flags, Nflag = args_has(args, 'N'); + int Cflag = args_has(args, 'C'); struct format_tree *ft; u_int count = args_count(args); struct evbuffer *evb; @@ -150,7 +151,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) server_client_print(tc, 0, evb); evbuffer_free(evb); } else if (tc != NULL) - status_message_set(tc, delay, 0, Nflag, "%s", msg); + status_message_set(tc, delay, 0, Nflag, Cflag, "%s", msg); free(msg); format_free(ft); diff --git a/cmd-if-shell.c b/cmd-if-shell.c index 205a8ce1..81518939 100644 --- a/cmd-if-shell.c +++ b/cmd-if-shell.c @@ -157,7 +157,7 @@ cmd_if_shell_callback(struct job *job) if (cmdlist == NULL) { if (cdata->item == NULL) { *error = toupper((u_char)*error); - status_message_set(c, -1, 1, 0, "%s", error); + status_message_set(c, -1, 1, 0, 0, "%s", error); } else cmdq_error(cdata->item, "%s", error); free(error); diff --git a/cmd-list-keys.c b/cmd-list-keys.c index b5050f09..ddfc0e0c 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -114,8 +114,8 @@ cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args, note = xstrdup(bd->note); tmp = utf8_padcstr(key, keywidth + 1); if (args_has(args, '1') && tc != NULL) { - status_message_set(tc, -1, 1, 0, "%s%s%s", prefix, tmp, - note); + status_message_set(tc, -1, 1, 0, 0, "%s%s%s", prefix, + tmp, note); } else cmdq_print(item, "%s%s%s", prefix, tmp, note); free(tmp); @@ -298,8 +298,8 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) free(cp); if (args_has(args, '1') && tc != NULL) { - status_message_set(tc, -1, 1, 0, "bind-key %s", - tmp); + status_message_set(tc, -1, 1, 0, 0, + "bind-key %s", tmp); } else cmdq_print(item, "bind-key %s", tmp); free(key); diff --git a/cmd-queue.c b/cmd-queue.c index 60d685f8..a6dfc592 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -893,7 +893,7 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...) c->retval = 1; } else { *msg = toupper((u_char) *msg); - status_message_set(c, -1, 1, 0, "%s", msg); + status_message_set(c, -1, 1, 0, 0, "%s", msg); } free(msg); diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 9e224c4e..be4c7cac 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -204,7 +204,7 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg) if (cmdlist == NULL) { if (cdata->item == NULL) { *error = toupper((u_char)*error); - status_message_set(c, -1, 1, 0, "%s", error); + status_message_set(c, -1, 1, 0, 0, "%s", error); } else cmdq_error(cdata->item, "%s", error); free(error); diff --git a/mode-tree.c b/mode-tree.c index e1170a3d..d43cf1b3 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -1297,7 +1297,7 @@ mode_tree_run_command(struct client *c, struct cmd_find_state *fs, if (status == CMD_PARSE_ERROR) { if (c != NULL) { *error = toupper((u_char)*error); - status_message_set(c, -1, 1, 0, "%s", error); + status_message_set(c, -1, 1, 0, 0, "%s", error); } free(error); } diff --git a/status.c b/status.c index 32833f8c..745ee3ae 100644 --- a/status.c +++ b/status.c @@ -470,7 +470,7 @@ status_redraw(struct client *c) /* Set a status line message. */ void status_message_set(struct client *c, int delay, int ignore_styles, - int ignore_keys, const char *fmt, ...) + int ignore_keys, int no_freeze, const char *fmt, ...) { struct timeval tv; va_list ap; @@ -514,7 +514,9 @@ status_message_set(struct client *c, int delay, int ignore_styles, c->message_ignore_keys = ignore_keys; c->message_ignore_styles = ignore_styles; - c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); + if (!no_freeze) + c->tty.flags |= TTY_FREEZE; + c->tty.flags |= TTY_NOCURSOR; c->flags |= CLIENT_REDRAWSTATUS; } diff --git a/tmux.1 b/tmux.1 index 2cb3b3c6..6f9c68e1 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6742,7 +6742,7 @@ The following keys are available in menus: .El .Tg display .It Xo Ic display-message -.Op Fl aIlNpv +.Op Fl aCIlNpv .Op Fl c Ar target-client .Op Fl d Ar delay .Op Fl t Ar target-pane @@ -6765,6 +6765,9 @@ option is used; a delay of zero waits for a key press. .Ql N ignores key presses and closes only after the delay expires. If +.Fl C +given, the pane will continue to be updated while the message is displayed. +If .Fl l is given, .Ar message diff --git a/tmux.h b/tmux.h index 1b6d889a..065bb042 100644 --- a/tmux.h +++ b/tmux.h @@ -2847,7 +2847,7 @@ struct style_range *status_get_range(struct client *, u_int, u_int); void status_init(struct client *); void status_free(struct client *); int status_redraw(struct client *); -void printflike(5, 6) status_message_set(struct client *, int, int, int, +void printflike(6, 7) status_message_set(struct client *, int, int, int, int, const char *, ...); void status_message_clear(struct client *); int status_message_redraw(struct client *); diff --git a/window-customize.c b/window-customize.c index 387254e0..c49e57af 100644 --- a/window-customize.c +++ b/window-customize.c @@ -1000,7 +1000,7 @@ window_customize_set_option_callback(struct client *c, void *itemdata, fail: *cause = toupper((u_char)*cause); - status_message_set(c, -1, 1, 0, "%s", cause); + status_message_set(c, -1, 1, 0, 0, "%s", cause); free(cause); return (0); } @@ -1203,7 +1203,7 @@ window_customize_set_command_callback(struct client *c, void *itemdata, fail: *error = toupper((u_char)*error); - status_message_set(c, -1, 1, 0, "%s", error); + status_message_set(c, -1, 1, 0, 0, "%s", error); free(error); return (0); } From 5d1a6acc84bb024aadf4288b4e3a9e7cc0289e83 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 10 Feb 2025 08:18:23 +0000 Subject: [PATCH 14/19] Align index numbers in trees, from David Mandelberg, GitHub issue 4360. --- mode-tree.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/mode-tree.c b/mode-tree.c index d43cf1b3..24cbea04 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -66,6 +66,7 @@ struct mode_tree_data { u_int line_size; u_int depth; + u_int maxdepth; u_int width; u_int height; @@ -196,6 +197,8 @@ mode_tree_build_lines(struct mode_tree_data *mtd, int flat = 1; mtd->depth = depth; + if (depth > mtd->maxdepth) + mtd->maxdepth = depth; TAILQ_FOREACH(mti, mtl, entry) { mtd->line_list = xreallocarray(mtd->line_list, mtd->line_size + 1, sizeof *mtd->line_list); @@ -528,6 +531,7 @@ mode_tree_build(struct mode_tree_data *mtd) TAILQ_INIT(&mtd->saved); mode_tree_clear_lines(mtd); + mtd->maxdepth = 0; mode_tree_build_lines(mtd, &mtd->children, 0); if (mtd->line_list != NULL && tag == UINT64_MAX) @@ -658,7 +662,7 @@ mode_tree_draw(struct mode_tree_data *mtd) char *text, *start, *key; const char *tag, *symbol; size_t size, n; - int keylen, pad; + int keylen, pad, namelen[mtd->maxdepth + 1]; if (mtd->line_size == 0) return; @@ -682,6 +686,15 @@ mode_tree_draw(struct mode_tree_data *mtd) keylen = mti->keylen + 3; } + for (i = 0; i < mtd->maxdepth + 1; i++) + namelen[i] = 0; + for (i = 0; i < mtd->line_size; i++) { + line = &mtd->line_list[i]; + mti = line->item; + if ((int)strlen(mti->name) > namelen[line->depth]) + namelen[line->depth] = strlen(mti->name); + } + for (i = 0; i < mtd->line_size; i++) { if (i < mtd->offset) continue; @@ -731,8 +744,9 @@ mode_tree_draw(struct mode_tree_data *mtd) tag = "*"; else tag = ""; - xasprintf(&text, "%-*s%s%s%s%s", keylen, key, start, mti->name, - tag, (mti->text != NULL) ? ": " : "" ); + xasprintf(&text, "%-*s%s%*s%s%s", keylen, key, start, + namelen[line->depth], mti->name, tag, + (mti->text != NULL) ? ": " : "" ); width = utf8_cstrwidth(text); if (width > w) width = w; From c4b9716873a0a5ecc68697c73793eaa553192d1b Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 13 Feb 2025 16:24:16 +0000 Subject: [PATCH 15/19] Look for imsg_add not _init now. --- configure.ac | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 4f90fcd5..98b67a4d 100644 --- a/configure.ac +++ b/configure.ac @@ -638,9 +638,9 @@ else AC_LIBOBJ(err) fi -# Look for imsg_init in libutil. -AC_SEARCH_LIBS(imsg_init, util, found_imsg_init=yes, found_imsg_init=no) -if test "x$found_imsg_init" = xyes; then +# Look for imsg_add in libutil. +AC_SEARCH_LIBS(imsg_add, util, found_imsg_add=yes, found_imsg_add=no) +if test "x$found_imsg_add" = xyes; then AC_DEFINE(HAVE_IMSG) else AC_LIBOBJ(imsg) From 47a56c11f26a66c97ce90af97f20a7b724dd515d Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 13 Feb 2025 16:31:25 +0000 Subject: [PATCH 16/19] Add a note about C-r and C-s behaviour, GitHub issue 4309. Also add a missing word, from jmc@. --- tmux.1 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tmux.1 b/tmux.1 index 6f9c68e1..ed6e7a53 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2329,6 +2329,15 @@ repeats the last search and does the same but reverses the direction (forward becomes backward and backward becomes forward). .Pp +The default incremental search key bindings, +.Ql C-r +and +.Ql C-s , +are designed to emulate +.Xr emacs 1 . +When first pressed they allow a new search term to be entered; if pressed with +an empty search term they repeat the previously used search term. +.Pp The .Ql next-prompt and @@ -6766,7 +6775,7 @@ option is used; a delay of zero waits for a key press. ignores key presses and closes only after the delay expires. If .Fl C -given, the pane will continue to be updated while the message is displayed. +is given, the pane will continue to be updated while the message is displayed. If .Fl l is given, From 250c88efdccd892bdbc99bfb565ace6fa90e5d0d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 20 Feb 2025 13:30:26 +0000 Subject: [PATCH 17/19] Add .swp, from Nikola Tadic. --- .gitignore | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index ec49a6de..bf012d5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,24 @@ -*.o -*~ -*.diff -*.patch *.core -core -tags +*.dSYM +*.diff +*.o +*.patch +*.swp +*~ .deps/ -compat/.dirstamp -aclocal.m4 -autom4te.cache/ -config.log -config.status -etc/ -tmux +.dirstamp Makefile Makefile.in -configure -tmux.1.* -*.dSYM +aclocal.m4 +autom4te.cache/ cmd-parse.c +compat/.dirstamp +config.log +config.status +configure +core +etc/ fuzz/*-fuzzer -.dirstamp +tags +tmux +tmux.1.* From 18331e39bf7e88132fd0f1eafb4424347b6efb4a Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 20 Feb 2025 13:32:07 +0000 Subject: [PATCH 18/19] Reset overlay_resize pointer when clearing overlay. --- server-client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/server-client.c b/server-client.c index 4581e9bc..847a8576 100644 --- a/server-client.c +++ b/server-client.c @@ -156,6 +156,7 @@ server_client_clear_overlay(struct client *c) c->overlay_draw = NULL; c->overlay_key = NULL; c->overlay_free = NULL; + c->overlay_resize = NULL; c->overlay_data = NULL; c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR); From 084e6ee9ec0cc58a8f3e673858c74a2b3952081b Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 20 Feb 2025 13:39:58 +0000 Subject: [PATCH 19/19] Add a -M flag to capture-pane to use the copy mode screen, GitHub issue 4358. --- cmd-capture-pane.c | 38 ++++++++++++++++++++++++++------------ tmux.1 | 5 ++++- tmux.h | 1 + window-copy.c | 11 +++++++++++ 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index 8f7250e8..81e1f7f9 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -39,8 +39,8 @@ const struct cmd_entry cmd_capture_pane_entry = { .name = "capture-pane", .alias = "capturep", - .args = { "ab:CeE:JNpPqS:Tt:", 0, 0, NULL }, - .usage = "[-aCeJNpPqT] " CMD_BUFFER_USAGE " [-E end-line] " + .args = { "ab:CeE:JMNpPqS:Tt:", 0, 0, NULL }, + .usage = "[-aCeJMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] " "[-S start-line] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -107,14 +107,16 @@ static char * cmd_capture_pane_history(struct args *args, struct cmdq_item *item, struct window_pane *wp, size_t *len) { - struct grid *gd; - const struct grid_line *gl; - struct grid_cell *gc = NULL; - int n, join_lines, flags = 0; - u_int i, sx, top, bottom, tmp; - char *cause, *buf, *line; - const char *Sflag, *Eflag; - size_t linelen; + struct grid *gd; + const struct grid_line *gl; + struct screen *s; + struct grid_cell *gc = NULL; + struct window_mode_entry *wme; + int n, join_lines, flags = 0; + u_int i, sx, top, bottom, tmp; + char *cause, *buf, *line; + const char *Sflag, *Eflag; + size_t linelen; sx = screen_size_x(&wp->base); if (args_has(args, 'a')) { @@ -126,8 +128,20 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, } return (xstrdup("")); } - } else + s = &wp->base; + } else if (args_has(args, 'M')) { + wme = TAILQ_FIRST(&wp->modes); + if (wme != NULL && wme->mode->get_screen != NULL) { + s = wme->mode->get_screen (wme); + gd = s->grid; + } else { + s = &wp->base; + gd = wp->base.grid; + } + } else { + s = &wp->base; gd = wp->base.grid; + } Sflag = args_get(args, 'S'); if (Sflag != NULL && strcmp(Sflag, "-") == 0) @@ -181,7 +195,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, buf = NULL; for (i = top; i <= bottom; i++) { - line = grid_string_cells(gd, 0, i, sx, &gc, flags, wp->screen); + line = grid_string_cells(gd, 0, i, sx, &gc, flags, s); linelen = strlen(line); buf = cmd_capture_pane_append(buf, len, line, linelen); diff --git a/tmux.1 b/tmux.1 index ed6e7a53..5c979758 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2554,7 +2554,7 @@ but a different format may be specified with .Fl F . .Tg capturep .It Xo Ic capture-pane -.Op Fl aAepPqCJN +.Op Fl aepPqCJMN .Op Fl b Ar buffer-name .Op Fl E Ar end-line .Op Fl S Ar start-line @@ -2573,6 +2573,9 @@ is given, the alternate screen is used, and the history is not accessible. If no alternate screen exists, an error will be returned unless .Fl q is given. +Similarly, if the pane is in a mode, +.Fl M +uses the screen for the mode. If .Fl e is given, the output includes escape sequences for text and background diff --git a/tmux.h b/tmux.h index 065bb042..cd93097e 100644 --- a/tmux.h +++ b/tmux.h @@ -1059,6 +1059,7 @@ struct window_mode { struct mouse_event *); void (*formats)(struct window_mode_entry *, struct format_tree *); + struct screen *(*get_screen)(struct window_mode_entry *); }; /* Active window mode. */ diff --git a/window-copy.c b/window-copy.c index 1cda6d39..7dcc8432 100644 --- a/window-copy.c +++ b/window-copy.c @@ -40,6 +40,7 @@ static void window_copy_free(struct window_mode_entry *); static void window_copy_resize(struct window_mode_entry *, u_int, u_int); static void window_copy_formats(struct window_mode_entry *, struct format_tree *); +static struct screen *window_copy_get_screen(struct window_mode_entry *); static void window_copy_scroll1(struct window_mode_entry *, struct window_pane *wp, int, u_int, int); static void window_copy_pageup1(struct window_mode_entry *, int); @@ -160,6 +161,7 @@ const struct window_mode window_copy_mode = { .key_table = window_copy_key_table, .command = window_copy_command, .formats = window_copy_formats, + .get_screen = window_copy_get_screen }; const struct window_mode window_view_mode = { @@ -171,6 +173,7 @@ const struct window_mode window_view_mode = { .key_table = window_copy_key_table, .command = window_copy_command, .formats = window_copy_formats, + .get_screen = window_copy_get_screen }; enum { @@ -972,6 +975,14 @@ window_copy_formats(struct window_mode_entry *wme, struct format_tree *ft) window_copy_cursor_hyperlink_cb); } +static struct screen * +window_copy_get_screen(struct window_mode_entry *wme) +{ + struct window_copy_mode_data *data = wme->data; + + return (data->backing); +} + static void window_copy_size_changed(struct window_mode_entry *wme) {