From 0dd19442061ac92b6d9b61a91e4b262c2f08921f Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:10:29 +0000 Subject: [PATCH 01/49] Tweak the default choose modes formats: - Only show pane title if it is not default and not empty. - Add a prettier time format and use that instead of long ctime(). - Remove clutter and change the order. --- format.c | 127 ++++++++++++++++++++++++++++++++++++------------ tmux.1 | 4 ++ window-buffer.c | 2 +- window-client.c | 3 +- window-tree.c | 8 +-- 5 files changed, 105 insertions(+), 39 deletions(-) diff --git a/format.c b/format.c index f0a5a3b1..c43baf05 100644 --- a/format.c +++ b/format.c @@ -100,6 +100,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_SESSIONS 0x80 #define FORMAT_WINDOWS 0x100 #define FORMAT_PANES 0x200 +#define FORMAT_PRETTY 0x400 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 10 @@ -1322,6 +1323,52 @@ format_quote(const char *s) return (out); } +/* Make a prettier time. */ +static char * +format_pretty_time(time_t t) +{ + struct tm now_tm, tm; + time_t now, age; + char s[6]; + int m; + + time(&now); + if (now < t) + now = t; + age = now - t; + + localtime_r(&now, &now_tm); + localtime_r(&t, &tm); + + /* Last 24 hours. */ + if (age < 24 * 3600) { + strftime(s, sizeof s, "%H:%M", &tm); + return (xstrdup(s)); + } + + /* This month or last 28 days. */ + if ((tm.tm_year == now_tm.tm_year && tm.tm_mon == now_tm.tm_mon) || + age < 28 * 24 * 3600) { + strftime(s, sizeof s, "%a%d", &tm); + return (xstrdup(s)); + } + + /* Last 12 months. */ + if (now_tm.tm_mon == 0) + m = 11; + else + m = now_tm.tm_mon - 1; + if ((tm.tm_year == now_tm.tm_year && tm.tm_mon < now_tm.tm_mon) || + (tm.tm_year == now_tm.tm_year - 1 && tm.tm_mon > now_tm.tm_mon)) { + strftime(s, sizeof s, "%d%b", &tm); + return (xstrdup(s)); + } + + /* Older than that. */ + strftime(s, sizeof s, "%h%y", &tm); + return (xstrdup(s)); +} + /* Find a format entry. */ static char * format_find(struct format_tree *ft, const char *key, int modifiers) @@ -1331,40 +1378,31 @@ format_find(struct format_tree *ft, const char *key, int modifiers) static char s[64]; struct options_entry *o; int idx; - char *found, *saved; + char *found = NULL, *saved; + const char *errstr; + time_t t = 0; - if (~modifiers & FORMAT_TIMESTRING) { - o = options_parse_get(global_options, key, &idx, 0); - if (o == NULL && ft->wp != NULL) - o = options_parse_get(ft->wp->options, key, &idx, 0); - if (o == NULL && ft->w != NULL) - o = options_parse_get(ft->w->options, key, &idx, 0); - if (o == NULL) - o = options_parse_get(global_w_options, key, &idx, 0); - if (o == NULL && ft->s != NULL) - o = options_parse_get(ft->s->options, key, &idx, 0); - if (o == NULL) - o = options_parse_get(global_s_options, key, &idx, 0); - if (o != NULL) { - found = options_tostring(o, idx, 1); - goto found; - } + o = options_parse_get(global_options, key, &idx, 0); + if (o == NULL && ft->wp != NULL) + o = options_parse_get(ft->wp->options, key, &idx, 0); + if (o == NULL && ft->w != NULL) + o = options_parse_get(ft->w->options, key, &idx, 0); + if (o == NULL) + o = options_parse_get(global_w_options, key, &idx, 0); + if (o == NULL && ft->s != NULL) + o = options_parse_get(ft->s->options, key, &idx, 0); + if (o == NULL) + o = options_parse_get(global_s_options, key, &idx, 0); + if (o != NULL) { + found = options_tostring(o, idx, 1); + goto found; } - found = NULL; - fe_find.key = (char *) key; + fe_find.key = (char *)key; fe = RB_FIND(format_entry_tree, &ft->tree, &fe_find); if (fe != NULL) { - if (modifiers & FORMAT_TIMESTRING) { - if (fe->t == 0) - return (NULL); - ctime_r(&fe->t, s); - s[strcspn(s, "\n")] = '\0'; - found = xstrdup(s); - goto found; - } if (fe->t != 0) { - xasprintf(&found, "%lld", (long long)fe->t); + t = fe->t; goto found; } if (fe->value == NULL && fe->cb != NULL) @@ -1390,7 +1428,28 @@ format_find(struct format_tree *ft, const char *key, int modifiers) return (NULL); found: - if (found == NULL) + if (modifiers & FORMAT_TIMESTRING) { + if (t == 0 && found != NULL) { + t = strtonum(found, 0, INT64_MAX, &errstr); + if (errstr != NULL) + t = 0; + free(found); + } + if (t == 0) + return (NULL); + if (modifiers & FORMAT_PRETTY) + found = format_pretty_time(t); + else { + ctime_r(&t, s); + s[strcspn(s, "\n")] = '\0'; + found = xstrdup(s); + } + return (found); + } + + if (t != 0) + xasprintf(&found, "%lld", (long long)t); + else if (found == NULL) return (NULL); if (modifiers & FORMAT_BASENAME) { saved = found; @@ -1532,7 +1591,7 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) cp++; /* Check single character modifiers with no arguments. */ - if (strchr("lbdtqETSWP<>", cp[0]) != NULL && + if (strchr("lbdqETSWP<>", cp[0]) != NULL && format_is_end(cp[1])) { format_add_modifier(&list, count, cp, 1, NULL, 0); cp++; @@ -1553,7 +1612,7 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) } /* Now try single character with arguments. */ - if (strchr("mCs=pe", cp[0]) == NULL) + if (strchr("mCst=pe", cp[0]) == NULL) break; c = cp[0]; @@ -1978,7 +2037,7 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, case 'e': if (fm->argc < 1 || fm->argc > 3) break; - mexp = fm; + mexp = fm; break; case 'l': modifiers |= FORMAT_LITERAL; @@ -1991,6 +2050,10 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, break; case 't': modifiers |= FORMAT_TIMESTRING; + if (fm->argc < 1) + break; + if (strchr(fm->argv[0], 'p') != NULL) + modifiers |= FORMAT_PRETTY; break; case 'q': modifiers |= FORMAT_QUOTE; diff --git a/tmux.1 b/tmux.1 index d1c44216..71876d83 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4353,6 +4353,10 @@ gives .Ql #{t:window_activity} gives .Ql Sun Oct 25 09:25:02 2015 . +Adding +.Ql p ( +.Ql `t/p` ) +will use shorter but less accurate time format for times in the past. The .Ql b:\& and diff --git a/window-buffer.c b/window-buffer.c index 37707233..dae8899e 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -37,7 +37,7 @@ static void window_buffer_key(struct window_mode_entry *, #define WINDOW_BUFFER_DEFAULT_COMMAND "paste-buffer -b '%%'" #define WINDOW_BUFFER_DEFAULT_FORMAT \ - "#{buffer_size} bytes (#{t:buffer_created})" + "#{t/p:buffer_created}: #{buffer_sample}" static const struct menu_item window_buffer_menu_items[] = { { "Paste", 'p', NULL }, diff --git a/window-client.c b/window-client.c index 4688cbf3..cd424dd7 100644 --- a/window-client.c +++ b/window-client.c @@ -37,8 +37,7 @@ static void window_client_key(struct window_mode_entry *, #define WINDOW_CLIENT_DEFAULT_COMMAND "detach-client -t '%%'" #define WINDOW_CLIENT_DEFAULT_FORMAT \ - "session #{session_name} " \ - "(#{client_width}x#{client_height}, #{t:client_activity})" + "#{t/p:client_activity}: session #{session_name}" static const struct menu_item window_client_menu_items[] = { { "Detach", 'd', NULL }, diff --git a/window-tree.c b/window-tree.c index 156aafd9..2eff4b8a 100644 --- a/window-tree.c +++ b/window-tree.c @@ -38,13 +38,13 @@ static void window_tree_key(struct window_mode_entry *, #define WINDOW_TREE_DEFAULT_FORMAT \ "#{?pane_format," \ "#{?pane_marked,#[reverse],}" \ - "#{pane_current_command}#{?pane_active,*,}#{?pane_marked,M,} \"#{pane_title}\"" \ + "#{pane_current_command}#{?pane_active,*,}#{?pane_marked,M,}" \ + "#{?#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}},: \"#{pane_title}\",}" \ "," \ "#{?window_format," \ "#{?window_marked_flag,#[reverse],}" \ - "#{window_name}#{window_flags} " \ - "(#{window_panes} panes)" \ - "#{?#{==:#{window_panes},1}, \"#{pane_title}\",}" \ + "#{window_name}#{window_flags}" \ + "#{?#{&&:#{==:#{window_panes},1},#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}}},: \"#{pane_title}\",}" \ "," \ "#{session_windows} windows" \ "#{?session_grouped, " \ From 471f697423df33d535a7cc1fac5f01f718969e34 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:13:37 +0000 Subject: [PATCH 02/49] Add an attribute for ACS. --- attributes.c | 4 +++- style.c | 4 ++-- tmux.1 | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/attributes.c b/attributes.c index ca88a056..b839f06d 100644 --- a/attributes.c +++ b/attributes.c @@ -31,7 +31,8 @@ attributes_tostring(int attr) if (attr == 0) return ("none"); - len = xsnprintf(buf, sizeof buf, "%s%s%s%s%s%s%s%s%s%s%s%s%s", + len = xsnprintf(buf, sizeof buf, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + (attr & GRID_ATTR_CHARSET) ? "acs," : "", (attr & GRID_ATTR_BRIGHT) ? "bright," : "", (attr & GRID_ATTR_DIM) ? "dim," : "", (attr & GRID_ATTR_UNDERSCORE) ? "underscore," : "", @@ -62,6 +63,7 @@ attributes_fromstring(const char *str) const char *name; int attr; } table[] = { + { "acs", GRID_ATTR_CHARSET }, { "bright", GRID_ATTR_BRIGHT }, { "bold", GRID_ATTR_BRIGHT }, { "dim", GRID_ATTR_DIM }, diff --git a/style.c b/style.c index 6ba4c524..da3b4c78 100644 --- a/style.c +++ b/style.c @@ -26,7 +26,7 @@ #include "tmux.h" /* Mask for bits not included in style. */ -#define STYLE_ATTR_MASK (~GRID_ATTR_CHARSET) +#define STYLE_ATTR_MASK (~0) /* Default style. */ static struct style style_default = { @@ -247,7 +247,7 @@ style_tostring(struct style *sy) colour_tostring(gc->bg)); comma = ","; } - if (gc->attr != 0 && gc->attr != GRID_ATTR_CHARSET) { + if (gc->attr != 0) { xsnprintf(s + off, sizeof s - off, "%s%s", comma, attributes_tostring(gc->attr)); comma = ","; diff --git a/tmux.1 b/tmux.1 index 71876d83..a519b6b5 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4661,7 +4661,8 @@ for the terminal default colour; or a hexadecimal RGB string such as Set the background colour. .It Ic none Set no attributes (turn off any active attributes). -.It Xo Ic bright +.It Xo Ic acs , +.Ic bright (or .Ic bold ) , .Ic dim , @@ -4681,6 +4682,8 @@ Set an attribute. Any of the attributes may be prefixed with .Ql no to unset. +.Ic acs +is the terminal alternate character set. .It Xo Ic align=left (or .Ic noalign ) , From 9dd58470e41bfb5b9d74028eed73f502e71152f0 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:16:25 +0000 Subject: [PATCH 03/49] Remove support for iTerm2's DSR 1337 extension and use the CSI > q extension now supported by a few different terminals. --- input.c | 16 +++++++-------- tmux.h | 2 +- tty-features.c | 2 +- tty-keys.c | 54 +++++++++++++++++++++++++++++--------------------- tty.c | 8 ++++---- 5 files changed, 45 insertions(+), 37 deletions(-) diff --git a/input.c b/input.c index 919cf15d..b62b1858 100644 --- a/input.c +++ b/input.c @@ -254,6 +254,7 @@ enum input_csi_type { INPUT_CSI_TBC, INPUT_CSI_VPA, INPUT_CSI_WINOPS, + INPUT_CSI_XDA, }; /* Control (CSI) command table. */ @@ -290,6 +291,7 @@ static const struct input_table_entry input_csi_table[] = { { 'm', "", INPUT_CSI_SGR }, { 'n', "", INPUT_CSI_DSR }, { 'q', " ", INPUT_CSI_DECSCUSR }, + { 'q', ">", INPUT_CSI_XDA }, { 'r', "", INPUT_CSI_DECSTBM }, { 's', "", INPUT_CSI_SCP }, { 't', "", INPUT_CSI_WINOPS }, @@ -1324,7 +1326,6 @@ input_csi_dispatch(struct input_ctx *ictx) struct input_table_entry *entry; int i, n, m; u_int cx, bg = ictx->cell.cell.bg; - char *copy, *cp; if (ictx->flags & INPUT_DISCARD) return (0); @@ -1456,13 +1457,6 @@ input_csi_dispatch(struct input_ctx *ictx) case 6: input_reply(ictx, "\033[%u;%uR", s->cy + 1, s->cx + 1); break; - case 1337: /* Terminal version, from iTerm2. */ - copy = xstrdup(getversion()); - for (cp = copy; *cp != '\0'; cp++) - *cp = toupper((u_char)*cp); - input_reply(ictx, "\033[TMUX %sn", copy); - free(copy); - break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; @@ -1597,6 +1591,12 @@ input_csi_dispatch(struct input_ctx *ictx) if (n != -1) screen_set_cursor_style(s, n); break; + case INPUT_CSI_XDA: + n = input_get(ictx, 0, 0, 0); + if (n != 0) + input_reply(ictx, "\033P>|tmux %s\033\\", getversion()); + break; + } ictx->last = -1; diff --git a/tmux.h b/tmux.h index 67dc8631..c58be54f 100644 --- a/tmux.h +++ b/tmux.h @@ -1247,7 +1247,7 @@ struct tty { #define TTY_FOCUS 0x40 #define TTY_BLOCK 0x80 #define TTY_HAVEDA 0x100 -#define TTY_HAVEDSR 0x200 +#define TTY_HAVEXDA 0x200 #define TTY_SYNCING 0x400 int flags; diff --git a/tty-features.c b/tty-features.c index 1996c750..a7f2a4b0 100644 --- a/tty-features.c +++ b/tty-features.c @@ -33,7 +33,7 @@ * - alternate escape (under XT). * * Also: - * - XT is used to decide whether to send DA and DSR; + * - XT is used to decide whether to send DA and XDA; * - DECSLRM and DECFRA use a flag instead of capabilities; * - UTF-8 is a separate flag on the client; needed for unattached clients. */ diff --git a/tty-keys.c b/tty-keys.c index aa775d69..9e8428f2 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -52,7 +52,7 @@ static int tty_keys_clipboard(struct tty *, const char *, size_t, size_t *); static int tty_keys_device_attributes(struct tty *, const char *, size_t, size_t *); -static int tty_keys_device_status_report(struct tty *, const char *, +static int tty_keys_extended_device_attributes(struct tty *, const char *, size_t, size_t *); /* Default raw keys. */ @@ -612,8 +612,8 @@ tty_keys_next(struct tty *tty) goto partial_key; } - /* Is this a device status report response? */ - switch (tty_keys_device_status_report(tty, buf, len, &size)) { + /* Is this an extended device attributes response? */ + switch (tty_keys_extended_device_attributes(tty, buf, len, &size)) { case 0: /* yes */ key = KEYC_UNKNOWN; goto complete_key; @@ -936,7 +936,7 @@ tty_keys_clipboard(__unused struct tty *tty, const char *buf, size_t len, *size = 0; - /* First three bytes are always \033]52;. */ + /* First five bytes are always \033]52;. */ if (buf[0] != '\033') return (-1); if (len == 1) @@ -1040,9 +1040,11 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, return (1); /* Copy the rest up to a 'c'. */ - for (i = 0; i < (sizeof tmp) - 1 && buf[3 + i] != 'c'; i++) { + for (i = 0; i < (sizeof tmp) - 1; i++) { if (3 + i == len) return (1); + if (buf[3 + i] == 'c') + break; tmp[i] = buf[3 + i]; } if (i == (sizeof tmp) - 1) @@ -1101,48 +1103,54 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, } /* - * Handle device status report input. Returns 0 for success, -1 for failure, 1 - * for partial. + * Handle extended device attributes input. Returns 0 for success, -1 for + * failure, 1 for partial. */ static int -tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len, - size_t *size) +tty_keys_extended_device_attributes(struct tty *tty, const char *buf, + size_t len, size_t *size) { struct client *c = tty->client; u_int i; char tmp[64]; *size = 0; - if (tty->flags & TTY_HAVEDSR) + if (tty->flags & TTY_HAVEXDA) return (-1); - /* First three bytes are always \033[. */ + /* First four bytes are always \033P>|. */ if (buf[0] != '\033') return (-1); if (len == 1) return (1); - if (buf[1] != '[') + if (buf[1] != 'P') return (-1); if (len == 2) return (1); - if (buf[2] != 'I' && buf[2] != 'T') + if (buf[2] != '>') return (-1); if (len == 3) return (1); + if (buf[3] != '|') + return (-1); + if (len == 4) + return (1); - /* Copy the rest up to a 'n'. */ - for (i = 0; i < (sizeof tmp) - 1 && buf[2 + i] != 'n'; i++) { - if (2 + i == len) + /* Copy the rest up to a '\033\\'. */ + for (i = 0; i < (sizeof tmp) - 1; i++) { + if (4 + i == len) return (1); - tmp[i] = buf[2 + i]; + if (buf[4 + i - 1] == '\033' && buf[4 + i] == '\\') + break; + tmp[i] = buf[4 + i]; } if (i == (sizeof tmp) - 1) return (-1); - tmp[i] = '\0'; - *size = 3 + i; + tmp[i - 1] = '\0'; + *size = 5 + i; /* Add terminal features. */ - if (strncmp(tmp, "ITERM2 ", 7) == 0) { + if (strncmp(tmp, "ITerm2 ", 7) == 0) { tty_add_features(&c->term_features, "256," "RGB," @@ -1152,7 +1160,7 @@ tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len, "sync," "title", ","); - } else if (strncmp(tmp, "TMUX ", 5) == 0) { + } else if (strncmp(tmp, "tmux ", 5) == 0) { tty_add_features(&c->term_features, "256," "RGB," @@ -1163,10 +1171,10 @@ tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len, "usstyle", ","); } - log_debug("%s: received DSR %.*s", c->name, (int)*size, buf); + log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf); tty_update_features(tty); - tty->flags |= TTY_HAVEDSR; + tty->flags |= TTY_HAVEXDA; return (0); } diff --git a/tty.c b/tty.c index 98a557b5..5d7d2c0d 100644 --- a/tty.c +++ b/tty.c @@ -287,7 +287,7 @@ tty_start_timer_callback(__unused int fd, __unused short events, void *data) struct client *c = tty->client; log_debug("%s: start timer fired", c->name); - tty->flags |= (TTY_HAVEDA|TTY_HAVEDSR); + tty->flags |= (TTY_HAVEDA|TTY_HAVEXDA); } void @@ -361,10 +361,10 @@ tty_send_requests(struct tty *tty) if (tty_term_flag(tty->term, TTYC_XT)) { if (~tty->flags & TTY_HAVEDA) tty_puts(tty, "\033[>c"); - if (~tty->flags & TTY_HAVEDSR) - tty_puts(tty, "\033[1337n"); + if (~tty->flags & TTY_HAVEXDA) + tty_puts(tty, "\033[>q"); } else - tty->flags |= (TTY_HAVEDA|TTY_HAVEDSR); + tty->flags |= (TTY_HAVEDA|TTY_HAVEXDA); } void From 41dec585df8723a8e268a80930d7f9ce395829dc Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:18:39 +0000 Subject: [PATCH 04/49] Response is iTerm2 not not ITerm2. --- tty-keys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tty-keys.c b/tty-keys.c index 9e8428f2..8c778b2a 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1150,7 +1150,7 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, *size = 5 + i; /* Add terminal features. */ - if (strncmp(tmp, "ITerm2 ", 7) == 0) { + if (strncmp(tmp, "iTerm2 ", 7) == 0) { tty_add_features(&c->term_features, "256," "RGB," From 4e0a718666e3c24e69be107d0a294a9ae9f59388 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:22:51 +0000 Subject: [PATCH 05/49] Add extension terminfo(5) capabilities for margins. --- tmux.1 | 2 ++ tmux.h | 10 +++++++--- tty-features.c | 15 ++++++++++++--- tty-term.c | 8 ++++++-- tty.c | 13 +++++-------- window-copy.c | 1 + 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/tmux.1 b/tmux.1 index a519b6b5..daa0739c 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5565,6 +5565,8 @@ to change the cursor colour from inside .Bd -literal -offset indent $ printf '\e033]12;red\e033\e\e' .Ed +.It Em \&Cmg, \&Clmg, \&Dsmg , \&Enmg +Set, clear, disable or enable DECSLRM margins. .It Em \&Smol Enable the overline attribute. .It Em \&Smulx diff --git a/tmux.h b/tmux.h index c58be54f..97024310 100644 --- a/tmux.h +++ b/tmux.h @@ -254,6 +254,8 @@ enum tty_code_code { TTYC_BOLD, TTYC_CIVIS, TTYC_CLEAR, + TTYC_CLMG, + TTYC_CMG, TTYC_CNORM, TTYC_COLORS, TTYC_CR, @@ -274,12 +276,14 @@ enum tty_code_code { TTYC_DIM, TTYC_DL, TTYC_DL1, + TTYC_DSMG, TTYC_E3, TTYC_ECH, TTYC_ED, TTYC_EL, TTYC_EL1, TTYC_ENACS, + TTYC_ENMG, TTYC_FSL, TTYC_HOME, TTYC_HPA, @@ -445,11 +449,11 @@ enum tty_code_code { TTYC_SITM, TTYC_SMACS, TTYC_SMCUP, - TTYC_SMOL, TTYC_SMKX, + TTYC_SMOL, TTYC_SMSO, - TTYC_SMULX, TTYC_SMUL, + TTYC_SMULX, TTYC_SMXX, TTYC_SS, TTYC_SYNC, @@ -458,7 +462,7 @@ enum tty_code_code { TTYC_U8, TTYC_VPA, TTYC_XENL, - TTYC_XT, + TTYC_XT }; /* Message codes. */ diff --git a/tty-features.c b/tty-features.c index a7f2a4b0..9eb446d4 100644 --- a/tty-features.c +++ b/tty-features.c @@ -34,7 +34,7 @@ * * Also: * - XT is used to decide whether to send DA and XDA; - * - DECSLRM and DECFRA use a flag instead of capabilities; + * - DECFRA uses a flag instead of capabilities; * - UTF-8 is a separate flag on the client; needed for unattached clients. */ @@ -84,7 +84,7 @@ static const char *tty_feature_rgb_capabilities[] = { static struct tty_feature tty_feature_rgb = { "RGB", tty_feature_rgb_capabilities, - (TERM_256COLOURS|TERM_RGBCOLOURS) + TERM_256COLOURS|TERM_RGBCOLOURS }; /* Terminal supports 256 colours. */ @@ -159,9 +159,16 @@ static struct tty_feature tty_feature_sync = { }; /* Terminal supports DECSLRM margins. */ +static const char *tty_feature_margins_capabilities[] = { + "Enmg=\\E[?69h", + "Dsmg=\\E[?69l", + "Clmg=\\E[s", + "Cmg=\\E[%i%p1%d;%p2%ds", + NULL +}; static struct tty_feature tty_feature_margins = { "margins", - NULL, + tty_feature_margins_capabilities, TERM_DECSLRM }; @@ -194,6 +201,8 @@ tty_add_features(int *feat, const char *s, const char *separators) char *next, *loop, *copy; u_int i; + log_debug("%s: %s", __func__, s); + loop = copy = xstrdup(s); while ((next = strsep(&loop, separators)) != NULL) { for (i = 0; i < nitems(tty_features); i++) { diff --git a/tty-term.c b/tty-term.c index f3dbae5c..78ba5a37 100644 --- a/tty-term.c +++ b/tty-term.c @@ -61,6 +61,8 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_BOLD] = { TTYCODE_STRING, "bold" }, [TTYC_CIVIS] = { TTYCODE_STRING, "civis" }, [TTYC_CLEAR] = { TTYCODE_STRING, "clear" }, + [TTYC_CLMG] = { TTYCODE_STRING, "Clmg" }, + [TTYC_CMG] = { TTYCODE_STRING, "Cmg" }, [TTYC_CNORM] = { TTYCODE_STRING, "cnorm" }, [TTYC_COLORS] = { TTYCODE_NUMBER, "colors" }, [TTYC_CR] = { TTYCODE_STRING, "Cr" }, @@ -81,12 +83,14 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_DIM] = { TTYCODE_STRING, "dim" }, [TTYC_DL1] = { TTYCODE_STRING, "dl1" }, [TTYC_DL] = { TTYCODE_STRING, "dl" }, + [TTYC_DSMG] = { TTYCODE_STRING, "Dsmg" }, [TTYC_E3] = { TTYCODE_STRING, "E3" }, [TTYC_ECH] = { TTYCODE_STRING, "ech" }, [TTYC_ED] = { TTYCODE_STRING, "ed" }, [TTYC_EL1] = { TTYCODE_STRING, "el1" }, [TTYC_EL] = { TTYCODE_STRING, "el" }, [TTYC_ENACS] = { TTYCODE_STRING, "enacs" }, + [TTYC_ENMG] = { TTYCODE_STRING, "Enmg" }, [TTYC_FSL] = { TTYCODE_STRING, "fsl" }, [TTYC_HOME] = { TTYCODE_STRING, "home" }, [TTYC_HPA] = { TTYCODE_STRING, "hpa" }, @@ -237,8 +241,8 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_OP] = { TTYCODE_STRING, "op" }, [TTYC_REV] = { TTYCODE_STRING, "rev" }, [TTYC_RGB] = { TTYCODE_FLAG, "RGB" }, - [TTYC_RI] = { TTYCODE_STRING, "ri" }, [TTYC_RIN] = { TTYCODE_STRING, "rin" }, + [TTYC_RI] = { TTYCODE_STRING, "ri" }, [TTYC_RMACS] = { TTYCODE_STRING, "rmacs" }, [TTYC_RMCUP] = { TTYCODE_STRING, "rmcup" }, [TTYC_RMKX] = { TTYCODE_STRING, "rmkx" }, @@ -265,7 +269,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_U8] = { TTYCODE_NUMBER, "U8" }, [TTYC_VPA] = { TTYCODE_STRING, "vpa" }, [TTYC_XENL] = { TTYCODE_FLAG, "xenl" }, - [TTYC_XT] = { TTYCODE_FLAG, "XT" }, + [TTYC_XT] = { TTYCODE_FLAG, "XT" } }; u_int diff --git a/tty.c b/tty.c index 5d7d2c0d..5d84c9e8 100644 --- a/tty.c +++ b/tty.c @@ -426,7 +426,7 @@ tty_stop_tty(struct tty *tty) } if (tty_use_margin(tty)) - tty_raw(tty, "\033[?69l"); /* DECLRMM */ + tty_raw(tty, tty_term_string(tty->term, TTYC_DSMG)); tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP)); setblocking(tty->fd, 1); @@ -473,7 +473,7 @@ tty_update_features(struct tty *tty) tty_term_apply_overrides(tty->term); if (tty_use_margin(tty)) - tty_puts(tty, "\033[?69h"); /* DECLRMM */ + tty_putcode(tty, TTYC_ENMG); } void @@ -2028,7 +2028,7 @@ tty_invalidate(struct tty *tty) if (tty->flags & TTY_STARTED) { if (tty_use_margin(tty)) - tty_puts(tty, "\033[?69h"); /* DECLRMM */ + tty_putcode(tty, TTYC_ENMG); tty_putcode(tty, TTYC_SGR0); tty->mode = ALL_MODES; @@ -2105,8 +2105,6 @@ tty_margin_pane(struct tty *tty, const struct tty_ctx *ctx) static void tty_margin(struct tty *tty, u_int rleft, u_int rright) { - char s[64]; - if (!tty_use_margin(tty)) return; if (tty->rleft == rleft && tty->rright == rright) @@ -2118,10 +2116,9 @@ tty_margin(struct tty *tty, u_int rleft, u_int rright) tty->rright = rright; if (rleft == 0 && rright == tty->sx - 1) - snprintf(s, sizeof s, "\033[s"); + tty_putcode(tty, TTYC_CLMG); else - snprintf(s, sizeof s, "\033[%u;%us", rleft + 1, rright + 1); - tty_puts(tty, s); + tty_putcode2(tty, TTYC_CMG, rleft, rright); tty->cx = tty->cy = UINT_MAX; } diff --git a/window-copy.c b/window-copy.c index 2bda5d56..a803f3b3 100644 --- a/window-copy.c +++ b/window-copy.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "tmux.h" From a29196ca6a6401053d3a17d0ffe2d560918683a4 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:26:33 +0000 Subject: [PATCH 06/49] Build list of paths and weed out duplicates before loading configs, and add TMUX_SOCK like TMUX_PATH for the socket directory. --- cfg.c | 55 ++++--------------------- tmux.c | 108 ++++++++++++++++++++++++++++++++++++++++--------- tmux.h | 7 +++- tty-features.c | 2 +- 4 files changed, 105 insertions(+), 67 deletions(-) diff --git a/cfg.c b/cfg.c index f67a765c..7c01f614 100644 --- a/cfg.c +++ b/cfg.c @@ -67,45 +67,12 @@ set_cfg_file(const char *path) cfg_file = xstrdup(path); } -static char * -expand_cfg_file(const char *path, const char *home) -{ - char *expanded, *name; - const char *end; - struct environ_entry *value; - - if (strncmp(path, "~/", 2) == 0) { - if (home == NULL) - return (NULL); - xasprintf(&expanded, "%s%s", home, path + 1); - return (expanded); - } - - if (*path == '$') { - end = strchr(path, '/'); - if (end == NULL) - name = xstrdup(path + 1); - else - name = xstrndup(path + 1, end - path - 1); - value = environ_find(global_environ, name); - free(name); - if (value == NULL) - return (NULL); - if (end == NULL) - end = ""; - xasprintf(&expanded, "%s%s", value->value, end); - return (expanded); - } - - return (xstrdup(path)); -} - void start_cfg(void) { - const char *home = find_home(); - struct client *c; - char *path, *copy, *next, *expanded; + struct client *c; + char **paths; + u_int i, n; /* * Configuration files are loaded without a client, so commands are run @@ -124,18 +91,12 @@ start_cfg(void) } if (cfg_file == NULL) { - path = copy = xstrdup(TMUX_CONF); - while ((next = strsep(&path, ":")) != NULL) { - expanded = expand_cfg_file(next, home); - if (expanded == NULL) { - log_debug("couldn't expand %s", next); - continue; - } - log_debug("expanded %s to %s", next, expanded); - load_cfg(expanded, c, NULL, CMD_PARSE_QUIET, NULL); - free(expanded); + expand_paths(TMUX_CONF, &paths, &n); + for (i = 0; i < n; i++) { + load_cfg(paths[i], c, NULL, CMD_PARSE_QUIET, NULL); + free(paths[i]); } - free(copy); + free(paths); } else load_cfg(cfg_file, c, NULL, 0, NULL); diff --git a/tmux.c b/tmux.c index 515f1543..db86dc52 100644 --- a/tmux.c +++ b/tmux.c @@ -109,34 +109,104 @@ areshell(const char *shell) return (0); } +static char * +expand_path(const char *path, const char *home) +{ + char *expanded, *name; + const char *end; + struct environ_entry *value; + + if (strncmp(path, "~/", 2) == 0) { + if (home == NULL) + return (NULL); + xasprintf(&expanded, "%s%s", home, path + 1); + return (expanded); + } + + if (*path == '$') { + end = strchr(path, '/'); + if (end == NULL) + name = xstrdup(path + 1); + else + name = xstrndup(path + 1, end - path - 1); + value = environ_find(global_environ, name); + free(name); + if (value == NULL) + return (NULL); + if (end == NULL) + end = ""; + xasprintf(&expanded, "%s%s", value->value, end); + return (expanded); + } + + return (xstrdup(path)); +} + +void +expand_paths(const char *s, char ***paths, u_int *n) +{ + const char *home = find_home(); + char *copy, *next, *tmp, resolved[PATH_MAX], *expanded; + u_int i; + + *paths = NULL; + *n = 0; + + copy = tmp = xstrdup(s); + while ((next = strsep(&tmp, ":")) != NULL) { + expanded = expand_path(next, home); + if (expanded == NULL) { + log_debug("%s: invalid path: %s", __func__, next); + continue; + } + if (realpath(expanded, resolved) == NULL) { + log_debug("%s: realpath(\"%s\") failed: %s", __func__, + expanded, strerror(errno)); + free(expanded); + continue; + } + free(expanded); + for (i = 0; i < *n; i++) { + if (strcmp(resolved, (*paths)[i]) == 0) + break; + } + if (i != *n) { + log_debug("%s: duplicate path: %s", __func__, resolved); + continue; + } + *paths = xreallocarray(*paths, (*n) + 1, sizeof *paths); + (*paths)[(*n)++] = xstrdup(resolved); + } + free(copy); +} + static char * make_label(const char *label, char **cause) { - char *base, resolved[PATH_MAX], *path, *s; - struct stat sb; - uid_t uid; + char **paths, *path, *base; + u_int i, n; + struct stat sb; + uid_t uid; *cause = NULL; - if (label == NULL) label = "default"; uid = getuid(); - if ((s = getenv("TMUX_TMPDIR")) != NULL && *s != '\0') - xasprintf(&base, "%s/tmux-%ld", s, (long)uid); - else - xasprintf(&base, "%s/tmux-%ld", _PATH_TMP, (long)uid); - if (realpath(base, resolved) == NULL && - strlcpy(resolved, base, sizeof resolved) >= sizeof resolved) { - errno = ERANGE; - free(base); - goto fail; + expand_paths(TMUX_SOCK, &paths, &n); + if (n == 0) { + xasprintf(cause, "no suitable socket path"); + return (NULL); } - free(base); + path = paths[0]; /* can only have one socket! */ + for (i = 1; i < n; i++) + free(paths[i]); + free(paths); - if (mkdir(resolved, S_IRWXU) != 0 && errno != EEXIST) + xasprintf(&base, "%s/tmux-%ld", path, (long)uid); + if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) goto fail; - if (lstat(resolved, &sb) != 0) + if (lstat(base, &sb) != 0) goto fail; if (!S_ISDIR(sb.st_mode)) { errno = ENOTDIR; @@ -146,11 +216,13 @@ make_label(const char *label, char **cause) errno = EACCES; goto fail; } - xasprintf(&path, "%s/%s", resolved, label); + xasprintf(&path, "%s/%s", base, label); + free(base); return (path); fail: - xasprintf(cause, "error creating %s (%s)", resolved, strerror(errno)); + xasprintf(cause, "error creating %s (%s)", base, strerror(errno)); + free(base); return (NULL); } diff --git a/tmux.h b/tmux.h index 97024310..cf8bbbf5 100644 --- a/tmux.h +++ b/tmux.h @@ -63,10 +63,13 @@ struct winlink; /* Client-server protocol version. */ #define PROTOCOL_VERSION 8 -/* Default configuration files. */ +/* Default configuration files and socket paths. */ #ifndef TMUX_CONF #define TMUX_CONF "/etc/tmux.conf:~/.tmux.conf" #endif +#ifndef TMUX_SOCK +#define TMUX_SOCK "$TMUX_TMPDIR:" _PATH_TMP +#endif /* Minimum layout cell size, NOT including border lines. */ #define PANE_MINIMUM 1 @@ -1746,6 +1749,8 @@ const char *sig2name(int); const char *find_cwd(void); const char *find_home(void); const char *getversion(void); +void expand_paths(const char *, char ***, u_int *); + /* proc.c */ struct imsg; diff --git a/tty-features.c b/tty-features.c index 9eb446d4..7505c96b 100644 --- a/tty-features.c +++ b/tty-features.c @@ -27,7 +27,7 @@ * Still hardcoded: * - bracket paste (sent if application asks for it); * - mouse (under kmous capability); - * - focus events (under focus-events option); + * - focus events (under XT and focus-events option); * - default colours (under AX or op capabilities); * - AIX colours (under colors >= 16); * - alternate escape (under XT). From 26312a7774f9752ac391017be0705e3a62dc525b Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:30:17 +0000 Subject: [PATCH 07/49] Move terminal features into a single file. --- tty-keys.c | 55 +++++++++++++++--------------------------------------- 1 file changed, 15 insertions(+), 40 deletions(-) diff --git a/tty-keys.c b/tty-keys.c index 8c778b2a..f5a3418f 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1070,28 +1070,13 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, ","); break; case 'M': /* mintty */ - tty_add_features(&c->term_features, - "256," - "RGB," - "title", - ","); + tty_default_features(&c->term_features, "mintty", 0); break; case 'T': /* tmux */ - tty_add_features(&c->term_features, - "256," - "RGB," - "ccolour," - "cstyle," - "overline," - "title," - "usstyle", - ","); + tty_default_features(&c->term_features, "tmux", 0); break; case 'U': /* rxvt-unicode */ - tty_add_features(&c->term_features, - "256," - "title", - ","); + tty_default_features(&c->term_features, "rxvt-unicode", 0); break; } log_debug("%s: received secondary DA %.*s", c->name, (int)*size, buf); @@ -1112,7 +1097,7 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, { struct client *c = tty->client; u_int i; - char tmp[64]; + char tmp[128]; *size = 0; if (tty->flags & TTY_HAVEXDA) @@ -1150,29 +1135,19 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, *size = 5 + i; /* Add terminal features. */ - if (strncmp(tmp, "iTerm2 ", 7) == 0) { - tty_add_features(&c->term_features, - "256," - "RGB," - "clipboard," - "cstyle," - "margins," - "sync," - "title", - ","); - } else if (strncmp(tmp, "tmux ", 5) == 0) { - tty_add_features(&c->term_features, - "256," - "RGB," - "ccolour," - "cstyle," - "overline," - "title," - "usstyle", - ","); - } + if (strncmp(tmp, "iTerm2 ", 7) == 0) + tty_default_features(&c->term_features, "iTerm2", 0); + else if (strncmp(tmp, "tmux ", 5) == 0) + tty_default_features(&c->term_features, "tmux", 0); + else if (strncmp(tmp, "XTerm(", 6) == 0) + tty_default_features(&c->term_features, "xterm", 0); + else if (strncmp(tmp, "mintty ", 7) == 0) + tty_default_features(&c->term_features, "mintty", 0); log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf); + free(c->term_type); + c->term_type = xstrdup(tmp); + tty_update_features(tty); tty->flags |= TTY_HAVEXDA; From aebeeec1e988bd74e273bbf6e79f8950895c38fd Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:39:40 +0000 Subject: [PATCH 08/49] Add feature and capabilities for focus reporting. Also document AX and XT even though they aren't tmux's, and add some bits for rxvt. --- tmux.1 | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tmux.1 b/tmux.1 index daa0739c..7918c780 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4463,7 +4463,8 @@ The following variables are available, where appropriate: .It Li "client_readonly" Ta "" Ta "1 if client is readonly" .It Li "client_session" Ta "" Ta "Name of the client's session" .It Li "client_termname" Ta "" Ta "Terminal name of client" -.It Li "client_termfeatures" Ta "" Ta "Terminal features of client" +.It Li "client_termtype" Ta "" Ta "Terminal type of client, if available" +.It Li "client_termfeatures" Ta "" Ta "Terminal features of client, if any" .It Li "client_tty" Ta "" Ta "Pseudo terminal of client" .It Li "client_utf8" Ta "" Ta "1 if client supports UTF-8" .It Li "client_width" Ta "" Ta "Width of client" @@ -5555,6 +5556,10 @@ It is not normally necessary to set these manually, instead the .Ic terminal-features option should be used. .Bl -tag -width Ds +.It Em \&AX +An existing extension that tells +.Nm +the terminal supports default colours. .It Em \&Cs , Cr Set the cursor colour. The first takes a single string argument and is used to set the colour; @@ -5567,6 +5572,19 @@ $ printf '\e033]12;red\e033\e\e' .Ed .It Em \&Cmg, \&Clmg, \&Dsmg , \&Enmg Set, clear, disable or enable DECSLRM margins. +These are set automatically if the terminal reports it is +.Em VT420 +compatible. +.It Em \&Dsbp , \&Enbp +Disable and enable bracketed paste. +These are set automatically if the +.Em XT +capability is present. +.It Em \&Dsfcs , \&Enfcs +Disable and enable focus reporting. +These are set automatically if the +.Em XT +capability is present. .It Em \&Smol Enable the overline attribute. .It Em \&Smulx @@ -5617,6 +5635,11 @@ See the option above and the .Xr xterm 1 man page. +.It Em \&XT +This is an existing extension capability that tmux uses to mean that the +terminal supports the +.Xr xterm 1 +title set sequences and to automatically set some of the capabilities above. .El .Sh CONTROL MODE .Nm From 21a39c997b82b50b0307e836e4f11f9db6a84e55 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:42:06 +0000 Subject: [PATCH 09/49] Do not redraw or update mode if nothing has changed. --- screen-redraw.c | 5 +++++ server-client.c | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 8e74fe97..5ca6024d 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -434,7 +434,11 @@ screen_redraw_screen(struct client *c) return; flags = screen_redraw_update(c, c->flags); + if ((flags & CLIENT_ALLREDRAWFLAGS) == 0) + return; + screen_redraw_set_context(c, &ctx); + tty_update_mode(&c->tty, c->tty.mode, NULL); tty_sync_start(&c->tty); if (flags & (CLIENT_REDRAWWINDOW|CLIENT_REDRAWBORDERS)) { @@ -470,6 +474,7 @@ screen_redraw_pane(struct client *c, struct window_pane *wp) return; screen_redraw_set_context(c, &ctx); + tty_update_mode(&c->tty, c->tty.mode, NULL); tty_sync_start(&c->tty); screen_redraw_draw_pane(&ctx, wp); diff --git a/server-client.c b/server-client.c index eb29aebb..ed138f8e 100644 --- a/server-client.c +++ b/server-client.c @@ -296,7 +296,9 @@ server_client_lost(struct client *c) if (c->flags & CLIENT_TERMINAL) tty_free(&c->tty); free(c->ttyname); + free(c->term_name); + free(c->term_type); status_free(c); @@ -1780,7 +1782,6 @@ server_client_check_redraw(struct client *c) if (!redraw) continue; log_debug("%s: redrawing pane %%%u", __func__, wp->id); - tty_update_mode(tty, mode, NULL); screen_redraw_pane(c, wp); } c->redraw_panes = 0; @@ -1788,7 +1789,6 @@ server_client_check_redraw(struct client *c) } if (c->flags & CLIENT_ALLREDRAWFLAGS) { - tty_update_mode(tty, mode, NULL); if (options_get_number(s->options, "set-titles")) server_client_set_title(c); screen_redraw_screen(c); From 7dbe623156e7b0e32e10e5e6445b7b7e448cc3a2 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:46:14 +0000 Subject: [PATCH 10/49] Instead of having a default set of terminals in terminal-overrides that get XT added and using that as a marker for xterm(1)-like, assume that if the terminfo(5) entry already has XT or the clear capability starts with CSI then the terminal is VT100-like and it should be safe to send DA requests. The DA responses trigger additional features being added. --- options-table.c | 2 +- tty-features.c | 105 ++++++++++++++++++++++++++++++++++++++++-------- tty-term.c | 30 +++++++++----- 3 files changed, 110 insertions(+), 27 deletions(-) diff --git a/options-table.c b/options-table.c index 23e5af6f..eaa7c2a7 100644 --- a/options-table.c +++ b/options-table.c @@ -261,7 +261,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, - .default_str = "tmux*:XT,screen*:XT,xterm*:XT", + .default_str = "", .separator = "," }, diff --git a/tty-features.c b/tty-features.c index 7505c96b..30d3d1a0 100644 --- a/tty-features.c +++ b/tty-features.c @@ -25,15 +25,12 @@ /* * Still hardcoded: - * - bracket paste (sent if application asks for it); * - mouse (under kmous capability); - * - focus events (under XT and focus-events option); * - default colours (under AX or op capabilities); * - AIX colours (under colors >= 16); - * - alternate escape (under XT). + * - alternate escape (if terminal is VT100-like). * * Also: - * - XT is used to decide whether to send DA and XDA; * - DECFRA uses a flag instead of capabilities; * - UTF-8 is a separate flag on the client; needed for unattached clients. */ @@ -51,7 +48,7 @@ static const char *tty_feature_title_capabilities[] = { "fsl=\\a", NULL }; -static struct tty_feature tty_feature_title = { +static const struct tty_feature tty_feature_title = { "title", tty_feature_title_capabilities, 0 @@ -62,7 +59,7 @@ static const char *tty_feature_clipboard_capabilities[] = { "Ms=\\E]52;%p1%s;%p2%s\\a", NULL }; -static struct tty_feature tty_feature_clipboard = { +static const struct tty_feature tty_feature_clipboard = { "clipboard", tty_feature_clipboard_capabilities, 0 @@ -81,7 +78,7 @@ static const char *tty_feature_rgb_capabilities[] = { "setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", NULL }; -static struct tty_feature tty_feature_rgb = { +static const struct tty_feature tty_feature_rgb = { "RGB", tty_feature_rgb_capabilities, TERM_256COLOURS|TERM_RGBCOLOURS @@ -94,7 +91,7 @@ static const char *tty_feature_256_capabilities[] = { "setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", NULL }; -static struct tty_feature tty_feature_256 = { +static const struct tty_feature tty_feature_256 = { "256", tty_feature_256_capabilities, TERM_256COLOURS @@ -105,7 +102,7 @@ static const char *tty_feature_overline_capabilities[] = { "Smol=\\E[53m", NULL }; -static struct tty_feature tty_feature_overline = { +static const struct tty_feature tty_feature_overline = { "overline", tty_feature_overline_capabilities, 0 @@ -117,19 +114,43 @@ static const char *tty_feature_usstyle_capabilities[] = { "Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m", NULL }; -static struct tty_feature tty_feature_usstyle = { +static const struct tty_feature tty_feature_usstyle = { "usstyle", tty_feature_usstyle_capabilities, 0 }; +/* Terminal supports bracketed paste. */ +static const char *tty_feature_bpaste_capabilities[] = { + "Enbp=\\E[?2004h", + "Dsbp=\\E[?2004l", + NULL +}; +static const struct tty_feature tty_feature_bpaste = { + "bpaste", + tty_feature_bpaste_capabilities, + 0 +}; + +/* Terminal supports focus reporting. */ +static const char *tty_feature_focus_capabilities[] = { + "Enfcs=\\E[?1004h", + "Dsfcs=\\E[?1004l", + NULL +}; +static const struct tty_feature tty_feature_focus = { + "focus", + tty_feature_focus_capabilities, + 0 +}; + /* Terminal supports cursor styles. */ static const char *tty_feature_cstyle_capabilities[] = { "Ss=\\E[%p1%d q", "Se=\\E[2 q", NULL }; -static struct tty_feature tty_feature_cstyle = { +static const struct tty_feature tty_feature_cstyle = { "cstyle", tty_feature_cstyle_capabilities, 0 @@ -141,18 +162,29 @@ static const char *tty_feature_ccolour_capabilities[] = { "Cr=\\E]112\\a", NULL }; -static struct tty_feature tty_feature_ccolour = { +static const struct tty_feature tty_feature_ccolour = { "ccolour", tty_feature_ccolour_capabilities, 0 }; +/* Terminal supports strikethrough. */ +static const char *tty_feature_strikethrough_capabilities[] = { + "smxx=\\E[9m", + NULL +}; +static const struct tty_feature tty_feature_strikethrough = { + "strikethrough", + tty_feature_strikethrough_capabilities, + 0 +}; + /* Terminal supports synchronized updates. */ static const char *tty_feature_sync_capabilities[] = { "Sync=\\EP=%p1%ds\\E\\\\", NULL }; -static struct tty_feature tty_feature_sync = { +static const struct tty_feature tty_feature_sync = { "sync", tty_feature_sync_capabilities, 0 @@ -166,14 +198,14 @@ static const char *tty_feature_margins_capabilities[] = { "Cmg=\\E[%i%p1%d;%p2%ds", NULL }; -static struct tty_feature tty_feature_margins = { +static const struct tty_feature tty_feature_margins = { "margins", tty_feature_margins_capabilities, TERM_DECSLRM }; /* Terminal supports DECFRA rectangle fill. */ -static struct tty_feature tty_feature_rectfill = { +static const struct tty_feature tty_feature_rectfill = { "rectfill", NULL, TERM_DECFRA @@ -182,13 +214,16 @@ static struct tty_feature tty_feature_rectfill = { /* Available terminal features. */ static const struct tty_feature *tty_features[] = { &tty_feature_256, - &tty_feature_clipboard, + &tty_feature_bpaste, &tty_feature_ccolour, + &tty_feature_clipboard, &tty_feature_cstyle, + &tty_feature_focus, &tty_feature_margins, &tty_feature_overline, &tty_feature_rectfill, &tty_feature_rgb, + &tty_feature_strikethrough, &tty_feature_sync, &tty_feature_title, &tty_feature_usstyle @@ -201,7 +236,7 @@ tty_add_features(int *feat, const char *s, const char *separators) char *next, *loop, *copy; u_int i; - log_debug("%s: %s", __func__, s); + log_debug("adding terminal features %s", s); loop = copy = xstrdup(s); while ((next = strsep(&loop, separators)) != NULL) { @@ -275,3 +310,39 @@ tty_apply_features(struct tty_term *term, int feat) term->features |= feat; return (1); } + +void +tty_default_features(int *feat, const char *name, u_int version) +{ + static struct { + const char *name; + u_int version; + const char *features; + } table[] = { +#define TTY_FEATURES_BASE_MODERN_XTERM "256,RGB,bpaste,clipboard,strikethrough,title" + { .name = "mintty", + .features = TTY_FEATURES_BASE_MODERN_XTERM ",ccolour,cstyle,margins,overline" + }, + { .name = "tmux", + .features = TTY_FEATURES_BASE_MODERN_XTERM ",ccolour,cstyle,focus,overline,usstyle" + }, + { .name = "rxvt-unicode", + .features = "256,bpaste,ccolour,cstyle,title" + }, + { .name = "iTerm2", + .features = TTY_FEATURES_BASE_MODERN_XTERM ",cstyle,margins,sync" + }, + { .name = "XTerm", + .features = TTY_FEATURES_BASE_MODERN_XTERM ",ccolour,cstyle,focus,margins,rectfill" + } + }; + u_int i; + + for (i = 0; i < nitems(table); i++) { + if (strcmp(table[i].name, name) != 0) + continue; + if (version != 0 && version < table[i].version) + continue; + tty_add_features(feat, table[i].features, ","); + } +} diff --git a/tty-term.c b/tty-term.c index 78ba5a37..67ca1bac 100644 --- a/tty-term.c +++ b/tty-term.c @@ -83,6 +83,8 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_DIM] = { TTYCODE_STRING, "dim" }, [TTYC_DL1] = { TTYCODE_STRING, "dl1" }, [TTYC_DL] = { TTYCODE_STRING, "dl" }, + [TTYC_DSFCS] = { TTYCODE_STRING, "Dsfcs" }, + [TTYC_DSBP] = { TTYCODE_STRING, "Dsbp" }, [TTYC_DSMG] = { TTYCODE_STRING, "Dsmg" }, [TTYC_E3] = { TTYCODE_STRING, "E3" }, [TTYC_ECH] = { TTYCODE_STRING, "ech" }, @@ -90,6 +92,8 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_EL1] = { TTYCODE_STRING, "el1" }, [TTYC_EL] = { TTYCODE_STRING, "el" }, [TTYC_ENACS] = { TTYCODE_STRING, "enacs" }, + [TTYC_ENBP] = { TTYCODE_STRING, "Enbp" }, + [TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" }, [TTYC_ENMG] = { TTYCODE_STRING, "Enmg" }, [TTYC_FSL] = { TTYCODE_STRING, "fsl" }, [TTYC_HOME] = { TTYCODE_STRING, "home" }, @@ -545,21 +549,29 @@ tty_term_create(struct tty *tty, char *name, int *feat, int fd, char **cause) goto error; } - /* These can be emulated so one of the two is required. */ - if (!tty_term_has(term, TTYC_CUD1) && !tty_term_has(term, TTYC_CUD)) { - xasprintf(cause, "terminal does not support cud1 or cud"); - goto error; + /* + * If TERM has XT or clear starts with CSI then it is safe to assume + * the terminal is derived from the VT100. This controls whether device + * attributes requests are sent to get more information. + * + * This is a bit of a hack but there aren't that many alternatives. + * Worst case tmux will just fall back to using whatever terminfo(5) + * says without trying to correct anything that is missing. + * + * Also add few features that VT100-like terminals should either + * support or safely ignore. + */ + s = tty_term_string(term, TTYC_CLEAR); + if (tty_term_flag(term, TTYC_XT) || strncmp(s, "\033[", 2) == 0) { + term->flags |= TERM_VT100LIKE; + tty_add_features(feat, "bpaste,focus,title", ","); } /* Add RGB feature if terminal has RGB colours. */ if ((tty_term_flag(term, TTYC_TC) || tty_term_has(term, TTYC_RGB)) && (!tty_term_has(term, TTYC_SETRGBF) || !tty_term_has(term, TTYC_SETRGBB))) - tty_add_features(feat, "RGB", ":,"); - - /* Add feature if terminal has XT. */ - if (tty_term_flag(term, TTYC_XT)) - tty_add_features(feat, "title", ":,"); + tty_add_features(feat, "RGB", ","); /* Apply the features and overrides again. */ tty_apply_features(term, *feat); From 428137d8765f6aeb56503d8d37e3b1c9b33994ce Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:49:50 +0000 Subject: [PATCH 11/49] Instead of forbidding invalid session names, sanitize them like window names. --- cmd-new-session.c | 35 ++++++++++++++--------------------- cmd-rename-session.c | 12 ++++-------- session.c | 19 ++++++++++++++----- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/cmd-new-session.c b/cmd-new-session.c index 353f7bed..f08155c0 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -70,13 +70,14 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) struct cmd_find_state *current = cmdq_get_current(item); struct cmd_find_state *target = cmdq_get_target(item); struct client *c = cmdq_get_client(item); - struct session *s, *as, *groupwith; + struct session *s, *as, *groupwith = NULL; struct environ *env; struct options *oo; struct termios tio, *tiop; - struct session_group *sg; - const char *errstr, *template, *group, *prefix, *tmp; + struct session_group *sg = NULL; + const char *errstr, *template, *group, *tmp; char *cause, *cwd = NULL, *cp, *newname = NULL; + char *name, *prefix = NULL; int detached, already_attached, is_control = 0; u_int sx, sy, dsx, dsy; struct spawn_context sc; @@ -98,11 +99,9 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) tmp = args_get(args, 's'); if (tmp != NULL) { - newname = format_single(item, tmp, c, NULL, NULL, NULL); - if (!session_check_name(newname)) { - cmdq_error(item, "bad session name: %s", newname); - goto fail; - } + name = format_single(item, tmp, c, NULL, NULL, NULL); + newname = session_check_name(name); + free(name); } if (args_has(args, 'A')) { if (newname != NULL) @@ -126,24 +125,16 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) group = args_get(args, 't'); if (group != NULL) { groupwith = target->s; - if (groupwith == NULL) { - if (!session_check_name(group)) { - cmdq_error(item, "bad group name: %s", group); - goto fail; - } + if (groupwith == NULL) sg = session_group_find(group); - } else + else sg = session_group_contains(groupwith); if (sg != NULL) - prefix = sg->name; + prefix = xstrdup(sg->name); else if (groupwith != NULL) - prefix = groupwith->name; + prefix = xstrdup(groupwith->name); else - prefix = group; - } else { - groupwith = NULL; - sg = NULL; - prefix = NULL; + prefix = session_check_name(group); } /* Set -d if no client. */ @@ -353,10 +344,12 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) free(cwd); free(newname); + free(prefix); return (CMD_RETURN_NORMAL); fail: free(cwd); free(newname); + free(prefix); return (CMD_RETURN_ERROR); } diff --git a/cmd-rename-session.c b/cmd-rename-session.c index 4b2c3d88..51b8ffc8 100644 --- a/cmd-rename-session.c +++ b/cmd-rename-session.c @@ -49,19 +49,15 @@ cmd_rename_session_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); struct cmd_find_state *target = cmdq_get_target(item); struct session *s = target->s; - char *newname; + char *newname, *tmp; - newname = format_single_from_target(item, args->argv[0]); + tmp = format_single_from_target(item, args->argv[0]); + newname = session_check_name(tmp); + free(tmp); if (strcmp(newname, s->name) == 0) { free(newname); return (CMD_RETURN_NORMAL); } - - if (!session_check_name(newname)) { - cmdq_error(item, "bad session name: %s", newname); - free(newname); - return (CMD_RETURN_ERROR); - } if (session_find(newname) != NULL) { cmdq_error(item, "duplicate session: %s", newname); free(newname); diff --git a/session.c b/session.c index be9c8e07..93d50b47 100644 --- a/session.c +++ b/session.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "tmux.h" @@ -123,7 +124,6 @@ session_create(const char *prefix, const char *name, const char *cwd, s->cwd = xstrdup(cwd); - s->curw = NULL; TAILQ_INIT(&s->lastw); RB_INIT(&s->windows); @@ -142,7 +142,6 @@ session_create(const char *prefix, const char *name, const char *cwd, s->name = xstrdup(name); s->id = next_session_id++; } else { - s->name = NULL; do { s->id = next_session_id++; free(s->name); @@ -232,11 +231,20 @@ session_destroy(struct session *s, int notify, const char *from) session_remove_ref(s, __func__); } -/* Check a session name is valid: not empty and no colons or periods. */ -int +/* Sanitize session name. */ +char * session_check_name(const char *name) { - return (*name != '\0' && name[strcspn(name, ":.")] == '\0'); + char *copy, *cp, *new_name; + + copy = xstrdup(name); + for (cp = copy; *cp != '\0'; cp++) { + if (*cp == ':' || *cp == '.') + *cp = '_'; + } + utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); + free(copy); + return (new_name); } /* Lock session if it has timed out. */ @@ -556,6 +564,7 @@ session_group_remove(struct session *s) TAILQ_REMOVE(&sg->sessions, s, gentry); if (TAILQ_EMPTY(&sg->sessions)) { RB_REMOVE(session_groups, &session_groups, sg); + free((void *)sg->name); free(sg); } } From 5bf96c2f2c40e93b8e66d7100f7b3dc9074a1ca6 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:53:23 +0000 Subject: [PATCH 12/49] Use a grid cell not a style for the pane style. --- grid.c | 23 ++++++++++++++++------- menu.c | 4 ++-- style.c | 29 ++-------------------------- tmux.h | 24 +++++++++++++++++++----- tty.c | 57 ++++++++++++++++++++++++-------------------------------- window.c | 18 +++++++++--------- 6 files changed, 72 insertions(+), 83 deletions(-) diff --git a/grid.c b/grid.c index 0cef412b..81f3709c 100644 --- a/grid.c +++ b/grid.c @@ -211,19 +211,28 @@ grid_check_y(struct grid *gd, const char *from, u_int py) return (0); } +/* Check if two styles are (visibly) the same. */ +int +grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2) +{ + if (gc1->fg != gc2->fg || gc1->bg != gc2->bg) + return (0); + if (gc1->attr != gc2->attr || gc1->flags != gc2->flags) + return (0); + return (1); +} + /* Compare grid cells. Return 1 if equal, 0 if not. */ int -grid_cells_equal(const struct grid_cell *gca, const struct grid_cell *gcb) +grid_cells_equal(const struct grid_cell *gc1, const struct grid_cell *gc2) { - if (gca->fg != gcb->fg || gca->bg != gcb->bg) + if (!grid_cells_look_equal(gc1, gc2)) return (0); - if (gca->attr != gcb->attr || gca->flags != gcb->flags) + if (gc1->data.width != gc2->data.width) return (0); - if (gca->data.width != gcb->data.width) + if (gc1->data.size != gc2->data.size) return (0); - if (gca->data.size != gcb->data.size) - return (0); - return (memcmp(gca->data.data, gcb->data.data, gca->data.size) == 0); + return (memcmp(gc1->data.data, gc2->data.data, gc1->data.size) == 0); } /* Free one line. */ diff --git a/menu.c b/menu.c index 39cb50c7..fd744e7d 100644 --- a/menu.c +++ b/menu.c @@ -73,7 +73,7 @@ menu_add_item(struct menu *menu, const struct menu_item *item, return; if (fs != NULL) - s = format_single(qitem, item->name, c, fs->s, fs->wl, fs->wp); + s = format_single_from_state(qitem, item->name, c, fs); else s = format_single(qitem, item->name, c, NULL, NULL, NULL); if (*s == '\0') { /* no item if empty after format expanded */ @@ -91,7 +91,7 @@ menu_add_item(struct menu *menu, const struct menu_item *item, cmd = item->command; if (cmd != NULL) { if (fs != NULL) - s = format_single(qitem, cmd, c, fs->s, fs->wl, fs->wp); + s = format_single_from_state(qitem, cmd, c, fs); else s = format_single(qitem, cmd, c, NULL, NULL, NULL); } else diff --git a/style.c b/style.c index da3b4c78..2ac78e97 100644 --- a/style.c +++ b/style.c @@ -59,6 +59,7 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) return (0); style_copy(&saved, sy); + log_debug("%s: %s", __func__, in); do { while (*in != '\0' && strchr(delimiters, *in) != NULL) in++; @@ -71,6 +72,7 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) memcpy(tmp, in, end); tmp[end] = '\0'; + log_debug("%s: %s", __func__, tmp); if (strcasecmp(tmp, "default") == 0) { sy->gc.fg = base->fg; sy->gc.bg = base->bg; @@ -285,30 +287,3 @@ style_copy(struct style *dst, struct style *src) { memcpy(dst, src, sizeof *dst); } - -/* Check if two styles are (visibly) the same. */ -int -style_equal(struct style *sy1, struct style *sy2) -{ - struct grid_cell *gc1 = &sy1->gc; - struct grid_cell *gc2 = &sy2->gc; - - if (gc1->fg != gc2->fg) - return (0); - if (gc1->bg != gc2->bg) - return (0); - if ((gc1->attr & STYLE_ATTR_MASK) != (gc2->attr & STYLE_ATTR_MASK)) - return (0); - if (sy1->fill != sy2->fill) - return (0); - if (sy1->align != sy2->align) - return (0); - return (1); -} - -/* Is this style default? */ -int -style_is_default(struct style *sy) -{ - return (style_equal(sy, &style_default)); -} diff --git a/tmux.h b/tmux.h index cf8bbbf5..4e19c4ef 100644 --- a/tmux.h +++ b/tmux.h @@ -279,6 +279,8 @@ enum tty_code_code { TTYC_DIM, TTYC_DL, TTYC_DL1, + TTYC_DSBP, + TTYC_DSFCS, TTYC_DSMG, TTYC_E3, TTYC_ECH, @@ -286,6 +288,8 @@ enum tty_code_code { TTYC_EL, TTYC_EL1, TTYC_ENACS, + TTYC_ENBP, + TTYC_ENFCS, TTYC_ENMG, TTYC_FSL, TTYC_HOME, @@ -924,8 +928,8 @@ struct window_pane { struct input_ctx *ictx; - struct style cached_style; - struct style cached_active_style; + struct grid_cell cached_gc; + struct grid_cell cached_active_gc; int *palette; int pipe_fd; @@ -1196,6 +1200,7 @@ struct tty_term { #define TERM_DECSLRM 0x4 #define TERM_DECFRA 0x8 #define TERM_RGBCOLOURS 0x10 +#define TERM_VT100LIKE 0x20 int flags; LIST_ENTRY(tty_term) entry; @@ -1504,6 +1509,7 @@ struct client { char *term_name; int term_features; + char *term_type; char *ttyname; struct tty tty; @@ -1819,7 +1825,14 @@ char *format_expand(struct format_tree *, const char *); char *format_single(struct cmdq_item *, const char *, struct client *, struct session *, struct winlink *, struct window_pane *); +char *format_single_from_state(struct cmdq_item *, const char *, + struct client *, struct cmd_find_state *); char *format_single_from_target(struct cmdq_item *, const char *); +struct format_tree *format_create_defaults(struct cmdq_item *, struct client *, + struct session *, struct winlink *, struct window_pane *); +struct format_tree *format_create_from_state(struct cmdq_item *, + struct client *, struct cmd_find_state *); +struct format_tree *format_create_from_target(struct cmdq_item *); void format_defaults(struct format_tree *, struct client *, struct session *, struct winlink *, struct window_pane *); void format_defaults_window(struct format_tree *, struct window *); @@ -2028,6 +2041,7 @@ const char *tty_term_describe(struct tty_term *, enum tty_code_code); void tty_add_features(int *, const char *, const char *); const char *tty_get_features(int); int tty_apply_features(struct tty_term *, int); +void tty_default_features(int *, const char *, u_int); /* tty-acs.c */ int tty_acs_needed(struct tty *); @@ -2349,6 +2363,8 @@ int attributes_fromstring(const char *); extern const struct grid_cell grid_default_cell; void grid_empty_line(struct grid *, u_int, u_int); int grid_cells_equal(const struct grid_cell *, const struct grid_cell *); +int grid_cells_look_equal(const struct grid_cell *, + const struct grid_cell *); struct grid *grid_create(u_int, u_int, u_int); void grid_destroy(struct grid *); int grid_compare(struct grid *, struct grid *); @@ -2713,7 +2729,7 @@ struct session *session_create(const char *, const char *, const char *, void session_destroy(struct session *, int, const char *); void session_add_ref(struct session *, const char *); void session_remove_ref(struct session *, const char *); -int session_check_name(const char *); +char *session_check_name(const char *); void session_update_activity(struct session *, struct timeval *); struct session *session_next_session(struct session *); struct session *session_previous_session(struct session *); @@ -2804,10 +2820,8 @@ int style_parse(struct style *,const struct grid_cell *, const char *style_tostring(struct style *); void style_apply(struct grid_cell *, struct options *, const char *); -int style_equal(struct style *, struct style *); void style_set(struct style *, const struct grid_cell *); void style_copy(struct style *, struct style *); -int style_is_default(struct style *); /* spawn.c */ struct winlink *spawn_window(struct spawn_context *, char **); diff --git a/tty.c b/tty.c index 5d84c9e8..99b433ae 100644 --- a/tty.c +++ b/tty.c @@ -330,13 +330,12 @@ tty_start_tty(struct tty *tty) tty_puts(tty, "\033[?1006l\033[?1005l"); } - if (tty_term_flag(tty->term, TTYC_XT)) { - if (options_get_number(global_options, "focus-events")) { - tty->flags |= TTY_FOCUS; - tty_puts(tty, "\033[?1004h"); - } - tty_puts(tty, "\033[?7727h"); + if (options_get_number(global_options, "focus-events")) { + tty->flags |= TTY_FOCUS; + tty_raw(tty, tty_term_string(tty->term, TTYC_ENFCS)); } + if (tty->term->flags & TERM_VT100LIKE) + tty_puts(tty, "\033[?7727h"); evtimer_set(&tty->start_timer, tty_start_timer_callback, tty); evtimer_add(&tty->start_timer, &tv); @@ -358,7 +357,7 @@ tty_send_requests(struct tty *tty) if (~tty->flags & TTY_STARTED) return; - if (tty_term_flag(tty->term, TTYC_XT)) { + if (tty->term->flags & TERM_VT100LIKE) { if (~tty->flags & TTY_HAVEDA) tty_puts(tty, "\033[>c"); if (~tty->flags & TTY_HAVEXDA) @@ -407,7 +406,7 @@ tty_stop_tty(struct tty *tty) tty_raw(tty, tty_term_string1(tty->term, TTYC_SS, 0)); } if (tty->mode & MODE_BRACKETPASTE) - tty_raw(tty, "\033[?2004l"); + tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP)); if (*tty->ccolour != '\0') tty_raw(tty, tty_term_string(tty->term, TTYC_CR)); @@ -417,13 +416,12 @@ tty_stop_tty(struct tty *tty) tty_raw(tty, "\033[?1006l\033[?1005l"); } - if (tty_term_flag(tty->term, TTYC_XT)) { - if (tty->flags & TTY_FOCUS) { - tty->flags &= ~TTY_FOCUS; - tty_raw(tty, "\033[?1004l"); - } - tty_raw(tty, "\033[?7727l"); + if (tty->flags & TTY_FOCUS) { + tty->flags &= ~TTY_FOCUS; + tty_raw(tty, tty_term_string(tty->term, TTYC_DSFCS)); } + if (tty->term->flags & TERM_VT100LIKE) + tty_raw(tty, "\033[?7727l"); if (tty_use_margin(tty)) tty_raw(tty, tty_term_string(tty->term, TTYC_DSMG)); @@ -676,7 +674,8 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) mode &= ~MODE_CURSOR; changed = mode ^ tty->mode; - log_debug("%s: update mode %x to %x", c->name, tty->mode, mode); + if (changed != 0) + log_debug("%s: update mode %x to %x", c->name, tty->mode, mode); if (changed & MODE_BLINKING) { if (tty_term_has(tty->term, TTYC_CVVIS)) @@ -729,9 +728,9 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) } if (changed & MODE_BRACKETPASTE) { if (mode & MODE_BRACKETPASTE) - tty_puts(tty, "\033[?2004h"); + tty_putcode(tty, TTYC_ENBP); else - tty_puts(tty, "\033[?2004l"); + tty_putcode(tty, TTYC_DSBP); } tty->mode = mode; } @@ -2691,27 +2690,19 @@ static void tty_default_colours(struct grid_cell *gc, struct window_pane *wp) { struct options *oo = wp->options; - struct style *style, *active_style; int c; if (wp->flags & PANE_STYLECHANGED) { wp->flags &= ~PANE_STYLECHANGED; - - active_style = options_get_style(oo, "window-active-style"); - style = options_get_style(oo, "window-style"); - - style_copy(&wp->cached_active_style, active_style); - style_copy(&wp->cached_style, style); - } else { - active_style = &wp->cached_active_style; - style = &wp->cached_style; + style_apply(&wp->cached_active_gc, oo, "window-active-style"); + style_apply(&wp->cached_gc, oo, "window-style"); } if (gc->fg == 8) { - if (wp == wp->window->active && active_style->gc.fg != 8) - gc->fg = active_style->gc.fg; + if (wp == wp->window->active && wp->cached_active_gc.fg != 8) + gc->fg = wp->cached_active_gc.fg; else - gc->fg = style->gc.fg; + gc->fg = wp->cached_gc.fg; if (gc->fg != 8) { c = window_pane_get_palette(wp, gc->fg); @@ -2721,10 +2712,10 @@ tty_default_colours(struct grid_cell *gc, struct window_pane *wp) } if (gc->bg == 8) { - if (wp == wp->window->active && active_style->gc.bg != 8) - gc->bg = active_style->gc.bg; + if (wp == wp->window->active && wp->cached_active_gc.bg != 8) + gc->bg = wp->cached_active_gc.bg; else - gc->bg = style->gc.bg; + gc->bg = wp->cached_gc.bg; if (gc->bg != 8) { c = window_pane_get_palette(wp, gc->bg); diff --git a/window.c b/window.c index b0194eaf..fbc4bb19 100644 --- a/window.c +++ b/window.c @@ -488,8 +488,8 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) void window_redraw_active_switch(struct window *w, struct window_pane *wp) { - struct style *sy1, *sy2; - int c1, c2; + struct grid_cell *gc1, *gc2; + int c1, c2; if (wp == w->active) return; @@ -499,18 +499,18 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) * If the active and inactive styles or palettes are different, * need to redraw the panes. */ - sy1 = &wp->cached_style; - sy2 = &wp->cached_active_style; - if (!style_equal(sy1, sy2)) + gc1 = &wp->cached_gc; + gc2 = &wp->cached_active_gc; + if (!grid_cells_look_equal(gc1, gc2)) wp->flags |= PANE_REDRAW; else { - c1 = window_pane_get_palette(wp, sy1->gc.fg); - c2 = window_pane_get_palette(wp, sy2->gc.fg); + c1 = window_pane_get_palette(wp, gc1->fg); + c2 = window_pane_get_palette(wp, gc2->fg); if (c1 != c2) wp->flags |= PANE_REDRAW; else { - c1 = window_pane_get_palette(wp, sy1->gc.bg); - c2 = window_pane_get_palette(wp, sy2->gc.bg); + c1 = window_pane_get_palette(wp, gc1->bg); + c2 = window_pane_get_palette(wp, gc2->bg); if (c1 != c2) wp->flags |= PANE_REDRAW; } From 0487029fc5f39d49e37c8821f4427565b9969671 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 14:55:38 +0000 Subject: [PATCH 13/49] Call format_defaults_window for panes as well. --- format.c | 62 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/format.c b/format.c index c43baf05..a2b2822f 100644 --- a/format.c +++ b/format.c @@ -2479,25 +2479,59 @@ format_single(struct cmdq_item *item, const char *fmt, struct client *c, struct format_tree *ft; char *expanded; - if (item != NULL) - ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); - else - ft = format_create(NULL, item, FORMAT_NONE, 0); - format_defaults(ft, c, s, wl, wp); - + ft = format_create_defaults(item, c, s, wl, wp); expanded = format_expand(ft, fmt); format_free(ft); return (expanded); } +/* Expand a single string using state. */ +char * +format_single_from_state(struct cmdq_item *item, const char *fmt, + struct client *c, struct cmd_find_state *fs) +{ + return (format_single(item, fmt, c, fs->s, fs->wl, fs->wp)); +} + /* Expand a single string using target. */ char * format_single_from_target(struct cmdq_item *item, const char *fmt) { - struct cmd_find_state *target = cmdq_get_target(item); - struct client *tc = cmdq_get_target_client(item); + struct client *tc = cmdq_get_target_client(item); - return (format_single(item, fmt, tc, target->s, target->wl, target->wp)); + return (format_single_from_state(item, fmt, tc, cmdq_get_target(item))); +} + +/* Create and add defaults. */ +struct format_tree * +format_create_defaults(struct cmdq_item *item, struct client *c, + struct session *s, struct winlink *wl, struct window_pane *wp) +{ + struct format_tree *ft; + + if (item != NULL) + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); + else + ft = format_create(NULL, item, FORMAT_NONE, 0); + format_defaults(ft, c, s, wl, wp); + return (ft); +} + +/* Create and add defaults using state. */ +struct format_tree * +format_create_from_state(struct cmdq_item *item, struct client *c, + struct cmd_find_state *fs) +{ + return (format_create_defaults(item, c, fs->s, fs->wl, fs->wp)); +} + +/* Create and add defaults using target. */ +struct format_tree * +format_create_from_target(struct cmdq_item *item) +{ + struct client *tc = cmdq_get_target_client(item); + + return (format_create_from_state(item, tc, cmdq_get_target(item))); } /* Set defaults for any of arguments that are not NULL. */ @@ -2516,7 +2550,7 @@ format_defaults(struct format_tree *ft, struct client *c, struct session *s, else log_debug("%s: s=none", __func__); if (wl != NULL) - log_debug("%s: wl=%u w=@%u", __func__, wl->idx, wl->window->id); + log_debug("%s: wl=%u", __func__, wl->idx); else log_debug("%s: wl=none", __func__); if (wp != NULL) @@ -2624,6 +2658,8 @@ format_defaults_client(struct format_tree *ft, struct client *c) format_add(ft, "client_termname", "%s", c->term_name); format_add(ft, "client_termfeatures", "%s", tty_get_features(c->term_features)); + if (c->term_type != NULL) + format_add(ft, "client_termtype", "%s", c->term_type); format_add_tv(ft, "client_created", &c->creation_time); format_add_tv(ft, "client_activity", &c->activity_time); @@ -2688,11 +2724,9 @@ format_defaults_winlink(struct format_tree *ft, struct winlink *wl) u_int ox, oy, sx, sy; if (ft->w == NULL) - ft->w = wl->window; + format_defaults_window(ft, w); ft->wl = wl; - format_defaults_window(ft, w); - if (c != NULL) { flag = tty_window_offset(&c->tty, &ox, &oy, &sx, &sy); format_add(ft, "window_bigger", "%d", flag); @@ -2752,7 +2786,7 @@ format_defaults_pane(struct format_tree *ft, struct window_pane *wp) struct window_mode_entry *wme; if (ft->w == NULL) - ft->w = w; + format_defaults_window(ft, w); ft->wp = wp; format_add(ft, "history_size", "%u", gd->hsize); From f03b61131b3407929fea187a309fb336017791d1 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:01:30 +0000 Subject: [PATCH 14/49] Drop having a separate type for style options and make them all strings, which allows formats to be expanded. Any styles without a '#{' are still validated when they are set but any with a '#{' are not. Formats are not expanded usefully in many cases yet, that will be changed later. To make this work, a few other changes: - set-option -a with a style option automatically appends a ",". - OSC 10 and 11 don't set the window-style option anymore, instead the fg and bg are stored in the pane struct and act as the defaults that can be overridden by window-style. - status-fg and -bg now override status-style instead of trying to keep them in sync. --- cmd-rename-window.c | 1 + cmd-resize-pane.c | 1 - cmd-respawn-pane.c | 1 + cmd-select-pane.c | 32 ++-- cmd-set-option.c | 57 +++--- input.c | 29 +-- menu.c | 3 +- mode-tree.c | 2 +- names.c | 1 + options-table.c | 94 ++++++---- options.c | 98 +++++----- screen-redraw.c | 439 ++++++++++++++++++++++++++------------------ status.c | 16 +- style.c | 34 +++- tmux.h | 31 ++-- tty.c | 16 +- window-copy.c | 4 +- window.c | 5 + 18 files changed, 514 insertions(+), 350 deletions(-) diff --git a/cmd-rename-window.c b/cmd-rename-window.c index 1fb17ad9..593e0b9e 100644 --- a/cmd-rename-window.c +++ b/cmd-rename-window.c @@ -54,6 +54,7 @@ cmd_rename_window_exec(struct cmd *self, struct cmdq_item *item) window_set_name(wl->window, newname); options_set_number(wl->window->options, "automatic-rename", 0); + server_redraw_window_borders(wl->window); server_status_window(wl->window); free(newname); diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 105f48e0..563c95fb 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -91,7 +91,6 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) else window_zoom(wp); server_redraw_window(w); - server_status_window(w); return (CMD_RETURN_NORMAL); } server_unzoom_window(w); diff --git a/cmd-respawn-pane.c b/cmd-respawn-pane.c index 498a89a7..9db280b4 100644 --- a/cmd-respawn-pane.c +++ b/cmd-respawn-pane.c @@ -90,6 +90,7 @@ cmd_respawn_pane_exec(struct cmd *self, struct cmdq_item *item) } wp->flags |= PANE_REDRAW; + server_redraw_window_borders(wp->window); server_status_window(wp->window); environ_free(sc.environ); diff --git a/cmd-select-pane.c b/cmd-select-pane.c index db110ff9..224370ab 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -91,9 +91,9 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) struct window *w = wl->window; struct session *s = target->s; struct window_pane *wp = target->wp, *lastwp, *markedwp; + struct options *oo = wp->options; char *title; const char *style; - struct style *sy; struct options_entry *o; if (entry == &cmd_last_pane_entry || args_has(args, 'l')) { @@ -147,22 +147,18 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } - if (args_has(args, 'P') || args_has(args, 'g')) { - if ((style = args_get(args, 'P')) != NULL) { - o = options_set_style(wp->options, "window-style", 0, - style); - if (o == NULL) { - cmdq_error(item, "bad style: %s", style); - return (CMD_RETURN_ERROR); - } - options_set_style(wp->options, "window-active-style", 0, - style); - wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); - } - if (args_has(args, 'g')) { - sy = options_get_style(wp->options, "window-style"); - cmdq_print(item, "%s", style_tostring(sy)); + style = args_get(args, 'P'); + if (style != NULL) { + o = options_set_string(oo, "window-style", 0, "%s", style); + if (o == NULL) { + cmdq_error(item, "bad style: %s", style); + return (CMD_RETURN_ERROR); } + options_set_string(oo, "window-active-style", 0, "%s", style); + wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); + } + if (args_has(args, 'g')) { + cmdq_print(item, "%s", options_get_string(oo, "window-style")); return (CMD_RETURN_NORMAL); } @@ -197,8 +193,10 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'T')) { title = format_single_from_target(item, args_get(args, 'T')); - if (screen_set_title(&wp->base, title)) + if (screen_set_title(&wp->base, title)) { + server_redraw_window_borders(wp->window); server_status_window(wp->window); + } free(title); return (CMD_RETURN_NORMAL); } diff --git a/cmd-set-option.c b/cmd-set-option.c index 1752d093..e04aa7ff 100644 --- a/cmd-set-option.c +++ b/cmd-set-option.c @@ -93,7 +93,6 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) char *name, *argument, *value = NULL, *cause; int window, idx, already, error, ambiguous; int scope; - struct style *sy; window = (cmd_get_entry(self) == &cmd_set_window_option_entry); @@ -232,16 +231,6 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) tty_keys_build(&loop->tty); } } - if (strcmp(name, "status-fg") == 0 || strcmp(name, "status-bg") == 0) { - sy = options_get_style(oo, "status-style"); - sy->gc.fg = options_get_number(oo, "status-fg"); - sy->gc.bg = options_get_number(oo, "status-bg"); - } - if (strcmp(name, "status-style") == 0) { - sy = options_get_style(oo, "status-style"); - options_set_number(oo, "status-fg", sy->gc.fg); - options_set_number(oo, "status-bg", sy->gc.bg); - } if (strcmp(name, "status") == 0 || strcmp(name, "status-interval") == 0) status_timer_start_all(); @@ -282,6 +271,29 @@ fail: return (CMD_RETURN_ERROR); } +static int +cmd_set_option_check_string(const struct options_table_entry *oe, + const char *value, char **cause) +{ + struct style sy; + + if (strcmp(oe->name, "default-shell") == 0 && !checkshell(value)) { + xasprintf(cause, "not a suitable shell: %s", value); + return (-1); + } + if (oe->pattern != NULL && fnmatch(oe->pattern, value, 0) != 0) { + xasprintf(cause, "value is invalid: %s", value); + return (-1); + } + if ((oe->flags & OPTIONS_TABLE_IS_STYLE) && + strstr(value, "#{") == NULL && + style_parse(&sy, &grid_default_cell, value) != 0) { + xasprintf(cause, "invalid style: %s", value); + return (-1); + } + return (0); +} + static int cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, struct options_entry *parent, const char *value) @@ -289,10 +301,9 @@ cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, const struct options_table_entry *oe; struct args *args = cmd_get_args(self); int append = args_has(args, 'a'); - struct options_entry *o; long long number; const char *errstr, *new; - char *old; + char *old, *cause; key_code key; oe = options_table_entry(parent); @@ -308,17 +319,12 @@ cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, old = xstrdup(options_get_string(oo, oe->name)); options_set_string(oo, oe->name, append, "%s", value); new = options_get_string(oo, oe->name); - if (strcmp(oe->name, "default-shell") == 0 && - !checkshell(new)) { + if (cmd_set_option_check_string(oe, new, &cause) != 0) { + cmdq_error(item, "%s", cause); + free(cause); + options_set_string(oo, oe->name, 0, "%s", old); free(old); - cmdq_error(item, "not a suitable shell: %s", value); - return (-1); - } - if (oe->pattern != NULL && fnmatch(oe->pattern, new, 0) != 0) { - options_set_string(oo, oe->name, 0, "%s", old); - free(old); - cmdq_error(item, "value is invalid: %s", value); return (-1); } free(old); @@ -350,13 +356,6 @@ cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, return (cmd_set_option_flag(item, oe, oo, value)); case OPTIONS_TABLE_CHOICE: return (cmd_set_option_choice(item, oe, oo, value)); - case OPTIONS_TABLE_STYLE: - o = options_set_style(oo, oe->name, append, value); - if (o == NULL) { - cmdq_error(item, "bad style: %s", value); - return (-1); - } - return (0); case OPTIONS_TABLE_COMMAND: break; } diff --git a/input.c b/input.c index b62b1858..8485a17a 100644 --- a/input.c +++ b/input.c @@ -1861,8 +1861,10 @@ input_csi_dispatch_winops(struct input_ctx *ictx) case 0: case 2: screen_pop_title(sctx->s); - if (wp != NULL) + if (wp != NULL) { + server_redraw_window_borders(wp->window); server_status_window(wp->window); + } break; } break; @@ -2253,8 +2255,10 @@ input_exit_osc(struct input_ctx *ictx) switch (option) { case 0: case 2: - if (screen_set_title(sctx->s, p) && wp != NULL) - server_status_window(ictx->wp->window); + if (screen_set_title(sctx->s, p) && wp != NULL) { + server_redraw_window_borders(wp->window); + server_status_window(wp->window); + } break; case 4: input_osc_4(ictx, p); @@ -2262,8 +2266,10 @@ input_exit_osc(struct input_ctx *ictx) case 7: if (utf8_isvalid(p)) { screen_set_path(sctx->s, p); - if (wp != NULL) + if (wp != NULL) { + server_redraw_window_borders(wp->window); server_status_window(wp->window); + } } break; case 10: @@ -2314,8 +2320,10 @@ input_exit_apc(struct input_ctx *ictx) return; log_debug("%s: \"%s\"", __func__, ictx->input_buf); - if (screen_set_title(sctx->s, ictx->input_buf) && wp != NULL) + if (screen_set_title(sctx->s, ictx->input_buf) && wp != NULL) { + server_redraw_window_borders(wp->window); server_status_window(wp->window); + } } /* Rename string started. */ @@ -2355,6 +2363,7 @@ input_exit_rename(struct input_ctx *ictx) } window_set_name(wp->window, ictx->input_buf); options_set_number(wp->window->options, "automatic-rename", 0); + server_redraw_window_borders(wp->window); server_status_window(wp->window); } @@ -2486,7 +2495,6 @@ input_osc_10(struct input_ctx *ictx, const char *p) { struct window_pane *wp = ictx->wp; u_int r, g, b; - char tmp[16]; if (wp == NULL) return; @@ -2495,9 +2503,7 @@ input_osc_10(struct input_ctx *ictx, const char *p) if (!input_osc_parse_colour(p, &r, &g, &b)) goto bad; - xsnprintf(tmp, sizeof tmp, "fg=#%02x%02x%02x", r, g, b); - options_set_style(wp->options, "window-style", 1, tmp); - options_set_style(wp->options, "window-active-style", 1, tmp); + wp->fg = colour_join_rgb(r, g, b); wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); return; @@ -2512,7 +2518,6 @@ input_osc_11(struct input_ctx *ictx, const char *p) { struct window_pane *wp = ictx->wp; u_int r, g, b; - char tmp[16]; if (wp == NULL) return; @@ -2521,9 +2526,7 @@ input_osc_11(struct input_ctx *ictx, const char *p) if (!input_osc_parse_colour(p, &r, &g, &b)) goto bad; - xsnprintf(tmp, sizeof tmp, "bg=#%02x%02x%02x", r, g, b); - options_set_style(wp->options, "window-style", 1, tmp); - options_set_style(wp->options, "window-active-style", 1, tmp); + wp->bg = colour_join_rgb(r, g, b); wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); return; diff --git a/menu.c b/menu.c index fd744e7d..2f92af34 100644 --- a/menu.c +++ b/menu.c @@ -151,8 +151,7 @@ menu_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0) u_int i, px = md->px, py = md->py; struct grid_cell gc; - memcpy(&gc, &grid_default_cell, sizeof gc); - style_apply(&gc, c->session->curw->window->options, "mode-style"); + style_apply(&gc, c->session->curw->window->options, "mode-style", NULL); screen_write_start(&ctx, NULL, s); screen_write_clearscreen(&ctx, 8); diff --git a/mode-tree.c b/mode-tree.c index 783ffcfa..645e2ae9 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -557,7 +557,7 @@ mode_tree_draw(struct mode_tree_data *mtd) memcpy(&gc0, &grid_default_cell, sizeof gc0); memcpy(&gc, &grid_default_cell, sizeof gc); - style_apply(&gc, oo, "mode-style"); + style_apply(&gc, oo, "mode-style", NULL); w = mtd->width; h = mtd->height; diff --git a/names.c b/names.c index 661ba06e..07c689d1 100644 --- a/names.c +++ b/names.c @@ -96,6 +96,7 @@ check_window_name(struct window *w) if (strcmp(name, w->name) != 0) { log_debug("@%u new name %s (was %s)", w->id, name, w->name); window_set_name(w, name); + server_redraw_window_borders(w); server_status_window(w); } else log_debug("@%u name not changed (still %s)", w->id, w->name); diff --git a/options-table.c b/options-table.c index eaa7c2a7..5c368586 100644 --- a/options-table.c +++ b/options-table.c @@ -401,15 +401,19 @@ const struct options_table_entry options_table[] = { }, { .name = "message-command-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=black,fg=yellow" + .default_str = "bg=black,fg=yellow", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "message-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=yellow,fg=black" + .default_str = "bg=yellow,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "mouse", @@ -473,13 +477,13 @@ const struct options_table_entry options_table[] = { { .name = "status-bg", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 2, + .default_num = 8, }, { .name = "status-fg", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0, + .default_num = 8, }, { .name = "status-format", @@ -526,9 +530,11 @@ const struct options_table_entry options_table[] = { }, { .name = "status-left-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "status-position", @@ -555,15 +561,19 @@ const struct options_table_entry options_table[] = { }, { .name = "status-right-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "status-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=green,fg=black" + .default_str = "bg=green,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "update-environment", @@ -666,9 +676,11 @@ const struct options_table_entry options_table[] = { }, { .name = "mode-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "bg=yellow,fg=black" + .default_str = "bg=yellow,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "monitor-activity", @@ -704,9 +716,11 @@ const struct options_table_entry options_table[] = { }, { .name = "pane-active-border-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "fg=green" + .default_str = "#{?pane_in_mode,fg=yellow,#{?synchronize-panes,fg=red,fg=green}}", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "pane-base-index", @@ -732,9 +746,11 @@ const struct options_table_entry options_table[] = { }, { .name = "pane-border-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "remain-on-exit", @@ -750,9 +766,11 @@ const struct options_table_entry options_table[] = { }, { .name = "window-active-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "window-size", @@ -763,21 +781,27 @@ const struct options_table_entry options_table[] = { }, { .name = "window-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "window-status-activity-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "reverse" + .default_str = "reverse", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "window-status-bell-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "reverse" + .default_str = "reverse", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "window-status-current-format", @@ -787,9 +811,11 @@ const struct options_table_entry options_table[] = { }, { .name = "window-status-current-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "window-status-format", @@ -799,9 +825,11 @@ const struct options_table_entry options_table[] = { }, { .name = "window-status-last-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "window-status-separator", @@ -811,9 +839,11 @@ const struct options_table_entry options_table[] = { }, { .name = "window-status-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," }, { .name = "wrap-search", diff --git a/options.c b/options.c index 7402b724..39a0d08f 100644 --- a/options.c +++ b/options.c @@ -53,6 +53,9 @@ struct options_entry { const struct options_table_entry *tableentry; union options_value value; + int cached; + struct style style; + RB_ENTRY(options_entry) entry; }; @@ -73,9 +76,6 @@ static struct options_entry *options_add(struct options *, const char *); (o)->tableentry->type == OPTIONS_TABLE_COLOUR || \ (o)->tableentry->type == OPTIONS_TABLE_FLAG || \ (o)->tableentry->type == OPTIONS_TABLE_CHOICE)) -#define OPTIONS_IS_STYLE(o) \ - ((o)->tableentry != NULL && \ - (o)->tableentry->type == OPTIONS_TABLE_STYLE) #define OPTIONS_IS_COMMAND(o) \ ((o)->tableentry != NULL && \ (o)->tableentry->type == OPTIONS_TABLE_COMMAND) @@ -123,8 +123,6 @@ options_value_tostring(struct options_entry *o, union options_value *ov, if (OPTIONS_IS_COMMAND(o)) return (cmd_list_print(ov->cmdlist, 0)); - if (OPTIONS_IS_STYLE(o)) - return (xstrdup(style_tostring(&ov->style))); if (OPTIONS_IS_NUMBER(o)) { switch (o->tableentry->type) { case OPTIONS_TABLE_NUMBER: @@ -146,7 +144,6 @@ options_value_tostring(struct options_entry *o, union options_value *ov, s = xstrdup(o->tableentry->choices[ov->number]); break; case OPTIONS_TABLE_STRING: - case OPTIONS_TABLE_STYLE: case OPTIONS_TABLE_COMMAND: fatalx("not a number option type"); } @@ -258,10 +255,6 @@ options_default(struct options *oo, const struct options_table_entry *oe) case OPTIONS_TABLE_STRING: ov->string = xstrdup(oe->default_str); break; - case OPTIONS_TABLE_STYLE: - style_set(&ov->style, &grid_default_cell); - style_parse(&ov->style, &grid_default_cell, oe->default_str); - break; default: ov->number = oe->default_num; break; @@ -653,25 +646,13 @@ options_get_number(struct options *oo, const char *name) return (o->value.number); } -struct style * -options_get_style(struct options *oo, const char *name) -{ - struct options_entry *o; - - o = options_get(oo, name); - if (o == NULL) - fatalx("missing option %s", name); - if (!OPTIONS_IS_STYLE(o)) - fatalx("option %s is not a style", name); - return (&o->value.style); -} - struct options_entry * options_set_string(struct options *oo, const char *name, int append, const char *fmt, ...) { struct options_entry *o; va_list ap; + const char *separator = ""; char *s, *value; va_start(ap, fmt); @@ -680,7 +661,12 @@ options_set_string(struct options *oo, const char *name, int append, o = options_get_only(oo, name); if (o != NULL && append && OPTIONS_IS_STRING(o)) { - xasprintf(&value, "%s%s", o->value.string, s); + if (*name != '@') { + separator = o->tableentry->separator; + if (separator == NULL) + separator = ""; + } + xasprintf(&value, "%s%s%s", o->value.string, separator, s); free(s); } else value = s; @@ -696,6 +682,7 @@ options_set_string(struct options *oo, const char *name, int append, fatalx("option %s is not a string", name); free(o->value.string); o->value.string = value; + o->cached = 0; return (o); } @@ -720,35 +707,6 @@ options_set_number(struct options *oo, const char *name, long long value) return (o); } -struct options_entry * -options_set_style(struct options *oo, const char *name, int append, - const char *value) -{ - struct options_entry *o; - struct style sy; - - if (*name == '@') - fatalx("user option %s must be a string", name); - - o = options_get_only(oo, name); - if (o != NULL && append && OPTIONS_IS_STYLE(o)) - style_copy(&sy, &o->value.style); - else - style_set(&sy, &grid_default_cell); - if (style_parse(&sy, &grid_default_cell, value) == -1) - return (NULL); - if (o == NULL) { - o = options_default(oo, options_parent_table_entry(oo, name)); - if (o == NULL) - return (NULL); - } - - if (!OPTIONS_IS_STYLE(o)) - fatalx("option %s is not a style", name); - style_copy(&o->value.style, &sy); - return (o); -} - int options_scope_from_name(struct args *args, int window, const char *name, struct cmd_find_state *fs, struct options **oo, @@ -874,3 +832,37 @@ options_scope_from_flags(struct args *args, int window, return (OPTIONS_TABLE_SESSION); } } + +struct style * +options_string_to_style(struct options *oo, const char *name, + struct format_tree *ft) +{ + struct options_entry *o; + const char *s; + char *expanded; + + o = options_get(oo, name); + if (o == NULL || !OPTIONS_IS_STRING(o)) + return (NULL); + + if (o->cached) + return (&o->style); + s = o->value.string; + log_debug("%s: %s is '%s'", __func__, name, s); + + style_set(&o->style, &grid_default_cell); + o->cached = (strstr(s, "#{") == NULL); + + if (ft != NULL && !o->cached) { + expanded = format_expand(ft, s); + if (style_parse(&o->style, &grid_default_cell, expanded) != 0) { + free(expanded); + return (NULL); + } + free(expanded); + } else { + if (style_parse(&o->style, &grid_default_cell, s) != 0) + return (NULL); + } + return (&o->style); +} diff --git a/screen-redraw.c b/screen-redraw.c index 5ca6024d..19ed6305 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -45,54 +45,197 @@ static void screen_redraw_draw_pane(struct screen_redraw_ctx *, #define CELL_BORDERS " xqlkmjwvtun~" -/* Check if cell is on the border of a particular pane. */ +const struct grid_cell screen_redraw_border_cell = { + { { ' ' }, 0, 1, 1 }, GRID_ATTR_CHARSET, 0, 8, 8, 0 +}; + +enum screen_redraw_border_type { + SCREEN_REDRAW_OUTSIDE, + SCREEN_REDRAW_INSIDE, + SCREEN_REDRAW_BORDER +}; + +/* Return if window has only two panes. */ static int -screen_redraw_cell_border1(struct window_pane *wp, u_int px, u_int py) +screen_redraw_two_panes(struct window *w, int direction) { - /* Inside pane. */ - if (px >= wp->xoff && px < wp->xoff + wp->sx && - py >= wp->yoff && py < wp->yoff + wp->sy) + struct window_pane *wp; + + wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); + if (wp == NULL) + return (0); /* one pane */ + if (TAILQ_NEXT(wp, entry) != NULL) + return (0); /* more than two panes */ + if (direction == 0 && wp->xoff == 0) return (0); + if (direction == 1 && wp->yoff == 0) + return (0); + return (1); +} + +/* Check if cell is on the border of a pane. */ +static enum screen_redraw_border_type +screen_redraw_pane_border(struct window_pane *wp, u_int px, u_int py, + int pane_status) +{ + u_int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy; + + /* Inside pane. */ + if (px >= wp->xoff && px < ex && py >= wp->yoff && py < ey) + return (SCREEN_REDRAW_INSIDE); /* Left/right borders. */ - if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= wp->yoff + wp->sy) { - if (wp->xoff != 0 && px == wp->xoff - 1) - return (1); - if (px == wp->xoff + wp->sx) - return (2); + if (pane_status == PANE_STATUS_OFF) { + if (screen_redraw_two_panes(wp->window, 0)) { + if (wp->xoff == 0 && px == wp->sx && py <= wp->sy / 2) + return (SCREEN_REDRAW_BORDER); + if (wp->xoff != 0 && + px == wp->xoff - 1 && + py > wp->sy / 2) + return (SCREEN_REDRAW_BORDER); + } else { + if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) { + if (wp->xoff != 0 && px == wp->xoff - 1) + return (SCREEN_REDRAW_BORDER); + if (px == ex) + return (SCREEN_REDRAW_BORDER); + } + } + } else { + if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) { + if (wp->xoff != 0 && px == wp->xoff - 1) + return (SCREEN_REDRAW_BORDER); + if (px == ex) + return (SCREEN_REDRAW_BORDER); + } } /* Top/bottom borders. */ - if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= wp->xoff + wp->sx) { - if (wp->yoff != 0 && py == wp->yoff - 1) - return (3); - if (py == wp->yoff + wp->sy) - return (4); + if (pane_status == PANE_STATUS_OFF) { + if (screen_redraw_two_panes(wp->window, 1)) { + if (wp->yoff == 0 && py == wp->sy && px <= wp->sx / 2) + return (SCREEN_REDRAW_BORDER); + if (wp->yoff != 0 && + py == wp->yoff - 1 && + px > wp->sx / 2) + return (SCREEN_REDRAW_BORDER); + } else { + if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) { + if (wp->yoff != 0 && py == wp->yoff - 1) + return (SCREEN_REDRAW_BORDER); + if (py == ey) + return (SCREEN_REDRAW_BORDER); + } + } + } else if (pane_status == PANE_STATUS_TOP) { + if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) { + if (wp->yoff != 0 && py == wp->yoff - 1) + return (SCREEN_REDRAW_BORDER); + } + } else { + if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) { + if (py == ey) + return (SCREEN_REDRAW_BORDER); + } } /* Outside pane. */ - return (-1); + return (SCREEN_REDRAW_OUTSIDE); } -/* Check if a cell is on the pane border. */ +/* Check if a cell is on a border. */ static int -screen_redraw_cell_border(struct client *c, u_int px, u_int py) +screen_redraw_cell_border(struct client *c, u_int px, u_int py, int pane_status) { struct window *w = c->session->curw->window; struct window_pane *wp; - int retval; + + /* Outside the window? */ + if (px > w->sx || py > w->sy) + return (0); + + /* On the window border? */ + if (px == w->sx || py == w->sy) + return (1); /* Check all the panes. */ TAILQ_FOREACH(wp, &w->panes, entry) { if (!window_pane_visible(wp)) continue; - if ((retval = screen_redraw_cell_border1(wp, px, py)) != -1) - return (!!retval); + switch (screen_redraw_pane_border(wp, px, py, pane_status)) { + case SCREEN_REDRAW_INSIDE: + return (0); + case SCREEN_REDRAW_BORDER: + return (1); + case SCREEN_REDRAW_OUTSIDE: + break; + } } return (0); } +/* Work out type of border cell from surrounding cells. */ +static int +screen_redraw_type_of_cell(struct client *c, u_int px, u_int py, + int pane_status) +{ + struct window *w = c->session->curw->window; + u_int sx = w->sx, sy = w->sy; + int borders = 0; + + /* + * Construct a bitmask of whether the cells to the left (bit 4), right, + * top, and bottom (bit 1) of this cell are borders. + */ + if (px == 0 || screen_redraw_cell_border(c, px - 1, py, pane_status)) + borders |= 8; + if (px <= sx && screen_redraw_cell_border(c, px + 1, py, pane_status)) + borders |= 4; + if (pane_status == PANE_STATUS_TOP) { + if (py != 0 && + screen_redraw_cell_border(c, px, py - 1, pane_status)) + borders |= 2; + } else { + if (py == 0 || + screen_redraw_cell_border(c, px, py - 1, pane_status)) + borders |= 2; + } + if (py <= sy && screen_redraw_cell_border(c, px, py + 1, pane_status)) + borders |= 1; + + /* + * Figure out what kind of border this cell is. Only one bit set + * doesn't make sense (can't have a border cell with no others + * connected). + */ + switch (borders) { + case 15: /* 1111, left right top bottom */ + return (CELL_JOIN); + case 14: /* 1110, left right top */ + return (CELL_BOTTOMJOIN); + case 13: /* 1101, left right bottom */ + return (CELL_TOPJOIN); + case 12: /* 1100, left right */ + return (CELL_TOPBOTTOM); + case 11: /* 1011, left top bottom */ + return (CELL_RIGHTJOIN); + case 10: /* 1010, left top */ + return (CELL_BOTTOMRIGHT); + case 9: /* 1001, left bottom */ + return (CELL_TOPRIGHT); + case 7: /* 0111, right top bottom */ + return (CELL_LEFTJOIN); + case 6: /* 0110, right top */ + return (CELL_BOTTOMLEFT); + case 5: /* 0101, right bottom */ + return (CELL_TOPLEFT); + case 3: /* 0011, top bottom */ + return (CELL_LEFTRIGHT); + } + return (CELL_OUTSIDE); +} + /* Check if cell inside a pane. */ static int screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, @@ -100,18 +243,21 @@ screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, { struct window *w = c->session->curw->window; struct window_pane *wp; - int borders; + int border; u_int right, line; *wpp = NULL; if (px > w->sx || py > w->sy) return (CELL_OUTSIDE); + if (px == w->sx || py == w->sy) /* window border */ + return (screen_redraw_type_of_cell(c, px, py, pane_status)); if (pane_status != PANE_STATUS_OFF) { - TAILQ_FOREACH(wp, &w->panes, entry) { + wp = w->active; + do { if (!window_pane_visible(wp)) - continue; + goto next1; if (pane_status == PANE_STATUS_TOP) line = wp->yoff - 1; @@ -121,129 +267,51 @@ screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, if (py == line && px >= wp->xoff + 2 && px <= right) return (CELL_INSIDE); - } + + next1: + wp = TAILQ_NEXT(wp, entry); + if (wp == NULL) + wp = TAILQ_FIRST(&w->panes); + } while (wp != w->active); } - TAILQ_FOREACH(wp, &w->panes, entry) { + wp = w->active; + do { if (!window_pane_visible(wp)) - continue; + goto next2; *wpp = wp; - /* If outside the pane and its border, skip it. */ - if ((wp->xoff != 0 && px < wp->xoff - 1) || - px > wp->xoff + wp->sx || - (wp->yoff != 0 && py < wp->yoff - 1) || - py > wp->yoff + wp->sy) - continue; - - /* If definitely inside, return so. */ - if (!screen_redraw_cell_border(c, px, py)) + /* + * If definitely inside, return. If not on border, skip. + * Otherwise work out the cell. + */ + border = screen_redraw_pane_border(wp, px, py, pane_status); + if (border == SCREEN_REDRAW_INSIDE) return (CELL_INSIDE); + if (border == SCREEN_REDRAW_OUTSIDE) + goto next2; + return (screen_redraw_type_of_cell(c, px, py, pane_status)); - /* - * Construct a bitmask of whether the cells to the left (bit - * 4), right, top, and bottom (bit 1) of this cell are borders. - */ - borders = 0; - if (px == 0 || screen_redraw_cell_border(c, px - 1, py)) - borders |= 8; - if (px <= w->sx && screen_redraw_cell_border(c, px + 1, py)) - borders |= 4; - if (pane_status == PANE_STATUS_TOP) { - if (py != 0 && screen_redraw_cell_border(c, px, py - 1)) - borders |= 2; - } else { - if (py == 0 || screen_redraw_cell_border(c, px, py - 1)) - borders |= 2; - } - if (py <= w->sy && screen_redraw_cell_border(c, px, py + 1)) - borders |= 1; - - /* - * Figure out what kind of border this cell is. Only one bit - * set doesn't make sense (can't have a border cell with no - * others connected). - */ - switch (borders) { - case 15: /* 1111, left right top bottom */ - return (CELL_JOIN); - case 14: /* 1110, left right top */ - return (CELL_BOTTOMJOIN); - case 13: /* 1101, left right bottom */ - return (CELL_TOPJOIN); - case 12: /* 1100, left right */ - return (CELL_TOPBOTTOM); - case 11: /* 1011, left top bottom */ - return (CELL_RIGHTJOIN); - case 10: /* 1010, left top */ - return (CELL_BOTTOMRIGHT); - case 9: /* 1001, left bottom */ - return (CELL_TOPRIGHT); - case 7: /* 0111, right top bottom */ - return (CELL_LEFTJOIN); - case 6: /* 0110, right top */ - return (CELL_BOTTOMLEFT); - case 5: /* 0101, right bottom */ - return (CELL_TOPLEFT); - case 3: /* 0011, top bottom */ - return (CELL_LEFTRIGHT); - } - } + next2: + wp = TAILQ_NEXT(wp, entry); + if (wp == NULL) + wp = TAILQ_FIRST(&w->panes); + } while (wp != w->active); return (CELL_OUTSIDE); } /* Check if the border of a particular pane. */ static int -screen_redraw_check_is(u_int px, u_int py, int type, int pane_status, - struct window *w, struct window_pane *wantwp, struct window_pane *wp) +screen_redraw_check_is(u_int px, u_int py, int pane_status, + struct window_pane *wp) { - int border; + enum screen_redraw_border_type border; - /* Is this off the active pane border? */ - border = screen_redraw_cell_border1(wantwp, px, py); - if (border == 0 || border == -1) - return (0); - if (pane_status == PANE_STATUS_TOP && border == 4) - return (0); - if (pane_status == PANE_STATUS_BOTTOM && border == 3) - return (0); - - /* If there are more than two panes, that's enough. */ - if (window_count_panes(w) != 2) + border = screen_redraw_pane_border(wp, px, py, pane_status); + if (border == SCREEN_REDRAW_BORDER) return (1); - - /* Else if the cell is not a border cell, forget it. */ - if (wp == NULL || (type == CELL_OUTSIDE || type == CELL_INSIDE)) - return (1); - - /* With status lines mark the entire line. */ - if (pane_status != PANE_STATUS_OFF) - return (1); - - /* Check if the pane covers the whole width. */ - if (wp->xoff == 0 && wp->sx == w->sx) { - /* This can either be the top pane or the bottom pane. */ - if (wp->yoff == 0) { /* top pane */ - if (wp == wantwp) - return (px <= wp->sx / 2); - return (px > wp->sx / 2); - } - return (0); - } - - /* Check if the pane covers the whole height. */ - if (wp->yoff == 0 && wp->sy == w->sy) { - /* This can either be the left pane or the right pane. */ - if (wp->xoff == 0) { /* left pane */ - if (wp == wantwp) - return (py <= wp->sy / 2); - return (py > wp->sy / 2); - } - return (0); - } - - return (1); + return (0); } /* Update pane status. */ @@ -259,15 +327,14 @@ screen_redraw_make_pane_status(struct client *c, struct window *w, struct screen_write_ctx ctx; struct screen old; - if (wp == w->active) - style_apply(&gc, w->options, "pane-active-border-style"); - else - style_apply(&gc, w->options, "pane-border-style"); - - fmt = options_get_string(w->options, "pane-border-format"); - ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS); - format_defaults(ft, c, NULL, NULL, wp); + format_defaults(ft, c, c->session, c->session->curw, wp); + + if (wp == w->active) + style_apply(&gc, w->options, "pane-active-border-style", ft); + else + style_apply(&gc, w->options, "pane-border-style", ft); + fmt = options_get_string(w->options, "pane-border-format"); expanded = format_expand_time(ft, fmt); if (wp->sx < 4) @@ -482,39 +549,73 @@ screen_redraw_pane(struct client *c, struct window_pane *wp) tty_reset(&c->tty); } -/* Draw a border cell. */ -static void -screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j, - struct grid_cell *m_active_gc, struct grid_cell *active_gc, - struct grid_cell *m_other_gc, struct grid_cell *other_gc) +/* Get border cell style. */ +static const struct grid_cell * +screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x, + u_int y, struct window_pane *wp) { struct client *c = ctx->c; struct session *s = c->session; struct window *w = s->curw->window; + struct options *oo = w->options; + struct grid_cell *gc; + struct format_tree *ft; + + if (wp->border_gc_set) + return (&wp->border_gc); + wp->border_gc_set = 1; + + ft = format_create_defaults(NULL, c, s, s->curw, wp); + gc = &wp->border_gc; + + if (screen_redraw_check_is(x, y, ctx->pane_status, w->active)) { + style_apply(gc, oo, "pane-active-border-style", ft); + gc->attr |= GRID_ATTR_CHARSET; + } else { + style_apply(gc, oo, "pane-border-style", ft); + gc->attr |= GRID_ATTR_CHARSET; + } + + format_free(ft); + return (gc); +} + +/* Draw a border cell. */ +static void +screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) +{ + struct client *c = ctx->c; + struct session *s = c->session; struct tty *tty = &c->tty; struct window_pane *wp; - struct window_pane *active = w->active; - struct window_pane *marked = marked_pane.wp; u_int type, x = ctx->ox + i, y = ctx->oy + j; - int flag, pane_status = ctx->pane_status; + int pane_status = ctx->pane_status; + const struct grid_cell *gc; + struct grid_cell copy; if (c->overlay_check != NULL && !c->overlay_check(c, x, y)) return; + type = screen_redraw_check_cell(c, x, y, pane_status, &wp); if (type == CELL_INSIDE) return; - flag = screen_redraw_check_is(x, y, type, pane_status, w, active, wp); - if (server_is_marked(s, s->curw, marked_pane.wp) && - screen_redraw_check_is(x, y, type, pane_status, w, marked, wp)) { - if (flag) - tty_attributes(tty, m_active_gc, NULL); - else - tty_attributes(tty, m_other_gc, NULL); - } else if (flag) - tty_attributes(tty, active_gc, NULL); - else - tty_attributes(tty, other_gc, NULL); + if (wp == NULL) + gc = &screen_redraw_border_cell; + else { + gc = screen_redraw_draw_borders_style(ctx, x, y, wp); + if (gc == NULL) + return; + + if (server_is_marked(s, s->curw, marked_pane.wp) && + screen_redraw_check_is(x, y, pane_status, marked_pane.wp)) { + memcpy(©, gc, sizeof copy); + copy.attr ^= GRID_ATTR_REVERSE; + gc = © + } + } + + tty_attributes(tty, gc, NULL); if (ctx->statustop) tty_cursor(tty, i, ctx->statuslines + j); else @@ -529,27 +630,17 @@ screen_redraw_draw_borders(struct screen_redraw_ctx *ctx) struct client *c = ctx->c; struct session *s = c->session; struct window *w = s->curw->window; - struct tty *tty = &c->tty; - struct options *oo = w->options; - struct grid_cell m_active_gc, active_gc, m_other_gc, other_gc; + struct window_pane *wp; u_int i, j; log_debug("%s: %s @%u", __func__, c->name, w->id); - style_apply(&other_gc, oo, "pane-border-style"); - style_apply(&active_gc, oo, "pane-active-border-style"); - active_gc.attr = other_gc.attr = GRID_ATTR_CHARSET; + TAILQ_FOREACH(wp, &w->panes, entry) + wp->border_gc_set = 0; - memcpy(&m_other_gc, &other_gc, sizeof m_other_gc); - m_other_gc.attr ^= GRID_ATTR_REVERSE; - memcpy(&m_active_gc, &active_gc, sizeof m_active_gc); - m_active_gc.attr ^= GRID_ATTR_REVERSE; - - for (j = 0; j < tty->sy - ctx->statuslines; j++) { - for (i = 0; i < tty->sx; i++) { - screen_redraw_draw_borders_cell(ctx, i, j, - &m_active_gc, &active_gc, &m_other_gc, &other_gc); - } + for (j = 0; j < c->tty.sy - ctx->statuslines; j++) { + for (i = 0; i < c->tty.sx; i++) + screen_redraw_draw_borders_cell(ctx, i, j); } } diff --git a/status.c b/status.c index 6beadb81..c9da873e 100644 --- a/status.c +++ b/status.c @@ -321,7 +321,7 @@ status_redraw(struct client *c) struct screen_write_ctx ctx; struct grid_cell gc; u_int lines, i, n, width = c->tty.sx; - int flags, force = 0, changed = 0; + int flags, force = 0, changed = 0, fg, bg; struct options_entry *o; union options_value *ov; struct format_tree *ft; @@ -339,7 +339,13 @@ status_redraw(struct client *c) return (1); /* Set up default colour. */ - style_apply(&gc, s->options, "status-style"); + style_apply(&gc, s->options, "status-style", NULL); + fg = options_get_number(s->options, "status-fg"); + if (fg != 8) + gc.fg = fg; + bg = options_get_number(s->options, "status-bg"); + if (bg != 8) + gc.bg = bg; if (!grid_cells_equal(&gc, &sl->style)) { force = 1; memcpy(&sl->style, &gc, sizeof sl->style); @@ -490,7 +496,7 @@ status_message_redraw(struct client *c) if (len > c->tty.sx) len = c->tty.sx; - style_apply(&gc, s->options, "message-style"); + style_apply(&gc, s->options, "message-style", NULL); screen_write_start(&ctx, NULL, sl->active); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); @@ -633,9 +639,9 @@ status_prompt_redraw(struct client *c) screen_init(sl->active, c->tty.sx, lines, 0); if (c->prompt_mode == PROMPT_COMMAND) - style_apply(&gc, s->options, "message-command-style"); + style_apply(&gc, s->options, "message-command-style", NULL); else - style_apply(&gc, s->options, "message-style"); + style_apply(&gc, s->options, "message-style", NULL); memcpy(&cursorgc, &gc, sizeof cursorgc); cursorgc.attr ^= GRID_ATTR_REVERSE; diff --git a/style.c b/style.c index 2ac78e97..3c615852 100644 --- a/style.c +++ b/style.c @@ -260,17 +260,37 @@ style_tostring(struct style *sy) return (s); } -/* Apply a style. */ +/* Apply a style on top of the given style. */ void -style_apply(struct grid_cell *gc, struct options *oo, const char *name) +style_add(struct grid_cell *gc, struct options *oo, const char *name, + struct format_tree *ft) { - struct style *sy; + struct style *sy; + struct format_tree *ft0 = NULL; - memcpy(gc, &grid_default_cell, sizeof *gc); - sy = options_get_style(oo, name); - gc->fg = sy->gc.fg; - gc->bg = sy->gc.bg; + if (ft == NULL) + ft = ft0 = format_create(NULL, NULL, 0, FORMAT_NOJOBS); + + sy = options_string_to_style(oo, name, ft); + if (sy == NULL) + sy = &style_default; + if (sy->gc.fg != 8) + gc->fg = sy->gc.fg; + if (sy->gc.bg != 8) + gc->bg = sy->gc.bg; gc->attr |= sy->gc.attr; + + if (ft0 != NULL) + format_free(ft0); +} + +/* Apply a style on top of the default style. */ +void +style_apply(struct grid_cell *gc, struct options *oo, const char *name, + struct format_tree *ft) +{ + memcpy(gc, &grid_default_cell, sizeof *gc); + style_add(gc, oo, name, ft); } /* Initialize style from cell. */ diff --git a/tmux.h b/tmux.h index 4e19c4ef..2e978813 100644 --- a/tmux.h +++ b/tmux.h @@ -896,6 +896,9 @@ struct window_pane { u_int xoff; u_int yoff; + int fg; + int bg; + int flags; #define PANE_REDRAW 0x1 #define PANE_DROP 0x2 @@ -950,6 +953,9 @@ struct window_pane { size_t written; size_t skipped; + int border_gc_set; + struct grid_cell border_gc; + TAILQ_ENTRY(window_pane) entry; RB_ENTRY(window_pane) tree_entry; }; @@ -1662,7 +1668,6 @@ enum options_table_type { OPTIONS_TABLE_COLOUR, OPTIONS_TABLE_FLAG, OPTIONS_TABLE_CHOICE, - OPTIONS_TABLE_STYLE, OPTIONS_TABLE_COMMAND }; @@ -1674,12 +1679,13 @@ enum options_table_type { #define OPTIONS_TABLE_IS_ARRAY 0x1 #define OPTIONS_TABLE_IS_HOOK 0x2 +#define OPTIONS_TABLE_IS_STYLE 0x4 struct options_table_entry { const char *name; enum options_table_type type; int scope; - int flags; + int flags; u_int minimum; u_int maximum; @@ -1718,7 +1724,7 @@ struct spawn_context { const char *name; char **argv; int argc; - struct environ *environ; + struct environ *environ; int idx; const char *cwd; @@ -1898,18 +1904,17 @@ struct options_entry *options_match_get(struct options *, const char *, int *, int, int *); const char *options_get_string(struct options *, const char *); long long options_get_number(struct options *, const char *); -struct style *options_get_style(struct options *, const char *); struct options_entry * printflike(4, 5) options_set_string(struct options *, const char *, int, const char *, ...); struct options_entry *options_set_number(struct options *, const char *, long long); -struct options_entry *options_set_style(struct options *, const char *, int, - const char *); int options_scope_from_name(struct args *, int, const char *, struct cmd_find_state *, struct options **, char **); int options_scope_from_flags(struct args *, int, struct cmd_find_state *, struct options **, char **); +struct style *options_string_to_style(struct options *, const char *, + struct format_tree *); /* options-table.c */ extern const struct options_table_entry options_table[]; @@ -2136,7 +2141,7 @@ enum cmd_retval cmd_attach_session(struct cmdq_item *, const char *, int, int, int, const char *, int); /* cmd-parse.c */ -void cmd_parse_empty(struct cmd_parse_input *); +void cmd_parse_empty(struct cmd_parse_input *); struct cmd_parse_result *cmd_parse_from_file(FILE *, struct cmd_parse_input *); struct cmd_parse_result *cmd_parse_from_string(const char *, struct cmd_parse_input *); @@ -2227,8 +2232,8 @@ void file_fire_done(struct client_file *); void file_fire_read(struct client_file *); int file_can_print(struct client *); void printflike(2, 3) file_print(struct client *, const char *, ...); -void file_vprint(struct client *, const char *, va_list); -void file_print_buffer(struct client *, void *, size_t); +void file_vprint(struct client *, const char *, va_list); +void file_print_buffer(struct client *, void *, size_t); void printflike(2, 3) file_error(struct client *, const char *, ...); void file_write(struct client *, const char *, int, const void *, size_t, client_file_cb, void *); @@ -2726,7 +2731,7 @@ struct session *session_find_by_id_str(const char *); struct session *session_find_by_id(u_int); struct session *session_create(const char *, const char *, const char *, struct environ *, struct options *, struct termios *); -void session_destroy(struct session *, int, const char *); +void session_destroy(struct session *, int, const char *); void session_add_ref(struct session *, const char *); void session_remove_ref(struct session *, const char *); char *session_check_name(const char *); @@ -2795,7 +2800,7 @@ struct menu *menu_create(const char *); void menu_add_items(struct menu *, const struct menu_item *, struct cmdq_item *, struct client *, struct cmd_find_state *); -void menu_add_item(struct menu *, const struct menu_item *, +void menu_add_item(struct menu *, const struct menu_item *, struct cmdq_item *, struct client *, struct cmd_find_state *); void menu_free(struct menu *); @@ -2818,8 +2823,10 @@ int popup_display(int, struct cmdq_item *, u_int, u_int, u_int, int style_parse(struct style *,const struct grid_cell *, const char *); const char *style_tostring(struct style *); +void style_add(struct grid_cell *, struct options *, + const char *, struct format_tree *); void style_apply(struct grid_cell *, struct options *, - const char *); + const char *, struct format_tree *); void style_set(struct style *, const struct grid_cell *); void style_copy(struct style *, struct style *); diff --git a/tty.c b/tty.c index 99b433ae..e378b45d 100644 --- a/tty.c +++ b/tty.c @@ -2686,6 +2686,14 @@ tty_try_colour(struct tty *tty, int colour, const char *type) return (-1); } +static void +tty_window_default_style(struct grid_cell *gc, struct window_pane *wp) +{ + memcpy(gc, &grid_default_cell, sizeof *gc); + gc->fg = wp->fg; + gc->bg = wp->bg; +} + static void tty_default_colours(struct grid_cell *gc, struct window_pane *wp) { @@ -2694,8 +2702,12 @@ tty_default_colours(struct grid_cell *gc, struct window_pane *wp) if (wp->flags & PANE_STYLECHANGED) { wp->flags &= ~PANE_STYLECHANGED; - style_apply(&wp->cached_active_gc, oo, "window-active-style"); - style_apply(&wp->cached_gc, oo, "window-style"); + + tty_window_default_style(&wp->cached_active_gc, wp); + style_add(&wp->cached_active_gc, oo, "window-active-style", + NULL); + tty_window_default_style(&wp->cached_gc, wp); + style_add(&wp->cached_gc, oo, "window-style", NULL); } if (gc->fg == 8) { diff --git a/window-copy.c b/window-copy.c index a803f3b3..2c50a1cd 100644 --- a/window-copy.c +++ b/window-copy.c @@ -3004,7 +3004,7 @@ window_copy_write_line(struct window_mode_entry *wme, size_t size = 0; u_int hsize = screen_hsize(data->backing); - style_apply(&gc, oo, "mode-style"); + style_apply(&gc, oo, "mode-style", NULL); gc.flags |= GRID_FLAG_NOPALETTE; if (py == 0 && s->rupper < s->rlower && !data->hide_position) { @@ -3311,7 +3311,7 @@ window_copy_set_selection(struct window_mode_entry *wme, int may_redraw, } /* Set colours and selection. */ - style_apply(&gc, oo, "mode-style"); + style_apply(&gc, oo, "mode-style", NULL); gc.flags |= GRID_FLAG_NOPALETTE; screen_set_selection(s, sx, sy, endsx, endsy, data->rectflag, data->modekeys, &gc); diff --git a/window.c b/window.c index fbc4bb19..7fea9d80 100644 --- a/window.c +++ b/window.c @@ -865,6 +865,9 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) wp->fd = -1; wp->event = NULL; + wp->fg = 8; + wp->bg = 8; + TAILQ_INIT(&wp->modes); wp->layout_cell = NULL; @@ -1096,6 +1099,7 @@ window_pane_set_mode(struct window_pane *wp, struct window_pane *swp, wp->screen = wme->screen; wp->flags |= (PANE_REDRAW|PANE_CHANGED); + server_redraw_window_borders(wp->window); server_status_window(wp->window); notify_pane("pane-mode-changed", wp); @@ -1127,6 +1131,7 @@ window_pane_reset_mode(struct window_pane *wp) } wp->flags |= (PANE_REDRAW|PANE_CHANGED); + server_redraw_window_borders(wp->window); server_status_window(wp->window); notify_pane("pane-mode-changed", wp); } From 80e52545a0cccba20fb4d96ed09ea2c129a30c93 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:06:03 +0000 Subject: [PATCH 15/49] Improve command prompt completion: - Show a menu with completions if there are multiple. - Don't complete argument stuff (options, layouts) at start of text. - For -t and -s, if there is no : then complete sessions but if there is a :, show a menu of all windows in the session rather than trying to complete the window name which is a bit useless if there are duplicates. --- menu.c | 35 ++++ server-client.c | 10 +- status.c | 507 +++++++++++++++++++++++++++++++----------------- tmux.h | 1 + 4 files changed, 373 insertions(+), 180 deletions(-) diff --git a/menu.c b/menu.c index 2f92af34..e78999c6 100644 --- a/menu.c +++ b/menu.c @@ -240,6 +240,16 @@ menu_key_cb(struct client *c, struct key_event *event) } while ((name == NULL || *name == '-') && md->choice != old); c->flags |= CLIENT_REDRAWOVERLAY; return (0); + case KEYC_BSPACE: + if (~md->flags & MENU_TAB) + break; + return (1); + case '\011': /* Tab */ + if (~md->flags & MENU_TAB) + break; + if (md->choice == count - 1) + return (1); + /* FALLTHROUGH */ case KEYC_DOWN: case 'j': if (old == -1) @@ -253,6 +263,31 @@ menu_key_cb(struct client *c, struct key_event *event) } while ((name == NULL || *name == '-') && md->choice != old); c->flags |= CLIENT_REDRAWOVERLAY; return (0); + case 'g': + case KEYC_PPAGE: + case '\002': /* C-b */ + if (md->choice > 5) + md->choice -= 5; + else + md->choice = 0; + while (md->choice != count && (name == NULL || *name == '-')) + md->choice++; + if (md->choice == count) + md->choice = -1; + c->flags |= CLIENT_REDRAWOVERLAY; + break; + case 'G': + case KEYC_NPAGE: + if (md->choice > count - 6) + md->choice = count - 1; + else + md->choice += 5; + while (md->choice != -1 && (name == NULL || *name == '-')) + md->choice--; + c->flags |= CLIENT_REDRAWOVERLAY; + break; + case '\006': /* C-f */ + break; case '\r': goto chosen; case '\033': /* Escape */ diff --git a/server-client.c b/server-client.c index ed138f8e..b21dac1d 100644 --- a/server-client.c +++ b/server-client.c @@ -1296,10 +1296,6 @@ server_client_handle_key(struct client *c, struct key_event *event) */ if (~c->flags & CLIENT_READONLY) { status_message_clear(c); - if (c->prompt_string != NULL) { - if (status_prompt_key(c, event->key) == 0) - return (0); - } if (c->overlay_key != NULL) { switch (c->overlay_key(c, event)) { case 0: @@ -1310,6 +1306,10 @@ server_client_handle_key(struct client *c, struct key_event *event) } } server_client_clear_overlay(c); + if (c->prompt_string != NULL) { + if (status_prompt_key(c, event->key) == 0) + return (0); + } } /* @@ -1564,6 +1564,8 @@ server_client_reset_state(struct client *c) } else { s = wp->screen; mode = s->mode; + if (c->prompt_string != NULL || c->message_string != NULL) + mode &= ~MODE_CURSOR; } log_debug("%s: client %s mode %x", __func__, c->name, mode); diff --git a/status.c b/status.c index c9da873e..0acf4233 100644 --- a/status.c +++ b/status.c @@ -37,9 +37,15 @@ 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 char **status_prompt_complete_list(u_int *, const char *); -static char *status_prompt_complete_prefix(char **, u_int); -static char *status_prompt_complete(struct session *, const char *); +static char *status_prompt_complete(struct client *, const char *, u_int); + +struct status_prompt_menu { + struct client *c; + u_int start; + u_int size; + char **list; + char flag; +}; /* Status prompt history. */ #define PROMPT_HISTORY 100 @@ -919,15 +925,88 @@ status_prompt_paste(struct client *c) return (1); } +/* Finish completion. */ +static int +status_prompt_replace_complete(struct client *c, const char *s) +{ + char word[64], *allocated = NULL; + size_t size, n, off, idx, used; + struct utf8_data *first, *last, *ud; + + if (c->prompt_buffer[0].size == 0) + return (0); + size = utf8_strlen(c->prompt_buffer); + + idx = c->prompt_index; + if (idx != 0) + idx--; + + /* Find the word we are in. */ + first = &c->prompt_buffer[idx]; + while (first > c->prompt_buffer && !status_prompt_space(first)) + first--; + while (first->size != 0 && status_prompt_space(first)) + first++; + last = &c->prompt_buffer[idx]; + while (last->size != 0 && !status_prompt_space(last)) + last++; + while (last > c->prompt_buffer && status_prompt_space(last)) + last--; + if (last->size != 0) + last++; + if (last <= first) + return (0); + if (s == NULL) { + used = 0; + for (ud = first; ud < last; ud++) { + if (used + ud->size >= sizeof word) + break; + memcpy(word + used, ud->data, ud->size); + used += ud->size; + } + if (ud != last) + return (0); + word[used] = '\0'; + } + + /* Try to complete it. */ + if (s == NULL) { + allocated = status_prompt_complete(c, word, + first - c->prompt_buffer); + if (allocated == NULL) + return (0); + s = allocated; + } + + /* Trim out word. */ + n = size - (last - c->prompt_buffer) + 1; /* with \0 */ + memmove(first, last, n * sizeof *c->prompt_buffer); + size -= last - first; + + /* Insert the new word. */ + size += strlen(s); + off = first - c->prompt_buffer; + c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, + sizeof *c->prompt_buffer); + first = c->prompt_buffer + off; + memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); + for (idx = 0; idx < strlen(s); idx++) + utf8_set(&first[idx], s[idx]); + c->prompt_index = (first - c->prompt_buffer) + strlen(s); + + free(allocated); + return (1); +} + /* Handle keys in prompt. */ int status_prompt_key(struct client *c, key_code key) { struct options *oo = c->session->options; - char *s, *cp, word[64], prefix = '='; + char *s, *cp, prefix = '='; const char *histstr, *ws = NULL, *keystring; - size_t size, n, off, idx, used; - struct utf8_data tmp, *first, *last, *ud; + size_t size, idx; + struct utf8_data tmp; int keys; if (c->prompt_flags & PROMPT_KEY) { @@ -992,63 +1071,9 @@ process_key: } break; case '\011': /* Tab */ - if (c->prompt_buffer[0].size == 0) - break; - - idx = c->prompt_index; - if (idx != 0) - idx--; - - /* Find the word we are in. */ - first = &c->prompt_buffer[idx]; - while (first > c->prompt_buffer && !status_prompt_space(first)) - first--; - while (first->size != 0 && status_prompt_space(first)) - first++; - last = &c->prompt_buffer[idx]; - while (last->size != 0 && !status_prompt_space(last)) - last++; - while (last > c->prompt_buffer && status_prompt_space(last)) - last--; - if (last->size != 0) - last++; - if (last <= first) - break; - - used = 0; - for (ud = first; ud < last; ud++) { - if (used + ud->size >= sizeof word) - break; - memcpy(word + used, ud->data, ud->size); - used += ud->size; - } - if (ud != last) - break; - word[used] = '\0'; - - /* And try to complete it. */ - if ((s = status_prompt_complete(c->session, word)) == NULL) - break; - - /* Trim out word. */ - n = size - (last - c->prompt_buffer) + 1; /* with \0 */ - memmove(first, last, n * sizeof *c->prompt_buffer); - size -= last - first; - - /* Insert the new word. */ - size += strlen(s); - off = first - c->prompt_buffer; - c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, - sizeof *c->prompt_buffer); - first = c->prompt_buffer + off; - memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); - for (idx = 0; idx < strlen(s); idx++) - utf8_set(&first[idx], s[idx]); - - c->prompt_index = (first - c->prompt_buffer) + strlen(s); - free(s); - - goto changed; + if (status_prompt_replace_complete(c, NULL)) + goto changed; + break; case KEYC_BSPACE: case '\010': /* C-h */ if (c->prompt_index != 0) { @@ -1330,14 +1355,13 @@ status_prompt_add_history(const char *line) } /* Build completion list. */ -char ** -status_prompt_complete_list(u_int *size, const char *s) +static char ** +status_prompt_complete_list(u_int *size, const char *s, int at_start) { char **list = NULL; const char **layout, *value, *cp; const struct cmd_entry **cmdent; const struct options_table_entry *oe; - u_int idx; size_t slen = strlen(s), valuelen; struct options_entry *o; struct options_array_item *a; @@ -1353,18 +1377,6 @@ status_prompt_complete_list(u_int *size, const char *s) list[(*size)++] = xstrdup((*cmdent)->name); } } - for (oe = options_table; oe->name != NULL; oe++) { - if (strncmp(oe->name, s, slen) == 0) { - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup(oe->name); - } - } - for (layout = layouts; *layout != NULL; layout++) { - if (strncmp(*layout, s, slen) == 0) { - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup(*layout); - } - } o = options_get_only(global_options, "command-alias"); if (o != NULL) { a = options_array_first(o); @@ -1383,8 +1395,21 @@ status_prompt_complete_list(u_int *size, const char *s) a = options_array_next(a); } } - for (idx = 0; idx < (*size); idx++) - log_debug("complete %u: %s", idx, list[idx]); + if (at_start) + return (list); + + for (oe = options_table; oe->name != NULL; oe++) { + if (strncmp(oe->name, s, slen) == 0) { + list = xreallocarray(list, (*size) + 1, sizeof *list); + list[(*size)++] = xstrdup(oe->name); + } + } + for (layout = layouts; *layout != NULL; layout++) { + if (strncmp(*layout, s, slen) == 0) { + list = xreallocarray(list, (*size) + 1, sizeof *list); + list[(*size)++] = xstrdup(*layout); + } + } return (list); } @@ -1409,124 +1434,254 @@ status_prompt_complete_prefix(char **list, u_int size) return (out); } +/* Complete word menu callback. */ +static void +status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key, + void *data) +{ + struct status_prompt_menu *spm = data; + struct client *c = spm->c; + u_int i; + char *s; + + if (key != KEYC_NONE) { + idx += spm->start; + if (spm->flag == '\0') + s = xstrdup(spm->list[idx]); + else + xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]); + if (status_prompt_replace_complete(c, s)) + c->flags |= CLIENT_REDRAWSTATUS; + free(s); + } + + for (i = 0; i < spm->size; i++) + free(spm->list[i]); + free(spm->list); +} + +/* Show complete word menu. */ +static int +status_prompt_complete_list_menu(struct client *c, char **list, u_int size, + u_int offset, char flag) +{ + struct menu *menu; + struct menu_item item; + struct status_prompt_menu *spm; + u_int lines = status_line_size(c), height, i; + u_int py; + + if (size <= 1) + return (0); + if (c->tty.sy - lines < 3) + return (0); + + spm = xmalloc(sizeof *spm); + spm->c = c; + spm->size = size; + spm->list = list; + spm->flag = flag; + + height = c->tty.sy - lines - 2; + if (height > 10) + height = 10; + if (height > size) + height = size; + spm->start = size - height; + + menu = menu_create(""); + for (i = spm->start; i < size; i++) { + item.name = list[i]; + item.key = '0' + (i - spm->start); + item.command = NULL; + menu_add_item(menu, &item, NULL, NULL, NULL); + } + + if (options_get_number(c->session->options, "status-position") == 0) + py = lines; + else + py = c->tty.sy - 3 - height; + offset += utf8_cstrwidth(c->prompt_string); + if (offset > 2) + offset -= 2; + else + offset = 0; + + if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, + py, c, NULL, status_prompt_menu_callback, spm) != 0) { + menu_free(menu); + free(spm); + return (0); + } + return (1); +} + +/* Show complete word menu. */ +static char * +status_prompt_complete_window_menu(struct client *c, struct session *s, + u_int offset, char flag) +{ + struct menu *menu; + struct menu_item item; + struct status_prompt_menu *spm; + struct winlink *wl; + char **list = NULL, *tmp; + u_int lines = status_line_size(c), height; + u_int py, size = 0; + + if (c->tty.sy - lines < 3) + return (NULL); + + spm = xmalloc(sizeof *spm); + spm->c = c; + spm->flag = flag; + + height = c->tty.sy - lines - 2; + if (height > 10) + height = 10; + spm->start = 0; + + menu = menu_create(""); + RB_FOREACH(wl, winlinks, &s->windows) { + list = xreallocarray(list, size + 1, sizeof *list); + xasprintf(&list[size++], "%s:%d", s->name, wl->idx); + + xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx, + wl->window->name); + item.name = tmp; + item.key = '0' + size - 1; + item.command = NULL; + menu_add_item(menu, &item, NULL, NULL, NULL); + free(tmp); + + if (size == height) + break; + } + if (size == 1) { + menu_free(menu); + xasprintf(&tmp, "-%c%s", flag, list[0]); + free(list[0]); + free(list); + return (tmp); + } + if (height > size) + height = size; + + spm->size = size; + spm->list = list; + + if (options_get_number(c->session->options, "status-position") == 0) + py = lines; + else + py = c->tty.sy - 3 - height; + offset += utf8_cstrwidth(c->prompt_string); + if (offset > 2) + offset -= 2; + else + offset = 0; + + if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, + py, c, NULL, status_prompt_menu_callback, spm) != 0) { + menu_free(menu); + free(spm); + return (NULL); + } + return (NULL); +} + +/* Sort complete list. */ +static int +status_prompt_complete_sort(const void *a, const void *b) +{ + const char **aa = (const char **)a, **bb = (const char **)b; + + return (strcmp(*aa, *bb)); +} + /* Complete word. */ static char * -status_prompt_complete(struct session *session, const char *s) +status_prompt_complete(struct client *c, const char *word, u_int offset) { - char **list = NULL; - const char *colon; + struct session *session, *loop; + const char *s, *colon; + size_t slen; + char **list = NULL, *copy = NULL, *out = NULL, *tmp; + char flag = '\0'; u_int size = 0, i; - struct session *s_loop; - struct winlink *wl; - struct window *w; - char *copy, *out, *tmp; - if (*s == '\0') + if (*word == '\0') return (NULL); - out = NULL; - if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) { - list = status_prompt_complete_list(&size, s); + if (strncmp(word, "-t", 2) != 0 && strncmp(word, "-s", 2) != 0) { + list = status_prompt_complete_list(&size, word, offset == 0); if (size == 0) out = NULL; else if (size == 1) xasprintf(&out, "%s ", list[0]); else out = status_prompt_complete_prefix(list, size); - for (i = 0; i < size; i++) - free(list[i]); - free(list); - return (out); - } - copy = xstrdup(s); - - colon = ":"; - if (copy[strlen(copy) - 1] == ':') - copy[strlen(copy) - 1] = '\0'; - else - colon = ""; - s = copy + 2; - - RB_FOREACH(s_loop, sessions, &sessions) { - if (strncmp(s_loop->name, s, strlen(s)) == 0) { - list = xreallocarray(list, size + 2, sizeof *list); - list[size++] = s_loop->name; - } - } - if (size == 1) { - out = xstrdup(list[0]); - if (session_find(list[0]) != NULL) - colon = ":"; - } else if (size != 0) - out = status_prompt_complete_prefix(list, size); - if (out != NULL) { - xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); - free(out); - out = tmp; goto found; } - colon = ""; - if (*s == ':') { - RB_FOREACH(wl, winlinks, &session->windows) { - xasprintf(&tmp, ":%s", wl->window->name); - if (strncmp(tmp, s, strlen(s)) == 0){ - list = xreallocarray(list, size + 1, - sizeof *list); - list[size++] = tmp; + s = word + 2; + slen = strlen(s); + + flag = word[1]; + offset += 2; + + colon = strchr(s, ':'); + + /* If there is no colon, complete as a session. */ + if (colon == NULL) { + RB_FOREACH(loop, sessions, &sessions) { + if (strncmp(loop->name, s, strlen(s)) != 0) continue; - } - free(tmp); - - xasprintf(&tmp, ":%d", wl->idx); - if (strncmp(tmp, s, strlen(s)) == 0) { - list = xreallocarray(list, size + 1, - sizeof *list); - list[size++] = tmp; - continue; - } - free(tmp); + list = xreallocarray(list, size + 2, sizeof *list); + xasprintf(&list[size++], "%s:", loop->name); } - } else { - RB_FOREACH(s_loop, sessions, &sessions) { - RB_FOREACH(wl, winlinks, &s_loop->windows) { - w = wl->window; - - xasprintf(&tmp, "%s:%s", s_loop->name, w->name); - if (strncmp(tmp, s, strlen(s)) == 0) { - list = xreallocarray(list, size + 1, - sizeof *list); - list[size++] = tmp; - continue; - } - free(tmp); - - xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx); - if (strncmp(tmp, s, strlen(s)) == 0) { - list = xreallocarray(list, size + 1, - sizeof *list); - list[size++] = tmp; - continue; - } - free(tmp); - } - } - } - if (size == 1) { - out = xstrdup(list[0]); - colon = " "; - } else if (size != 0) out = status_prompt_complete_prefix(list, size); - if (out != NULL) { - xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); - out = tmp; + if (out != NULL) { + xasprintf(&tmp, "-%c%s", flag, out); + free(out); + out = tmp; + } + goto found; } - for (i = 0; i < size; i++) - free((void *)list[i]); + /* If there is a colon but no period, find session and show a menu. */ + if (strchr(colon + 1, '.') == NULL) { + if (*s == ':') + session = c->session; + else { + copy = xstrdup(s); + *strchr(copy, ':') = '\0'; + session = session_find(copy); + free(copy); + if (session == NULL) + goto found; + } + out = status_prompt_complete_window_menu(c, session, offset, + flag); + if (out == NULL) + return (NULL); + } found: - free(copy); - free(list); + if (size != 0) { + qsort(list, size, sizeof *list, status_prompt_complete_sort); + for (i = 0; i < size; i++) + log_debug("complete %u: %s", i, list[i]); + } + + if (out != NULL && strcmp(word, out) == 0) { + free(out); + out = NULL; + } + if (out != NULL || + !status_prompt_complete_list_menu(c, list, size, offset, flag)) { + for (i = 0; i < size; i++) + free(list[i]); + free(list); + } return (out); } diff --git a/tmux.h b/tmux.h index 2e978813..486b8b4c 100644 --- a/tmux.h +++ b/tmux.h @@ -2796,6 +2796,7 @@ __dead void printflike(1, 2) fatalx(const char *, ...); /* menu.c */ #define MENU_NOMOUSE 0x1 +#define MENU_TAB 0x2 struct menu *menu_create(const char *); void menu_add_items(struct menu *, const struct menu_item *, struct cmdq_item *, struct client *, From 2391fe23ab09cfe8667abdcc3338a958ab3da31d Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:11:52 +0000 Subject: [PATCH 16/49] Copy mode search improvements: - Add styles for the search marking styles (copy-mode-match-style and copy-mode-current-match-style). - Show the current match (the one with the cursor on it) in a different style. - Copying without a selection will copy the current match if there is one. --- options-table.c | 16 ++++++ screen-write.c | 39 +------------ tmux.1 | 16 ++++++ tmux.h | 2 - window-copy.c | 148 +++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 167 insertions(+), 54 deletions(-) diff --git a/options-table.c b/options-table.c index 5c368586..a89758c5 100644 --- a/options-table.c +++ b/options-table.c @@ -656,6 +656,22 @@ const struct options_table_entry options_table[] = { .default_num = 1 }, + { .name = "copy-mode-match-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "bg=cyan,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," + }, + + { .name = "copy-mode-current-match-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "bg=magenta,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = "," + }, + { .name = "main-pane-height", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, diff --git a/screen-write.c b/screen-write.c index afa1e96a..e3e51020 100644 --- a/screen-write.c +++ b/screen-write.c @@ -344,44 +344,9 @@ screen_write_vnputs(struct screen_write_ctx *ctx, ssize_t maxlen, free(msg); } -/* Copy from another screen. Assumes target region is big enough. */ -void -screen_write_copy(struct screen_write_ctx *ctx, struct screen *src, u_int px, - u_int py, u_int nx, u_int ny, bitstr_t *mbs, const struct grid_cell *mgc) -{ - struct screen *s = ctx->s; - struct grid *gd = src->grid; - struct grid_cell gc; - u_int xx, yy, cx, cy, b; - - if (nx == 0 || ny == 0) - return; - - cx = s->cx; - cy = s->cy; - - for (yy = py; yy < py + ny; yy++) { - for (xx = px; xx < px + nx; xx++) { - grid_get_cell(gd, xx, yy, &gc); - if (mbs != NULL) { - b = (yy * screen_size_x(src)) + xx; - if (bit_test(mbs, b)) { - gc.attr = mgc->attr; - gc.fg = mgc->fg; - gc.bg = mgc->bg; - } - } - if (xx + gc.data.width <= px + nx) - screen_write_cell(ctx, &gc); - } - cy++; - screen_write_set_cursor(ctx, cx, cy); - } -} - /* - * Copy from another screen but without the selection stuff. Also assumes the - * target region is already big enough. + * Copy from another screen but without the selection stuff. Assumes the target + * region is already big enough. */ void screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, diff --git a/tmux.1 b/tmux.1 index 7918c780..5c873fdd 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3717,6 +3717,22 @@ If suffixed by .Ql % , this is a percentage of the window size. .Pp +.It Ic copy-mode-match-style Ar style +Set the style of search matches in copy mode. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp +.It Ic copy-mode-current-match-style Ar style +Set the style of the current search match in copy mode. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp .It Xo Ic mode-keys .Op Ic vi | emacs .Xc diff --git a/tmux.h b/tmux.h index 486b8b4c..e2443660 100644 --- a/tmux.h +++ b/tmux.h @@ -2434,8 +2434,6 @@ void screen_write_vnputs(struct screen_write_ctx *, ssize_t, const struct grid_cell *, const char *, va_list); void screen_write_putc(struct screen_write_ctx *, const struct grid_cell *, u_char); -void screen_write_copy(struct screen_write_ctx *, struct screen *, u_int, - u_int, u_int, u_int, bitstr_t *, const struct grid_cell *); void screen_write_fast_copy(struct screen_write_ctx *, struct screen *, u_int, u_int, u_int, u_int); void screen_write_hline(struct screen_write_ctx *, u_int, int, int); diff --git a/window-copy.c b/window-copy.c index 2c50a1cd..f5a07a10 100644 --- a/window-copy.c +++ b/window-copy.c @@ -257,12 +257,13 @@ struct window_copy_mode_data { int searchtype; int searchregex; char *searchstr; - bitstr_t *searchmark; + u_char *searchmark; u_int searchcount; int searchthis; int searchx; int searchy; int searcho; + u_char searchgen; int timeout; /* search has timed out */ #define WINDOW_COPY_SEARCH_TIMEOUT 10 @@ -362,10 +363,7 @@ window_copy_common_init(struct window_mode_entry *wme) data->searchregex = 0; data->searchstr = NULL; } - data->searchmark = NULL; data->searchx = data->searchy = data->searcho = -1; - data->timeout = 0; - data->viewmode = 0; data->jumptype = WINDOW_COPY_OFF; data->jumpchar = '\0'; @@ -2860,7 +2858,7 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, const struct grid_line *gl; int found, cis, which = -1; int cflags = REG_EXTENDED; - u_int px, py, b, nfound = 0, width; + u_int px, py, i, b, nfound = 0, width; u_int ssize = 1; char *sbuf; regex_t reg; @@ -2880,7 +2878,8 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, cis = window_copy_is_lowercase(data->searchstr); free(data->searchmark); - data->searchmark = bit_alloc((gd->hsize + gd->sy) * gd->sx); + data->searchmark = xcalloc(gd->hsize + gd->sy, gd->sx); + data->searchgen = 1; if (regex) { sbuf = xmalloc(ssize); @@ -2921,7 +2920,12 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, which = nfound; b = (py * gd->sx) + px; - bit_nset(data->searchmark, b, b + width - 1); + for (i = b; i < b + width; i++) + data->searchmark[i] = data->searchgen; + if (data->searchgen == UCHAR_MAX) + data->searchgen = 1; + else + data->searchgen++; px++; } @@ -2991,6 +2995,114 @@ window_copy_goto_line(struct window_mode_entry *wme, const char *linestr) window_copy_redraw_screen(wme); } +static void +window_copy_match_start_end(struct window_copy_mode_data *data, u_int at, + u_int *start, u_int *end) +{ + struct grid *gd = data->backing->grid; + u_int last = (gd->hsize + gd->sy) * gd->sx - 1; + u_char mark = data->searchmark[at]; + + *start = *end = at; + while (*start != 0 && data->searchmark[*start] == mark) + (*start)--; + if (data->searchmark[*start] != mark) + (*start)++; + while (*end != last && data->searchmark[*end] == mark) + (*end)++; + if (data->searchmark[*end] != mark) + (*end)--; +} + +static char * +window_copy_match_at_cursor(struct window_copy_mode_data *data) +{ + struct grid *gd = data->backing->grid; + struct grid_cell gc; + u_int at, start, end, cy, px, py; + u_int sx = screen_size_x(data->backing); + char *buf = NULL; + size_t len = 0; + + if (data->searchmark == NULL) + return (NULL); + + cy = screen_hsize(data->backing) - data->oy + data->cy; + at = (cy * sx) + data->cx; + if (data->searchmark[at] == 0) + return (NULL); + window_copy_match_start_end(data, at, &start, &end); + + /* + * Cells will not be set in the marked array unless they are valid text + * and wrapping will be taken care of, so we can just copy. + */ + for (at = start; at <= end; at++) { + py = at / sx; + px = at % (py * sx); + + grid_get_cell(gd, px, py, &gc); + buf = xrealloc(buf, len + gc.data.size + 1); + memcpy(buf + len, gc.data.data, gc.data.size); + len += gc.data.size; + } + if (len != 0) + buf[len] = '\0'; + return (buf); +} + +static void +window_copy_update_style(struct window_mode_entry *wme, u_int fx, u_int fy, + struct grid_cell *gc, const struct grid_cell *mgc, + const struct grid_cell *cgc) +{ + struct window_copy_mode_data *data = wme->data; + u_int mark, at, start, end, cy; + u_int sx = screen_size_x(data->backing); + + if (data->searchmark == NULL) + return; + mark = data->searchmark[(fy * sx) + fx]; + if (mark == 0) + return; + + cy = screen_hsize(data->backing) - data->oy + data->cy; + at = (cy * sx) + data->cx; + if (data->searchmark[at] == mark) { + window_copy_match_start_end(data, at, &start, &end); + if (at >= start && at <= end) { + gc->attr = cgc->attr; + gc->fg = cgc->fg; + gc->bg = cgc->bg; + } + return; + } + + gc->attr = mgc->attr; + gc->fg = mgc->fg; + gc->bg = mgc->bg; +} + +static void +window_copy_write_one(struct window_mode_entry *wme, + struct screen_write_ctx *ctx, u_int py, u_int fy, u_int nx, + const struct grid_cell *mgc, const struct grid_cell *cgc) +{ + struct window_copy_mode_data *data = wme->data; + struct grid *gd = data->backing->grid; + struct grid_cell gc; + u_int fx; + + screen_write_cursormove(ctx, 0, py, 0); + for (fx = 0; fx < nx; fx++) { + grid_get_cell(gd, fx, fy, &gc); + if (fx + gc.data.width <= nx) { + window_copy_update_style(wme, fx, fy, &gc, mgc, cgc); + screen_write_cell(ctx, &gc); + } + } +} + static void window_copy_write_line(struct window_mode_entry *wme, struct screen_write_ctx *ctx, u_int py) @@ -2999,13 +3111,17 @@ window_copy_write_line(struct window_mode_entry *wme, struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; struct options *oo = wp->window->options; - struct grid_cell gc; + struct grid_cell gc, mgc, cgc; char hdr[512]; size_t size = 0; u_int hsize = screen_hsize(data->backing); style_apply(&gc, oo, "mode-style", NULL); gc.flags |= GRID_FLAG_NOPALETTE; + style_apply(&mgc, oo, "copy-mode-match-style", NULL); + mgc.flags |= GRID_FLAG_NOPALETTE; + style_apply(&cgc, oo, "copy-mode-current-match-style", NULL); + cgc.flags |= GRID_FLAG_NOPALETTE; if (py == 0 && s->rupper < s->rlower && !data->hide_position) { if (data->searchmark == NULL) { @@ -3035,15 +3151,13 @@ window_copy_write_line(struct window_mode_entry *wme, size = 0; if (size < screen_size_x(s)) { - screen_write_cursormove(ctx, 0, py, 0); - screen_write_copy(ctx, data->backing, 0, hsize - data->oy + py, - screen_size_x(s) - size, 1, data->searchmark, &gc); + window_copy_write_one(wme, ctx, py, hsize - data->oy + py, + screen_size_x(s) - size, &mgc, &cgc); } if (py == data->cy && data->cx == screen_size_x(s)) { - memcpy(&gc, &grid_default_cell, sizeof gc); screen_write_cursormove(ctx, screen_size_x(s) - 1, py, 0); - screen_write_putc(ctx, &gc, '$'); + screen_write_putc(ctx, &grid_default_cell, '$'); } } @@ -3354,8 +3468,12 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len) u_int firstsx, lastex, restex, restsx, selx; int keys; - if (data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE) - return (NULL); + if (data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE) { + buf = window_copy_match_at_cursor(data); + if (buf != NULL) + *len = strlen(buf); + return (buf); + } buf = xmalloc(1); off = 0; From 463864f5a25a0015a4f2795ba8a14e0e0bb2fa7a Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:16:36 +0000 Subject: [PATCH 17/49] Add -W and -T flags to command-prompt to only complete a window and a target, also complete aliases. --- cmd-command-prompt.c | 8 +++- key-bindings.c | 4 +- status.c | 112 +++++++++++++++++++++++++++++-------------- tmux.1 | 10 +++- tmux.h | 2 + 5 files changed, 95 insertions(+), 41 deletions(-) diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index e53c4320..b8e3bd5c 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:", 0, 1 }, - .usage = "[-1kiN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " " + .args = { "1kiI:Np:Tt:W", 0, 1 }, + .usage = "[-1kiNTW] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " " "[template]", .flags = CMD_CLIENT_TFLAG, @@ -121,6 +121,10 @@ 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, prompt, input, cmd_command_prompt_callback, cmd_command_prompt_free, cdata, cdata->flags); free(prompt); diff --git a/key-bindings.c b/key-bindings.c index 09a4eafa..85bfb788 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -243,12 +243,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 -pindex \"select-window -t ':%%'\"", + "bind -N 'Prompt for window index to select' \"'\" command-prompt -Wpindex \"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 \"move-window -t '%%'\"", + "bind -N 'Move the current window' . command-prompt -T \"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", diff --git a/status.c b/status.c index 0acf4233..77b92717 100644 --- a/status.c +++ b/status.c @@ -38,6 +38,8 @@ static const char *status_prompt_down_history(u_int *); static void status_prompt_add_history(const char *); static char *status_prompt_complete(struct client *, const char *, u_int); +static char *status_prompt_complete_window_menu(struct client *, + struct session *, u_int, char); struct status_prompt_menu { struct client *c; @@ -933,13 +935,11 @@ status_prompt_replace_complete(struct client *c, const char *s) size_t size, n, off, idx, used; struct utf8_data *first, *last, *ud; - if (c->prompt_buffer[0].size == 0) - return (0); - size = utf8_strlen(c->prompt_buffer); - + /* Work out where the cursor currently is. */ idx = c->prompt_index; if (idx != 0) idx--; + size = utf8_strlen(c->prompt_buffer); /* Find the word we are in. */ first = &c->prompt_buffer[idx]; @@ -954,7 +954,7 @@ status_prompt_replace_complete(struct client *c, const char *s) last--; if (last->size != 0) last++; - if (last <= first) + if (last < first) return (0); if (s == NULL) { used = 0; @@ -1071,7 +1071,15 @@ process_key: } break; case '\011': /* Tab */ - if (status_prompt_replace_complete(c, NULL)) + if (c->prompt_flags & PROMPT_WINDOW) { + s = status_prompt_complete_window_menu(c, c->session, + 0, '\0'); + if (s != NULL) { + free(c->prompt_buffer); + c->prompt_buffer = utf8_fromcstr(s); + c->prompt_index = utf8_strlen(c->prompt_buffer); + } + } else if (status_prompt_replace_complete(c, NULL)) goto changed; break; case KEYC_BSPACE: @@ -1376,6 +1384,11 @@ status_prompt_complete_list(u_int *size, const char *s, int at_start) list = xreallocarray(list, (*size) + 1, sizeof *list); list[(*size)++] = xstrdup((*cmdent)->name); } + if ((*cmdent)->alias != NULL && + strncmp((*cmdent)->alias, s, slen) == 0) { + list = xreallocarray(list, (*size) + 1, sizeof *list); + list[(*size)++] = xstrdup((*cmdent)->alias); + } } o = options_get_only(global_options, "command-alias"); if (o != NULL) { @@ -1450,7 +1463,12 @@ 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 (status_prompt_replace_complete(c, s)) + if (c->prompt_flags & PROMPT_WINDOW) { + free(c->prompt_buffer); + c->prompt_buffer = utf8_fromcstr(s); + c->prompt_index = utf8_strlen(c->prompt_buffer); + c->flags |= CLIENT_REDRAWSTATUS; + } else if (status_prompt_replace_complete(c, s)) c->flags |= CLIENT_REDRAWSTATUS; free(s); } @@ -1544,10 +1562,14 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, menu = menu_create(""); RB_FOREACH(wl, winlinks, &s->windows) { list = xreallocarray(list, size + 1, sizeof *list); - xasprintf(&list[size++], "%s:%d", s->name, wl->idx); - - xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx, - wl->window->name); + if (c->prompt_flags & PROMPT_WINDOW) { + xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name); + xasprintf(&list[size++], "%d", wl->idx); + } else { + xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx, + wl->window->name); + xasprintf(&list[size++], "%s:%d", s->name, wl->idx); + } item.name = tmp; item.key = '0' + size - 1; item.command = NULL; @@ -1559,8 +1581,11 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, } if (size == 1) { menu_free(menu); - xasprintf(&tmp, "-%c%s", flag, list[0]); - free(list[0]); + if (flag != '\0') { + xasprintf(&tmp, "-%c%s", flag, list[0]); + free(list[0]); + } else + tmp = list[0]; free(list); return (tmp); } @@ -1598,21 +1623,45 @@ status_prompt_complete_sort(const void *a, const void *b) return (strcmp(*aa, *bb)); } +/* Complete a session. */ +static char * +status_prompt_complete_session(char ***list, u_int *size, const char *s, + char flag) +{ + struct session *loop; + char *out, *tmp; + + RB_FOREACH(loop, sessions, &sessions) { + if (*s != '\0' && strncmp(loop->name, s, strlen(s)) != 0) + continue; + *list = xreallocarray(*list, (*size) + 2, sizeof **list); + xasprintf(&(*list)[(*size)++], "%s:", loop->name); + } + out = status_prompt_complete_prefix(*list, *size); + if (out != NULL && flag != '\0') { + xasprintf(&tmp, "-%c%s", flag, out); + free(out); + out = tmp; + } + return (out); +} + /* Complete word. */ static char * status_prompt_complete(struct client *c, const char *word, u_int offset) { - struct session *session, *loop; + struct session *session; const char *s, *colon; - size_t slen; - char **list = NULL, *copy = NULL, *out = NULL, *tmp; + char **list = NULL, *copy = NULL, *out = NULL; char flag = '\0'; u_int size = 0, i; - if (*word == '\0') + if (*word == '\0' && (~c->prompt_flags & PROMPT_TARGET)) return (NULL); - if (strncmp(word, "-t", 2) != 0 && strncmp(word, "-s", 2) != 0) { + if ((~c->prompt_flags & PROMPT_TARGET) && + strncmp(word, "-t", 2) != 0 && + strncmp(word, "-s", 2) != 0) { list = status_prompt_complete_list(&size, word, offset == 0); if (size == 0) out = NULL; @@ -1623,28 +1672,19 @@ status_prompt_complete(struct client *c, const char *word, u_int offset) goto found; } - s = word + 2; - slen = strlen(s); - - flag = word[1]; - offset += 2; - + if (c->prompt_flags & PROMPT_TARGET) { + s = word; + flag = '\0'; + } else { + s = word + 2; + flag = word[1]; + offset += 2; + } colon = strchr(s, ':'); /* If there is no colon, complete as a session. */ if (colon == NULL) { - RB_FOREACH(loop, sessions, &sessions) { - if (strncmp(loop->name, s, strlen(s)) != 0) - continue; - list = xreallocarray(list, size + 2, sizeof *list); - xasprintf(&list[size++], "%s:", loop->name); - } - out = status_prompt_complete_prefix(list, size); - if (out != NULL) { - xasprintf(&tmp, "-%c%s", flag, out); - free(out); - out = tmp; - } + out = status_prompt_complete_session(&list, &size, s, flag); goto found; } diff --git a/tmux.1 b/tmux.1 index 5c873fdd..4911493a 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4969,7 +4969,7 @@ session option. Commands related to the status line are as follows: .Bl -tag -width Ds .It Xo Ic command-prompt -.Op Fl 1ikN +.Op Fl 1ikNTW .Op Fl I Ar inputs .Op Fl p Ar prompts .Op Fl t Ar target-client @@ -5028,6 +5028,14 @@ 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. +.Fl T +tells +.Nm +that the prompt is for a target which affects what completions are offered when +.Em Tab +is pressed; +.Fl W +is similar but indicates the prompt is for a window. .Pp The following keys have a special meaning in the command prompt, depending on the value of the diff --git a/tmux.h b/tmux.h index e2443660..52477ed9 100644 --- a/tmux.h +++ b/tmux.h @@ -1602,6 +1602,8 @@ struct client { #define PROMPT_INCREMENTAL 0x4 #define PROMPT_NOFORMAT 0x8 #define PROMPT_KEY 0x10 +#define PROMPT_WINDOW 0x20 +#define PROMPT_TARGET 0x40 int prompt_flags; struct session *session; From 58fb81d19a9a99eb5dce42dc7b145b9679074fe5 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:18:17 +0000 Subject: [PATCH 18/49] Complete partial window indexes properly. --- status.c | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/status.c b/status.c index 77b92717..397faf19 100644 --- a/status.c +++ b/status.c @@ -39,7 +39,7 @@ static void status_prompt_add_history(const char *); static char *status_prompt_complete(struct client *, const char *, u_int); static char *status_prompt_complete_window_menu(struct client *, - struct session *, u_int, char); + struct session *, const char *, u_int, char); struct status_prompt_menu { struct client *c; @@ -1071,15 +1071,7 @@ process_key: } break; case '\011': /* Tab */ - if (c->prompt_flags & PROMPT_WINDOW) { - s = status_prompt_complete_window_menu(c, c->session, - 0, '\0'); - if (s != NULL) { - free(c->prompt_buffer); - c->prompt_buffer = utf8_fromcstr(s); - c->prompt_index = utf8_strlen(c->prompt_buffer); - } - } else if (status_prompt_replace_complete(c, NULL)) + if (status_prompt_replace_complete(c, NULL)) goto changed; break; case KEYC_BSPACE: @@ -1537,7 +1529,7 @@ status_prompt_complete_list_menu(struct client *c, char **list, u_int size, /* Show complete word menu. */ static char * status_prompt_complete_window_menu(struct client *c, struct session *s, - u_int offset, char flag) + const char *word, u_int offset, char flag) { struct menu *menu; struct menu_item item; @@ -1561,6 +1553,15 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, menu = menu_create(""); RB_FOREACH(wl, winlinks, &s->windows) { + if (word != NULL && *word != '\0') { + xasprintf(&tmp, "%d", wl->idx); + if (strncmp(tmp, word, strlen(word)) != 0) { + free(tmp); + continue; + } + free(tmp); + } + list = xreallocarray(list, size + 1, sizeof *list); if (c->prompt_flags & PROMPT_WINDOW) { xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name); @@ -1579,6 +1580,10 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, if (size == height) break; } + if (size == 0) { + menu_free(menu); + return (NULL); + } if (size == 1) { menu_free(menu); if (flag != '\0') { @@ -1656,10 +1661,11 @@ status_prompt_complete(struct client *c, const char *word, u_int offset) char flag = '\0'; u_int size = 0, i; - if (*word == '\0' && (~c->prompt_flags & PROMPT_TARGET)) + if (*word == '\0' && + ((c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) == 0)) return (NULL); - if ((~c->prompt_flags & PROMPT_TARGET) && + if (((c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) == 0) && strncmp(word, "-t", 2) != 0 && strncmp(word, "-s", 2) != 0) { list = status_prompt_complete_list(&size, word, offset == 0); @@ -1672,7 +1678,7 @@ status_prompt_complete(struct client *c, const char *word, u_int offset) goto found; } - if (c->prompt_flags & PROMPT_TARGET) { + if (c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) { s = word; flag = '\0'; } else { @@ -1680,6 +1686,13 @@ status_prompt_complete(struct client *c, const char *word, u_int offset) flag = word[1]; offset += 2; } + + /* If this is a window completion, open the window menu. */ + if (c->prompt_flags & PROMPT_WINDOW) { + out = status_prompt_complete_window_menu(c, c->session, s, + offset, '\0'); + goto found; + } colon = strchr(s, ':'); /* If there is no colon, complete as a session. */ @@ -1700,8 +1713,8 @@ status_prompt_complete(struct client *c, const char *word, u_int offset) if (session == NULL) goto found; } - out = status_prompt_complete_window_menu(c, session, offset, - flag); + out = status_prompt_complete_window_menu(c, session, colon + 1, + offset, flag); if (out == NULL) return (NULL); } From a3cbc014c386fd1f171e3139ed71c67503e1ccb3 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:19:04 +0000 Subject: [PATCH 19/49] Use formats for status-style and message-style. --- status.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/status.c b/status.c index 397faf19..a4844294 100644 --- a/status.c +++ b/status.c @@ -346,8 +346,15 @@ status_redraw(struct client *c) if (c->tty.sy == 0 || lines == 0) return (1); + /* Create format tree. */ + flags = FORMAT_STATUS; + if (c->flags & CLIENT_STATUSFORCE) + flags |= FORMAT_FORCE; + ft = format_create(c, NULL, FORMAT_NONE, flags); + format_defaults(ft, c, NULL, NULL, NULL); + /* Set up default colour. */ - style_apply(&gc, s->options, "status-style", NULL); + style_apply(&gc, s->options, "status-style", ft); fg = options_get_number(s->options, "status-fg"); if (fg != 8) gc.fg = fg; @@ -367,13 +374,6 @@ status_redraw(struct client *c) } screen_write_start(&ctx, NULL, &sl->screen); - /* Create format tree. */ - flags = FORMAT_STATUS; - if (c->flags & CLIENT_STATUSFORCE) - flags |= FORMAT_FORCE; - ft = format_create(c, NULL, FORMAT_NONE, flags); - format_defaults(ft, c, NULL, NULL, NULL); - /* Write the status lines. */ o = options_get(s->options, "status-format"); if (o == NULL) { @@ -490,6 +490,7 @@ status_message_redraw(struct client *c) size_t len; u_int lines, offset; struct grid_cell gc; + struct format_tree *ft; if (c->tty.sx == 0 || c->tty.sy == 0) return (0); @@ -504,7 +505,9 @@ status_message_redraw(struct client *c) if (len > c->tty.sx) len = c->tty.sx; - style_apply(&gc, s->options, "message-style", NULL); + ft = format_create_defaults(NULL, c, NULL, NULL, NULL); + style_apply(&gc, s->options, "message-style", ft); + format_free(ft); screen_write_start(&ctx, NULL, sl->active); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); @@ -636,6 +639,7 @@ status_prompt_redraw(struct client *c) u_int i, lines, offset, left, start, width; u_int pcursor, pwidth; struct grid_cell gc, cursorgc; + struct format_tree *ft; if (c->tty.sx == 0 || c->tty.sy == 0) return (0); @@ -646,10 +650,12 @@ status_prompt_redraw(struct client *c) lines = 1; screen_init(sl->active, c->tty.sx, lines, 0); + ft = format_create_defaults(NULL, c, NULL, NULL, NULL); if (c->prompt_mode == PROMPT_COMMAND) - style_apply(&gc, s->options, "message-command-style", NULL); + style_apply(&gc, s->options, "message-command-style", ft); else - style_apply(&gc, s->options, "message-style", NULL); + style_apply(&gc, s->options, "message-style", ft); + format_free(ft); memcpy(&cursorgc, &gc, sizeof cursorgc); cursorgc.attr ^= GRID_ATTR_REVERSE; From 78595457f965975cf5a24e8bbab6dcb153020b6e Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:24:28 +0000 Subject: [PATCH 20/49] Add 'e' key in buffer mode to open the buffer in an editor. --- cmd-display-menu.c | 2 +- format-draw.c | 2 +- job.c | 7 +++ options-table.c | 6 ++ paste.c | 9 +++ popup.c | 9 ++- server.c | 1 + tmux.1 | 5 ++ tmux.c | 1 + tmux.h | 5 +- window-buffer.c | 134 +++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 177 insertions(+), 4 deletions(-) diff --git a/cmd-display-menu.c b/cmd-display-menu.c index 0a5c7f78..ae322444 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -313,7 +313,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) else if (args_has(args, 'E')) flags |= POPUP_CLOSEEXIT; if (popup_display(flags, item, px, py, w, h, nlines, lines, shellcmd, - cmd, cwd, tc, target) != 0) + cmd, cwd, tc, target, NULL, NULL) != 0) return (CMD_RETURN_NORMAL); return (CMD_RETURN_WAIT); } diff --git a/format-draw.c b/format-draw.c index 3ac33ce4..4a4fc6bc 100644 --- a/format-draw.c +++ b/format-draw.c @@ -738,7 +738,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, /* * Draw the screens. How they are arranged depends on where the list - * appearsq. + * appears. */ switch (list_align) { case STYLE_ALIGN_DEFAULT: diff --git a/job.c b/job.c index 997a6574..efc0199e 100644 --- a/job.c +++ b/job.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -285,6 +286,12 @@ job_check_died(pid_t pid, int status) } if (job == NULL) return; + if (WIFSTOPPED(status)) { + if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU) + return; + killpg(job->pid, SIGCONT); + return; + } log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid); job->status = status; diff --git a/options-table.c b/options-table.c index a89758c5..5ca63a78 100644 --- a/options-table.c +++ b/options-table.c @@ -210,6 +210,12 @@ const struct options_table_entry options_table[] = { .default_str = "screen" }, + { .name = "editor", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .default_str = _PATH_VI + }, + { .name = "escape-time", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_SERVER, diff --git a/paste.c b/paste.c index 0c9bc7b5..e98b5771 100644 --- a/paste.c +++ b/paste.c @@ -297,6 +297,15 @@ paste_set(char *data, size_t size, const char *name, char **cause) return (0); } +/* Set paste data without otherwise changing it. */ +void +paste_replace(struct paste_buffer *pb, char *data, size_t size) +{ + free(pb->data); + pb->data = data; + pb->size = size; +} + /* Convert start of buffer into a nice string. */ char * paste_make_sample(struct paste_buffer *pb) diff --git a/popup.c b/popup.c index 5d39e599..160b5aa3 100644 --- a/popup.c +++ b/popup.c @@ -40,6 +40,8 @@ struct popup_data { struct job *job; struct input_ctx *ictx; int status; + popup_close_cb cb; + void *arg; u_int px; u_int py; @@ -150,6 +152,9 @@ popup_free_cb(struct client *c) struct cmdq_item *item = pd->item; u_int i; + if (pd->cb != NULL) + pd->cb(pd->status, pd->arg); + if (item != NULL) { if (pd->ictx != NULL && cmdq_get_client(item) != NULL && @@ -403,7 +408,7 @@ int popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx, u_int sy, u_int nlines, const char **lines, const char *shellcmd, const char *cmd, const char *cwd, struct client *c, - struct cmd_find_state *fs) + struct cmd_find_state *fs, popup_close_cb cb, void *arg) { struct popup_data *pd; u_int i; @@ -422,6 +427,8 @@ popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx, pd->c = c; pd->c->references++; + pd->cb = cb; + pd->arg = arg; pd->status = 128 + SIGHUP; if (fs != NULL) diff --git a/server.c b/server.c index 55430e73..be655b6a 100644 --- a/server.c +++ b/server.c @@ -481,4 +481,5 @@ server_child_stopped(pid_t pid, int status) } } } + job_check_died(pid, status); } diff --git a/tmux.1 b/tmux.1 index 4911493a..d0c4f25b 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3113,6 +3113,10 @@ Set the time in milliseconds for which waits after an escape is input to determine if it is part of a function or meta key sequences. The default is 500 milliseconds. +.It Ic editor Ar shell-command +Set the command used when +.Nm +runs an editor. .It Xo Ic exit-empty .Op Ic on | off .Xc @@ -5339,6 +5343,7 @@ The following keys may be used in buffer mode: .It Li "P" Ta "Paste tagged buffers" .It Li "d" Ta "Delete selected buffer" .It Li "D" Ta "Delete tagged buffers" +.It Li "e" Ta "Open the buffer in an editor" .It Li "f" Ta "Enter a format to filter items" .It Li "O" Ta "Change sort field" .It Li "r" Ta "Reverse sort order" diff --git a/tmux.c b/tmux.c index db86dc52..137c7eeb 100644 --- a/tmux.c +++ b/tmux.c @@ -443,6 +443,7 @@ main(int argc, char **argv) /* Override keys to vi if VISUAL or EDITOR are set. */ if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) { + options_set_string(global_options, "editor", 0, "%s", s); if (strrchr(s, '/') != NULL) s = strrchr(s, '/') + 1; if (strstr(s, "vi") != NULL) diff --git a/tmux.h b/tmux.h index 52477ed9..c2e7b099 100644 --- a/tmux.h +++ b/tmux.h @@ -1806,6 +1806,7 @@ void paste_free(struct paste_buffer *); void paste_add(const char *, char *, size_t); int paste_rename(const char *, const char *, char **); int paste_set(char *, size_t, const char *, char **); +void paste_replace(struct paste_buffer *, char *, size_t); char *paste_make_sample(struct paste_buffer *); /* format.c */ @@ -2813,12 +2814,14 @@ int menu_display(struct menu *, int, struct cmdq_item *, u_int, #define POPUP_WRITEKEYS 0x1 #define POPUP_CLOSEEXIT 0x2 #define POPUP_CLOSEEXITZERO 0x4 +typedef void (*popup_close_cb)(int, void *); u_int popup_width(struct cmdq_item *, u_int, const char **, struct client *, struct cmd_find_state *); u_int popup_height(u_int, const char **); int popup_display(int, struct cmdq_item *, u_int, u_int, u_int, u_int, u_int, const char **, const char *, const char *, - const char *, struct client *, struct cmd_find_state *); + const char *, struct client *, struct cmd_find_state *, + popup_close_cb, void *); /* style.c */ int style_parse(struct style *,const struct grid_cell *, diff --git a/window-buffer.c b/window-buffer.c index dae8899e..53dfeca4 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -18,9 +18,12 @@ #include +#include +#include #include #include #include +#include #include #include "tmux.h" @@ -95,6 +98,13 @@ struct window_buffer_modedata { u_int item_size; }; +struct window_buffer_editdata { + u_int wp_id; + char *path; + char *name; + struct paste_buffer *pb; +}; + static struct window_buffer_itemdata * window_buffer_add_item(struct window_buffer_modedata *data) { @@ -353,6 +363,126 @@ window_buffer_do_paste(void *modedata, void *itemdata, struct client *c, mode_tree_run_command(c, NULL, data->command, item->name); } +static void +window_buffer_finish_edit(struct window_buffer_editdata *ed) +{ + unlink(ed->path); + free(ed->path); + free(ed->name); + free(ed); +} + +static void +window_buffer_edit_close_cb(int status, void *arg) +{ + struct window_buffer_editdata *ed = arg; + FILE *f; + off_t len; + char *buf; + size_t oldlen; + const char *oldbuf; + struct paste_buffer *pb; + struct window_pane *wp; + struct window_buffer_modedata *data; + struct window_mode_entry *wme; + + if (status != 0) { + window_buffer_finish_edit(ed); + return; + } + + pb = paste_get_name(ed->name); + if (pb == NULL || pb != ed->pb) { + window_buffer_finish_edit(ed); + return; + } + + f = fopen(ed->path, "r"); + if (f != NULL) { + fseeko(f, 0, SEEK_END); + len = ftello(f); + fseeko(f, 0, SEEK_SET); + + if (len > 0 && + (uintmax_t)len <= (uintmax_t)SIZE_MAX && + (buf = malloc(len)) != NULL && + fread(buf, len, 1, f) == 1) { + oldbuf = paste_buffer_data(pb, &oldlen); + if (oldlen != '\0' && + oldbuf[oldlen - 1] != '\n' && + buf[len - 1] == '\n') + len--; + if (len != 0) + paste_replace(pb, buf, len); + } + fclose(f); + } + + wp = window_pane_find_by_id(ed->wp_id); + if (wp != NULL) { + wme = TAILQ_FIRST(&wp->modes); + if (wme->mode == &window_buffer_mode) { + data = wme->data; + mode_tree_build(data->data); + mode_tree_draw(data->data); + } + wp->flags |= PANE_REDRAW; + } + window_buffer_finish_edit(ed); +} + +static void +window_buffer_start_edit(struct window_buffer_modedata *data, + struct window_buffer_itemdata *item, struct client *c) +{ + struct paste_buffer *pb; + int fd; + FILE *f; + const char *buf; + size_t len; + struct window_buffer_editdata *ed; + char *cmd; + char path[] = _PATH_TMP "tmux.XXXXXXXX"; + const char *editor; + u_int px, py, sx, sy; + + if ((pb = paste_get_name(item->name)) == NULL) + return; + buf = paste_buffer_data(pb, &len); + + editor = options_get_string(global_options, "editor"); + if (*editor == '\0') + return; + + fd = mkstemp(path); + if (fd == -1) + return; + f = fdopen(fd, "w"); + if (fwrite(buf, len, 1, f) != 1) { + fclose(f); + return; + } + fclose(f); + + ed = xcalloc(1, sizeof *ed); + ed->wp_id = data->wp->id; + ed->path = xstrdup(path); + ed->name = xstrdup(paste_buffer_name(pb)); + ed->pb = pb; + + sx = c->tty.sx * 9 / 10; + sy = c->tty.sy * 9 / 10; + px = (c->tty.sx / 2) - (sx / 2); + py = (c->tty.sy / 2) - (sy / 2); + + xasprintf(&cmd, "%s %s", editor, path); + if (popup_display(POPUP_WRITEKEYS|POPUP_CLOSEEXIT, NULL, px, py, sx, sy, + 0, NULL, cmd, NULL, _PATH_TMP, c, NULL, window_buffer_edit_close_cb, + ed) != 0) + window_buffer_finish_edit(ed); + free(cmd); +} + static void window_buffer_key(struct window_mode_entry *wme, struct client *c, __unused struct session *s, __unused struct winlink *wl, key_code key, @@ -366,6 +496,10 @@ window_buffer_key(struct window_mode_entry *wme, struct client *c, finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); switch (key) { + case 'e': + item = mode_tree_get_current(mtd); + window_buffer_start_edit(data, item, c); + break; case 'd': item = mode_tree_get_current(mtd); window_buffer_do_delete(data, item, c, key); From edeb81ba9e7ef3665f055cc54fee6cfdb3979fea Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:25:24 +0000 Subject: [PATCH 21/49] Add -e for new-session to set environment variables. --- cmd-new-session.c | 16 +++++++++++----- tmux.1 | 6 ++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/cmd-new-session.c b/cmd-new-session.c index f08155c0..9815e1e1 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -39,10 +39,10 @@ const struct cmd_entry cmd_new_session_entry = { .name = "new-session", .alias = "new", - .args = { "Ac:dDEF:n:Ps:t:x:Xy:", 0, -1 }, - .usage = "[-AdDEPX] [-c start-directory] [-F format] [-n window-name] " - "[-s session-name] " CMD_TARGET_SESSION_USAGE " [-x width] " - "[-y height] [command]", + .args = { "Ac:dDe:EF:n:Ps:t:x:Xy:", 0, -1 }, + .usage = "[-AdDEPX] [-c start-directory] [-e environment] [-F format] " + "[-n window-name] [-s session-name] " + CMD_TARGET_SESSION_USAGE " [-x width] [-y height] [command]", .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL }, @@ -75,7 +75,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) struct options *oo; struct termios tio, *tiop; struct session_group *sg = NULL; - const char *errstr, *template, *group, *tmp; + const char *errstr, *template, *group, *tmp, *add; char *cause, *cwd = NULL, *cp, *newname = NULL; char *name, *prefix = NULL; int detached, already_attached, is_control = 0; @@ -83,6 +83,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) struct spawn_context sc; enum cmd_retval retval; struct cmd_find_state fs; + struct args_value *value; if (cmd_get_entry(self) == &cmd_has_session_entry) { /* @@ -254,6 +255,11 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) env = environ_create(); if (c != NULL && !args_has(args, 'E')) environ_update(global_s_options, c->environ, env); + add = args_first_value(args, 'e', &value); + while (add != NULL) { + environ_put(env, add, 0); + add = args_next_value(&value); + } s = session_create(prefix, newname, cwd, env, oo, tiop); /* Spawn the initial window. */ diff --git a/tmux.1 b/tmux.1 index d0c4f25b..5b7bb3db 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1094,6 +1094,7 @@ Lock all clients attached to .It Xo Ic new-session .Op Fl AdDEPX .Op Fl c Ar start-directory +.Op Fl e Ar environment .Op Fl F Ar format .Op Fl n Ar window-name .Op Fl s Ar session-name @@ -1200,6 +1201,11 @@ If is used, the .Ic update-environment option will not be applied. +.Fl e +takes the form +.Ql VARIABLE=value +and sets an environment variable for the newly created session; it may be +specified multiple times. .It Xo Ic refresh-client .Op Fl cDlLRSU .Op Fl C Ar XxY From 379ca54c80837d09dff53ffa7b9ea3b80d87096b Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:27:08 +0000 Subject: [PATCH 22/49] Rename and tidy some stuff in struct tty_ctx. --- screen-write.c | 3 + tmux.h | 14 +++-- tty.c | 148 +++++++++++++++++++++++-------------------------- 3 files changed, 81 insertions(+), 84 deletions(-) diff --git a/screen-write.c b/screen-write.c index e3e51020..32424b39 100644 --- a/screen-write.c +++ b/screen-write.c @@ -112,6 +112,9 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, ttyctx->wp = ctx->wp; + ttyctx->sx = screen_size_x(s); + ttyctx->sy = screen_size_y(s); + ttyctx->ocx = s->cx; ttyctx->ocy = s->cy; diff --git a/tmux.h b/tmux.h index c2e7b099..e9a5db8a 100644 --- a/tmux.h +++ b/tmux.h @@ -1305,19 +1305,21 @@ struct tty_ctx { u_int orupper; u_int orlower; - /* Pane offset. */ + /* Target region (usually pane) offset and size. */ u_int xoff; u_int yoff; + u_int sx; + u_int sy; /* The background colour used for clearing (erasing). */ u_int bg; - /* Window offset and size. */ + /* Containing region (usually window) offset and size. */ int bigger; - u_int ox; - u_int oy; - u_int sx; - u_int sy; + u_int wox; + u_int woy; + u_int wsx; + u_int wsy; }; /* Saved message entry. */ diff --git a/tty.c b/tty.c index e378b45d..aaf4d639 100644 --- a/tty.c +++ b/tty.c @@ -75,9 +75,8 @@ static void tty_default_attributes(struct tty *, struct window_pane *, #define tty_use_margin(tty) \ (tty->term->flags & TERM_DECSLRM) - -#define tty_pane_full_width(tty, ctx) \ - ((ctx)->xoff == 0 && screen_size_x((ctx)->wp->screen) >= (tty)->sx) +#define tty_full_width(tty, ctx) \ + ((ctx)->xoff == 0 && (ctx)->sx >= (tty)->sx) #define TTY_BLOCK_INTERVAL (100000 /* 100 milliseconds */) #define TTY_BLOCK_START(tty) (1 + ((tty)->sx * (tty)->sy) * 8) @@ -897,9 +896,7 @@ tty_update_client_offset(struct client *c) static int tty_large_region(__unused struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - - return (ctx->orlower - ctx->orupper >= screen_size_y(wp->screen) / 2); + return (ctx->orlower - ctx->orupper >= ctx->sy / 2); } /* @@ -933,7 +930,6 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; struct window_pane *wp = ctx->wp; - struct screen *s = wp->screen; u_int i; /* @@ -947,7 +943,7 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) } if (ctx->ocy < ctx->orupper || ctx->ocy > ctx->orlower) { - for (i = ctx->ocy; i < screen_size_y(s); i++) + for (i = ctx->ocy; i < ctx->sy; i++) tty_draw_pane(tty, ctx, i); } else { for (i = ctx->orupper; i <= ctx->orlower; i++) @@ -970,8 +966,8 @@ tty_is_visible(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, else lines = 0; - if (xoff + nx <= ctx->ox || xoff >= ctx->ox + ctx->sx || - yoff + ny <= ctx->oy || yoff >= lines + ctx->oy + ctx->sy) + if (xoff + nx <= ctx->wox || xoff >= ctx->wox + ctx->wsx || + yoff + ny <= ctx->woy || yoff >= lines + ctx->woy + ctx->wsy) return (0); return (1); } @@ -986,28 +982,28 @@ tty_clamp_line(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, if (!tty_is_visible(tty, ctx, px, py, nx, 1)) return (0); - *ry = ctx->yoff + py - ctx->oy; + *ry = ctx->yoff + py - ctx->woy; - if (xoff >= ctx->ox && xoff + nx <= ctx->ox + ctx->sx) { + if (xoff >= ctx->wox && xoff + nx <= ctx->wox + ctx->wsx) { /* All visible. */ *i = 0; - *x = ctx->xoff + px - ctx->ox; + *x = ctx->xoff + px - ctx->wox; *rx = nx; - } else if (xoff < ctx->ox && xoff + nx > ctx->ox + ctx->sx) { + } else if (xoff < ctx->wox && xoff + nx > ctx->wox + ctx->wsx) { /* Both left and right not visible. */ - *i = ctx->ox; + *i = ctx->wox; *x = 0; - *rx = ctx->sx; - } else if (xoff < ctx->ox) { + *rx = ctx->wsx; + } else if (xoff < ctx->wox) { /* Left not visible. */ - *i = ctx->ox - (ctx->xoff + px); + *i = ctx->wox - (ctx->xoff + px); *x = 0; *rx = nx - *i; } else { /* Right not visible. */ *i = 0; - *x = (ctx->xoff + px) - ctx->ox; - *rx = ctx->sx - *x; + *x = (ctx->xoff + px) - ctx->wox; + *rx = ctx->wsx - *x; } if (*rx > nx) fatalx("%s: x too big, %u > %u", __func__, *rx, nx); @@ -1083,50 +1079,50 @@ tty_clamp_area(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, if (!tty_is_visible(tty, ctx, px, py, nx, ny)) return (0); - if (xoff >= ctx->ox && xoff + nx <= ctx->ox + ctx->sx) { + if (xoff >= ctx->wox && xoff + nx <= ctx->wox + ctx->wsx) { /* All visible. */ *i = 0; - *x = ctx->xoff + px - ctx->ox; + *x = ctx->xoff + px - ctx->wox; *rx = nx; - } else if (xoff < ctx->ox && xoff + nx > ctx->ox + ctx->sx) { + } else if (xoff < ctx->wox && xoff + nx > ctx->wox + ctx->wsx) { /* Both left and right not visible. */ - *i = ctx->ox; + *i = ctx->wox; *x = 0; - *rx = ctx->sx; - } else if (xoff < ctx->ox) { + *rx = ctx->wsx; + } else if (xoff < ctx->wox) { /* Left not visible. */ - *i = ctx->ox - (ctx->xoff + px); + *i = ctx->wox - (ctx->xoff + px); *x = 0; *rx = nx - *i; } else { /* Right not visible. */ *i = 0; - *x = (ctx->xoff + px) - ctx->ox; - *rx = ctx->sx - *x; + *x = (ctx->xoff + px) - ctx->wox; + *rx = ctx->wsx - *x; } if (*rx > nx) fatalx("%s: x too big, %u > %u", __func__, *rx, nx); - if (yoff >= ctx->oy && yoff + ny <= ctx->oy + ctx->sy) { + if (yoff >= ctx->woy && yoff + ny <= ctx->woy + ctx->wsy) { /* All visible. */ *j = 0; - *y = ctx->yoff + py - ctx->oy; + *y = ctx->yoff + py - ctx->woy; *ry = ny; - } else if (yoff < ctx->oy && yoff + ny > ctx->oy + ctx->sy) { + } else if (yoff < ctx->woy && yoff + ny > ctx->woy + ctx->wsy) { /* Both top and bottom not visible. */ - *j = ctx->oy; + *j = ctx->woy; *y = 0; - *ry = ctx->sy; - } else if (yoff < ctx->oy) { + *ry = ctx->wsy; + } else if (yoff < ctx->woy) { /* Top not visible. */ - *j = ctx->oy - (ctx->yoff + py); + *j = ctx->woy - (ctx->yoff + py); *y = 0; *ry = ny - *j; } else { /* Bottom not visible. */ *j = 0; - *y = (ctx->yoff + py) - ctx->oy; - *ry = ctx->sy - *y; + *y = (ctx->yoff + py) - ctx->woy; + *ry = ctx->wsy - *y; } if (*ry > ny) fatalx("%s: y too big, %u > %u", __func__, *ry, ny); @@ -1222,7 +1218,7 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) { struct window_pane *wp = ctx->wp; struct screen *s = wp->screen; - u_int nx = screen_size_x(s), i, x, rx, ry; + u_int nx = ctx->sx, i, x, rx, ry; log_debug("%s: %s %u %d", __func__, tty->client->name, py, ctx->bigger); @@ -1495,8 +1491,8 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *), break; } - ctx->bigger = tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, - &ctx->sx, &ctx->sy); + ctx->bigger = tty_window_offset(&c->tty, &ctx->wox, &ctx->woy, + &ctx->wsx, &ctx->wsy); ctx->xoff = wp->xoff; ctx->yoff = wp->yoff; @@ -1514,7 +1510,7 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) struct window_pane *wp = ctx->wp; if (ctx->bigger || - !tty_pane_full_width(tty, ctx) || + !tty_full_width(tty, ctx) || tty_fake_bce(tty, wp, ctx->bg) || (!tty_term_has(tty->term, TTYC_ICH) && !tty_term_has(tty->term, TTYC_ICH1))) { @@ -1535,7 +1531,7 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) struct window_pane *wp = ctx->wp; if (ctx->bigger || - !tty_pane_full_width(tty, ctx) || + !tty_full_width(tty, ctx) || tty_fake_bce(tty, wp, ctx->bg) || (!tty_term_has(tty->term, TTYC_DCH) && !tty_term_has(tty->term, TTYC_DCH1))) { @@ -1573,7 +1569,7 @@ void tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) { if (ctx->bigger || - !tty_pane_full_width(tty, ctx) || + !tty_full_width(tty, ctx) || tty_fake_bce(tty, ctx->wp, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || !tty_term_has(tty->term, TTYC_IL1) || @@ -1597,7 +1593,7 @@ void tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) { if (ctx->bigger || - !tty_pane_full_width(tty, ctx) || + !tty_full_width(tty, ctx) || tty_fake_bce(tty, ctx->wp, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || !tty_term_has(tty->term, TTYC_DL1) || @@ -1621,23 +1617,20 @@ void tty_cmd_clearline(struct tty *tty, const struct tty_ctx *ctx) { struct window_pane *wp = ctx->wp; - u_int nx; tty_default_attributes(tty, wp, ctx->bg); - nx = screen_size_x(wp->screen); - tty_clear_pane_line(tty, ctx, ctx->ocy, 0, nx, ctx->bg); + tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->sx, ctx->bg); } void tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx) { struct window_pane *wp = ctx->wp; - u_int nx; + u_int nx = ctx->sx - ctx->ocx; tty_default_attributes(tty, wp, ctx->bg); - nx = screen_size_x(wp->screen) - ctx->ocx; tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, nx, ctx->bg); } @@ -1660,7 +1653,7 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) return; if (ctx->bigger || - (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || + (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, wp, 8) || !tty_term_has(tty->term, TTYC_CSR) || (!tty_term_has(tty->term, TTYC_RI) && @@ -1692,7 +1685,7 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) return; if (ctx->bigger || - (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || + (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, wp, 8) || !tty_term_has(tty->term, TTYC_CSR) || wp->sx == 1 || @@ -1731,7 +1724,7 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) u_int i; if (ctx->bigger || - (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || + (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, wp, 8) || !tty_term_has(tty->term, TTYC_CSR) || wp->sx == 1 || @@ -1768,7 +1761,7 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) u_int i; if (ctx->bigger || - (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || + (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, wp, 8) || !tty_term_has(tty->term, TTYC_CSR) || (!tty_term_has(tty->term, TTYC_RI) && @@ -1801,18 +1794,18 @@ tty_cmd_clearendofscreen(struct tty *tty, const struct tty_ctx *ctx) tty_default_attributes(tty, wp, ctx->bg); - tty_region_pane(tty, ctx, 0, screen_size_y(wp->screen) - 1); + tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); px = 0; - nx = screen_size_x(wp->screen); + nx = ctx->sx; py = ctx->ocy + 1; - ny = screen_size_y(wp->screen) - ctx->ocy - 1; + ny = ctx->sy - ctx->ocy - 1; tty_clear_pane_area(tty, ctx, py, ny, px, nx, ctx->bg); px = ctx->ocx; - nx = screen_size_x(wp->screen) - ctx->ocx; + nx = ctx->sx - ctx->ocx; py = ctx->ocy; tty_clear_pane_line(tty, ctx, py, px, nx, ctx->bg); @@ -1826,11 +1819,11 @@ tty_cmd_clearstartofscreen(struct tty *tty, const struct tty_ctx *ctx) tty_default_attributes(tty, wp, ctx->bg); - tty_region_pane(tty, ctx, 0, screen_size_y(wp->screen) - 1); + tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); px = 0; - nx = screen_size_x(wp->screen); + nx = ctx->sx; py = 0; ny = ctx->ocy; @@ -1851,13 +1844,13 @@ tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx) tty_default_attributes(tty, wp, ctx->bg); - tty_region_pane(tty, ctx, 0, screen_size_y(wp->screen) - 1); + tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); px = 0; - nx = screen_size_x(wp->screen); + nx = ctx->sx; py = 0; - ny = screen_size_y(wp->screen); + ny = ctx->sy; tty_clear_pane_area(tty, ctx, py, ny, px, nx, ctx->bg); } @@ -1866,7 +1859,6 @@ void tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) { struct window_pane *wp = ctx->wp; - struct screen *s = wp->screen; u_int i, j; if (ctx->bigger) { @@ -1876,12 +1868,12 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) tty_attributes(tty, &grid_default_cell, wp); - tty_region_pane(tty, ctx, 0, screen_size_y(s) - 1); + tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); - for (j = 0; j < screen_size_y(s); j++) { + for (j = 0; j < ctx->sy; j++) { tty_cursor_pane(tty, ctx, 0, j); - for (i = 0; i < screen_size_x(s); i++) + for (i = 0; i < ctx->sx; i++) tty_putc(tty, 'E'); } } @@ -1892,9 +1884,9 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, 1, 1)) return; - if (ctx->xoff + ctx->ocx - ctx->ox > tty->sx - 1 && + if (ctx->xoff + ctx->ocx - ctx->wox > tty->sx - 1 && ctx->ocy == ctx->orlower && - tty_pane_full_width(tty, ctx)) + tty_full_width(tty, ctx)) tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); @@ -1912,10 +1904,10 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) return; if (ctx->bigger && - (ctx->xoff + ctx->ocx < ctx->ox || - ctx->xoff + ctx->ocx + ctx->num > ctx->ox + ctx->sx)) { + (ctx->xoff + ctx->ocx < ctx->wox || + ctx->xoff + ctx->ocx + ctx->num > ctx->wox + ctx->wsx)) { if (!ctx->wrapped || - !tty_pane_full_width(tty, ctx) || + !tty_full_width(tty, ctx) || (tty->term->flags & TERM_NOXENL) || ctx->xoff + ctx->ocx != 0 || ctx->yoff + ctx->ocy != tty->cy + 1 || @@ -2052,8 +2044,8 @@ static void tty_region_pane(struct tty *tty, const struct tty_ctx *ctx, u_int rupper, u_int rlower) { - tty_region(tty, ctx->yoff + rupper - ctx->oy, - ctx->yoff + rlower - ctx->oy); + tty_region(tty, ctx->yoff + rupper - ctx->woy, + ctx->yoff + rlower - ctx->woy); } /* Set region at absolute position. */ @@ -2096,8 +2088,8 @@ tty_margin_off(struct tty *tty) static void tty_margin_pane(struct tty *tty, const struct tty_ctx *ctx) { - tty_margin(tty, ctx->xoff - ctx->ox, - ctx->xoff + ctx->wp->sx - 1 - ctx->ox); + tty_margin(tty, ctx->xoff - ctx->wox, + ctx->xoff + ctx->wp->sx - 1 - ctx->wox); } /* Set margin at absolute position. */ @@ -2130,7 +2122,7 @@ tty_cursor_pane_unless_wrap(struct tty *tty, const struct tty_ctx *ctx, u_int cx, u_int cy) { if (!ctx->wrapped || - !tty_pane_full_width(tty, ctx) || + !tty_full_width(tty, ctx) || (tty->term->flags & TERM_NOXENL) || ctx->xoff + cx != 0 || ctx->yoff + cy != tty->cy + 1 || @@ -2145,7 +2137,7 @@ tty_cursor_pane_unless_wrap(struct tty *tty, const struct tty_ctx *ctx, static void tty_cursor_pane(struct tty *tty, const struct tty_ctx *ctx, u_int cx, u_int cy) { - tty_cursor(tty, ctx->xoff + cx - ctx->ox, ctx->yoff + cy - ctx->oy); + tty_cursor(tty, ctx->xoff + cx - ctx->wox, ctx->yoff + cy - ctx->woy); } /* Move cursor to absolute position. */ From 9605b080f6c942ff2e51a2ba538cccc91c91c161 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:34:08 +0000 Subject: [PATCH 23/49] Do not hoke into struct window_pane from the tty code and instead set everything up in tty_ctx. Provide a way to initialize the tty_ctx from a callback and use it to let popups draw directly through input_parse in the same way as panes do, rather than forcing a full redraw on every change. --- cmd-display-panes.c | 8 +- format-draw.c | 8 +- input.c | 37 ++--- menu.c | 16 +- mode-tree.c | 2 +- popup.c | 70 +++++++-- screen-redraw.c | 18 ++- screen-write.c | 167 +++++++++++++++++--- server-client.c | 19 +-- server-fn.c | 2 +- status.c | 6 +- tmux.h | 55 +++++-- tty.c | 370 +++++++++++++++++++------------------------- window-clock.c | 2 +- window-copy.c | 24 +-- window.c | 20 --- 16 files changed, 468 insertions(+), 356 deletions(-) diff --git a/cmd-display-panes.c b/cmd-display-panes.c index 0f12b14b..a13a06ae 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -131,9 +131,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, gc.bg = active_colour; else gc.bg = colour; - gc.flags |= GRID_FLAG_NOPALETTE; - - tty_attributes(tty, &gc, wp); + tty_attributes(tty, &gc, &grid_default_cell, NULL); for (ptr = buf; *ptr != '\0'; ptr++) { if (*ptr < '0' || *ptr > '9') continue; @@ -160,9 +158,7 @@ draw_text: gc.fg = active_colour; else gc.fg = colour; - gc.flags |= GRID_FLAG_NOPALETTE; - - tty_attributes(tty, &gc, wp); + tty_attributes(tty, &gc, &grid_default_cell, NULL); tty_puts(tty, buf); tty_cursor(tty, 0, 0); diff --git a/format-draw.c b/format-draw.c index 4a4fc6bc..3751082e 100644 --- a/format-draw.c +++ b/format-draw.c @@ -242,7 +242,7 @@ format_draw_left(struct screen_write_ctx *octx, u_int available, u_int ocx, /* If there is no list left, pass off to the no list function. */ if (width_list == 0) { - screen_write_start(&ctx, NULL, left); + screen_write_start(&ctx, left); screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1); screen_write_stop(&ctx); @@ -334,7 +334,7 @@ format_draw_centre(struct screen_write_ctx *octx, u_int available, u_int ocx, /* If there is no list left, pass off to the no list function. */ if (width_list == 0) { - screen_write_start(&ctx, NULL, centre); + screen_write_start(&ctx, centre); screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1); screen_write_stop(&ctx); @@ -431,7 +431,7 @@ format_draw_right(struct screen_write_ctx *octx, u_int available, u_int ocx, /* If there is no list left, pass off to the no list function. */ if (width_list == 0) { - screen_write_start(&ctx, NULL, right); + screen_write_start(&ctx, right); screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1); screen_write_stop(&ctx); @@ -536,7 +536,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, */ for (i = 0; i < TOTAL; i++) { screen_init(&s[i], size, 1, 0); - screen_write_start(&ctx[i], NULL, &s[i]); + screen_write_start(&ctx[i], &s[i]); screen_write_clearendofline(&ctx[i], current_default.bg); width[i] = 0; } diff --git a/input.c b/input.c index 8485a17a..03b81c69 100644 --- a/input.c +++ b/input.c @@ -842,9 +842,9 @@ input_reset(struct input_ctx *ictx, int clear) if (clear && wp != NULL) { if (TAILQ_EMPTY(&wp->modes)) - screen_write_start(sctx, wp, &wp->base); + screen_write_start_pane(sctx, wp, &wp->base); else - screen_write_start(sctx, NULL, &wp->base); + screen_write_start(sctx, &wp->base); screen_write_reset(sctx); screen_write_stop(sctx); } @@ -960,9 +960,9 @@ input_parse_buffer(struct window_pane *wp, u_char *buf, size_t len) /* NULL wp if there is a mode set as don't want to update the tty. */ if (TAILQ_EMPTY(&wp->modes)) - screen_write_start(sctx, wp, &wp->base); + screen_write_start_pane(sctx, wp, &wp->base); else - screen_write_start(sctx, NULL, &wp->base); + screen_write_start(sctx, &wp->base); log_debug("%s: %%%u %s, %zu bytes: %.*s", __func__, wp->id, ictx->state->name, len, (int)len, buf); @@ -973,15 +973,15 @@ input_parse_buffer(struct window_pane *wp, u_char *buf, size_t len) /* Parse given input for screen. */ void -input_parse_screen(struct input_ctx *ictx, struct screen *s, u_char *buf, - size_t len) +input_parse_screen(struct input_ctx *ictx, struct screen *s, + screen_write_init_ctx_cb cb, void *arg, u_char *buf, size_t len) { struct screen_write_ctx *sctx = &ictx->ctx; if (len == 0) return; - screen_write_start(sctx, NULL, s); + screen_write_start_callback(sctx, s, cb, arg); input_parse(ictx, buf, len); screen_write_stop(sctx); } @@ -1632,7 +1632,6 @@ static void input_csi_dispatch_rm_private(struct input_ctx *ictx) { struct screen_write_ctx *sctx = &ictx->ctx; - struct window_pane *wp = ictx->wp; struct grid_cell *gc = &ictx->cell.cell; u_int i; @@ -1677,16 +1676,10 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) break; case 47: case 1047: - if (wp != NULL) - window_pane_alternate_off(wp, gc, 0); - else - screen_alternate_off(sctx->s, gc, 0); + screen_write_alternateoff(sctx, gc, 0); break; case 1049: - if (wp != NULL) - window_pane_alternate_off(wp, gc, 1); - else - screen_alternate_off(sctx->s, gc, 1); + screen_write_alternateoff(sctx, gc, 1); break; case 2004: screen_write_mode_clear(sctx, MODE_BRACKETPASTE); @@ -1782,16 +1775,10 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) break; case 47: case 1047: - if (wp != NULL) - window_pane_alternate_on(wp, gc, 0); - else - screen_alternate_on(sctx->s, gc, 0); + screen_write_alternateon(sctx, gc, 0); break; case 1049: - if (wp != NULL) - window_pane_alternate_on(wp, gc, 1); - else - screen_alternate_on(sctx->s, gc, 1); + screen_write_alternateon(sctx, gc, 1); break; case 2004: screen_write_mode_set(sctx, MODE_BRACKETPASTE); @@ -2595,7 +2582,7 @@ input_osc_52(struct input_ctx *ictx, const char *p) return; } - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); screen_write_setselection(&ctx, out, outlen); screen_write_stop(&ctx); notify_pane("pane-set-clipboard", wp); diff --git a/menu.c b/menu.c index e78999c6..07fc8fa8 100644 --- a/menu.c +++ b/menu.c @@ -130,14 +130,12 @@ menu_free(struct menu *menu) free(menu); } -static int +static struct screen * menu_mode_cb(struct client *c, __unused u_int *cx, __unused u_int *cy) { struct menu_data *md = c->overlay_data; - if (~md->flags & MENU_NOMOUSE) - return (MODE_MOUSE_ALL); - return (0); + return (&md->s); } static void @@ -153,13 +151,15 @@ menu_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0) style_apply(&gc, c->session->curw->window->options, "mode-style", NULL); - screen_write_start(&ctx, NULL, s); + screen_write_start(&ctx, s); screen_write_clearscreen(&ctx, 8); screen_write_menu(&ctx, menu, md->choice, &gc); screen_write_stop(&ctx); - for (i = 0; i < screen_size_y(&md->s); i++) - tty_draw_line(tty, NULL, s, 0, i, menu->width + 4, px, py + i); + for (i = 0; i < screen_size_y(&md->s); i++) { + tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i, + &grid_default_cell, NULL); + } } static void @@ -349,6 +349,8 @@ menu_display(struct menu *menu, int flags, struct cmdq_item *item, u_int px, if (fs != NULL) cmd_find_copy_state(&md->fs, fs); screen_init(&md->s, menu->width + 4, menu->count + 2, 0); + if (~md->flags & MENU_NOMOUSE) + md->s.mode |= MODE_MOUSE_ALL; md->px = px; md->py = py; diff --git a/mode-tree.c b/mode-tree.c index 645e2ae9..8d210d72 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -562,7 +562,7 @@ mode_tree_draw(struct mode_tree_data *mtd) w = mtd->width; h = mtd->height; - screen_write_start(&ctx, NULL, s); + screen_write_start(&ctx, s); screen_write_clearscreen(&ctx, 8); if (mtd->line_size > 10) diff --git a/popup.c b/popup.c index 160b5aa3..9937d586 100644 --- a/popup.c +++ b/popup.c @@ -57,6 +57,44 @@ struct popup_data { u_int lb; }; +static void +popup_redraw_cb(const struct tty_ctx *ttyctx) +{ + struct popup_data *pd = ttyctx->arg; + + pd->c->flags |= CLIENT_REDRAWOVERLAY; +} + +static int +popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c) +{ + struct popup_data *pd = ttyctx->arg; + + if (pd->c->flags & CLIENT_REDRAWOVERLAY) + return (-1); + + ttyctx->bigger = 0; + ttyctx->wox = 0; + ttyctx->woy = 0; + ttyctx->wsx = c->tty.sx; + ttyctx->wsy = c->tty.sy; + + ttyctx->xoff = ttyctx->rxoff = pd->px + 1; + ttyctx->yoff = ttyctx->ryoff = pd->py + 1; + + return (1); +} + +static void +popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx) +{ + struct popup_data *pd = ctx->arg; + + ttyctx->redraw_cb = popup_redraw_cb; + ttyctx->set_client_cb = popup_set_client_cb; + ttyctx->arg = pd; +} + static void popup_write_screen(struct client *c, struct popup_data *pd) { @@ -72,7 +110,7 @@ popup_write_screen(struct client *c, struct popup_data *pd) else format_defaults(ft, c, NULL, NULL, NULL); - screen_write_start(&ctx, NULL, &pd->s); + screen_write_start(&ctx, &pd->s); screen_write_clearscreen(&ctx, 8); y = 0; @@ -98,7 +136,7 @@ popup_write_screen(struct client *c, struct popup_data *pd) screen_write_stop(&ctx); } -static int +static struct screen * popup_mode_cb(struct client *c, u_int *cx, u_int *cy) { struct popup_data *pd = c->overlay_data; @@ -107,7 +145,7 @@ popup_mode_cb(struct client *c, u_int *cx, u_int *cy) return (0); *cx = pd->px + 1 + pd->s.cx; *cy = pd->py + 1 + pd->s.cy; - return (pd->s.mode); + return (&pd->s); } static int @@ -132,7 +170,7 @@ popup_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0) u_int i, px = pd->px, py = pd->py; screen_init(&s, pd->sx, pd->sy, 0); - screen_write_start(&ctx, NULL, &s); + screen_write_start(&ctx, &s); screen_write_clearscreen(&ctx, 8); screen_write_box(&ctx, pd->sx, pd->sy); screen_write_cursormove(&ctx, 1, 1, 0); @@ -140,8 +178,10 @@ popup_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0) screen_write_stop(&ctx); c->overlay_check = NULL; - for (i = 0; i < pd->sy; i++) - tty_draw_line(tty, NULL, &s, 0, i, pd->sx, px, py + i); + for (i = 0; i < pd->sy; i++){ + tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, + &grid_default_cell, NULL); + } c->overlay_check = popup_check_cb; } @@ -327,15 +367,23 @@ popup_job_update_cb(struct job *job) { struct popup_data *pd = job_get_data(job); struct evbuffer *evb = job_get_event(job)->input; + struct client *c = pd->c; struct screen *s = &pd->s; void *data = EVBUFFER_DATA(evb); size_t size = EVBUFFER_LENGTH(evb); - if (size != 0) { - input_parse_screen(pd->ictx, s, data, size); - evbuffer_drain(evb, size); - pd->c->flags |= CLIENT_REDRAWOVERLAY; - } + if (size == 0) + return; + + c->overlay_check = NULL; + c->tty.flags &= ~TTY_FREEZE; + + input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size); + + c->tty.flags |= TTY_FREEZE; + c->overlay_check = popup_check_cb; + + evbuffer_drain(evb, size); } static void diff --git a/screen-redraw.c b/screen-redraw.c index 19ed6305..0f83479c 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -346,7 +346,7 @@ screen_redraw_make_pane_status(struct client *c, struct window *w, screen_init(&wp->status_screen, width, 1, 0); wp->status_screen.mode = 0; - screen_write_start(&ctx, NULL, &wp->status_screen); + screen_write_start(&ctx, &wp->status_screen); gc.attr |= GRID_ATTR_CHARSET; for (i = 0; i < width; i++) @@ -423,7 +423,8 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) if (ctx->statustop) yoff += ctx->statuslines; - tty_draw_line(tty, NULL, s, i, 0, width, x, yoff - ctx->oy); + tty_draw_line(tty, s, i, 0, width, x, yoff - ctx->oy, + &grid_default_cell, NULL); } tty_cursor(tty, 0, 0); } @@ -615,7 +616,7 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) } } - tty_attributes(tty, gc, NULL); + tty_attributes(tty, gc, &grid_default_cell, NULL); if (ctx->statustop) tty_cursor(tty, i, ctx->statuslines + j); else @@ -676,8 +677,10 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) y = 0; else y = c->tty.sy - ctx->statuslines; - for (i = 0; i < ctx->statuslines; i++) - tty_draw_line(tty, NULL, s, 0, i, UINT_MAX, 0, y + i); + for (i = 0; i < ctx->statuslines; i++) { + tty_draw_line(tty, s, 0, i, UINT_MAX, 0, y + i, + &grid_default_cell, NULL); + } } /* Draw one pane. */ @@ -688,6 +691,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct window *w = c->session->curw->window; struct tty *tty = &c->tty; struct screen *s; + struct grid_cell defaults; u_int i, j, top, x, y, width; log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); @@ -731,6 +735,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", __func__, c->name, wp->id, i, j, x, y, width); - tty_draw_line(tty, wp, s, i, j, width, x, y); + tty_default_colours(&defaults, wp); + tty_draw_line(tty, s, i, j, width, x, y, &defaults, + wp->palette); } } diff --git a/screen-write.c b/screen-write.c index 32424b39..909b8531 100644 --- a/screen-write.c +++ b/screen-write.c @@ -27,8 +27,8 @@ static void screen_write_collect_clear(struct screen_write_ctx *, u_int, u_int); static int screen_write_collect_clear_end(struct screen_write_ctx *, u_int, u_int, u_int); -static int screen_write_collect_clear_start(struct screen_write_ctx *, u_int, - u_int, u_int); +static int screen_write_collect_clear_start(struct screen_write_ctx *, + u_int, u_int, u_int); static void screen_write_collect_scroll(struct screen_write_ctx *); static void screen_write_collect_flush(struct screen_write_ctx *, int, const char *); @@ -101,6 +101,50 @@ screen_write_set_cursor(struct screen_write_ctx *ctx, int cx, int cy) evtimer_add(&w->offset_timer, &tv); } +/* Do a full redraw. */ +static void +screen_write_redraw_cb(const struct tty_ctx *ttyctx) +{ + struct window_pane *wp = ttyctx->arg; + + wp->flags |= PANE_REDRAW; +} + +/* Update context for client. */ +static int +screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c) +{ + struct window_pane *wp = ttyctx->arg; + + if (c->session->curw->window != wp->window) + return (0); + if (wp->layout_cell == NULL) + return (0); + + if (wp->flags & (PANE_REDRAW|PANE_DROP)) + return (-1); + if (c->flags & CLIENT_REDRAWPANES) { + /* + * Redraw is already deferred to redraw another pane - redraw + * this one also when that happens. + */ + log_debug("adding %%%u to deferred redraw", wp->id); + wp->flags |= PANE_REDRAW; + return (-1); + } + + ttyctx->bigger = tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, + &ttyctx->wsx, &ttyctx->wsy); + + ttyctx->xoff = ttyctx->rxoff = wp->xoff; + ttyctx->yoff = ttyctx->ryoff = wp->yoff; + + if (status_at_line(c) == 0) + ttyctx->yoff += status_line_size(c); + + return (1); +} + /* Set up context for TTY command. */ static void screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, @@ -110,17 +154,35 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, memset(ttyctx, 0, sizeof *ttyctx); - ttyctx->wp = ctx->wp; + if (ctx->wp != NULL) { + tty_default_colours(&ttyctx->defaults, ctx->wp); + ttyctx->palette = ctx->wp->palette; + } else { + memcpy(&ttyctx->defaults, &grid_default_cell, + sizeof ttyctx->defaults); + ttyctx->palette = NULL; + } + ttyctx->s = s; ttyctx->sx = screen_size_x(s); ttyctx->sy = screen_size_y(s); ttyctx->ocx = s->cx; ttyctx->ocy = s->cy; - ttyctx->orlower = s->rlower; ttyctx->orupper = s->rupper; + if (ctx->init_ctx_cb != NULL) + ctx->init_ctx_cb(ctx, ttyctx); + else { + ttyctx->redraw_cb = screen_write_redraw_cb; + if (ctx->wp == NULL) + ttyctx->set_client_cb = NULL; + else + ttyctx->set_client_cb = screen_write_set_client_cb; + ttyctx->arg = ctx->wp; + } + if (ctx->wp != NULL && !ctx->sync && (sync || ctx->wp != ctx->wp->window->active)) { @@ -151,18 +213,13 @@ screen_write_free_list(struct screen *s) free(s->write_list); } -/* Initialize writing with a window. */ -void -screen_write_start(struct screen_write_ctx *ctx, struct window_pane *wp, - struct screen *s) +/* Set up for writing. */ +static void +screen_write_init(struct screen_write_ctx *ctx, struct screen *s) { memset(ctx, 0, sizeof *ctx); - ctx->wp = wp; - if (wp != NULL && s == NULL) - ctx->s = wp->screen; - else - ctx->s = s; + ctx->s = s; if (ctx->s->write_list == NULL) screen_write_make_list(ctx->s); @@ -170,17 +227,51 @@ screen_write_start(struct screen_write_ctx *ctx, struct window_pane *wp, ctx->scrolled = 0; ctx->bg = 8; +} + +/* Initialize writing with a pane. */ +void +screen_write_start_pane(struct screen_write_ctx *ctx, struct window_pane *wp, + struct screen *s) +{ + if (s == NULL) + s = wp->screen; + screen_write_init(ctx, s); + ctx->wp = wp; if (log_get_level() != 0) { - if (wp != NULL) { - log_debug("%s: size %ux%u, pane %%%u (at %u,%u)", - __func__, screen_size_x(ctx->s), - screen_size_y(ctx->s), wp->id, wp->xoff, wp->yoff); - } else { - log_debug("%s: size %ux%u, no pane", - __func__, screen_size_x(ctx->s), - screen_size_y(ctx->s)); - } + log_debug("%s: size %ux%u, pane %%%u (at %u,%u)", + __func__, screen_size_x(ctx->s), screen_size_y(ctx->s), + wp->id, wp->xoff, wp->yoff); + } +} + +/* Initialize writing with a callback. */ +void +screen_write_start_callback(struct screen_write_ctx *ctx, struct screen *s, + screen_write_init_ctx_cb cb, void *arg) +{ + screen_write_init(ctx, s); + + ctx->init_ctx_cb = cb; + ctx->arg = arg; + + if (log_get_level() != 0) { + log_debug("%s: size %ux%u, with callback", __func__, + screen_size_x(ctx->s), screen_size_y(ctx->s)); + } +} + + +/* Initialize writing. */ +void +screen_write_start(struct screen_write_ctx *ctx, struct screen *s) +{ + screen_write_init(ctx, s); + + if (log_get_level() != 0) { + log_debug("%s: size %ux%u, no pane", __func__, + screen_size_x(ctx->s), screen_size_y(ctx->s)); } } @@ -1799,3 +1890,35 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len) tty_write(tty_cmd_rawstring, &ttyctx); } + +/* Turn alternate screen on. */ +void +screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, + int cursor) +{ + struct tty_ctx ttyctx; + struct window_pane *wp = ctx->wp; + + if (wp != NULL && !options_get_number(wp->options, "alternate-screen")) + return; + screen_alternate_on(ctx->s, gc, cursor); + + screen_write_initctx(ctx, &ttyctx, 1); + ttyctx.redraw_cb(&ttyctx); +} + +/* Turn alternate screen off. */ +void +screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc, + int cursor) +{ + struct tty_ctx ttyctx; + struct window_pane *wp = ctx->wp; + + if (wp != NULL && !options_get_number(wp->options, "alternate-screen")) + return; + screen_alternate_off(ctx->s, gc, cursor); + + screen_write_initctx(ctx, &ttyctx, 1); + ttyctx.redraw_cb(&ttyctx); +} diff --git a/server-client.c b/server-client.c index b21dac1d..a45e9812 100644 --- a/server-client.c +++ b/server-client.c @@ -1542,9 +1542,9 @@ server_client_reset_state(struct client *c) struct tty *tty = &c->tty; struct window *w = c->session->curw->window; struct window_pane *wp = w->active, *loop; - struct screen *s; + struct screen *s = NULL; struct options *oo = c->session->options; - int mode, cursor, flags; + int mode = 0, cursor, flags; u_int cx = 0, cy = 0, ox, oy, sx, sy; if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) @@ -1556,17 +1556,14 @@ server_client_reset_state(struct client *c) /* Get mode from overlay if any, else from screen. */ if (c->overlay_draw != NULL) { - s = NULL; - if (c->overlay_mode == NULL) - mode = 0; - else - mode = c->overlay_mode(c, &cx, &cy); - } else { + if (c->overlay_mode != NULL) + s = c->overlay_mode(c, &cx, &cy); + } else s = wp->screen; + if (s != NULL) mode = s->mode; - if (c->prompt_string != NULL || c->message_string != NULL) - mode &= ~MODE_CURSOR; - } + if (c->prompt_string != NULL || c->message_string != NULL) + mode &= ~MODE_CURSOR; log_debug("%s: client %s mode %x", __func__, c->name, mode); /* Reset region and margin. */ diff --git a/server-fn.c b/server-fn.c index da1371ae..fde1d8e8 100644 --- a/server-fn.c +++ b/server-fn.c @@ -318,7 +318,7 @@ server_destroy_pane(struct window_pane *wp, int notify) if (notify) notify_pane("pane-died", wp); - screen_write_start(&ctx, wp, &wp->base); + screen_write_start_pane(&ctx, wp, &wp->base); screen_write_scrollregion(&ctx, 0, screen_size_y(ctx.s) - 1); screen_write_cursormove(&ctx, 0, screen_size_y(ctx.s) - 1, 0); screen_write_linefeed(&ctx, 1, 8); diff --git a/status.c b/status.c index a4844294..b5442550 100644 --- a/status.c +++ b/status.c @@ -372,7 +372,7 @@ status_redraw(struct client *c) screen_resize(&sl->screen, width, lines, 0); changed = force = 1; } - screen_write_start(&ctx, NULL, &sl->screen); + screen_write_start(&ctx, &sl->screen); /* Write the status lines. */ o = options_get(s->options, "status-format"); @@ -509,7 +509,7 @@ status_message_redraw(struct client *c) style_apply(&gc, s->options, "message-style", ft); format_free(ft); - screen_write_start(&ctx, NULL, sl->active); + screen_write_start(&ctx, sl->active); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); screen_write_cursormove(&ctx, 0, lines - 1, 0); for (offset = 0; offset < c->tty.sx; offset++) @@ -664,7 +664,7 @@ status_prompt_redraw(struct client *c) if (start > c->tty.sx) start = c->tty.sx; - screen_write_start(&ctx, NULL, sl->active); + screen_write_start(&ctx, sl->active); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); screen_write_cursormove(&ctx, 0, lines - 1, 0); for (offset = 0; offset < c->tty.sx; offset++) diff --git a/tmux.h b/tmux.h index e9a5db8a..53417cd7 100644 --- a/tmux.h +++ b/tmux.h @@ -55,7 +55,11 @@ struct mouse_event; struct options; struct options_array_item; struct options_entry; +struct screen_write_collect_item; +struct screen_write_collect_line; +struct screen_write_ctx; struct session; +struct tty_ctx; struct tmuxpeer; struct tmuxproc; struct winlink; @@ -786,13 +790,16 @@ struct screen { }; /* Screen write context. */ -struct screen_write_collect_item; -struct screen_write_collect_line; +typedef void (*screen_write_init_ctx_cb)(struct screen_write_ctx *, + struct tty_ctx *); struct screen_write_ctx { struct window_pane *wp; struct screen *s; int sync; + screen_write_init_ctx_cb init_ctx_cb; + void *arg; + struct screen_write_collect_item *item; u_int scrolled; u_int bg; @@ -1252,8 +1259,6 @@ struct tty { struct termios tio; struct grid_cell cell; - - int last_wp; struct grid_cell last_cell; #define TTY_NOCURSOR 0x1 @@ -1285,8 +1290,14 @@ struct tty { }; /* TTY command context. */ +typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *); +typedef int (*tty_ctx_set_client_cb)(struct tty_ctx *, struct client *); struct tty_ctx { - struct window_pane *wp; + struct screen *s; + + tty_ctx_redraw_cb redraw_cb; + tty_ctx_set_client_cb set_client_cb; + void *arg; const struct grid_cell *cell; int wrapped; @@ -1308,12 +1319,18 @@ struct tty_ctx { /* Target region (usually pane) offset and size. */ u_int xoff; u_int yoff; + u_int rxoff; + u_int ryoff; u_int sx; u_int sy; /* The background colour used for clearing (erasing). */ u_int bg; + /* The default colours and palette. */ + struct grid_cell defaults; + int *palette; + /* Containing region (usually window) offset and size. */ int bigger; u_int wox; @@ -1492,7 +1509,7 @@ RB_HEAD(client_files, client_file); typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); typedef void (*prompt_free_cb)(void *); typedef int (*overlay_check_cb)(struct client *, u_int, u_int); -typedef int (*overlay_mode_cb)(struct client *, u_int *, u_int *); +typedef struct screen *(*overlay_mode_cb)(struct client *, u_int *, u_int *); typedef void (*overlay_draw_cb)(struct client *, struct screen_redraw_ctx *); typedef int (*overlay_key_cb)(struct client *, struct key_event *); typedef void (*overlay_free_cb)(struct client *); @@ -1969,7 +1986,7 @@ void tty_update_window_offset(struct window *); void tty_update_client_offset(struct client *); void tty_raw(struct tty *, const char *); void tty_attributes(struct tty *, const struct grid_cell *, - struct window_pane *); + const struct grid_cell *, int *); void tty_reset(struct tty *); void tty_region_off(struct tty *); void tty_margin_off(struct tty *); @@ -1992,8 +2009,8 @@ void tty_send_requests(struct tty *); void tty_stop_tty(struct tty *); void tty_set_title(struct tty *, const char *); void tty_update_mode(struct tty *, int, struct screen *); -void tty_draw_line(struct tty *, struct window_pane *, struct screen *, - u_int, u_int, u_int, u_int, u_int); +void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, + u_int, u_int, const struct grid_cell *, int *); void tty_sync_start(struct tty *); void tty_sync_end(struct tty *); int tty_open(struct tty *, char **); @@ -2024,6 +2041,7 @@ void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *); void tty_cmd_setselection(struct tty *, const struct tty_ctx *); void tty_cmd_rawstring(struct tty *, const struct tty_ctx *); void tty_cmd_syncstart(struct tty *, const struct tty_ctx *); +void tty_default_colours(struct grid_cell *, struct window_pane *); /* tty-term.c */ extern struct tty_terms tty_terms; @@ -2342,8 +2360,8 @@ void input_reset(struct input_ctx *, int); struct evbuffer *input_pending(struct input_ctx *); void input_parse_pane(struct window_pane *); void input_parse_buffer(struct window_pane *, u_char *, size_t); -void input_parse_screen(struct input_ctx *, struct screen *, u_char *, - size_t); +void input_parse_screen(struct input_ctx *, struct screen *, + screen_write_init_ctx_cb, void *, u_char *, size_t); /* input-key.c */ int input_key_pane(struct window_pane *, key_code, struct mouse_event *); @@ -2426,8 +2444,11 @@ char *grid_view_string_cells(struct grid *, u_int, u_int, u_int); /* screen-write.c */ void screen_write_make_list(struct screen *); void screen_write_free_list(struct screen *); -void screen_write_start(struct screen_write_ctx *, struct window_pane *, - struct screen *); +void screen_write_start_pane(struct screen_write_ctx *, + struct window_pane *, struct screen *); +void screen_write_start(struct screen_write_ctx *, struct screen *); +void screen_write_start_callback(struct screen_write_ctx *, struct screen *, + screen_write_init_ctx_cb, void *); void screen_write_stop(struct screen_write_ctx *); void screen_write_reset(struct screen_write_ctx *); size_t printflike(1, 2) screen_write_strlen(const char *, ...); @@ -2481,6 +2502,10 @@ void screen_write_collect_add(struct screen_write_ctx *, void screen_write_cell(struct screen_write_ctx *, const struct grid_cell *); void screen_write_setselection(struct screen_write_ctx *, u_char *, u_int); void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int); +void screen_write_alternateon(struct screen_write_ctx *, + struct grid_cell *, int); +void screen_write_alternateoff(struct screen_write_ctx *, + struct grid_cell *, int); /* screen-redraw.c */ void screen_redraw_screen(struct client *); @@ -2569,10 +2594,6 @@ struct window_pane *window_pane_find_by_id_str(const char *); struct window_pane *window_pane_find_by_id(u_int); int window_pane_destroy_ready(struct window_pane *); void window_pane_resize(struct window_pane *, u_int, u_int); -void window_pane_alternate_on(struct window_pane *, - struct grid_cell *, int); -void window_pane_alternate_off(struct window_pane *, - struct grid_cell *, int); void window_pane_set_palette(struct window_pane *, u_int, int); void window_pane_unset_palette(struct window_pane *, u_int); void window_pane_reset_palette(struct window_pane *); diff --git a/tty.c b/tty.c index aaf4d639..388f6f5f 100644 --- a/tty.c +++ b/tty.c @@ -34,7 +34,7 @@ static int tty_log_fd = -1; -static int tty_client_ready(struct client *, struct window_pane *); +static int tty_client_ready(struct client *); static void tty_set_italics(struct tty *); static int tty_try_colour(struct tty *, int, const char *); @@ -45,12 +45,9 @@ static void tty_cursor_pane_unless_wrap(struct tty *, const struct tty_ctx *, u_int, u_int); static void tty_invalidate(struct tty *); static void tty_colours(struct tty *, const struct grid_cell *); -static void tty_check_fg(struct tty *, struct window_pane *, - struct grid_cell *); -static void tty_check_bg(struct tty *, struct window_pane *, - struct grid_cell *); -static void tty_check_us(struct tty *, struct window_pane *, - struct grid_cell *); +static void tty_check_fg(struct tty *, int *, struct grid_cell *); +static void tty_check_bg(struct tty *, int *, struct grid_cell *); +static void tty_check_us(struct tty *, int *, struct grid_cell *); static void tty_colours_fg(struct tty *, const struct grid_cell *); static void tty_colours_bg(struct tty *, const struct grid_cell *); static void tty_colours_us(struct tty *, const struct grid_cell *); @@ -61,17 +58,17 @@ static void tty_region(struct tty *, u_int, u_int); static void tty_margin_pane(struct tty *, const struct tty_ctx *); static void tty_margin(struct tty *, u_int, u_int); static int tty_large_region(struct tty *, const struct tty_ctx *); -static int tty_fake_bce(const struct tty *, struct window_pane *, u_int); +static int tty_fake_bce(const struct tty *, const struct grid_cell *, + u_int); static void tty_redraw_region(struct tty *, const struct tty_ctx *); static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); static void tty_repeat_space(struct tty *, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); static void tty_cell(struct tty *, const struct grid_cell *, - struct window_pane *); -static void tty_default_colours(struct grid_cell *, struct window_pane *); -static void tty_default_attributes(struct tty *, struct window_pane *, - u_int); + const struct grid_cell *, int *); +static void tty_default_attributes(struct tty *, const struct grid_cell *, + int *, u_int); #define tty_use_margin(tty) \ (tty->term->flags & TERM_DECSLRM) @@ -888,6 +885,27 @@ tty_update_client_offset(struct client *c) c->flags |= (CLIENT_REDRAWWINDOW|CLIENT_REDRAWSTATUS); } +/* Get a palette entry. */ +static int +tty_get_palette(int *palette, int c) +{ + int new; + + if (palette == NULL) + return (-1); + + new = -1; + if (c < 8) + new = palette[c]; + else if (c >= 90 && c <= 97) + new = palette[8 + c - 90]; + else if (c & COLOUR_FLAG_256) + new = palette[c & ~COLOUR_FLAG_256]; + if (new == 0) + return (-1); + return (new); +} + /* * Is the region large enough to be worth redrawing once later rather than * probably several times now? Currently yes if it is more than 50% of the @@ -904,18 +922,11 @@ tty_large_region(__unused struct tty *tty, const struct tty_ctx *ctx) * emulated. */ static int -tty_fake_bce(const struct tty *tty, struct window_pane *wp, u_int bg) +tty_fake_bce(const struct tty *tty, const struct grid_cell *gc, u_int bg) { - struct grid_cell gc; - if (tty_term_flag(tty->term, TTYC_BCE)) return (0); - - memcpy(&gc, &grid_default_cell, sizeof gc); - if (wp != NULL) - tty_default_colours(&gc, wp); - - if (!COLOUR_DEFAULT(bg) || !COLOUR_DEFAULT(gc.bg)) + if (!COLOUR_DEFAULT(bg) || !COLOUR_DEFAULT(gc->bg)) return (1); return (0); } @@ -929,16 +940,15 @@ static void tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) { struct client *c = tty->client; - struct window_pane *wp = ctx->wp; u_int i; /* - * If region is large, schedule a window redraw. In most cases this is - * likely to be followed by some more scrolling. + * If region is large, schedule a redraw. In most cases this is likely + * to be followed by some more scrolling. */ if (tty_large_region(tty, ctx)) { - log_debug("%s: %s, large redraw of %%%u", __func__, c->name, wp->id); - wp->flags |= PANE_REDRAW; + log_debug("%s: %s large redraw", __func__, c->name); + ctx->redraw_cb(ctx); return; } @@ -977,8 +987,7 @@ static int tty_clamp_line(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, u_int nx, u_int *i, u_int *x, u_int *rx, u_int *ry) { - struct window_pane *wp = ctx->wp; - u_int xoff = wp->xoff + px; + u_int xoff = ctx->rxoff + px; if (!tty_is_visible(tty, ctx, px, py, nx, 1)) return (0); @@ -1013,8 +1022,8 @@ tty_clamp_line(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, /* Clear a line. */ static void -tty_clear_line(struct tty *tty, struct window_pane *wp, u_int py, u_int px, - u_int nx, u_int bg) +tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, + u_int px, u_int nx, u_int bg) { struct client *c = tty->client; @@ -1025,7 +1034,7 @@ tty_clear_line(struct tty *tty, struct window_pane *wp, u_int py, u_int px, return; /* If genuine BCE is available, can try escape sequences. */ - if (!tty_fake_bce(tty, wp, bg)) { + if (!tty_fake_bce(tty, defaults, bg)) { /* Off the end of the line, use EL if available. */ if (px + nx >= tty->sx && tty_term_has(tty->term, TTYC_EL)) { tty_cursor(tty, px, py); @@ -1064,7 +1073,7 @@ tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py, log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); if (tty_clamp_line(tty, ctx, px, py, nx, &i, &x, &rx, &ry)) - tty_clear_line(tty, ctx->wp, ry, x, rx, bg); + tty_clear_line(tty, &ctx->defaults, ry, x, rx, bg); } /* Clamp area position to visible part of pane. */ @@ -1073,8 +1082,7 @@ tty_clamp_area(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, u_int nx, u_int ny, u_int *i, u_int *j, u_int *x, u_int *y, u_int *rx, u_int *ry) { - struct window_pane *wp = ctx->wp; - u_int xoff = wp->xoff + px, yoff = wp->yoff + py; + u_int xoff = ctx->rxoff + px, yoff = ctx->ryoff + py; if (!tty_is_visible(tty, ctx, px, py, nx, ny)) return (0); @@ -1132,8 +1140,8 @@ tty_clamp_area(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, /* Clear an area, adjusting to visible part of pane. */ static void -tty_clear_area(struct tty *tty, struct window_pane *wp, u_int py, u_int ny, - u_int px, u_int nx, u_int bg) +tty_clear_area(struct tty *tty, const struct grid_cell *defaults, u_int py, + u_int ny, u_int px, u_int nx, u_int bg) { struct client *c = tty->client; u_int yy; @@ -1146,7 +1154,7 @@ tty_clear_area(struct tty *tty, struct window_pane *wp, u_int py, u_int ny, return; /* If genuine BCE is available, can try escape sequences. */ - if (!tty_fake_bce(tty, wp, bg)) { + if (!tty_fake_bce(tty, defaults, bg)) { /* Use ED if clearing off the bottom of the terminal. */ if (px == 0 && px + nx >= tty->sx && @@ -1199,7 +1207,7 @@ tty_clear_area(struct tty *tty, struct window_pane *wp, u_int py, u_int ny, /* Couldn't use an escape sequence, loop over the lines. */ for (yy = py; yy < py + ny; yy++) - tty_clear_line(tty, wp, yy, px, nx, bg); + tty_clear_line(tty, defaults, yy, px, nx, bg); } /* Clear an area in a pane. */ @@ -1210,24 +1218,26 @@ tty_clear_pane_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, u_int i, j, x, y, rx, ry; if (tty_clamp_area(tty, ctx, px, py, nx, ny, &i, &j, &x, &y, &rx, &ry)) - tty_clear_area(tty, ctx->wp, y, ry, x, rx, bg); + tty_clear_area(tty, &ctx->defaults, y, ry, x, rx, bg); } static void tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) { - struct window_pane *wp = ctx->wp; - struct screen *s = wp->screen; - u_int nx = ctx->sx, i, x, rx, ry; + struct screen *s = ctx->s; + u_int nx = ctx->sx, i, x, rx, ry; log_debug("%s: %s %u %d", __func__, tty->client->name, py, ctx->bigger); if (!ctx->bigger) { - tty_draw_line(tty, wp, s, 0, py, nx, ctx->xoff, ctx->yoff + py); + tty_draw_line(tty, s, 0, py, nx, ctx->xoff, ctx->yoff + py, + &ctx->defaults, ctx->palette); return; } - if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry)) - tty_draw_line(tty, wp, s, i, py, rx, x, ry); + if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry)) { + tty_draw_line(tty, s, i, py, rx, x, ry, &ctx->defaults, + ctx->palette); + } } static const struct grid_cell * @@ -1265,8 +1275,8 @@ tty_check_overlay(struct tty *tty, u_int px, u_int py) } void -tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, - u_int px, u_int py, u_int nx, u_int atx, u_int aty) +tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, + u_int atx, u_int aty, const struct grid_cell *defaults, int *palette) { struct grid *gd = s->grid; struct grid_cell gc, last; @@ -1314,8 +1324,7 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, gl = NULL; else gl = grid_get_line(gd, gd->hsize + py - 1); - if (wp == NULL || - gl == NULL || + if (gl == NULL || (~gl->flags & GRID_LINE_WRAPPED) || atx != 0 || tty->cx < tty->sx || @@ -1324,8 +1333,8 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, atx == 0 && px + sx != nx && tty_term_has(tty->term, TTYC_EL1) && - !tty_fake_bce(tty, wp, 8)) { - tty_default_attributes(tty, wp, 8); + !tty_fake_bce(tty, defaults, 8)) { + tty_default_attributes(tty, defaults, palette, 8); tty_cursor(tty, nx - 1, aty); tty_putcode(tty, TTYC_EL1); cleared = 1; @@ -1352,11 +1361,11 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, gcp->us != last.us || ux + width + gcp->data.width > nx || (sizeof buf) - len < gcp->data.size)) { - tty_attributes(tty, &last, wp); + tty_attributes(tty, &last, defaults, palette); if (last.flags & GRID_FLAG_CLEARED) { log_debug("%s: %zu cleared", __func__, len); - tty_clear_line(tty, wp, aty, atx + ux, width, - last.bg); + tty_clear_line(tty, defaults, aty, atx + ux, + width, last.bg); } else { if (!wrapped || atx != 0 || ux != 0) tty_cursor(tty, atx + ux, aty); @@ -1376,7 +1385,7 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, if (!tty_check_overlay(tty, atx + ux, aty)) ux += gcp->data.width; else if (ux + gcp->data.width > nx) { - tty_attributes(tty, &last, wp); + tty_attributes(tty, &last, defaults, palette); tty_cursor(tty, atx + ux, aty); for (j = 0; j < gcp->data.width; j++) { if (ux + j > nx) @@ -1385,7 +1394,7 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, ux++; } } else if (gcp->attr & GRID_ATTR_CHARSET) { - tty_attributes(tty, &last, wp); + tty_attributes(tty, &last, defaults, palette); tty_cursor(tty, atx + ux, aty); for (j = 0; j < gcp->data.size; j++) tty_putc(tty, gcp->data.data[j]); @@ -1397,10 +1406,11 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, } } if (len != 0 && ((~last.flags & GRID_FLAG_CLEARED) || last.bg != 8)) { - tty_attributes(tty, &last, wp); + tty_attributes(tty, &last, defaults, palette); if (last.flags & GRID_FLAG_CLEARED) { log_debug("%s: %zu cleared (end)", __func__, len); - tty_clear_line(tty, wp, aty, atx + ux, width, last.bg); + tty_clear_line(tty, defaults, aty, atx + ux, width, + last.bg); } else { if (!wrapped || atx != 0 || ux != 0) tty_cursor(tty, atx + ux, aty); @@ -1412,8 +1422,8 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, if (!cleared && ux < nx) { log_debug("%s: %u to end of line (%zu cleared)", __func__, nx - ux, len); - tty_default_attributes(tty, wp, 8); - tty_clear_line(tty, wp, aty, atx + ux, nx - ux, 8); + tty_default_attributes(tty, defaults, palette, 8); + tty_clear_line(tty, defaults, aty, atx + ux, nx - ux, 8); } tty->flags = (tty->flags & ~TTY_NOCURSOR) | flags; @@ -1451,7 +1461,7 @@ tty_sync_end(struct tty *tty) } static int -tty_client_ready(struct client *c, struct window_pane *wp) +tty_client_ready(struct client *c) { if (c->session == NULL || c->tty.term == NULL) return (0); @@ -1459,10 +1469,6 @@ tty_client_ready(struct client *c, struct window_pane *wp) return (0); if (c->tty.flags & TTY_FREEZE) return (0); - if (c->session->curw->window != wp->window) - return (0); - if (wp->layout_cell == NULL) - return (0); return (1); } @@ -1470,36 +1476,19 @@ void tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *), struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - struct client *c; + struct client *c; + int state; - if (wp == NULL) + if (ctx->set_client_cb == NULL) return; - if (wp->flags & (PANE_REDRAW|PANE_DROP)) - return; - TAILQ_FOREACH(c, &clients, entry) { - if (!tty_client_ready(c, wp)) + if (!tty_client_ready(c)) continue; - if (c->flags & CLIENT_REDRAWPANES) { - /* - * Redraw is already deferred to redraw another pane - - * redraw this one also when that happens. - */ - log_debug("adding %%%u to deferred redraw", wp->id); - wp->flags |= PANE_REDRAW; + state = ctx->set_client_cb(ctx, c); + if (state == -1) break; - } - - ctx->bigger = tty_window_offset(&c->tty, &ctx->wox, &ctx->woy, - &ctx->wsx, &ctx->wsy); - - ctx->xoff = wp->xoff; - ctx->yoff = wp->yoff; - - if (status_at_line(c) == 0) - ctx->yoff += status_line_size(c); - + if (state == 0) + continue; cmdfn(&c->tty, ctx); } } @@ -1507,18 +1496,16 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *), void tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - if (ctx->bigger || !tty_full_width(tty, ctx) || - tty_fake_bce(tty, wp, ctx->bg) || + tty_fake_bce(tty, &ctx->defaults, ctx->bg) || (!tty_term_has(tty->term, TTYC_ICH) && !tty_term_has(tty->term, TTYC_ICH1))) { tty_draw_pane(tty, ctx, ctx->ocy); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); @@ -1528,18 +1515,16 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - if (ctx->bigger || !tty_full_width(tty, ctx) || - tty_fake_bce(tty, wp, ctx->bg) || + tty_fake_bce(tty, &ctx->defaults, ctx->bg) || (!tty_term_has(tty->term, TTYC_DCH) && !tty_term_has(tty->term, TTYC_DCH1))) { tty_draw_pane(tty, ctx, ctx->ocy); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); @@ -1554,12 +1539,12 @@ tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, ctx->wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); if (tty_term_has(tty->term, TTYC_ECH) && - !tty_fake_bce(tty, ctx->wp, 8)) + !tty_fake_bce(tty, &ctx->defaults, 8)) tty_putcode1(tty, TTYC_ECH, ctx->num); else tty_repeat_space(tty, ctx->num); @@ -1570,16 +1555,16 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) { if (ctx->bigger || !tty_full_width(tty, ctx) || - tty_fake_bce(tty, ctx->wp, ctx->bg) || + tty_fake_bce(tty, &ctx->defaults, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || !tty_term_has(tty->term, TTYC_IL1) || - ctx->wp->sx == 1 || - ctx->wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, ctx->wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); @@ -1594,16 +1579,16 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) { if (ctx->bigger || !tty_full_width(tty, ctx) || - tty_fake_bce(tty, ctx->wp, ctx->bg) || + tty_fake_bce(tty, &ctx->defaults, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || !tty_term_has(tty->term, TTYC_DL1) || - ctx->wp->sx == 1 || - ctx->wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, ctx->wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); @@ -1616,9 +1601,7 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearline(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->sx, ctx->bg); } @@ -1626,10 +1609,9 @@ tty_cmd_clearline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int nx = ctx->sx - ctx->ocx; + u_int nx = ctx->sx - ctx->ocx; - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, nx, ctx->bg); } @@ -1637,9 +1619,7 @@ tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearstartofline(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->ocx + 1, ctx->bg); } @@ -1647,24 +1627,22 @@ tty_cmd_clearstartofline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - if (ctx->ocy != ctx->orupper) return; if (ctx->bigger || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || - tty_fake_bce(tty, wp, 8) || + tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || (!tty_term_has(tty->term, TTYC_RI) && !tty_term_has(tty->term, TTYC_RIN)) || - ctx->wp->sx == 1 || - ctx->wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1679,22 +1657,20 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - if (ctx->ocy != ctx->orlower) return; if (ctx->bigger || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || - tty_fake_bce(tty, wp, 8) || + tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || - wp->sx == 1 || - wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1720,20 +1696,19 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int i; + u_int i; if (ctx->bigger || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || - tty_fake_bce(tty, wp, 8) || + tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || - wp->sx == 1 || - wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1757,22 +1732,21 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int i; + u_int i; if (ctx->bigger || (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || - tty_fake_bce(tty, wp, 8) || + tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || (!tty_term_has(tty->term, TTYC_RI) && !tty_term_has(tty->term, TTYC_RIN)) || - wp->sx == 1 || - wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1789,10 +1763,9 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearendofscreen(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int px, py, nx, ny; + u_int px, py, nx, ny; - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1814,10 +1787,9 @@ tty_cmd_clearendofscreen(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearstartofscreen(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int px, py, nx, ny; + u_int px, py, nx, ny; - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1839,10 +1811,9 @@ tty_cmd_clearstartofscreen(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int px, py, nx, ny; + u_int px, py, nx, ny; - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1858,15 +1829,14 @@ tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int i, j; + u_int i, j; if (ctx->bigger) { - wp->flags |= PANE_REDRAW; + ctx->redraw_cb(ctx); return; } - tty_attributes(tty, &grid_default_cell, wp); + tty_attributes(tty, &grid_default_cell, &ctx->defaults, ctx->palette); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1892,14 +1862,12 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_cell(tty, ctx->cell, ctx->wp); + tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette); } void tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, ctx->num, 1)) return; @@ -1915,14 +1883,14 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) tty->cy == tty->rlower) tty_draw_pane(tty, ctx, ctx->ocy); else - wp->flags |= PANE_REDRAW; + ctx->redraw_cb(ctx); return; } tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_attributes(tty, ctx->cell, ctx->wp); + tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette); tty_putn(tty, ctx->ptr, ctx->num, ctx->num); } @@ -1958,7 +1926,8 @@ tty_cmd_syncstart(struct tty *tty, __unused const struct tty_ctx *ctx) } static void -tty_cell(struct tty *tty, const struct grid_cell *gc, struct window_pane *wp) +tty_cell(struct tty *tty, const struct grid_cell *gc, + const struct grid_cell *defaults, int *palette) { const struct grid_cell *gcp; @@ -1973,7 +1942,7 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, struct window_pane *wp) return; /* Set the attributes. */ - tty_attributes(tty, gc, wp); + tty_attributes(tty, gc, defaults, palette); /* Get the cell and if ASCII write with putc to do ACS translation. */ gcp = tty_check_codeset(tty, gc); @@ -1999,21 +1968,16 @@ tty_reset(struct tty *tty) tty_putcode(tty, TTYC_SGR0); memcpy(gc, &grid_default_cell, sizeof *gc); } - memcpy(&tty->last_cell, &grid_default_cell, sizeof tty->last_cell); - tty->last_wp = -1; } static void tty_invalidate(struct tty *tty) { memcpy(&tty->cell, &grid_default_cell, sizeof tty->cell); - memcpy(&tty->last_cell, &grid_default_cell, sizeof tty->last_cell); - tty->last_wp = -1; tty->cx = tty->cy = UINT_MAX; - tty->rupper = tty->rleft = UINT_MAX; tty->rlower = tty->rright = UINT_MAX; @@ -2089,7 +2053,7 @@ static void tty_margin_pane(struct tty *tty, const struct tty_ctx *ctx) { tty_margin(tty, ctx->xoff - ctx->wox, - ctx->xoff + ctx->wp->sx - 1 - ctx->wox); + ctx->xoff + ctx->sx - 1 - ctx->wox); } /* Set margin at absolute position. */ @@ -2282,27 +2246,24 @@ out: void tty_attributes(struct tty *tty, const struct grid_cell *gc, - struct window_pane *wp) + const struct grid_cell *defaults, int *palette) { struct grid_cell *tc = &tty->cell, gc2; int changed; - /* Ignore cell if it is the same as the last one. */ - if (wp != NULL && - (int)wp->id == tty->last_wp && - ~(wp->flags & PANE_STYLECHANGED) && - gc->attr == tty->last_cell.attr && - gc->fg == tty->last_cell.fg && - gc->bg == tty->last_cell.bg && - gc->us == tty->last_cell.us) - return; - tty->last_wp = (wp != NULL ? (int)wp->id : -1); - memcpy(&tty->last_cell, gc, sizeof tty->last_cell); - /* Copy cell and update default colours. */ memcpy(&gc2, gc, sizeof gc2); - if (wp != NULL) - tty_default_colours(&gc2, wp); + if (gc2.fg == 8) + gc2.fg = defaults->fg; + if (gc2.bg == 8) + gc2.bg = defaults->bg; + + /* Ignore cell if it is the same as the last one. */ + if (gc2.attr == tty->last_cell.attr && + gc2.fg == tty->last_cell.fg && + gc2.bg == tty->last_cell.bg && + gc2.us == tty->last_cell.us) + return; /* * If no setab, try to use the reverse attribute as a best-effort for a @@ -2320,9 +2281,9 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, } /* Fix up the colours if necessary. */ - tty_check_fg(tty, wp, &gc2); - tty_check_bg(tty, wp, &gc2); - tty_check_us(tty, wp, &gc2); + tty_check_fg(tty, palette, &gc2); + tty_check_bg(tty, palette, &gc2); + tty_check_us(tty, palette, &gc2); /* * If any bits are being cleared or the underline colour is now default, @@ -2377,6 +2338,8 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, tty_putcode(tty, TTYC_SMOL); if ((changed & GRID_ATTR_CHARSET) && tty_acs_needed(tty)) tty_putcode(tty, TTYC_SMACS); + + memcpy(&tty->last_cell, &gc2, sizeof tty->last_cell); } static void @@ -2441,7 +2404,7 @@ tty_colours(struct tty *tty, const struct grid_cell *gc) } static void -tty_check_fg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) +tty_check_fg(struct tty *tty, int *palette, struct grid_cell *gc) { u_char r, g, b; u_int colours; @@ -2456,7 +2419,7 @@ tty_check_fg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) c = gc->fg; if (c < 8 && gc->attr & GRID_ATTR_BRIGHT) c += 90; - if ((c = window_pane_get_palette(wp, c)) != -1) + if ((c = tty_get_palette(palette, c)) != -1) gc->fg = c; } @@ -2497,7 +2460,7 @@ tty_check_fg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) } static void -tty_check_bg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) +tty_check_bg(struct tty *tty, int *palette, struct grid_cell *gc) { u_char r, g, b; u_int colours; @@ -2505,7 +2468,7 @@ tty_check_bg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) /* Perform substitution if this pane has a palette. */ if (~gc->flags & GRID_FLAG_NOPALETTE) { - if ((c = window_pane_get_palette(wp, gc->bg)) != -1) + if ((c = tty_get_palette(palette, gc->bg)) != -1) gc->bg = c; } @@ -2548,14 +2511,13 @@ tty_check_bg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) } static void -tty_check_us(__unused struct tty *tty, struct window_pane *wp, - struct grid_cell *gc) +tty_check_us(__unused struct tty *tty, int *palette, struct grid_cell *gc) { int c; /* Perform substitution if this pane has a palette. */ if (~gc->flags & GRID_FLAG_NOPALETTE) { - if ((c = window_pane_get_palette(wp, gc->us)) != -1) + if ((c = tty_get_palette(palette, gc->us)) != -1) gc->us = c; } @@ -2686,11 +2648,12 @@ tty_window_default_style(struct grid_cell *gc, struct window_pane *wp) gc->bg = wp->bg; } -static void +void tty_default_colours(struct grid_cell *gc, struct window_pane *wp) { struct options *oo = wp->options; - int c; + + memcpy(gc, &grid_default_cell, sizeof *gc); if (wp->flags & PANE_STYLECHANGED) { wp->flags &= ~PANE_STYLECHANGED; @@ -2707,12 +2670,6 @@ tty_default_colours(struct grid_cell *gc, struct window_pane *wp) gc->fg = wp->cached_active_gc.fg; else gc->fg = wp->cached_gc.fg; - - if (gc->fg != 8) { - c = window_pane_get_palette(wp, gc->fg); - if (c != -1) - gc->fg = c; - } } if (gc->bg == 8) { @@ -2720,21 +2677,16 @@ tty_default_colours(struct grid_cell *gc, struct window_pane *wp) gc->bg = wp->cached_active_gc.bg; else gc->bg = wp->cached_gc.bg; - - if (gc->bg != 8) { - c = window_pane_get_palette(wp, gc->bg); - if (c != -1) - gc->bg = c; - } } } static void -tty_default_attributes(struct tty *tty, struct window_pane *wp, u_int bg) +tty_default_attributes(struct tty *tty, const struct grid_cell *defaults, + int *palette, u_int bg) { - static struct grid_cell gc; + struct grid_cell gc; memcpy(&gc, &grid_default_cell, sizeof gc); gc.bg = bg; - tty_attributes(tty, &gc, wp); + tty_attributes(tty, &gc, defaults, palette); } diff --git a/window-clock.c b/window-clock.c index 45d4d47b..8cef3f9a 100644 --- a/window-clock.c +++ b/window-clock.c @@ -219,7 +219,7 @@ window_clock_draw_screen(struct window_mode_entry *wme) colour = options_get_number(wp->window->options, "clock-mode-colour"); style = options_get_number(wp->window->options, "clock-mode-style"); - screen_write_start(&ctx, NULL, s); + screen_write_start(&ctx, s); t = time(NULL); tm = localtime(&t); diff --git a/window-copy.c b/window-copy.c index f5a07a10..9f84ade9 100644 --- a/window-copy.c +++ b/window-copy.c @@ -406,7 +406,7 @@ window_copy_init(struct window_mode_entry *wme, data->screen.cx = data->cx; data->screen.cy = data->cy; - screen_write_start(&ctx, NULL, &data->screen); + screen_write_start(&ctx, &data->screen); for (i = 0; i < screen_size_y(&data->screen); i++) window_copy_write_line(wme, &ctx, i); screen_write_cursormove(&ctx, data->cx, data->cy, 0); @@ -473,7 +473,7 @@ window_copy_vadd(struct window_pane *wp, const char *fmt, va_list ap) memcpy(&gc, &grid_default_cell, sizeof gc); old_hsize = screen_hsize(data->backing); - screen_write_start(&back_ctx, NULL, backing); + screen_write_start(&back_ctx, backing); if (data->backing_written) { /* * On the second or later line, do a CRLF before writing @@ -489,7 +489,7 @@ window_copy_vadd(struct window_pane *wp, const char *fmt, va_list ap) data->oy += screen_hsize(data->backing) - old_hsize; - screen_write_start(&ctx, wp, &data->screen); + screen_write_start_pane(&ctx, wp, &data->screen); /* * If the history has changed, draw the top line. @@ -713,7 +713,7 @@ window_copy_size_changed(struct window_mode_entry *wme) window_copy_clear_selection(wme); window_copy_clear_marks(wme); - screen_write_start(&ctx, NULL, s); + screen_write_start(&ctx, s); window_copy_write_lines(wme, &ctx, 0, screen_size_y(s)); screen_write_stop(&ctx); @@ -2822,7 +2822,7 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex) fy = screen_hsize(data->backing) - data->oy + data->cy; screen_init(&ss, screen_write_strlen("%s", str), 1, 0); - screen_write_start(&ctx, NULL, &ss); + screen_write_start(&ctx, &ss); screen_write_nputs(&ctx, -1, &grid_default_cell, "%s", str); screen_write_stop(&ctx); @@ -2867,7 +2867,7 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, if (ssp == NULL) { width = screen_write_strlen("%s", data->searchstr); screen_init(&ss, width, 1, 0); - screen_write_start(&ctx, NULL, &ss); + screen_write_start(&ctx, &ss); screen_write_nputs(&ctx, -1, &grid_default_cell, "%s", data->searchstr); screen_write_stop(&ctx); @@ -3207,7 +3207,7 @@ window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny) struct screen_write_ctx ctx; u_int i; - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); for (i = py; i < py + ny; i++) window_copy_write_line(wme, &ctx, i); screen_write_cursormove(&ctx, data->cx, data->cy, 0); @@ -3326,7 +3326,7 @@ window_copy_update_cursor(struct window_mode_entry *wme, u_int cx, u_int cy) if (data->cx == screen_size_x(s)) window_copy_redraw_lines(wme, data->cy, 1); else { - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); screen_write_cursormove(&ctx, data->cx, data->cy, 0); screen_write_stop(&ctx); } @@ -3579,7 +3579,7 @@ window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix, struct screen_write_ctx ctx; if (options_get_number(global_options, "set-clipboard") != 0) { - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); screen_write_setselection(&ctx, buf, len); screen_write_stop(&ctx); notify_pane("pane-set-clipboard", wp); @@ -3636,7 +3636,7 @@ window_copy_append_selection(struct window_mode_entry *wme) return; if (options_get_number(global_options, "set-clipboard") != 0) { - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); screen_write_setselection(&ctx, buf, len); screen_write_stop(&ctx); notify_pane("pane-set-clipboard", wp); @@ -4399,7 +4399,7 @@ window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) window_copy_search_marks(wme, NULL, data->searchregex); window_copy_update_selection(wme, 0, 0); - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); screen_write_cursormove(&ctx, 0, 0, 0); screen_write_deleteline(&ctx, ny, 8); window_copy_write_lines(wme, &ctx, screen_size_y(s) - ny, ny); @@ -4435,7 +4435,7 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) window_copy_search_marks(wme, NULL, data->searchregex); window_copy_update_selection(wme, 0, 0); - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); screen_write_cursormove(&ctx, 0, 0, 0); screen_write_insertline(&ctx, ny, 8); window_copy_write_lines(wme, &ctx, 0, ny); diff --git a/window.c b/window.c index 7fea9d80..b28a2257 100644 --- a/window.c +++ b/window.c @@ -995,26 +995,6 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) wp->flags |= (PANE_RESIZE|PANE_RESIZED); } -void -window_pane_alternate_on(struct window_pane *wp, struct grid_cell *gc, - int cursor) -{ - if (!options_get_number(wp->options, "alternate-screen")) - return; - screen_alternate_on(&wp->base, gc, cursor); - wp->flags |= PANE_REDRAW; -} - -void -window_pane_alternate_off(struct window_pane *wp, struct grid_cell *gc, - int cursor) -{ - if (!options_get_number(wp->options, "alternate-screen")) - return; - screen_alternate_off(&wp->base, gc, cursor); - wp->flags |= PANE_REDRAW; -} - void window_pane_set_palette(struct window_pane *wp, u_int n, int colour) { From 2df75aa11727538451d7402b5ad1579d7493ce4c Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:35:19 +0000 Subject: [PATCH 24/49] Use VIS_CSTYLE for paste buffers also. --- paste.c | 2 +- window-buffer.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paste.c b/paste.c index e98b5771..46f218d2 100644 --- a/paste.c +++ b/paste.c @@ -312,7 +312,7 @@ paste_make_sample(struct paste_buffer *pb) { char *buf; size_t len, used; - const int flags = VIS_OCTAL|VIS_TAB|VIS_NL; + const int flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL; const size_t width = 200; len = pb->size; diff --git a/window-buffer.c b/window-buffer.c index 53dfeca4..74be73a4 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -233,7 +233,7 @@ window_buffer_draw(__unused void *modedata, void *itemdata, while (end != pdata + psize && *end != '\n') end++; buf = xreallocarray(buf, 4, end - start + 1); - utf8_strvis(buf, start, end - start, VIS_OCTAL|VIS_TAB); + utf8_strvis(buf, start, end - start, VIS_OCTAL|VIS_CSTYLE|VIS_TAB); if (*buf != '\0') { screen_write_cursormove(ctx, cx, cy + i, 0); screen_write_nputs(ctx, sx, &grid_default_cell, "%s", From 3fb4d4df43b9e2bb5661d3a820cdc26a38f09d9e Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:36:57 +0000 Subject: [PATCH 25/49] Do not need to work out status line offset, we already have it. --- tty.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tty.c b/tty.c index 388f6f5f..c8efeac7 100644 --- a/tty.c +++ b/tty.c @@ -963,21 +963,16 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) /* Is this position visible in the pane? */ static int -tty_is_visible(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, - u_int nx, u_int ny) +tty_is_visible(__unused struct tty *tty, const struct tty_ctx *ctx, u_int px, + u_int py, u_int nx, u_int ny) { - u_int xoff = ctx->xoff + px, yoff = ctx->yoff + py, lines; + u_int xoff = ctx->rxoff + px, yoff = ctx->ryoff + py; if (!ctx->bigger) return (1); - if (status_at_line(tty->client) == 0) - lines = status_line_size(tty->client); - else - lines = 0; - if (xoff + nx <= ctx->wox || xoff >= ctx->wox + ctx->wsx || - yoff + ny <= ctx->woy || yoff >= lines + ctx->woy + ctx->wsy) + yoff + ny <= ctx->woy || yoff >= ctx->woy + ctx->wsy) return (0); return (1); } From d056144aa173c7b20d2a78e7d0a5134569e135db Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:38:14 +0000 Subject: [PATCH 26/49] Try to search the entire history first for up to 200 ms so a search count can be shown. If it takes too long, search the visible text only. --- menu.c | 1 + window-copy.c | 131 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 91 insertions(+), 41 deletions(-) diff --git a/menu.c b/menu.c index 07fc8fa8..62010a58 100644 --- a/menu.c +++ b/menu.c @@ -351,6 +351,7 @@ menu_display(struct menu *menu, int flags, struct cmdq_item *item, u_int px, screen_init(&md->s, menu->width + 4, menu->count + 2, 0); if (~md->flags & MENU_NOMOUSE) md->s.mode |= MODE_MOUSE_ALL; + md->s.mode &= ~MODE_CURSOR; md->px = px; md->py = py; diff --git a/window-copy.c b/window-copy.c index 9f84ade9..38e87b67 100644 --- a/window-copy.c +++ b/window-copy.c @@ -258,7 +258,8 @@ struct window_copy_mode_data { int searchregex; char *searchstr; u_char *searchmark; - u_int searchcount; + int searchcount; + int searchmore; int searchthis; int searchx; int searchy; @@ -266,7 +267,8 @@ struct window_copy_mode_data { u_char searchgen; int timeout; /* search has timed out */ -#define WINDOW_COPY_SEARCH_TIMEOUT 10 +#define WINDOW_COPY_SEARCH_TIMEOUT 10000 +#define WINDOW_COPY_SEARCH_ALL_TIMEOUT 200 int jumptype; char jumpchar; @@ -2847,6 +2849,15 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex) return (found); } +static uint64_t +window_copy_get_time(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + return ((tv.tv_sec * 1000ULL) + (tv.tv_usec / 1000ULL)); +} + static int window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, int regex) @@ -2856,13 +2867,13 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, struct screen_write_ctx ctx; struct grid *gd = s->grid; const struct grid_line *gl; - int found, cis, which = -1; + int found, cis, which = -1, stopped = 0; int cflags = REG_EXTENDED; u_int px, py, i, b, nfound = 0, width; - u_int ssize = 1; + u_int ssize = 1, start, end; char *sbuf; regex_t reg; - time_t tstart, t; + uint64_t stop = 0, tstart, t; if (ssp == NULL) { width = screen_write_strlen("%s", data->searchstr); @@ -2877,10 +2888,6 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, cis = window_copy_is_lowercase(data->searchstr); - free(data->searchmark); - data->searchmark = xcalloc(gd->hsize + gd->sy, gd->sx); - data->searchgen = 1; - if (regex) { sbuf = xmalloc(ssize); sbuf[0] = '\0'; @@ -2893,13 +2900,18 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, return (0); } } - time(&tstart); - for (py = gd->hsize - data->oy; py > 0; py--) { - gl = grid_peek_line(gd, py - 1); - if (~gl->flags & GRID_LINE_WRAPPED) - break; - } - for (; py < gd->hsize - data->oy + gd->sy; py++) { + tstart = window_copy_get_time(); + + start = 0; + end = gd->hsize + gd->sy; + stop = window_copy_get_time() + WINDOW_COPY_SEARCH_ALL_TIMEOUT; + +again: + free(data->searchmark); + data->searchmark = xcalloc(gd->hsize + gd->sy, gd->sx); + data->searchgen = 1; + + for (py = start; py < end; py++) { px = 0; for (;;) { if (regex) { @@ -2930,29 +2942,60 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, px++; } - time(&t); + t = window_copy_get_time(); if (t - tstart > WINDOW_COPY_SEARCH_TIMEOUT) { data->timeout = 1; break; } + if (stop != 0 && t > stop) { + stopped = 1; + break; + } } + if (data->timeout) { + window_copy_clear_marks(wme); + goto out; + } + + if (stopped && stop != 0) { + /* Try again but just the visible context. */ + for (start = gd->hsize - data->oy; start > 0; start--) { + gl = grid_peek_line(gd, start - 1); + if (~gl->flags & GRID_LINE_WRAPPED) + break; + } + end = gd->hsize - data->oy + gd->sy; + stop = 0; + goto again; + } + + if (stopped) { + data->searchthis = -1; + if (nfound > 1000) + data->searchcount = 1000; + else if (nfound > 100) + data->searchcount = 100; + else if (nfound > 10) + data->searchcount = 10; + else + data->searchcount = -1; + data->searchmore = 1; + } else { + if (which != -1) + data->searchthis = 1 + nfound - which; + else + data->searchthis = -1; + data->searchcount = nfound; + data->searchmore = 0; + } + +out: + if (ssp == &ss) + screen_free(&ss); if (regex) { free(sbuf); regfree(®); } - if (data->timeout) { - window_copy_clear_marks(wme); - return (1); - } - - if (which != -1) - data->searchthis = 1 + nfound - which; - else - data->searchthis = -1; - data->searchcount = nfound; - - if (ssp == &ss) - screen_free(&ss); return (1); } @@ -3057,25 +3100,28 @@ window_copy_update_style(struct window_mode_entry *wme, u_int fx, u_int fy, const struct grid_cell *cgc) { struct window_copy_mode_data *data = wme->data; - u_int mark, at, start, end, cy; + u_int mark, start, end, cy, cursor, current; u_int sx = screen_size_x(data->backing); if (data->searchmark == NULL) return; - mark = data->searchmark[(fy * sx) + fx]; + + current = (fy * sx) + fx; + + mark = data->searchmark[current]; if (mark == 0) return; cy = screen_hsize(data->backing) - data->oy + data->cy; - at = (cy * sx) + data->cx; - if (data->searchmark[at] == mark) { - window_copy_match_start_end(data, at, &start, &end); - if (at >= start && at <= end) { + cursor = (cy * sx) + data->cx; + if (data->searchmark[cursor] == mark) { + window_copy_match_start_end(data, cursor, &start, &end); + if (current >= start && current <= end) { gc->attr = cgc->attr; gc->fg = cgc->fg; gc->bg = cgc->bg; + return; } - return; } gc->attr = mgc->attr; @@ -3133,13 +3179,16 @@ window_copy_write_line(struct window_mode_entry *wme, "[%u/%u]", data->oy, hsize); } } else { - if (data->searchthis == -1) { + if (data->searchcount == -1) { size = xsnprintf(hdr, sizeof hdr, - "(%u results) [%d/%u]", data->searchcount, - data->oy, hsize); + "[%u/%u]", data->oy, hsize); + } else if (data->searchthis == -1) { + size = xsnprintf(hdr, sizeof hdr, + "(%d%s results) [%u/%u]", data->searchcount, + data->searchmore ? "+" : "", data->oy, hsize); } else { size = xsnprintf(hdr, sizeof hdr, - "(%u/%u results) [%d/%u]", data->searchthis, + "(%d/%d results) [%u/%u]", data->searchthis, data->searchcount, data->oy, hsize); } } From beb214bcb3e2a76e153d0570aed72454b9748a9b Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:40:04 +0000 Subject: [PATCH 27/49] Add formats for after hook command arguments. --- arguments.c | 54 +++++++++++++++++++++++++++++++++++++---------------- cmd-queue.c | 39 +++++++++++++++++++++++++++++++++++++- tmux.h | 2 ++ 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/arguments.c b/arguments.c index 547cec00..712e61c5 100644 --- a/arguments.c +++ b/arguments.c @@ -56,11 +56,11 @@ args_cmp(struct args_entry *a1, struct args_entry *a2) /* Find a flag in the arguments tree. */ static struct args_entry * -args_find(struct args *args, u_char ch) +args_find(struct args *args, u_char flag) { struct args_entry entry; - entry.flag = ch; + entry.flag = flag; return (RB_FIND(args_tree, &args->tree, &entry)); } @@ -249,11 +249,11 @@ args_escape(const char *s) /* Return if an argument is present. */ int -args_has(struct args *args, u_char ch) +args_has(struct args *args, u_char flag) { struct args_entry *entry; - entry = args_find(args, ch); + entry = args_find(args, flag); if (entry == NULL) return (0); return (entry->count); @@ -261,15 +261,15 @@ args_has(struct args *args, u_char ch) /* Set argument value in the arguments tree. */ void -args_set(struct args *args, u_char ch, const char *s) +args_set(struct args *args, u_char flag, const char *s) { struct args_entry *entry; struct args_value *value; - entry = args_find(args, ch); + entry = args_find(args, flag); if (entry == NULL) { entry = xcalloc(1, sizeof *entry); - entry->flag = ch; + entry->flag = flag; entry->count = 1; TAILQ_INIT(&entry->values); RB_INSERT(args_tree, &args->tree, entry); @@ -285,22 +285,44 @@ args_set(struct args *args, u_char ch, const char *s) /* Get argument value. Will be NULL if it isn't present. */ const char * -args_get(struct args *args, u_char ch) +args_get(struct args *args, u_char flag) { struct args_entry *entry; - if ((entry = args_find(args, ch)) == NULL) + if ((entry = args_find(args, flag)) == NULL) + return (NULL); + if (TAILQ_EMPTY(&entry->values)) return (NULL); return (TAILQ_LAST(&entry->values, args_values)->value); } +/* Get first argument. */ +u_char +args_first(struct args *args, struct args_entry **entry) +{ + *entry = RB_MIN(args_tree, &args->tree); + if (*entry == NULL) + return (0); + return ((*entry)->flag); +} + +/* Get next argument. */ +u_char +args_next(struct args_entry **entry) +{ + *entry = RB_NEXT(args_tree, &args->tree, *entry); + if (*entry == NULL) + return (0); + return ((*entry)->flag); +} + /* Get first value in argument. */ const char * -args_first_value(struct args *args, u_char ch, struct args_value **value) +args_first_value(struct args *args, u_char flag, struct args_value **value) { struct args_entry *entry; - if ((entry = args_find(args, ch)) == NULL) + if ((entry = args_find(args, flag)) == NULL) return (NULL); *value = TAILQ_FIRST(&entry->values); @@ -323,15 +345,15 @@ args_next_value(struct args_value **value) /* Convert an argument value to a number. */ long long -args_strtonum(struct args *args, u_char ch, long long minval, long long maxval, - char **cause) +args_strtonum(struct args *args, u_char flag, long long minval, + long long maxval, char **cause) { const char *errstr; long long ll; struct args_entry *entry; struct args_value *value; - if ((entry = args_find(args, ch)) == NULL) { + if ((entry = args_find(args, flag)) == NULL) { *cause = xstrdup("missing"); return (0); } @@ -349,13 +371,13 @@ args_strtonum(struct args *args, u_char ch, long long minval, long long maxval, /* Convert an argument to a number which may be a percentage. */ long long -args_percentage(struct args *args, u_char ch, long long minval, +args_percentage(struct args *args, u_char flag, long long minval, long long maxval, long long curval, char **cause) { const char *value; struct args_entry *entry; - if ((entry = args_find(args, ch)) == NULL) { + if ((entry = args_find(args, flag)) == NULL) { *cause = xstrdup("missing"); return (0); } diff --git a/cmd-queue.c b/cmd-queue.c index 59f86c64..620a3e93 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -341,9 +341,15 @@ cmdq_insert_hook(struct session *s, struct cmdq_item *item, struct cmd_find_state *current, const char *fmt, ...) { struct cmdq_state *state = item->state; + struct cmd *cmd = item->cmd; + struct args *args = cmd_get_args(cmd); + struct args_entry *entryp; + struct args_value *valuep; struct options *oo; va_list ap; - char *name; + char *name, tmp[32], flag, *arguments; + int i; + const char *value; struct cmdq_item *new_item; struct cmdq_state *new_state; struct options_entry *o; @@ -375,6 +381,37 @@ cmdq_insert_hook(struct session *s, struct cmdq_item *item, new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS); cmdq_add_format(new_state, "hook", "%s", name); + arguments = args_print(args); + cmdq_add_format(new_state, "hook_arguments", "%s", arguments); + free(arguments); + + for (i = 0; i < args->argc; i++) { + xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i); + cmdq_add_format(new_state, tmp, "%s", args->argv[i]); + } + flag = args_first(args, &entryp); + while (flag != 0) { + value = args_get(args, flag); + if (value == NULL) { + xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); + cmdq_add_format(new_state, tmp, "1"); + } else { + xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); + cmdq_add_format(new_state, tmp, "%s", value); + } + + i = 0; + value = args_first_value(args, flag, &valuep); + while (value != NULL) { + xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i); + cmdq_add_format(new_state, tmp, "%s", value); + i++; + value = args_next_value(&valuep); + } + + flag = args_next(&entryp); + } + a = options_array_first(o); while (a != NULL) { cmdlist = options_array_item_value(a)->cmdlist; diff --git a/tmux.h b/tmux.h index 53417cd7..811b5fb6 100644 --- a/tmux.h +++ b/tmux.h @@ -2088,6 +2088,8 @@ char *args_print(struct args *); char *args_escape(const char *); int args_has(struct args *, u_char); const char *args_get(struct args *, u_char); +u_char args_first(struct args *, struct args_entry **); +u_char args_next(struct args_entry **); const char *args_first_value(struct args *, u_char, struct args_value **); const char *args_next_value(struct args_value **); long long args_strtonum(struct args *, u_char, long long, long long, From 4e053685df9f4d1c398148712ab0529a7e9d32e7 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:40:44 +0000 Subject: [PATCH 28/49] Export TERM_PROGRAM and TERM_PROGRAM_VERSION like various other terminals. --- environ.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/environ.c b/environ.c index 0ce717df..940109b0 100644 --- a/environ.c +++ b/environ.c @@ -252,6 +252,8 @@ environ_for_session(struct session *s, int no_TERM) if (!no_TERM) { value = options_get_string(global_options, "default-terminal"); environ_set(env, "TERM", 0, "%s", value); + environ_set(env, "TERM_PROGRAM", 0, "%s", "tmux"); + environ_set(env, "TERM_PROGRAM_VERSION", 0, "%s", getversion()); } if (s != NULL) From 469eda7e44fe6d502c976ebc34bbd97e6c6ed3e5 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:41:54 +0000 Subject: [PATCH 29/49] Only redraw popup on the client it belongs to. --- popup.c | 4 +++- tmux.1 | 2 +- tty-keys.c | 5 +---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/popup.c b/popup.c index 9937d586..4b47df2b 100644 --- a/popup.c +++ b/popup.c @@ -70,8 +70,10 @@ popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c) { struct popup_data *pd = ttyctx->arg; + if (c != pd->c) + return (0); if (pd->c->flags & CLIENT_REDRAWOVERLAY) - return (-1); + return (0); ttyctx->bigger = 0; ttyctx->wox = 0; diff --git a/tmux.1 b/tmux.1 index 5b7bb3db..eafa3751 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4553,7 +4553,7 @@ The following variables are available, where appropriate: .It Li "pane_marked" Ta "" Ta "1 if this is the marked pane" .It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set" .It Li "pane_mode" Ta "" Ta "Name of pane mode, if any" -.It Li "pane_path" Ta "#T" Ta "Path of pane (can be set by application)" +.It Li "pane_path" Ta "" Ta "Path of pane (can be set by application)" .It Li "pane_pid" Ta "" Ta "PID of first process in pane" .It Li "pane_pipe" Ta "" Ta "1 if pane is being piped" .It Li "pane_right" Ta "" Ta "Right of pane" diff --git a/tty-keys.c b/tty-keys.c index f5a3418f..dc064a17 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1064,10 +1064,7 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, /* Add terminal features. */ switch (p[0]) { case 41: /* VT420 */ - tty_add_features(&c->term_features, - "margins," - "rectfill", - ","); + tty_add_features(&c->term_features, "margins,rectfill", ","); break; case 'M': /* mintty */ tty_default_features(&c->term_features, "mintty", 0); From cf9baddd6f844e8a26f1e7c59ba1c9eb3358571f Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:45:29 +0000 Subject: [PATCH 30/49] Change the existing client flags for control mode to apply for any client, use the same mechanism for the read-only flag and add an ignore-size flag. refresh-client -F has become -f (-F stays for backwards compatibility) and attach-session and switch-client now have -f flags also. A new format "client_flags" lists the flags and is shown by list-clients by default. This separates the read-only flag from "ignore size" behaviour (new ignore-size) flag - both behaviours are useful in different circumstances. attach -r and switchc -r remain and set or toggle both flags together. --- cmd-attach-session.c | 15 +++++++----- cmd-list-clients.c | 8 +++--- cmd-new-session.c | 8 +++--- cmd-refresh-client.c | 25 ++++++------------- cmd-switch-client.c | 10 +++++--- format.c | 2 +- resize.c | 8 +++--- server-client.c | 58 ++++++++++++++++++++++++++++++++++++++++++++ tmux.1 | 46 +++++++++++++++++++++++++++-------- tmux.h | 6 +++-- 10 files changed, 136 insertions(+), 50 deletions(-) diff --git a/cmd-attach-session.c b/cmd-attach-session.c index 8c30c767..38d9c024 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -37,8 +37,9 @@ const struct cmd_entry cmd_attach_session_entry = { .name = "attach-session", .alias = "attach", - .args = { "c:dErt:x", 0, 0 }, - .usage = "[-dErx] [-c working-directory] " CMD_TARGET_SESSION_USAGE, + .args = { "c:dEf:rt:x", 0, 0 }, + .usage = "[-dErx] [-c working-directory] [-f flags] " + CMD_TARGET_SESSION_USAGE, /* -t is special */ @@ -48,7 +49,7 @@ const struct cmd_entry cmd_attach_session_entry = { enum cmd_retval cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, - int xflag, int rflag, const char *cflag, int Eflag) + int xflag, int rflag, const char *cflag, int Eflag, const char *fflag) { struct cmd_find_state *current = cmdq_get_current(item); struct cmd_find_state target; @@ -101,6 +102,10 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, free((void *)s->cwd); s->cwd = format_single(item, cflag, c, s, wl, wp); } + if (fflag) + server_client_set_flags(c, fflag); + if (rflag) + c->flags |= (CLIENT_READONLY|CLIENT_IGNORESIZE); c->last_session = c->session; if (c->session != NULL) { @@ -135,8 +140,6 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, free(cause); return (CMD_RETURN_ERROR); } - if (rflag) - c->flags |= CLIENT_READONLY; if (dflag || xflag) { if (xflag) @@ -182,5 +185,5 @@ cmd_attach_session_exec(struct cmd *self, struct cmdq_item *item) return (cmd_attach_session(item, args_get(args, 't'), args_has(args, 'd'), args_has(args, 'x'), args_has(args, 'r'), - args_get(args, 'c'), args_has(args, 'E'))); + args_get(args, 'c'), args_has(args, 'E'), args_get(args, 'f'))); } diff --git a/cmd-list-clients.c b/cmd-list-clients.c index 75118c8e..d450f017 100644 --- a/cmd-list-clients.c +++ b/cmd-list-clients.c @@ -28,10 +28,10 @@ * List all clients. */ -#define LIST_CLIENTS_TEMPLATE \ - "#{client_name}: #{session_name} " \ - "[#{client_width}x#{client_height} #{client_termname}]" \ - "#{?client_utf8, (utf8),} #{?client_readonly, (ro),}" +#define LIST_CLIENTS_TEMPLATE \ + "#{client_name}: #{session_name} " \ + "[#{client_width}x#{client_height} #{client_termname}] " \ + "#{?client_flags,(,}#{client_flags}#{?client_flags,),}" static enum cmd_retval cmd_list_clients_exec(struct cmd *, struct cmdq_item *); diff --git a/cmd-new-session.c b/cmd-new-session.c index 9815e1e1..a9a0376b 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -39,9 +39,9 @@ const struct cmd_entry cmd_new_session_entry = { .name = "new-session", .alias = "new", - .args = { "Ac:dDe:EF:n:Ps:t:x:Xy:", 0, -1 }, + .args = { "Ac:dDe:EF:f:n:Ps:t:x:Xy:", 0, -1 }, .usage = "[-AdDEPX] [-c start-directory] [-e environment] [-F format] " - "[-n window-name] [-s session-name] " + "[-f flags] [-n window-name] [-s session-name] " CMD_TARGET_SESSION_USAGE " [-x width] [-y height] [command]", .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL }, @@ -112,7 +112,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (as != NULL) { retval = cmd_attach_session(item, as->name, args_has(args, 'D'), args_has(args, 'X'), 0, NULL, - args_has(args, 'E')); + args_has(args, 'E'), args_get(args, 'f')); free(newname); return (retval); } @@ -306,6 +306,8 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) * taking this session and needs to get MSG_READY and stay around. */ if (!detached) { + if (args_has(args, 'f')) + server_client_set_flags(c, args_get(args, 'f')); if (!already_attached) { if (~c->flags & CLIENT_CONTROL) proc_send(c->peer, MSG_READY, -1, NULL, 0); diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index 5514ff73..c53a6a78 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -34,8 +34,8 @@ const struct cmd_entry cmd_refresh_client_entry = { .name = "refresh-client", .alias = "refresh", - .args = { "cC:DF:lLRSt:U", 0, 1 }, - .usage = "[-cDlLRSU] [-C XxY] [-F flags] " CMD_TARGET_CLIENT_USAGE + .args = { "cC:Df:F:lLRSt:U", 0, 1 }, + .usage = "[-cDlLRSU] [-C XxY] [-f flags] " CMD_TARGET_CLIENT_USAGE " [adjustment]", .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG, @@ -50,7 +50,6 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) struct tty *tty = &tc->tty; struct window *w; const char *size, *errstr; - char *copy, *next, *s; u_int x, y, adjust; if (args_has(args, 'c') || @@ -108,7 +107,12 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } - if (args_has(args, 'C') || args_has(args, 'F')) { + if (args_has(args, 'F')) /* -F is an alias for -f */ + server_client_set_flags(tc, args_get(args, 'F')); + if (args_has(args, 'f')) + server_client_set_flags(tc, args_get(args, 'f')); + + if (args_has(args, 'C')) { if (args_has(args, 'C')) { if (!(tc->flags & CLIENT_CONTROL)) { cmdq_error(item, "not a control client"); @@ -129,19 +133,6 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) tc->flags |= CLIENT_SIZECHANGED; recalculate_sizes(); } - if (args_has(args, 'F')) { - if (!(tc->flags & CLIENT_CONTROL)) { - cmdq_error(item, "not a control client"); - return (CMD_RETURN_ERROR); - } - s = copy = xstrdup(args_get(args, 'F')); - while ((next = strsep(&s, ",")) != NULL) { - /* Unknown flags are ignored. */ - if (strcmp(next, "no-output") == 0) - tc->flags |= CLIENT_CONTROL_NOOUTPUT; - } - free(copy); - } return (CMD_RETURN_NORMAL); } diff --git a/cmd-switch-client.c b/cmd-switch-client.c index 82510ce6..d062b946 100644 --- a/cmd-switch-client.c +++ b/cmd-switch-client.c @@ -34,7 +34,7 @@ const struct cmd_entry cmd_switch_client_entry = { .name = "switch-client", .alias = "switchc", - .args = { "lc:Enpt:rT:Z", 0, 0 }, + .args = { "lc:EFnpt:rT:Z", 0, 0 }, .usage = "[-ElnprZ] [-c target-client] [-t target-session] " "[-T key-table]", @@ -74,8 +74,12 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) wl = target.wl; wp = target.wp; - if (args_has(args, 'r')) - tc->flags ^= CLIENT_READONLY; + if (args_has(args, 'r')) { + if (tc->flags & CLIENT_READONLY) + tc->flags &= ~(CLIENT_READONLY|CLIENT_IGNORESIZE); + else + tc->flags |= (CLIENT_READONLY|CLIENT_IGNORESIZE); + } tablename = args_get(args, 'T'); if (tablename != NULL) { diff --git a/format.c b/format.c index a2b2822f..3b9af626 100644 --- a/format.c +++ b/format.c @@ -2678,11 +2678,11 @@ format_defaults_client(struct format_tree *ft, struct client *c) format_add(ft, "client_utf8", "%d", 1); else format_add(ft, "client_utf8", "%d", 0); - if (c->flags & CLIENT_READONLY) format_add(ft, "client_readonly", "%d", 1); else format_add(ft, "client_readonly", "%d", 0); + format_add(ft, "client_flags", "%s", server_client_get_flags(c)); s = c->session; if (s != NULL) diff --git a/resize.c b/resize.c index 96d733f0..15d146d8 100644 --- a/resize.c +++ b/resize.c @@ -72,17 +72,17 @@ ignore_client_size(struct client *c) return (1); if (c->flags & CLIENT_NOSIZEFLAGS) return (1); - if (c->flags & CLIENT_READONLY) { + if (c->flags & CLIENT_IGNORESIZE) { /* - * Ignore readonly clients if there are any attached clients - * that aren't readonly. + * Ignore flagged clients if there are any attached clients + * that aren't flagged. */ TAILQ_FOREACH (loop, &clients, entry) { if (loop->session == NULL) continue; if (loop->flags & CLIENT_NOSIZEFLAGS) continue; - if (~loop->flags & CLIENT_READONLY) + if (~loop->flags & CLIENT_IGNORESIZE) return (1); } } diff --git a/server-client.c b/server-client.c index a45e9812..86b7a1b8 100644 --- a/server-client.c +++ b/server-client.c @@ -2253,3 +2253,61 @@ server_client_get_cwd(struct client *c, struct session *s) return (home); return ("/"); } + +/* Set client flags. */ +void +server_client_set_flags(struct client *c, const char *flags) +{ + char *s, *copy, *next; + int flag, not; + + s = copy = xstrdup (flags); + while ((next = strsep(&s, ",")) != NULL) { + not = (*next == '!'); + if (not) + next++; + + if (strcmp(next, "no-output") == 0) + flag = CLIENT_CONTROL_NOOUTPUT; + else if (strcmp(next, "read-only") == 0) + flag = CLIENT_READONLY; + else if (strcmp(next, "ignore-size") == 0) + flag = CLIENT_IGNORESIZE; + else + continue; + + log_debug("client %s set flag %s", c->name, next); + if (not) + c->flags &= ~flag; + else + c->flags |= flag; + } + free(copy); + +} + +/*Get client flags. This is only flags useful to show to users. */ +const char * +server_client_get_flags(struct client *c) +{ + static char s[256]; + + *s = '\0'; + if (c->flags & CLIENT_ATTACHED) + strlcat(s, "attached,", sizeof s); + if (c->flags & CLIENT_CONTROL) + strlcat(s, "control-mode,", sizeof s); + if (c->flags & CLIENT_IGNORESIZE) + strlcat(s, "ignore-size,", sizeof s); + if (c->flags & CLIENT_CONTROL_NOOUTPUT) + strlcat(s, "no-output,", sizeof s); + if (c->flags & CLIENT_READONLY) + strlcat(s, "read-only,", sizeof s); + if (c->flags & CLIENT_SUSPENDED) + strlcat(s, "suspended,", sizeof s); + if (c->flags & CLIENT_UTF8) + strlcat(s, "UTF-8,", sizeof s); + if (*s != '\0') + s[strlen(s) - 1] = '\0'; + return (s); +} diff --git a/tmux.1 b/tmux.1 index eafa3751..97d57d5b 100644 --- a/tmux.1 +++ b/tmux.1 @@ -947,6 +947,7 @@ The following commands are available to manage clients and sessions: .It Xo Ic attach-session .Op Fl dErx .Op Fl c Ar working-directory +.Op Fl f Ar flags .Op Fl t Ar target-session .Xc .D1 (alias: Ic attach ) @@ -964,12 +965,30 @@ is given, send .Dv SIGHUP to the parent process of the client as well as detaching the client, typically causing it to exit. +.Fl f +sets a comma-separated list of client flags. +The flags are: +.Bl -tag -width Ds +.It read-only +the client is read-only +.It ignore-size +the client does not affect the size of other clients +.It no-output +the client does not receive pane output in control mode +.El +.Pp +A leading +.Ql ! +turns a flag off if the client is already attached. .Fl r -signifies the client is read-only (only keys bound to the +is an alias for +.Fl f +.Ar read-only,ignore-size . +When a client is read-only, only keys bound to the .Ic detach-client or .Ic switch-client -commands have any effect) +commands have any effect. .Pp If no server is started, .Ic attach-session @@ -1095,6 +1114,7 @@ Lock all clients attached to .Op Fl AdDEPX .Op Fl c Ar start-directory .Op Fl e Ar environment +.Op Fl f Ar flags .Op Fl F Ar format .Op Fl n Ar window-name .Op Fl s Ar session-name @@ -1132,6 +1152,9 @@ or is given, the .Ic default-size option is set for the session. +.Fl f +sets a comma-separated list of client flags (see +.Ic attach-session ) . .Pp If run from a terminal, any .Xr termios 4 @@ -1209,7 +1232,7 @@ specified multiple times. .It Xo Ic refresh-client .Op Fl cDlLRSU .Op Fl C Ar XxY -.Op Fl F Ar flags +.Op Fl f Ar flags .Op Fl t Ar target-client .Op Ar adjustment .Xc @@ -1252,12 +1275,10 @@ window, changing the current window in the attached session will reset it. .Pp .Fl C -sets the width and height of a control client and -.Fl F -sets a comma-separated list of flags. -Currently the only flag available is -.Ql no-output -to disable receiving pane output. +sets the width and height of a control client. +.Fl f +sets a comma-separated list of client flags, see +.Ic attach-session . .Pp .Fl l requests the clipboard from the client using the @@ -1377,7 +1398,11 @@ or is used, the client is moved to the last, next or previous session respectively. .Fl r -toggles whether a client is read-only (see the +toggles the client +.Ic read-only +and +.Ic ignore-size +flags (see the .Ic attach-session command). .Pp @@ -4480,6 +4505,7 @@ The following variables are available, where appropriate: .It Li "client_control_mode" Ta "" Ta "1 if client is in control mode" .It Li "client_created" Ta "" Ta "Time client created" .It Li "client_discarded" Ta "" Ta "Bytes discarded when client behind" +.It Li "client_flags" Ta "" Ta "List of client flags" .It Li "client_height" Ta "" Ta "Height of client" .It Li "client_key_table" Ta "" Ta "Current key table" .It Li "client_last_session" Ta "" Ta "Name of the client's last session" diff --git a/tmux.h b/tmux.h index 811b5fb6..7a687c09 100644 --- a/tmux.h +++ b/tmux.h @@ -1568,7 +1568,7 @@ struct client { #define CLIENT_CONTROLCONTROL 0x4000 #define CLIENT_FOCUSED 0x8000 #define CLIENT_UTF8 0x10000 -/* 0x20000 unused */ +#define CLIENT_IGNORESIZE 0x20000 #define CLIENT_IDENTIFIED 0x40000 #define CLIENT_STATUSFORCE 0x80000 #define CLIENT_DOUBLECLICK 0x100000 @@ -2163,7 +2163,7 @@ char *cmd_template_replace(const char *, const char *, int); /* cmd-attach-session.c */ enum cmd_retval cmd_attach_session(struct cmdq_item *, const char *, int, int, - int, const char *, int); + int, const char *, int, const char *); /* cmd-parse.c */ void cmd_parse_empty(struct cmd_parse_input *); @@ -2302,6 +2302,8 @@ void server_client_push_stderr(struct client *); void printflike(2, 3) server_client_add_message(struct client *, const char *, ...); const char *server_client_get_cwd(struct client *, struct session *); +void server_client_set_flags(struct client *, const char *); +const char *server_client_get_flags(struct client *); /* server-fn.c */ void server_redraw_client(struct client *); From 4de0bd4c5c9eec6dd71cac87b91e736944896c22 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:46:01 +0000 Subject: [PATCH 31/49] Add M-+ and M-- to expand and collapse all items in tree mode. --- mode-tree.c | 12 +++++++++++- tmux.1 | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/mode-tree.c b/mode-tree.c index 8d210d72..c08c802d 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -887,7 +887,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, struct mouse_event *m, u_int *xp, u_int *yp) { struct mode_tree_line *line; - struct mode_tree_item *current, *parent; + struct mode_tree_item *current, *parent, *mti; u_int i, x, y; int choice; key_code tmp; @@ -1058,6 +1058,16 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mode_tree_build(mtd); } break; + case '-'|KEYC_ESCAPE: + TAILQ_FOREACH(mti, &mtd->children, entry) + mti->expanded = 0; + mode_tree_build(mtd); + break; + case '+'|KEYC_ESCAPE: + TAILQ_FOREACH(mti, &mtd->children, entry) + mti->expanded = 1; + mode_tree_build(mtd); + break; case '?': case '/': case '\023': /* C-s */ diff --git a/tmux.1 b/tmux.1 index 97d57d5b..d115329d 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1903,6 +1903,10 @@ The following keys may be used in tree mode: .It Li "Enter" Ta "Choose selected item" .It Li "Up" Ta "Select previous item" .It Li "Down" Ta "Select next item" +.It Li "+" Ta "Expand selected item" +.It Li "-" Ta "Collapse selected item" +.It Li "M-+" Ta "Expand all items" +.It Li "M--" Ta "Collapse all items" .It Li "x" Ta "Kill selected item" .It Li "X" Ta "Kill tagged items" .It Li "<" Ta "Scroll list of previews left" From 367b4e4e0f1d78589398cddb609b33be870af30c Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:47:22 +0000 Subject: [PATCH 32/49] Change message log to be per server rather than per client and include every command that is run. --- cmd-queue.c | 25 +++++++++++++++++++++++ cmd-show-messages.c | 18 +++++++++++------ format.c | 4 +--- options-table.c | 2 +- server-client.c | 49 +-------------------------------------------- server.c | 35 ++++++++++++++++++++++++++++++++ status.c | 2 +- tmux.1 | 9 ++------- tmux.h | 18 +++++++++-------- 9 files changed, 88 insertions(+), 74 deletions(-) diff --git a/cmd-queue.c b/cmd-queue.c index 620a3e93..26e2f2f9 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -534,6 +534,28 @@ cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, return (CMD_RETURN_NORMAL); } +/* Add message with command. */ +static void +cmdq_add_message(struct cmdq_item *item) +{ + struct client *c = item->client; + struct cmdq_state *state = item->state; + const char *name, *key; + char *tmp; + + tmp = cmd_print(item->cmd); + if (c != NULL) { + name = c->name; + if (c->session != NULL && state->event.key != KEYC_NONE) { + key = key_string_lookup_key(state->event.key); + server_add_message("%s key %s: %s", name, key, tmp); + } else + server_add_message("%s command: %s", name, tmp); + } else + server_add_message("command: %s", tmp); + free(tmp); +} + /* Fire command on command queue. */ static enum cmd_retval cmdq_fire_command(struct cmdq_item *item) @@ -549,6 +571,8 @@ cmdq_fire_command(struct cmdq_item *item) int flags, quiet = 0; char *tmp; + if (cfg_finished) + cmdq_add_message(item); if (log_get_level() > 1) { tmp = cmd_print(cmd); log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp); @@ -819,6 +843,7 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...) cmd_get_source(cmd, &file, &line); cfg_add_cause("%s:%u: %s", file, line, msg); } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { + server_add_message("%s message: %s", c->name, msg); if (~c->flags & CLIENT_UTF8) { tmp = msg; msg = utf8_sanitize(tmp); diff --git a/cmd-show-messages.c b/cmd-show-messages.c index d734ca65..2fa68f2a 100644 --- a/cmd-show-messages.c +++ b/cmd-show-messages.c @@ -72,10 +72,10 @@ static enum cmd_retval cmd_show_messages_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); - struct client *tc = cmdq_get_target_client(item); struct message_entry *msg; - char *tim; + char *s; int done, blank; + struct format_tree *ft; done = blank = 0; if (args_has(args, 'T')) { @@ -89,11 +89,17 @@ cmd_show_messages_exec(struct cmd *self, struct cmdq_item *item) if (done) return (CMD_RETURN_NORMAL); - TAILQ_FOREACH(msg, &tc->message_log, entry) { - tim = ctime(&msg->msg_time); - *strchr(tim, '\n') = '\0'; - cmdq_print(item, "%s %s", tim, msg->msg); + ft = format_create_from_target(item); + TAILQ_FOREACH_REVERSE(msg, &message_log, message_list, entry) { + format_add(ft, "message_text", "%s", msg->msg); + format_add(ft, "message_number", "%u", msg->msg_num); + format_add_tv(ft, "message_time", &msg->msg_time); + + s = format_expand(ft, SHOW_MESSAGES_TEMPLATE); + cmdq_print(item, "%s", s); + free(s); } + format_free(ft); return (CMD_RETURN_NORMAL); } diff --git a/format.c b/format.c index 3b9af626..9f01588d 100644 --- a/format.c +++ b/format.c @@ -46,8 +46,6 @@ static void format_job_timer(int, short, void *); static char *format_find(struct format_tree *, const char *, int); static void format_add_cb(struct format_tree *, const char *, format_cb); -static void format_add_tv(struct format_tree *, const char *, - struct timeval *); static int format_replace(struct format_tree *, const char *, size_t, char **, size_t *, size_t *); static void format_defaults_session(struct format_tree *, @@ -1260,7 +1258,7 @@ format_add(struct format_tree *ft, const char *key, const char *fmt, ...) } /* Add a key and time. */ -static void +void format_add_tv(struct format_tree *ft, const char *key, struct timeval *tv) { struct format_entry *fe, *fe_now; diff --git a/options-table.c b/options-table.c index 5ca63a78..320a380f 100644 --- a/options-table.c +++ b/options-table.c @@ -253,7 +253,7 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .minimum = 0, .maximum = INT_MAX, - .default_num = 100 + .default_num = 1000 }, { .name = "set-clipboard", diff --git a/server-client.c b/server-client.c index 86b7a1b8..0bd00fc4 100644 --- a/server-client.c +++ b/server-client.c @@ -213,23 +213,12 @@ server_client_create(int fd) c->queue = cmdq_new(); c->tty.fd = -1; - c->title = NULL; - - c->session = NULL; - c->last_session = NULL; c->tty.sx = 80; c->tty.sy = 24; status_init(c); - c->message_string = NULL; - TAILQ_INIT(&c->message_log); - - c->prompt_string = NULL; - c->prompt_buffer = NULL; - c->prompt_index = 0; - RB_INIT(&c->files); c->flags |= CLIENT_FOCUSED; @@ -272,7 +261,6 @@ server_client_open(struct client *c, char **cause) void server_client_lost(struct client *c) { - struct message_entry *msg, *msg1; struct client_file *cf; c->flags |= CLIENT_DEAD; @@ -313,11 +301,6 @@ server_client_lost(struct client *c) free(c->message_string); if (event_initialized(&c->message_timer)) evtimer_del(&c->message_timer); - TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) { - free(msg->msg); - TAILQ_REMOVE(&c->message_log, msg, entry); - free(msg); - } free(c->prompt_saved); free(c->prompt_string); @@ -1128,6 +1111,7 @@ server_client_key_callback(struct cmdq_item *item, void *data) c->tty.mouse_drag_update(c, m); goto out; } + event->key = key; } /* Find affected pane. */ @@ -2204,37 +2188,6 @@ server_client_dispatch_read_done(struct client *c, struct imsg *imsg) file_fire_done(cf); } -/* Add to client message log. */ -void -server_client_add_message(struct client *c, const char *fmt, ...) -{ - struct message_entry *msg, *msg1; - char *s; - va_list ap; - u_int limit; - - va_start(ap, fmt); - xvasprintf(&s, fmt, ap); - va_end(ap); - - log_debug("message %s (client %p)", s, c); - - msg = xcalloc(1, sizeof *msg); - msg->msg_time = time(NULL); - msg->msg_num = c->message_next++; - msg->msg = s; - TAILQ_INSERT_TAIL(&c->message_log, msg, entry); - - limit = options_get_number(global_options, "message-limit"); - TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) { - if (msg->msg_num + limit >= c->message_next) - break; - free(msg->msg); - TAILQ_REMOVE(&c->message_log, msg, entry); - free(msg); - } -} - /* Get client working directory. */ const char * server_client_get_cwd(struct client *c, struct session *s) diff --git a/server.c b/server.c index be655b6a..a4216b87 100644 --- a/server.c +++ b/server.c @@ -51,6 +51,9 @@ static struct event server_ev_accept; struct cmd_find_state marked_pane; +static u_int message_next; +struct message_list message_log; + static int server_loop(void); static void server_send_exit(void); static void server_accept(int, short, void *); @@ -195,6 +198,7 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base, TAILQ_INIT(&clients); RB_INIT(&sessions); key_bindings_init(); + TAILQ_INIT(&message_log); gettimeofday(&start_time, NULL); @@ -483,3 +487,34 @@ server_child_stopped(pid_t pid, int status) } job_check_died(pid, status); } + +/* Add to message log. */ +void +server_add_message(const char *fmt, ...) +{ + struct message_entry *msg, *msg1; + char *s; + va_list ap; + u_int limit; + + va_start(ap, fmt); + xvasprintf(&s, fmt, ap); + va_end(ap); + + log_debug("message: %s", s); + + msg = xcalloc(1, sizeof *msg); + gettimeofday(&msg->msg_time, NULL); + msg->msg_num = message_next++; + msg->msg = s; + TAILQ_INSERT_TAIL(&message_log, msg, entry); + + limit = options_get_number(global_options, "message-limit"); + TAILQ_FOREACH_SAFE(msg, &message_log, entry, msg1) { + if (msg->msg_num + limit >= message_next) + break; + free(msg->msg); + TAILQ_REMOVE(&message_log, msg, entry); + free(msg); + } +} diff --git a/status.c b/status.c index b5442550..375cad4d 100644 --- a/status.c +++ b/status.c @@ -436,7 +436,7 @@ status_message_set(struct client *c, const char *fmt, ...) xvasprintf(&c->message_string, fmt, ap); va_end(ap); - server_client_add_message(c, "%s", c->message_string); + server_add_message("%s message: %s", c->name, c->message_string); delay = options_get_number(c->session->options, "display-time"); if (delay > 0) { diff --git a/tmux.1 b/tmux.1 index d115329d..bac95e88 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1311,15 +1311,10 @@ Rename the session to .Op Fl t Ar target-client .Xc .D1 (alias: Ic showmsgs ) -Show client messages or server information. -Any messages displayed on the status line are saved in a per-client message -log, up to a maximum of the limit set by the +Show server messages or information. +Messages are stored, up to a maximum of the limit set by the .Ar message-limit server option. -With -.Fl t , -display the log for -.Ar target-client . .Fl J and .Fl T diff --git a/tmux.h b/tmux.h index 7a687c09..3c074519 100644 --- a/tmux.h +++ b/tmux.h @@ -1341,11 +1341,13 @@ struct tty_ctx { /* Saved message entry. */ struct message_entry { - char *msg; - u_int msg_num; - time_t msg_time; - TAILQ_ENTRY(message_entry) entry; + char *msg; + u_int msg_num; + struct timeval msg_time; + + TAILQ_ENTRY(message_entry) entry; }; +TAILQ_HEAD(message_list, message_entry); /* Parsed arguments structures. */ struct args_entry; @@ -1603,8 +1605,6 @@ struct client { char *message_string; struct event message_timer; - u_int message_next; - TAILQ_HEAD(, message_entry) message_log; char *prompt_string; struct utf8_data *prompt_buffer; @@ -1846,6 +1846,8 @@ void format_free(struct format_tree *); void format_merge(struct format_tree *, struct format_tree *); void printflike(3, 4) format_add(struct format_tree *, const char *, const char *, ...); +void format_add_tv(struct format_tree *, const char *, + struct timeval *); void format_each(struct format_tree *, void (*)(const char *, const char *, void *), void *); char *format_expand_time(struct format_tree *, const char *); @@ -2269,6 +2271,7 @@ void file_push(struct client_file *); extern struct tmuxproc *server_proc; extern struct clients clients; extern struct cmd_find_state marked_pane; +extern struct message_list message_log; void server_set_marked(struct session *, struct winlink *, struct window_pane *); void server_clear_marked(void); @@ -2278,6 +2281,7 @@ int server_check_marked(void); int server_start(struct tmuxproc *, int, struct event_base *, int, char *); void server_update_socket(void); void server_add_accept(int); +void printflike(1, 2) server_add_message(const char *, ...); /* server-client.c */ u_int server_client_how_many(void); @@ -2299,8 +2303,6 @@ void server_client_exec(struct client *, const char *); void server_client_loop(void); void server_client_push_stdout(struct client *); void server_client_push_stderr(struct client *); -void printflike(2, 3) server_client_add_message(struct client *, const char *, - ...); const char *server_client_get_cwd(struct client *, struct session *); void server_client_set_flags(struct client *, const char *); const char *server_client_get_flags(struct client *); From daa95810b53c25d10c90b85ef6fbf8ab32479e23 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:48:35 +0000 Subject: [PATCH 33/49] Allow a custom time format to be given to the t format modifier. --- format.c | 55 ++++++++++++++++++++++++++++++++++++++++++++----------- tmux.1 | 15 +++++++++++++++ 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/format.c b/format.c index 9f01588d..9cc8195c 100644 --- a/format.c +++ b/format.c @@ -44,7 +44,6 @@ typedef void (*format_cb)(struct format_tree *, struct format_entry *); static char *format_job_get(struct format_tree *, const char *); static void format_job_timer(int, short, void *); -static char *format_find(struct format_tree *, const char *, int); static void format_add_cb(struct format_tree *, const char *, format_cb); static int format_replace(struct format_tree *, const char *, size_t, char **, size_t *, size_t *); @@ -122,8 +121,8 @@ struct format_tree { struct cmdq_item *item; struct client *client; - u_int tag; int flags; + u_int tag; time_t time; u_int loop; @@ -1369,16 +1368,17 @@ format_pretty_time(time_t t) /* Find a format entry. */ static char * -format_find(struct format_tree *ft, const char *key, int modifiers) +format_find(struct format_tree *ft, const char *key, int modifiers, + const char *time_format) { struct format_entry *fe, fe_find; struct environ_entry *envent; - static char s[64]; struct options_entry *o; int idx; - char *found = NULL, *saved; + char *found = NULL, *saved, s[512]; const char *errstr; time_t t = 0; + struct tm tm; o = options_parse_get(global_options, key, &idx, 0); if (o == NULL && ft->wp != NULL) @@ -1438,8 +1438,13 @@ found: if (modifiers & FORMAT_PRETTY) found = format_pretty_time(t); else { - ctime_r(&t, s); - s[strcspn(s, "\n")] = '\0'; + if (time_format != NULL) { + localtime_r(&t, &tm); + strftime(s, sizeof s, time_format, &tm); + } else { + ctime_r(&t, s); + s[strcspn(s, "\n")] = '\0'; + } found = xstrdup(s); } return (found); @@ -1467,6 +1472,30 @@ found: return (found); } +/* Remove escaped characters from string. */ +static char * +format_strip(const char *s) +{ + char *out, *cp; + int brackets = 0; + + cp = out = xmalloc(strlen(s) + 1); + for (; *s != '\0'; s++) { + if (*s == '#' && s[1] == '{') + brackets++; + if (*s == '#' && strchr(",#{}:", s[1]) != NULL) { + if (brackets != 0) + *cp++ = *s; + continue; + } + if (*s == '}') + brackets--; + *cp++ = *s; + } + *cp = '\0'; + return (out); +} + /* Skip until end. */ const char * format_skip(const char *s, const char *end) @@ -1476,7 +1505,7 @@ format_skip(const char *s, const char *end) for (; *s != '\0'; s++) { if (*s == '#' && s[1] == '{') brackets++; - if (*s == '#' && strchr(",#{}", s[1]) != NULL) { + if (*s == '#' && strchr(",#{}:", s[1]) != NULL) { s++; continue; } @@ -1584,7 +1613,7 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) *count = 0; while (*cp != '\0' && *cp != ':') { - /* Skip and separator character. */ + /* Skip any separator character. */ if (*cp == ';') cp++; @@ -1975,6 +2004,7 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, { struct window_pane *wp = ft->wp; const char *errptr, *copy, *cp, *marker = NULL; + const char *time_format = NULL; char *copy0, *condition, *found, *new; char *value, *left, *right; size_t valuelen; @@ -2052,6 +2082,9 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, break; if (strchr(fm->argv[0], 'p') != NULL) modifiers |= FORMAT_PRETTY; + else if (fm->argc >= 2 && + strchr(fm->argv[0], 'f') != NULL) + time_format = format_strip(fm->argv[1]); break; case 'q': modifiers |= FORMAT_QUOTE; @@ -2178,7 +2211,7 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, condition = xstrndup(copy + 1, cp - (copy + 1)); format_log(ft, "condition is: %s", condition); - found = format_find(ft, condition, modifiers); + found = format_find(ft, condition, modifiers, time_format); if (found == NULL) { /* * If the condition not found, try to expand it. If @@ -2223,7 +2256,7 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, value = xstrdup(""); } else { /* Neither: look up directly. */ - value = format_find(ft, copy, modifiers); + value = format_find(ft, copy, modifiers, time_format); if (value == NULL) { format_log(ft, "format '%s' not found", copy); value = xstrdup(""); diff --git a/tmux.1 b/tmux.1 index bac95e88..852ae80f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4407,6 +4407,21 @@ Adding .Ql p ( .Ql `t/p` ) will use shorter but less accurate time format for times in the past. +A custom format may be given using an +.Ql f +suffix (note that +.Ql % +must be escaped as +.Ql %% +if the format is separately being passed through +.Xr strftime 3 , +for example in the +.Ic status-left +option): +.Ql #{t/f/%%H#:%%M:window_activity} , +see +.Xr strftime 3 . +.Pp The .Ql b:\& and From 6ea6d46d0a1b206ece54a05af4f3fe7a3d6fe4cc Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:49:20 +0000 Subject: [PATCH 34/49] Store and restore cursor position when copy mode is resized, from Anindya Mukherjee. --- grid.c | 2 +- screen.c | 52 ++++++++++++++++++++++++++------------------------ tmux.h | 3 +-- window-copy.c | 53 +++++++++++++++++++++++++++++++++++++++------------ 4 files changed, 70 insertions(+), 40 deletions(-) diff --git a/grid.c b/grid.c index 81f3709c..2437e2ea 100644 --- a/grid.c +++ b/grid.c @@ -1285,7 +1285,7 @@ grid_reflow(struct grid *gd, u_int sx) /* * If the line is exactly right or the first character is wider - * than the targe width, just move it across unchanged. + * than the target width, just move it across unchanged. */ if (width == sx || first > sx) { grid_reflow_move(target, gl); diff --git a/screen.c b/screen.c index f416ac37..227934dd 100644 --- a/screen.c +++ b/screen.c @@ -49,7 +49,7 @@ struct screen_title_entry { TAILQ_HEAD(screen_titles, screen_title_entry); static void screen_resize_y(struct screen *, u_int, int, u_int *); -static void screen_reflow(struct screen *, u_int, u_int *, u_int *); +static void screen_reflow(struct screen *, u_int, u_int *, u_int *, int); /* Free titles stack. */ static void @@ -220,27 +220,19 @@ screen_pop_title(struct screen *s) } } -/* Resize screen and return cursor position. */ +/* Resize screen with options. */ void screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow, - int eat_empty, u_int *cx, u_int *cy) + int eat_empty, int cursor) { - u_int tcx, tcy; + u_int cx = s->cx, cy = s->grid->hsize + s->cy; if (s->write_list != NULL) screen_write_free_list(s); - if (cx == NULL) - cx = &tcx; - *cx = s->cx; - - if (cy == NULL) - cy = т - *cy = s->grid->hsize + s->cy; - log_debug("%s: new size %ux%u, now %ux%u (cursor %u,%u = %u,%u)", __func__, sx, sy, screen_size_x(s), screen_size_y(s), s->cx, s->cy, - *cx, *cy); + cx, cy); if (sx < 1) sx = 1; @@ -254,20 +246,21 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow, reflow = 0; if (sy != screen_size_y(s)) - screen_resize_y(s, sy, eat_empty, cy); + screen_resize_y(s, sy, eat_empty, &cy); if (reflow) - screen_reflow(s, sx, cx, cy); + screen_reflow(s, sx, &cx, &cy, cursor); - if (*cy >= s->grid->hsize) { - s->cx = *cx; - s->cy = (*cy) - s->grid->hsize; + if (cy >= s->grid->hsize) { + s->cx = cx; + s->cy = cy - s->grid->hsize; } else { s->cx = 0; s->cy = 0; } + log_debug("%s: cursor finished at %u,%u = %u,%u", __func__, s->cx, - s->cy, *cx, *cy); + s->cy, cx, cy); if (s->write_list != NULL) screen_write_make_list(s); @@ -277,7 +270,7 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow, void screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) { - screen_resize_cursor(s, sx, sy, reflow, 1, NULL, NULL); + screen_resize_cursor(s, sx, sy, reflow, 1, 1); } static void @@ -525,17 +518,26 @@ screen_select_cell(struct screen *s, struct grid_cell *dst, /* Reflow wrapped lines. */ static void -screen_reflow(struct screen *s, u_int new_x, u_int *cx, u_int *cy) +screen_reflow(struct screen *s, u_int new_x, u_int *cx, u_int *cy, int cursor) { u_int wx, wy; - grid_wrap_position(s->grid, *cx, *cy, &wx, &wy); - log_debug("%s: cursor %u,%u is %u,%u", __func__, *cx, *cy, wx, wy); + if (cursor) { + grid_wrap_position(s->grid, *cx, *cy, &wx, &wy); + log_debug("%s: cursor %u,%u is %u,%u", __func__, *cx, *cy, wx, + wy); + } grid_reflow(s->grid, new_x); - grid_unwrap_position(s->grid, cx, cy, wx, wy); - log_debug("%s: new cursor is %u,%u", __func__, *cx,* cy); + if (cursor) { + grid_unwrap_position(s->grid, cx, cy, wx, wy); + log_debug("%s: new cursor is %u,%u", __func__, *cx, *cy); + } + else { + *cx = 0; + *cy = s->grid->hsize; + } } /* diff --git a/tmux.h b/tmux.h index 3c074519..bd746fe7 100644 --- a/tmux.h +++ b/tmux.h @@ -2529,8 +2529,7 @@ void screen_set_path(struct screen *, const char *); void screen_push_title(struct screen *); void screen_pop_title(struct screen *); void screen_resize(struct screen *, u_int, u_int, int); -void screen_resize_cursor(struct screen *, u_int, u_int, int, int, u_int *, - u_int *); +void screen_resize_cursor(struct screen *, u_int, u_int, int, int, int); void screen_set_selection(struct screen *, u_int, u_int, u_int, u_int, u_int, int, struct grid_cell *); void screen_clear_selection(struct screen *); diff --git a/window-copy.c b/window-copy.c index 38e87b67..f500a65e 100644 --- a/window-copy.c +++ b/window-copy.c @@ -306,8 +306,9 @@ window_copy_clone_screen(struct screen *src, struct screen *hint, u_int *cx, u_int *cy, int trim) { struct screen *dst; - u_int sy; const struct grid_line *gl; + u_int sy, wx, wy; + int reflow; dst = xcalloc(1, sizeof *dst); @@ -324,6 +325,12 @@ window_copy_clone_screen(struct screen *src, struct screen *hint, u_int *cx, screen_size_x(src), sy, screen_size_x(hint), screen_hsize(src) + screen_size_y(src)); screen_init(dst, screen_size_x(src), sy, screen_hlimit(src)); + + /* + * Ensure history is on for the backing grid so lines are not deleted + * during resizing. + */ + dst->grid->flags |= GRID_HISTORY; grid_duplicate_lines(dst->grid, 0, src->grid, 0, sy); dst->grid->sy = sy - screen_hsize(src); @@ -337,8 +344,19 @@ window_copy_clone_screen(struct screen *src, struct screen *hint, u_int *cx, dst->cy = src->cy; } + if (cx != NULL && cy != NULL) { + *cx = dst->cx; + *cy = screen_hsize(dst) + dst->cy; + reflow = (screen_size_x(hint) != screen_size_x(dst)); + } + else + reflow = 0; + if (reflow) + grid_wrap_position(dst->grid, *cx, *cy, &wx, &wy); screen_resize_cursor(dst, screen_size_x(hint), screen_size_y(hint), 1, - 0, cx, cy); + 0, 0); + if (reflow) + grid_unwrap_position(dst->grid, cx, cy, wx, wy); return (dst); } @@ -392,13 +410,12 @@ window_copy_init(struct window_mode_entry *wme, data->backing = window_copy_clone_screen(base, &data->screen, &cx, &cy, wme->swp != wme->wp); + data->cx = cx; if (cy < screen_hsize(data->backing)) { - data->cx = cx; data->cy = 0; data->oy = screen_hsize(data->backing) - cy; } else { - data->cx = data->backing->cx; - data->cy = data->backing->cy; + data->cy = cy - screen_hsize(data->backing); data->oy = 0; } @@ -731,16 +748,28 @@ window_copy_resize(struct window_mode_entry *wme, u_int sx, u_int sy) { struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; + struct grid *gd = data->backing->grid; + u_int cx, cy, wx, wy; + int reflow; screen_resize(s, sx, sy, 0); - screen_resize_cursor(data->backing, sx, sy, 1, 0, NULL, NULL); + cx = data->cx; + cy = gd->hsize + data->cy - data->oy; + reflow = (gd->sx != sx); + if (reflow) + grid_wrap_position(gd, cx, cy, &wx, &wy); + screen_resize_cursor(data->backing, sx, sy, 1, 0, 0); + if (reflow) + grid_unwrap_position(gd, &cx, &cy, wx, wy); - if (data->cy > sy - 1) - data->cy = sy - 1; - if (data->cx > sx) - data->cx = sx; - if (data->oy > screen_hsize(data->backing)) - data->oy = screen_hsize(data->backing); + data->cx = cx; + if (cy < gd->hsize) { + data->cy = 0; + data->oy = gd->hsize - cy; + } else { + data->cy = cy - gd->hsize; + data->oy = 0; + } window_copy_size_changed(wme); window_copy_redraw_screen(wme); From 472d77fd0f4af8431267473df3cf109030760fa1 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 15:54:20 +0000 Subject: [PATCH 35/49] Support embedded styles in the display-message message, GitHub issue 2206. --- alerts.c | 8 +++++--- cmd-display-message.c | 2 +- cmd-list-keys.c | 2 +- cmd-queue.c | 2 +- mode-tree.c | 2 +- status.c | 8 ++++++-- tmux.h | 4 +++- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/alerts.c b/alerts.c index 6fe88a09..9675934a 100644 --- a/alerts.c +++ b/alerts.c @@ -316,8 +316,10 @@ 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, "%s in current window", type); - else - status_message_set(c, "%s in window %d", type, wl->idx); + status_message_set(c, 1, "%s in current window", type); + else { + status_message_set(c, 1, "%s in window %d", type, + wl->idx); + } } } diff --git a/cmd-display-message.c b/cmd-display-message.c index 4e69f03a..634f0a93 100644 --- a/cmd-display-message.c +++ b/cmd-display-message.c @@ -117,7 +117,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'p')) cmdq_print(item, "%s", msg); else if (tc != NULL) - status_message_set(tc, "%s", msg); + status_message_set(tc, 0, "%s", msg); free(msg); format_free(ft); diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 1141bbb5..4e7ba39c 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -112,7 +112,7 @@ 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, "%s%s%s", prefix, tmp, note); + status_message_set(tc, 1, "%s%s%s", prefix, tmp, note); else cmdq_print(item, "%s%s%s", prefix, tmp, note); free(tmp); diff --git a/cmd-queue.c b/cmd-queue.c index 26e2f2f9..a40053a6 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -856,7 +856,7 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...) c->retval = 1; } else { *msg = toupper((u_char) *msg); - status_message_set(c, "%s", msg); + status_message_set(c, 1, "%s", msg); } free(msg); diff --git a/mode-tree.c b/mode-tree.c index c08c802d..b3b3f335 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -1110,7 +1110,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, "%s", error); + status_message_set(c, 1, "%s", error); } free(error); } diff --git a/status.c b/status.c index 375cad4d..b5fa0824 100644 --- a/status.c +++ b/status.c @@ -423,7 +423,7 @@ status_redraw(struct client *c) /* Set a status line message. */ void -status_message_set(struct client *c, const char *fmt, ...) +status_message_set(struct client *c, int ignore_styles, const char *fmt, ...) { struct timeval tv; va_list ap; @@ -433,6 +433,7 @@ status_message_set(struct client *c, const char *fmt, ...) status_push_screen(c); va_start(ap, fmt); + c->message_ignore_styles = ignore_styles; xvasprintf(&c->message_string, fmt, ap); va_end(ap); @@ -515,7 +516,10 @@ status_message_redraw(struct client *c) for (offset = 0; offset < c->tty.sx; offset++) screen_write_putc(&ctx, &gc, ' '); screen_write_cursormove(&ctx, 0, lines - 1, 0); - screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); + if (c->message_ignore_styles) + screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); + else + format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL); screen_write_stop(&ctx); if (grid_compare(sl->active->grid, old_screen.grid) == 0) { diff --git a/tmux.h b/tmux.h index bd746fe7..385ba212 100644 --- a/tmux.h +++ b/tmux.h @@ -1603,6 +1603,7 @@ struct client { uint64_t redraw_panes; + int message_ignore_styles; char *message_string; struct event message_timer; @@ -2340,7 +2341,8 @@ 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(2, 3) status_message_set(struct client *, const char *, ...); +void printflike(3, 4) status_message_set(struct client *, int, const char *, + ...); void status_message_clear(struct client *); int status_message_redraw(struct client *); void status_prompt_set(struct client *, const char *, const char *, From d67245c734c9c600ad6d186570a1230aa21b80c8 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:02:24 +0000 Subject: [PATCH 36/49] Add a customize mode where keys and options may be browsed and changed, includes adding a brief description of each option. Bound to "C" by default. --- Makefile | 1 + cmd-choose-tree.c | 17 +- cmd-list-keys.c | 8 +- cmd-run-shell.c | 3 +- cmd-set-option.c | 216 +------ cmd-show-messages.c | 4 + cmd-show-options.c | 8 +- cmd.c | 2 + format-draw.c | 2 +- format.c | 2 +- key-bindings.c | 60 +- mode-tree.c | 97 ++- options-table.c | 358 ++++++++--- options.c | 252 +++++++- screen-write.c | 92 ++- style.c | 7 +- tmux.1 | 47 ++ tmux.h | 33 +- window-buffer.c | 4 +- window-client.c | 2 +- window-customize.c | 1430 +++++++++++++++++++++++++++++++++++++++++++ window-tree.c | 2 +- 22 files changed, 2284 insertions(+), 363 deletions(-) create mode 100644 window-customize.c diff --git a/Makefile b/Makefile index 0f1c94c4..7fb664ad 100644 --- a/Makefile +++ b/Makefile @@ -120,6 +120,7 @@ SRCS= alerts.c \ window-client.c \ window-clock.c \ window-copy.c \ + window-customize.c \ window-tree.c \ window.c \ xmalloc.c \ diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 0ada8fd4..a58469ac 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -68,6 +68,19 @@ const struct cmd_entry cmd_choose_buffer_entry = { .exec = cmd_choose_tree_exec }; +const struct cmd_entry cmd_customize_mode_entry = { + .name = "customize-mode", + .alias = NULL, + + .args = { "F:f:Nt:Z", 0, 0 }, + .usage = "[-NZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE, + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = 0, + .exec = cmd_choose_tree_exec +}; + static enum cmd_retval cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item) { @@ -84,7 +97,9 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item) if (server_client_how_many() == 0) return (CMD_RETURN_NORMAL); mode = &window_client_mode; - } else + } else if (cmd_get_entry(self) == &cmd_customize_mode_entry) + mode = &window_customize_mode; + else mode = &window_tree_mode; window_pane_set_mode(wp, NULL, mode, target, args); diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 4e7ba39c..60ef73af 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -68,7 +68,8 @@ cmd_list_keys_get_width(const char *tablename, key_code only) while (bd != NULL) { if ((only != KEYC_UNKNOWN && bd->key != only) || KEYC_IS_MOUSE(bd->key) || - bd->note == NULL) { + bd->note == NULL || + *bd->note == '\0') { bd = key_bindings_next(table, bd); continue; } @@ -99,14 +100,15 @@ cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args, while (bd != NULL) { if ((only != KEYC_UNKNOWN && bd->key != only) || KEYC_IS_MOUSE(bd->key) || - (bd->note == NULL && !args_has(args, 'a'))) { + ((bd->note == NULL || *bd->note == '\0') && + !args_has(args, 'a'))) { bd = key_bindings_next(table, bd); continue; } found = 1; key = key_string_lookup_key(bd->key); - if (bd->note == NULL) + if (bd->note == NULL || *bd->note == '\0') note = cmd_list_print(bd->cmdlist, 1); else note = xstrdup(bd->note); diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 82d4a1a2..3792aaae 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -190,7 +190,8 @@ cmd_run_shell_callback(struct job *job) retcode = WTERMSIG(status); xasprintf(&msg, "'%s' terminated by signal %d", cmd, retcode); retcode += 128; - } + } else + retcode = 0; if (msg != NULL) cmd_run_shell_print(job, msg); free(msg); diff --git a/cmd-set-option.c b/cmd-set-option.c index e04aa7ff..c6f83796 100644 --- a/cmd-set-option.c +++ b/cmd-set-option.c @@ -30,15 +30,6 @@ static enum cmd_retval cmd_set_option_exec(struct cmd *, struct cmdq_item *); -static int cmd_set_option_set(struct cmd *, struct cmdq_item *, - struct options *, struct options_entry *, const char *); -static int cmd_set_option_flag(struct cmdq_item *, - const struct options_table_entry *, struct options *, - const char *); -static int cmd_set_option_choice(struct cmdq_item *, - const struct options_table_entry *, struct options *, - const char *); - const struct cmd_entry cmd_set_option_entry = { .name = "set-option", .alias = "set", @@ -84,10 +75,6 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); int append = args_has(args, 'a'); struct cmd_find_state *target = cmdq_get_target(item); - struct client *loop; - struct session *s = target->s; - struct window *w; - struct window_pane *wp; struct options *oo; struct options_entry *parent, *o; char *name, *argument, *value = NULL, *cause; @@ -138,7 +125,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) parent = options_get(oo, name); /* Check that array options and indexes match up. */ - if (idx != -1 && (*name == '@' || !options_isarray(parent))) { + if (idx != -1 && (*name == '@' || !options_is_array(parent))) { cmdq_error(item, "not an array: %s", argument); goto fail; } @@ -185,10 +172,15 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) goto fail; } options_set_string(oo, name, append, "%s", value); - } else if (idx == -1 && !options_isarray(parent)) { - error = cmd_set_option_set(self, item, oo, parent, value); - if (error != 0) + } else if (idx == -1 && !options_is_array(parent)) { + error = options_from_string(oo, options_table_entry(parent), + options_table_entry(parent)->name, value, + args_has(args, 'a'), &cause); + if (error != 0) { + cmdq_error(item, "%s", cause); + free(cause); goto fail; + } } else { if (value == NULL) { cmdq_error(item, "empty value"); @@ -212,51 +204,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) } } - /* Update timers and so on for various options. */ - if (strcmp(name, "automatic-rename") == 0) { - RB_FOREACH(w, windows, &windows) { - if (w->active == NULL) - continue; - if (options_get_number(w->options, "automatic-rename")) - w->active->flags |= PANE_CHANGED; - } - } - if (strcmp(name, "key-table") == 0) { - TAILQ_FOREACH(loop, &clients, entry) - server_client_set_key_table(loop, NULL); - } - if (strcmp(name, "user-keys") == 0) { - TAILQ_FOREACH(loop, &clients, entry) { - if (loop->tty.flags & TTY_OPENED) - tty_keys_build(&loop->tty); - } - } - if (strcmp(name, "status") == 0 || - strcmp(name, "status-interval") == 0) - status_timer_start_all(); - if (strcmp(name, "monitor-silence") == 0) - alerts_reset_all(); - if (strcmp(name, "window-style") == 0 || - strcmp(name, "window-active-style") == 0) { - RB_FOREACH(wp, window_pane_tree, &all_window_panes) - wp->flags |= PANE_STYLECHANGED; - } - if (strcmp(name, "pane-border-status") == 0) { - RB_FOREACH(w, windows, &windows) - layout_fix_panes(w); - } - RB_FOREACH(s, sessions, &sessions) - status_update_cache(s); - - /* - * Update sizes and redraw. May not always be necessary but do it - * anyway. - */ - recalculate_sizes(); - TAILQ_FOREACH(loop, &clients, entry) { - if (loop->session != NULL) - server_redraw_client(loop); - } + options_push_changes(name); out: free(argument); @@ -270,147 +218,3 @@ fail: free(name); return (CMD_RETURN_ERROR); } - -static int -cmd_set_option_check_string(const struct options_table_entry *oe, - const char *value, char **cause) -{ - struct style sy; - - if (strcmp(oe->name, "default-shell") == 0 && !checkshell(value)) { - xasprintf(cause, "not a suitable shell: %s", value); - return (-1); - } - if (oe->pattern != NULL && fnmatch(oe->pattern, value, 0) != 0) { - xasprintf(cause, "value is invalid: %s", value); - return (-1); - } - if ((oe->flags & OPTIONS_TABLE_IS_STYLE) && - strstr(value, "#{") == NULL && - style_parse(&sy, &grid_default_cell, value) != 0) { - xasprintf(cause, "invalid style: %s", value); - return (-1); - } - return (0); -} - -static int -cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, - struct options_entry *parent, const char *value) -{ - const struct options_table_entry *oe; - struct args *args = cmd_get_args(self); - int append = args_has(args, 'a'); - long long number; - const char *errstr, *new; - char *old, *cause; - key_code key; - - oe = options_table_entry(parent); - if (value == NULL && - oe->type != OPTIONS_TABLE_FLAG && - oe->type != OPTIONS_TABLE_CHOICE) { - cmdq_error(item, "empty value"); - return (-1); - } - - switch (oe->type) { - case OPTIONS_TABLE_STRING: - old = xstrdup(options_get_string(oo, oe->name)); - options_set_string(oo, oe->name, append, "%s", value); - new = options_get_string(oo, oe->name); - if (cmd_set_option_check_string(oe, new, &cause) != 0) { - cmdq_error(item, "%s", cause); - free(cause); - - options_set_string(oo, oe->name, 0, "%s", old); - free(old); - return (-1); - } - free(old); - return (0); - case OPTIONS_TABLE_NUMBER: - number = strtonum(value, oe->minimum, oe->maximum, &errstr); - if (errstr != NULL) { - cmdq_error(item, "value is %s: %s", errstr, value); - return (-1); - } - options_set_number(oo, oe->name, number); - return (0); - case OPTIONS_TABLE_KEY: - key = key_string_lookup_string(value); - if (key == KEYC_UNKNOWN) { - cmdq_error(item, "bad key: %s", value); - return (-1); - } - options_set_number(oo, oe->name, key); - return (0); - case OPTIONS_TABLE_COLOUR: - if ((number = colour_fromstring(value)) == -1) { - cmdq_error(item, "bad colour: %s", value); - return (-1); - } - options_set_number(oo, oe->name, number); - return (0); - case OPTIONS_TABLE_FLAG: - return (cmd_set_option_flag(item, oe, oo, value)); - case OPTIONS_TABLE_CHOICE: - return (cmd_set_option_choice(item, oe, oo, value)); - case OPTIONS_TABLE_COMMAND: - break; - } - return (-1); -} - -static int -cmd_set_option_flag(struct cmdq_item *item, - const struct options_table_entry *oe, struct options *oo, - const char *value) -{ - int flag; - - if (value == NULL || *value == '\0') - flag = !options_get_number(oo, oe->name); - else if (strcmp(value, "1") == 0 || - strcasecmp(value, "on") == 0 || - strcasecmp(value, "yes") == 0) - flag = 1; - else if (strcmp(value, "0") == 0 || - strcasecmp(value, "off") == 0 || - strcasecmp(value, "no") == 0) - flag = 0; - else { - cmdq_error(item, "bad value: %s", value); - return (-1); - } - options_set_number(oo, oe->name, flag); - return (0); -} - -static int -cmd_set_option_choice(struct cmdq_item *item, - const struct options_table_entry *oe, struct options *oo, - const char *value) -{ - const char **cp; - int n, choice = -1; - - if (value == NULL) { - choice = options_get_number(oo, oe->name); - if (choice < 2) - choice = !choice; - } else { - n = 0; - for (cp = oe->choices; *cp != NULL; cp++) { - if (strcmp(*cp, value) == 0) - choice = n; - n++; - } - if (choice == -1) { - cmdq_error(item, "unknown value: %s", value); - return (-1); - } - } - options_set_number(oo, oe->name, choice); - return (0); -} diff --git a/cmd-show-messages.c b/cmd-show-messages.c index 2fa68f2a..deb0487c 100644 --- a/cmd-show-messages.c +++ b/cmd-show-messages.c @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -29,6 +30,9 @@ * Show client message log. */ +#define SHOW_MESSAGES_TEMPLATE \ + "#{t/p:message_time}: #{message_text}" + static enum cmd_retval cmd_show_messages_exec(struct cmd *, struct cmdq_item *); diff --git a/cmd-show-options.c b/cmd-show-options.c index e2aec930..8e70eaa9 100644 --- a/cmd-show-options.c +++ b/cmd-show-options.c @@ -151,7 +151,7 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item, xasprintf(&tmp, "%s[%d]", name, idx); name = tmp; } else { - if (options_isarray(o)) { + if (options_is_array(o)) { a = options_array_first(o); if (a == NULL) { if (!args_has(args, 'v')) @@ -168,10 +168,10 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item, } } - value = options_tostring(o, idx, 0); + value = options_to_string(o, idx, 0); if (args_has(args, 'v')) cmdq_print(item, "%s", value); - else if (options_isstring(o)) { + else if (options_is_string(o)) { escaped = args_escape(value); if (parent) cmdq_print(item, "%s* %s", name, escaped); @@ -229,7 +229,7 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, } else parent = 0; - if (!options_isarray(o)) + if (!options_is_array(o)) cmd_show_options_print(self, item, o, -1, parent); else if ((a = options_array_first(o)) == NULL) { if (!args_has(args, 'v')) { diff --git a/cmd.c b/cmd.c index bc807cbe..8a977023 100644 --- a/cmd.c +++ b/cmd.c @@ -40,6 +40,7 @@ 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; extern const struct cmd_entry cmd_copy_mode_entry; +extern const struct cmd_entry cmd_customize_mode_entry; extern const struct cmd_entry cmd_delete_buffer_entry; extern const struct cmd_entry cmd_detach_client_entry; extern const struct cmd_entry cmd_display_menu_entry; @@ -130,6 +131,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_command_prompt_entry, &cmd_confirm_before_entry, &cmd_copy_mode_entry, + &cmd_customize_mode_entry, &cmd_delete_buffer_entry, &cmd_detach_client_entry, &cmd_display_menu_entry, diff --git a/format-draw.c b/format-draw.c index 3751082e..bd32b2a8 100644 --- a/format-draw.c +++ b/format-draw.c @@ -547,7 +547,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, */ cp = expanded; while (*cp != '\0') { - if (cp[0] != '#' || cp[1] != '[') { + if (cp[0] != '#' || cp[1] != '[' || sy.ignore) { /* See if this is a UTF-8 character. */ if ((more = utf8_open(ud, *cp)) == UTF8_MORE) { while (*++cp != '\0' && more == UTF8_MORE) diff --git a/format.c b/format.c index 9cc8195c..c7f7b90a 100644 --- a/format.c +++ b/format.c @@ -1392,7 +1392,7 @@ format_find(struct format_tree *ft, const char *key, int modifiers, if (o == NULL) o = options_parse_get(global_s_options, key, &idx, 0); if (o != NULL) { - found = options_tostring(o, idx, 1); + found = options_to_string(o, idx, 1); goto found; } diff --git a/key-bindings.c b/key-bindings.c index 85bfb788..ecd2f7dc 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -89,9 +89,8 @@ key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2) } static void -key_bindings_free(struct key_table *table, struct key_binding *bd) +key_bindings_free(struct key_binding *bd) { - RB_REMOVE(key_bindings, &table->key_bindings, bd); cmd_list_free(bd->cmdlist); free((void *)bd->note); free(bd); @@ -110,6 +109,7 @@ key_bindings_get_table(const char *name, int create) table = xmalloc(sizeof *table); table->name = xstrdup(name); RB_INIT(&table->key_bindings); + RB_INIT(&table->default_key_bindings); table->references = 1; /* one reference in key_tables */ RB_INSERT(key_tables, &key_tables, table); @@ -138,8 +138,14 @@ key_bindings_unref_table(struct key_table *table) if (--table->references != 0) return; - RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) - key_bindings_free(table, bd); + RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) { + RB_REMOVE(key_bindings, &table->key_bindings, bd); + key_bindings_free(bd); + } + RB_FOREACH_SAFE(bd, key_bindings, &table->default_key_bindings, bd1) { + RB_REMOVE(key_bindings, &table->default_key_bindings, bd); + key_bindings_free(bd); + } free((void *)table->name); free(table); @@ -154,6 +160,15 @@ key_bindings_get(struct key_table *table, key_code key) return (RB_FIND(key_bindings, &table->key_bindings, &bd)); } +struct key_binding * +key_bindings_get_default(struct key_table *table, key_code key) +{ + struct key_binding bd; + + bd.key = key; + return (RB_FIND(key_bindings, &table->default_key_bindings, &bd)); +} + struct key_binding * key_bindings_first(struct key_table *table) { @@ -176,8 +191,10 @@ key_bindings_add(const char *name, key_code key, const char *note, int repeat, table = key_bindings_get_table(name, 1); bd = key_bindings_get(table, key & ~KEYC_XTERM); - if (bd != NULL) - key_bindings_free(table, bd); + if (bd != NULL) { + RB_REMOVE(key_bindings, &table->key_bindings, bd); + key_bindings_free(bd); + } bd = xcalloc(1, sizeof *bd); bd->key = key; @@ -203,9 +220,12 @@ key_bindings_remove(const char *name, key_code key) bd = key_bindings_get(table, key & ~KEYC_XTERM); if (bd == NULL) return; - key_bindings_free(table, bd); - if (RB_EMPTY(&table->key_bindings)) { + RB_REMOVE(key_bindings, &table->key_bindings, bd); + key_bindings_free(bd); + + if (RB_EMPTY(&table->key_bindings) && + RB_EMPTY(&table->default_key_bindings)) { RB_REMOVE(key_tables, &key_tables, table); key_bindings_unref_table(table); } @@ -228,6 +248,28 @@ key_bindings_remove_table(const char *name) } } +static enum cmd_retval +key_bindings_init_done(__unused struct cmdq_item *item, __unused void *data) +{ + struct key_table *table; + struct key_binding *bd, *new_bd; + + RB_FOREACH(table, key_tables, &key_tables) { + RB_FOREACH(bd, key_bindings, &table->key_bindings) { + new_bd = xcalloc(1, sizeof *bd); + new_bd->key = bd->key; + if (bd->note != NULL) + new_bd->note = xstrdup(bd->note); + new_bd->flags = bd->flags; + new_bd->cmdlist = bd->cmdlist; + new_bd->cmdlist->references++; + RB_INSERT(key_bindings, &table->default_key_bindings, + new_bd); + } + } + return (CMD_RETURN_NORMAL); +} + void key_bindings_init(void) { @@ -278,6 +320,7 @@ key_bindings_init(void) "bind -N 'Toggle the marked pane' m select-pane -m", "bind -N 'Select the next window' n next-window", "bind -N 'Select the next pane' o select-pane -t:.+", + "bind -N 'Customize options' C customize-mode -Z", "bind -N 'Select the previous pane' p previous-window", "bind -N 'Display pane numbers' q display-panes", "bind -N 'Redraw the current client' r refresh-client", @@ -524,6 +567,7 @@ key_bindings_init(void) cmdq_append(NULL, cmdq_get_command(pr->cmdlist, NULL)); cmd_list_free(pr->cmdlist); } + cmdq_append(NULL, cmdq_get_callback(key_bindings_init_done, NULL)); } static enum cmd_retval diff --git a/mode-tree.c b/mode-tree.c index b3b3f335..131830d6 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -45,6 +45,7 @@ struct mode_tree_data { mode_tree_draw_cb drawcb; mode_tree_search_cb searchcb; mode_tree_menu_cb menucb; + mode_tree_height_cb heightcb; struct mode_tree_list children; struct mode_tree_list saved; @@ -79,6 +80,7 @@ struct mode_tree_item { int expanded; int tagged; + int draw_as_parent; struct mode_tree_list children; TAILQ_ENTRY(mode_tree_item) entry; @@ -210,7 +212,7 @@ mode_tree_clear_tagged(struct mode_tree_list *mtl) } } -static void +void mode_tree_up(struct mode_tree_data *mtd, int wrap) { if (mtd->current == 0) { @@ -247,6 +249,12 @@ mode_tree_get_current(struct mode_tree_data *mtd) return (mtd->line_list[mtd->current].item->itemdata); } +const char * +mode_tree_get_current_name(struct mode_tree_data *mtd) +{ + return (mtd->line_list[mtd->current].item->name); +} + void mode_tree_expand_current(struct mode_tree_data *mtd) { @@ -256,6 +264,15 @@ mode_tree_expand_current(struct mode_tree_data *mtd) } } +void +mode_tree_collapse_current(struct mode_tree_data *mtd) +{ + if (mtd->line_list[mtd->current].item->expanded) { + mtd->line_list[mtd->current].item->expanded = 0; + mode_tree_build(mtd); + } +} + static int mode_tree_get_tag(struct mode_tree_data *mtd, uint64_t tag, u_int *found) { @@ -343,7 +360,8 @@ mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb, struct mode_tree_data * mode_tree_start(struct window_pane *wp, struct args *args, mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb, - mode_tree_search_cb searchcb, mode_tree_menu_cb menucb, void *modedata, + mode_tree_search_cb searchcb, mode_tree_menu_cb menucb, + mode_tree_height_cb heightcb, void *modedata, const struct menu_item *menu, const char **sort_list, u_int sort_size, struct screen **s) { @@ -381,6 +399,7 @@ mode_tree_start(struct window_pane *wp, struct args *args, mtd->drawcb = drawcb; mtd->searchcb = searchcb; mtd->menucb = menucb; + mtd->heightcb = heightcb; TAILQ_INIT(&mtd->children); @@ -404,6 +423,27 @@ mode_tree_zoom(struct mode_tree_data *mtd, struct args *args) mtd->zoomed = -1; } +static void +mode_tree_set_height(struct mode_tree_data *mtd) +{ + struct screen *s = &mtd->screen; + u_int height; + + if (mtd->heightcb != NULL) { + height = mtd->heightcb(mtd, screen_size_y(s)); + if (height < screen_size_y(s)) + mtd->height = screen_size_y(s) - height; + } else { + mtd->height = (screen_size_y(s) / 3) * 2; + if (mtd->height > mtd->line_size) + mtd->height = screen_size_y(s) / 2; + } + if (mtd->height < 10) + mtd->height = screen_size_y(s); + if (screen_size_y(s) - mtd->height < 2) + mtd->height = screen_size_y(s); +} + void mode_tree_build(struct mode_tree_data *mtd) { @@ -434,15 +474,9 @@ mode_tree_build(struct mode_tree_data *mtd) mode_tree_set_current(mtd, tag); mtd->width = screen_size_x(s); - if (mtd->preview) { - mtd->height = (screen_size_y(s) / 3) * 2; - if (mtd->height > mtd->line_size) - mtd->height = screen_size_y(s) / 2; - if (mtd->height < 10) - mtd->height = screen_size_y(s); - if (screen_size_y(s) - mtd->height < 2) - mtd->height = screen_size_y(s); - } else + if (mtd->preview) + mode_tree_set_height(mtd); + else mtd->height = screen_size_y(s); mode_tree_check_selected(mtd); } @@ -494,7 +528,7 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, struct mode_tree_item *mti, *saved; log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag, - name, text); + name, (text == NULL ? "" : text)); mti = xcalloc(1, sizeof *mti); mti->parent = parent; @@ -502,7 +536,8 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, mti->tag = tag; mti->name = xstrdup(name); - mti->text = xstrdup(text); + if (text != NULL) + mti->text = xstrdup(text); saved = mode_tree_find_item(&mtd->saved, tag); if (saved != NULL) { @@ -524,6 +559,12 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, return (mti); } +void +mode_tree_draw_as_parent(struct mode_tree_item *mti) +{ + mti->draw_as_parent = 1; +} + void mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti) { @@ -621,8 +662,8 @@ mode_tree_draw(struct mode_tree_data *mtd) tag = "*"; else tag = ""; - xasprintf(&text, "%-*s%s%s%s: ", keylen, key, start, mti->name, - tag); + xasprintf(&text, "%-*s%s%s%s%s", keylen, key, start, mti->name, + tag, (mti->text != NULL) ? ": " : "" ); width = utf8_cstrwidth(text); if (width > w) width = w; @@ -636,11 +677,17 @@ mode_tree_draw(struct mode_tree_data *mtd) if (i != mtd->current) { screen_write_clearendofline(&ctx, 8); screen_write_nputs(&ctx, w, &gc0, "%s", text); - format_draw(&ctx, &gc0, w - width, mti->text, NULL); + if (mti->text != NULL) { + format_draw(&ctx, &gc0, w - width, mti->text, + NULL); + } } else { screen_write_clearendofline(&ctx, gc.bg); screen_write_nputs(&ctx, w, &gc, "%s", text); - format_draw(&ctx, &gc, w - width, mti->text, NULL); + if (mti->text != NULL) { + format_draw(&ctx, &gc, w - width, mti->text, + NULL); + } } free(text); @@ -658,13 +705,18 @@ mode_tree_draw(struct mode_tree_data *mtd) line = &mtd->line_list[mtd->current]; mti = line->item; + if (mti->draw_as_parent) + mti = mti->parent; screen_write_cursormove(&ctx, 0, h, 0); screen_write_box(&ctx, w, sy - h); - xasprintf(&text, " %s (sort: %s%s)", mti->name, - mtd->sort_list[mtd->sort_crit.field], - mtd->sort_crit.reversed ? ", reversed" : ""); + if (mtd->sort_list != NULL) { + xasprintf(&text, " %s (sort: %s%s)", mti->name, + mtd->sort_list[mtd->sort_crit.field], + mtd->sort_crit.reversed ? ", reversed" : ""); + } else + xasprintf(&text, " %s", mti->name); if (w - 2 >= strlen(text)) { screen_write_cursormove(&ctx, 1, h, 0); screen_write_puts(&ctx, &gc0, "%s", text); @@ -680,7 +732,8 @@ mode_tree_draw(struct mode_tree_data *mtd) else screen_write_puts(&ctx, &gc0, "active"); screen_write_puts(&ctx, &gc0, ") "); - } + } else + screen_write_puts(&ctx, &gc0, " "); } free(text); @@ -1027,7 +1080,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, break; case 'O': mtd->sort_crit.field++; - if (mtd->sort_crit.field == mtd->sort_size) + if (mtd->sort_crit.field >= mtd->sort_size) mtd->sort_crit.field = 0; mode_tree_build(mtd); break; diff --git a/options-table.c b/options-table.c index 320a380f..55eb5b9f 100644 --- a/options-table.c +++ b/options-table.c @@ -103,9 +103,9 @@ static const char *options_table_window_size_list[] = { "," \ "#[range=window|#{window_index} list=focus " \ "#{?#{!=:#{window-status-current-style},default}," \ - "#{window-status-current-style}," \ - "#{window-status-style}" \ - "}" \ + "#{window-status-current-style}," \ + "#{window-status-style}" \ + "}" \ "#{?#{&&:#{window_last_flag}," \ "#{!=:#{window-status-last-style},default}}, " \ "#{window-status-last-style}," \ @@ -175,6 +175,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_KEY, .scope = OPTIONS_TABLE_SERVER, .default_num = '\177', + .text = "The key to send for backspace." }, { .name = "buffer-limit", @@ -182,7 +183,9 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .minimum = 1, .maximum = INT_MAX, - .default_num = 50 + .default_num = 50, + .text = "The maximum number of automatic buffers. " + "When this is reached, the oldest buffer is deleted." }, { .name = "command-alias", @@ -195,25 +198,31 @@ const struct options_table_entry options_table[] = { "info=show-messages -JT," "choose-window=choose-tree -w," "choose-session=choose-tree -s", - .separator = "," + .separator = ",", + .text = "Array of command aliases. " + "Each entry is an alias and a command separated by '='." }, { .name = "copy-command", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = "" + .default_str = "", + .text = "Shell command run when text is copied. " + "If empty, no command is run." }, { .name = "default-terminal", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = "screen" + .default_str = "screen", + .text = "Default for the 'TERM' environment variable." }, { .name = "editor", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = _PATH_VI + .default_str = _PATH_VI, + .text = "Editor run to edit files." }, { .name = "escape-time", @@ -221,31 +230,38 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .minimum = 0, .maximum = INT_MAX, - .default_num = 500 + .default_num = 500, + .text = "Time to wait before assuming a key is Escape." }, { .name = "exit-empty", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, - .default_num = 1 + .default_num = 1, + .text = "Whether the server should exit if there are no sessions." }, { .name = "exit-unattached", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, - .default_num = 0 + .default_num = 0, + .text = "Whether the server should exit if there are no attached " + "clients." }, { .name = "focus-events", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, - .default_num = 0 + .default_num = 0, + .text = "Whether to send focus events to applications." }, { .name = "history-file", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = "" + .default_str = "", + .text = "Location of the command prompt history file. " + "Empty does not write a history file." }, { .name = "message-limit", @@ -253,14 +269,18 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .minimum = 0, .maximum = INT_MAX, - .default_num = 1000 + .default_num = 1000, + .text = "Maximum number of server messages to keep." }, { .name = "set-clipboard", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SERVER, .choices = options_table_set_clipboard_list, - .default_num = 1 + .default_num = 1, + .text = "Whether to attempt to set the system clipboard ('on' or " + "'external') and whether to allow applications to create " + "paste buffers with an escape sequence ('on' only)." }, { .name = "terminal-overrides", @@ -268,7 +288,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "", - .separator = "," + .separator = ",", + .text = "List of terminal capabilities overrides." }, { .name = "terminal-features", @@ -276,8 +297,10 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "xterm*:clipboard:ccolour:cstyle:title," - "screen*:title", - .separator = "," + "screen*:title", + .separator = ",", + .text = "List of terminal features, used if they cannot be " + "automatically detected." }, { .name = "user-keys", @@ -285,7 +308,10 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "", - .separator = "," + .separator = ",", + .text = "User key assignments. " + "Each sequence in the list is translated into a key: " + "'User0', 'User1' and so on." }, /* Session options. */ @@ -293,7 +319,8 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_bell_action_list, - .default_num = ALERT_OTHER + .default_num = ALERT_OTHER, + .text = "Action to take on an activity alert." }, { .name = "assume-paste-time", @@ -302,6 +329,9 @@ const struct options_table_entry options_table[] = { .minimum = 0, .maximum = INT_MAX, .default_num = 1, + .unit = "milliseconds", + .text = "Maximum time between input to assume it pasting rather " + "than typing." }, { .name = "base-index", @@ -309,57 +339,69 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 0 + .default_num = 0, + .text = "Default index of the first window in each session." }, { .name = "bell-action", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_bell_action_list, - .default_num = ALERT_ANY + .default_num = ALERT_ANY, + .text = "Action to take on a bell alert." }, { .name = "default-command", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "" + .default_str = "", + .text = "Default command to run in new panes. If empty, a shell is " + "started." }, { .name = "default-shell", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = _PATH_BSHELL + .default_str = _PATH_BSHELL, + .text = "Location of default shell." }, { .name = "default-size", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .pattern = "[0-9]*x[0-9]*", - .default_str = "80x24" + .default_str = "80x24", + .text = "Initial size of new sessions." }, { .name = "destroy-unattached", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether to destroy sessions when they have no attached " + "clients." }, { .name = "detach-on-destroy", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 1 + .default_num = 1, + .text = "Whether to detach when a session is destroyed, or switch " + "the client to another session if any exist." }, { .name = "display-panes-active-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 1 + .default_num = 1, + .text = "Colour of the active pane for 'display-panes'." }, { .name = "display-panes-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 4 + .default_num = 4, + .text = "Colour of not active panes for 'display-panes'." }, { .name = "display-panes-time", @@ -367,7 +409,9 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 1, .maximum = INT_MAX, - .default_num = 1000 + .default_num = 1000, + .unit = "milliseconds", + .text = "Time for which 'display-panes' should show pane numbers." }, { .name = "display-time", @@ -375,7 +419,9 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 750 + .default_num = 750, + .unit = "milliseconds", + .text = "Time for which status line messages should appear." }, { .name = "history-limit", @@ -383,13 +429,19 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 2000 + .default_num = 2000, + .unit = "lines", + .text = "Maximum number of lines to keep in the history for each " + "pane. " + "If changed, the new value applies only to new panes." }, { .name = "key-table", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "root" + .default_str = "root", + .text = "Default key table. " + "Key presses are first looked up in this table." }, { .name = "lock-after-time", @@ -397,13 +449,16 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 0 + .default_num = 0, + .unit = "seconds", + .text = "Time after which a client is locked if not used." }, { .name = "lock-command", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "lock -np" + .default_str = "lock -np", + .text = "Shell command to run to lock a client." }, { .name = "message-command-style", @@ -411,7 +466,9 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .default_str = "bg=black,fg=yellow", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the command prompt when in command mode, if " + "'mode-keys' is set to 'vi'." }, { .name = "message-style", @@ -419,31 +476,39 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .default_str = "bg=yellow,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the command prompt." }, { .name = "mouse", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether the mouse is recognised and mouse key bindings are " + "executed. " + "Applications inside panes can use the mouse even when 'off'." }, { .name = "prefix", .type = OPTIONS_TABLE_KEY, .scope = OPTIONS_TABLE_SESSION, .default_num = '\002', + .text = "The prefix key." }, { .name = "prefix2", .type = OPTIONS_TABLE_KEY, .scope = OPTIONS_TABLE_SESSION, .default_num = KEYC_NONE, + .text = "A second prefix key." }, { .name = "renumber-windows", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether windows are automatically renumbered rather than " + "leaving gaps." }, { .name = "repeat-time", @@ -451,45 +516,56 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = SHRT_MAX, - .default_num = 500 + .default_num = 500, + .unit = "milliseconds", + .text = "Time to wait for a key binding to repeat, if it is bound " + "with the '-r' flag." }, { .name = "set-titles", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether to set the terminal title, if supported." }, { .name = "set-titles-string", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "#S:#I:#W - \"#T\" #{session_alerts}" + .default_str = "#S:#I:#W - \"#T\" #{session_alerts}", + .text = "Format of the terminal title to set." }, { .name = "silence-action", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_bell_action_list, - .default_num = ALERT_OTHER + .default_num = ALERT_OTHER, + .text = "Action to take on a silence alert." }, { .name = "status", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_list, - .default_num = 1 + .default_num = 1, + .text = "Number of lines in the status line." }, { .name = "status-bg", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, .default_num = 8, + .text = "Background colour of the status line. This option is " + "deprecated, use 'status-style' instead." }, { .name = "status-fg", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, .default_num = 8, + .text = "Foreground colour of the status line. This option is " + "deprecated, use 'status-style' instead." }, { .name = "status-format", @@ -497,6 +573,11 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .flags = OPTIONS_TABLE_IS_ARRAY, .default_arr = options_table_status_format_default, + .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 " + "as 'status-left'." }, { .name = "status-interval", @@ -504,27 +585,32 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 15 + .default_num = 15, + .unit = "seconds", + .text = "Number of seconds between status line updates." }, { .name = "status-justify", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_justify_list, - .default_num = 0 + .default_num = 0, + .text = "Position of the window list in the status line." }, { .name = "status-keys", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_keys_list, - .default_num = MODEKEY_EMACS + .default_num = MODEKEY_EMACS, + .text = "Key set to use at the command prompt." }, { .name = "status-left", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "[#S] " + .default_str = "[#{session_name}] ", + .text = "Contents of the left side of the status line." }, { .name = "status-left-length", @@ -532,7 +618,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = SHRT_MAX, - .default_num = 10 + .default_num = 10, + .text = "Maximum width of the left side of the status line." }, { .name = "status-left-style", @@ -540,22 +627,26 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the left side of the status line." }, { .name = "status-position", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_position_list, - .default_num = 1 + .default_num = 1, + .text = "Position of the status line." }, { .name = "status-right", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .default_str = "#{?window_bigger," - "[#{window_offset_x}#,#{window_offset_y}] ,}" - "\"#{=21:pane_title}\" %H:%M %d-%b-%y" + "[#{window_offset_x}#,#{window_offset_y}] ,}" + "\"#{=21:pane_title}\" %H:%M %d-%b-%y", + .text = "Contents of the right side of the status line." + }, { .name = "status-right-length", @@ -563,7 +654,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = SHRT_MAX, - .default_num = 40 + .default_num = 40, + .text = "Maximum width of the right side of the status line." }, { .name = "status-right-style", @@ -571,7 +663,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the right side of the status line." }, { .name = "status-style", @@ -579,7 +672,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .default_str = "bg=green,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the status line." }, { .name = "update-environment", @@ -587,79 +681,100 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "DISPLAY KRB5CCNAME SSH_ASKPASS SSH_AUTH_SOCK " - "SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY" + "SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY", + .text = "List of environment variables to update in the session " + "environment when a client is attached." }, { .name = "visual-activity", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_visual_bell_list, - .default_num = VISUAL_OFF + .default_num = VISUAL_OFF, + .text = "How activity alerts should be shown: a message ('on'), " + "a message and a bell ('both') or nothing ('off')." }, { .name = "visual-bell", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_visual_bell_list, - .default_num = VISUAL_OFF + .default_num = VISUAL_OFF, + .text = "How bell alerts should be shown: a message ('on'), " + "a message and a bell ('both') or nothing ('off')." }, { .name = "visual-silence", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_visual_bell_list, - .default_num = VISUAL_OFF + .default_num = VISUAL_OFF, + .text = "How silence alerts should be shown: a message ('on'), " + "a message and a bell ('both') or nothing ('off')." }, { .name = "word-separators", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = " " + .default_str = " ", + .text = "Characters considered to separate words." }, /* Window options. */ { .name = "aggressive-resize", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0 + .default_num = 0, + .text = "When 'window-size' is 'smallest', whether the maximum size " + "of a window is the smallest attached session where it is " + "the current window ('on') or the smallest session it is " + "linked to ('off')." }, { .name = "allow-rename", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_num = 0 + .default_num = 0, + .text = "Whether applications are allowed to use the escape sequence " + "to rename windows." }, { .name = "alternate-screen", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_num = 1 + .default_num = 1, + .text = "Whether applications are allowed to use the alternate " + "screen." }, { .name = "automatic-rename", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether windows are automatically renamed." }, { .name = "automatic-rename-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "#{?pane_in_mode,[tmux],#{pane_current_command}}" - "#{?pane_dead,[dead],}" + "#{?pane_dead,[dead],}", + .text = "Format used to automatically rename windows." }, { .name = "clock-mode-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 4 + .default_num = 4, + .text = "Colour of the clock in clock mode." }, { .name = "clock-mode-style", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_clock_mode_style_list, - .default_num = 1 + .default_num = 1, + .text = "Time format of the clock in clock mode." }, { .name = "copy-mode-match-style", @@ -667,7 +782,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "bg=cyan,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of search matches in copy mode." }, { .name = "copy-mode-current-match-style", @@ -675,26 +791,32 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "bg=magenta,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the current search match in copy mode." }, { .name = "main-pane-height", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "24" + .default_str = "24", + .text = "Height of the main pane in the 'main-horizontal' layout. " + "This may be a percentage, for example '10%'." }, { .name = "main-pane-width", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "80" + .default_str = "80", + .text = "Width of the main pane in the 'main-vertical' layout. " + "This may be a percentage, for example '10%'." }, { .name = "mode-keys", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_mode_keys_list, - .default_num = MODEKEY_EMACS + .default_num = MODEKEY_EMACS, + .text = "Key set used in copy mode." }, { .name = "mode-style", @@ -702,19 +824,22 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "bg=yellow,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of indicators and highlighting in modes." }, { .name = "monitor-activity", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0 + .default_num = 0, + .text = "Whether an alert is triggered by activity." }, { .name = "monitor-bell", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether an alert is triggered by a bell." }, { .name = "monitor-silence", @@ -722,19 +847,26 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .minimum = 0, .maximum = INT_MAX, - .default_num = 0 + .default_num = 0, + .text = "Time after which an alert is triggered by silence. " + "Zero means no alert." + }, { .name = "other-pane-height", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "0" + .default_str = "0", + .text = "Height of the other panes in the 'main-horizontal' layout. " + "This may be a percentage, for example '10%'." }, { .name = "other-pane-width", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "0" + .default_str = "0", + .text = "Height of the other panes in the 'main-vertical' layout. " + "This may be a percentage, for example '10%'." }, { .name = "pane-active-border-style", @@ -742,7 +874,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "#{?pane_in_mode,fg=yellow,#{?synchronize-panes,fg=red,fg=green}}", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the active pane border." }, { .name = "pane-base-index", @@ -750,21 +883,24 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .minimum = 0, .maximum = USHRT_MAX, - .default_num = 0 + .default_num = 0, + .text = "Index of the first pane in each window." }, { .name = "pane-border-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "#{?pane_active,#[reverse],}#{pane_index}#[default] " - "\"#{pane_title}\"" + "\"#{pane_title}\"", + .text = "Format of text in the pane status lines." }, { .name = "pane-border-status", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_pane_status_list, - .default_num = PANE_STATUS_OFF + .default_num = PANE_STATUS_OFF, + .text = "Position of the pane status lines." }, { .name = "pane-border-style", @@ -772,19 +908,23 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the pane status lines." }, { .name = "remain-on-exit", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_num = 0 + .default_num = 0, + .text = "Whether panes should remain ('on') or be automatically " + "killed ('off') when the program inside exits." }, { .name = "synchronize-panes", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0 + .default_num = 0, + .text = "Whether typing should be sent to all panes simultaneously." }, { .name = "window-active-style", @@ -792,14 +932,20 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Default style of the active pane." }, { .name = "window-size", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_window_size_list, - .default_num = WINDOW_SIZE_LATEST + .default_num = WINDOW_SIZE_LATEST, + .text = "How window size is calculated. " + "'latest' uses the size of the most recently used client, " + "'largest' the largest client, 'smallest' the smallest " + "client and 'manual' a size set by the 'resize-window' " + "command." }, { .name = "window-style", @@ -807,7 +953,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Default style of panes that are not the active pane." }, { .name = "window-status-activity-style", @@ -815,7 +962,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "reverse", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of windows in the status line with an activity alert." }, { .name = "window-status-bell-style", @@ -823,13 +971,15 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "reverse", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of windows in the status line with a bell alert." }, { .name = "window-status-current-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "#I:#W#{?window_flags,#{window_flags}, }" + .default_str = "#I:#W#{?window_flags,#{window_flags}, }", + .text = "Format of the current window in the status line." }, { .name = "window-status-current-style", @@ -837,13 +987,16 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the current window in the status line." }, { .name = "window-status-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "#I:#W#{?window_flags,#{window_flags}, }" + .default_str = "#I:#W#{?window_flags,#{window_flags}, }", + .text = "Format of windows in the status line, except the current " + "window." }, { .name = "window-status-last-style", @@ -851,13 +1004,15 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the last window in the status line." }, { .name = "window-status-separator", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = " " + .default_str = " ", + .text = "Separator between windows in the status line." }, { .name = "window-status-style", @@ -865,19 +1020,24 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of windows in the status line, except the current and " + "last windows." }, { .name = "wrap-search", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether searching in copy mode should wrap at the top or " + "bottom." }, { .name = "xterm-keys", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether xterm-style function key sequences should be sent." }, /* Hook options. */ diff --git a/options.c b/options.c index 39a0d08f..58b7f7c1 100644 --- a/options.c +++ b/options.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -116,7 +117,7 @@ options_value_free(struct options_entry *o, union options_value *ov) } static char * -options_value_tostring(struct options_entry *o, union options_value *ov, +options_value_to_string(struct options_entry *o, union options_value *ov, int numeric) { char *s; @@ -175,6 +176,12 @@ options_free(struct options *oo) free(oo); } +struct options * +options_get_parent(struct options *oo) +{ + return (oo->parent); +} + void options_set_parent(struct options *oo, struct options *parent) { @@ -262,6 +269,35 @@ options_default(struct options *oo, const struct options_table_entry *oe) return (o); } +char * +options_default_to_string(const struct options_table_entry *oe) +{ + char *s; + + switch (oe->type) { + case OPTIONS_TABLE_STRING: + case OPTIONS_TABLE_COMMAND: + s = xstrdup(oe->default_str); + break; + case OPTIONS_TABLE_NUMBER: + xasprintf(&s, "%lld", oe->default_num); + break; + case OPTIONS_TABLE_KEY: + s = xstrdup(key_string_lookup_key(oe->default_num)); + break; + case OPTIONS_TABLE_COLOUR: + s = xstrdup(colour_tostring(oe->default_num)); + break; + case OPTIONS_TABLE_FLAG: + s = xstrdup(oe->default_num ? "on" : "off"); + break; + case OPTIONS_TABLE_CHOICE: + s = xstrdup(oe->choices[oe->default_num]); + break; + } + return (s); +} + static struct options_entry * options_add(struct options *oo, const char *name) { @@ -299,6 +335,12 @@ options_name(struct options_entry *o) return (o->name); } +struct options * +options_owner(struct options_entry *o) +{ + return (o->owner); +} + const struct options_table_entry * options_table_entry(struct options_entry *o) { @@ -492,19 +534,19 @@ options_array_item_value(struct options_array_item *a) } int -options_isarray(struct options_entry *o) +options_is_array(struct options_entry *o) { return (OPTIONS_IS_ARRAY(o)); } int -options_isstring(struct options_entry *o) +options_is_string(struct options_entry *o) { return (OPTIONS_IS_STRING(o)); } char * -options_tostring(struct options_entry *o, int idx, int numeric) +options_to_string(struct options_entry *o, int idx, int numeric) { struct options_array_item *a; @@ -514,9 +556,9 @@ options_tostring(struct options_entry *o, int idx, int numeric) a = options_array_item(o, idx); if (a == NULL) return (xstrdup("")); - return (options_value_tostring(o, &a->value, numeric)); + return (options_value_to_string(o, &a->value, numeric)); } - return (options_value_tostring(o, &o->value, numeric)); + return (options_value_to_string(o, &o->value, numeric)); } char * @@ -866,3 +908,201 @@ options_string_to_style(struct options *oo, const char *name, } return (&o->style); } + +static int +options_from_string_check(const struct options_table_entry *oe, + const char *value, char **cause) +{ + struct style sy; + + if (oe == NULL) + return (0); + if (strcmp(oe->name, "default-shell") == 0 && !checkshell(value)) { + xasprintf(cause, "not a suitable shell: %s", value); + return (-1); + } + if (oe->pattern != NULL && fnmatch(oe->pattern, value, 0) != 0) { + xasprintf(cause, "value is invalid: %s", value); + return (-1); + } + if ((oe->flags & OPTIONS_TABLE_IS_STYLE) && + strstr(value, "#{") == NULL && + style_parse(&sy, &grid_default_cell, value) != 0) { + xasprintf(cause, "invalid style: %s", value); + return (-1); + } + return (0); +} + +static int +options_from_string_flag(struct options *oo, const char *name, + const char *value, char **cause) +{ + int flag; + + if (value == NULL || *value == '\0') + flag = !options_get_number(oo, name); + else if (strcmp(value, "1") == 0 || + strcasecmp(value, "on") == 0 || + strcasecmp(value, "yes") == 0) + flag = 1; + else if (strcmp(value, "0") == 0 || + strcasecmp(value, "off") == 0 || + strcasecmp(value, "no") == 0) + flag = 0; + else { + xasprintf(cause, "bad value: %s", value); + return (-1); + } + options_set_number(oo, name, flag); + return (0); +} + +static int +options_from_string_choice(const struct options_table_entry *oe, + struct options *oo, const char *name, const char *value, char **cause) +{ + const char **cp; + int n, choice = -1; + + if (value == NULL) { + choice = options_get_number(oo, name); + if (choice < 2) + choice = !choice; + } else { + n = 0; + for (cp = oe->choices; *cp != NULL; cp++) { + if (strcmp(*cp, value) == 0) + choice = n; + n++; + } + if (choice == -1) { + xasprintf(cause, "unknown value: %s", value); + return (-1); + } + } + options_set_number(oo, name, choice); + return (0); +} + +int +options_from_string(struct options *oo, const struct options_table_entry *oe, + const char *name, const char *value, int append, char **cause) +{ + enum options_table_type type; + long long number; + const char *errstr, *new; + char *old; + key_code key; + + if (oe != NULL) { + if (value == NULL && + oe->type != OPTIONS_TABLE_FLAG && + oe->type != OPTIONS_TABLE_CHOICE) { + xasprintf(cause, "empty value"); + return (-1); + } + type = oe->type; + } else { + if (*name != '@') { + xasprintf(cause, "bad option name"); + return (-1); + } + type = OPTIONS_TABLE_STRING; + } + + switch (type) { + case OPTIONS_TABLE_STRING: + old = xstrdup(options_get_string(oo, name)); + options_set_string(oo, name, append, "%s", value); + + new = options_get_string(oo, name); + if (options_from_string_check(oe, new, cause) != 0) { + options_set_string(oo, name, 0, "%s", old); + free(old); + return (-1); + } + free(old); + return (0); + case OPTIONS_TABLE_NUMBER: + number = strtonum(value, oe->minimum, oe->maximum, &errstr); + if (errstr != NULL) { + xasprintf(cause, "value is %s: %s", errstr, value); + return (-1); + } + options_set_number(oo, name, number); + return (0); + case OPTIONS_TABLE_KEY: + key = key_string_lookup_string(value); + if (key == KEYC_UNKNOWN) { + xasprintf(cause, "bad key: %s", value); + return (-1); + } + options_set_number(oo, name, key); + return (0); + case OPTIONS_TABLE_COLOUR: + if ((number = colour_fromstring(value)) == -1) { + xasprintf(cause, "bad colour: %s", value); + return (-1); + } + options_set_number(oo, name, number); + return (0); + case OPTIONS_TABLE_FLAG: + return (options_from_string_flag(oo, name, value, cause)); + case OPTIONS_TABLE_CHOICE: + return (options_from_string_choice(oe, oo, name, value, cause)); + case OPTIONS_TABLE_COMMAND: + break; + } + return (-1); +} + +void +options_push_changes(const char *name) +{ + struct client *loop; + struct session *s; + struct window *w; + struct window_pane *wp; + + if (strcmp(name, "automatic-rename") == 0) { + RB_FOREACH(w, windows, &windows) { + if (w->active == NULL) + continue; + if (options_get_number(w->options, "automatic-rename")) + w->active->flags |= PANE_CHANGED; + } + } + if (strcmp(name, "key-table") == 0) { + TAILQ_FOREACH(loop, &clients, entry) + server_client_set_key_table(loop, NULL); + } + if (strcmp(name, "user-keys") == 0) { + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->tty.flags & TTY_OPENED) + tty_keys_build(&loop->tty); + } + } + if (strcmp(name, "status") == 0 || + strcmp(name, "status-interval") == 0) + status_timer_start_all(); + if (strcmp(name, "monitor-silence") == 0) + alerts_reset_all(); + if (strcmp(name, "window-style") == 0 || + strcmp(name, "window-active-style") == 0) { + RB_FOREACH(wp, window_pane_tree, &all_window_panes) + wp->flags |= PANE_STYLECHANGED; + } + if (strcmp(name, "pane-border-status") == 0) { + RB_FOREACH(w, windows, &windows) + layout_fix_panes(w); + } + RB_FOREACH(s, sessions, &sessions) + status_update_cache(s); + + recalculate_sizes(); + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->session != NULL) + server_redraw_client(loop); + } +} diff --git a/screen-write.c b/screen-write.c index 909b8531..9571cbec 100644 --- a/screen-write.c +++ b/screen-write.c @@ -360,7 +360,97 @@ screen_write_strlen(const char *fmt, ...) return (size); } -/* Write simple string (no UTF-8 or maximum length). */ +/* Write string wrapped over lines. */ +int +screen_write_text(struct screen_write_ctx *ctx, u_int cx, u_int width, + u_int lines, int more, const struct grid_cell *gcp, const char *fmt, ...) +{ + struct screen *s = ctx->s; + va_list ap; + char *tmp; + u_int cy = s->cy, i, end, next, idx = 0, at, left; + struct utf8_data *text; + struct grid_cell gc; + + memcpy(&gc, gcp, sizeof gc); + + va_start(ap, fmt); + xvasprintf(&tmp, fmt, ap); + va_end(ap); + + text = utf8_fromcstr(tmp); + free(tmp); + + left = (cx + width) - s->cx; + for (;;) { + /* Find the end of what can fit on the line. */ + at = 0; + for (end = idx; text[end].size != 0; end++) { + if (text[end].size == 1 && text[end].data[0] == '\n') + break; + if (at + text[end].width > left) + break; + at += text[end].width; + } + + /* + * If we're on a space, that's the end. If not, walk back to + * try and find one. + */ + if (text[end].size == 0) + next = end; + else if (text[end].size == 1 && text[end].data[0] == '\n') + next = end + 1; + else if (text[end].size == 1 && text[end].data[0] == ' ') + next = end + 1; + else { + for (i = end; i > idx; i--) { + if (text[i].size == 1 && text[i].data[0] == ' ') + break; + } + if (i != idx) { + next = i + 1; + end = i; + } else + next = end; + } + + /* Print the line. */ + for (i = idx; i < end; i++) { + utf8_copy(&gc.data, &text[i]); + screen_write_cell(ctx, &gc); + } + + /* If at the bottom, stop. */ + idx = next; + if (s->cy == cy + lines - 1 || text[idx].size == 0) + break; + + screen_write_cursormove(ctx, cx, s->cy + 1, 0); + left = width; + } + + /* + * Fail if on the last line and there is more to come or at the end, or + * if the text was not entirely consumed. + */ + if ((s->cy == cy + lines - 1 && (!more || s->cx == cx + width)) || + text[idx].size != 0) { + free(text); + return (0); + } + free(text); + + /* + * If no more to come, move to the next line. Otherwise, leave on + * the same line (except if at the end). + */ + if (!more || s->cx == cx + width) + screen_write_cursormove(ctx, cx, s->cy + 1, 0); + return (1); +} + +/* Write simple string (no maximum length). */ void screen_write_puts(struct screen_write_ctx *ctx, const struct grid_cell *gcp, const char *fmt, ...) diff --git a/style.c b/style.c index 3c615852..08614f9c 100644 --- a/style.c +++ b/style.c @@ -31,6 +31,7 @@ /* Default style. */ static struct style style_default = { { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 }, + 0, 8, STYLE_ALIGN_DEFAULT, @@ -78,7 +79,11 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) sy->gc.bg = base->bg; sy->gc.attr = base->attr; sy->gc.flags = base->flags; - } else if (strcasecmp(tmp, "push-default") == 0) + } else if (strcasecmp(tmp, "ignore") == 0) + sy->ignore = 1; + else if (strcasecmp(tmp, "noignore") == 0) + sy->ignore = 0; + else if (strcasecmp(tmp, "push-default") == 0) sy->default_type = STYLE_DEFAULT_PUSH; else if (strcasecmp(tmp, "pop-default") == 0) sy->default_type = STYLE_DEFAULT_POP; diff --git a/tmux.1 b/tmux.1 index 852ae80f..45c004ff 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1952,6 +1952,53 @@ includes all sessions in any session groups in the tree rather than only the first. This command works only if at least one client is attached. .It Xo +.Ic customize-mode +.Op Fl NZ +.Op Fl F Ar format +.Op Fl f Ar filter +.Op Fl t Ar target-pane +.Op Ar template +.Xc +Put a pane into customize mode, where options and key bindings may be browsed +and modified from a list. +Option values in the list are shown for the active pane in the current window. +.Fl Z +zooms the pane. +The following keys may be used in customize mode: +.Bl -column "Key" "Function" -offset indent +.It Sy "Key" Ta Sy "Function" +.It Li "Enter" Ta "Set pane, window, session or global option value" +.It Li "Up" Ta "Select previous item" +.It Li "Down" Ta "Select next item" +.It Li "+" Ta "Expand selected item" +.It Li "-" Ta "Collapse selected item" +.It Li "M-+" Ta "Expand all items" +.It Li "M--" Ta "Collapse all items" +.It Li "s" Ta "Set option value or key attribute" +.It Li "S" Ta "Set global option value" +.It Li "w" Ta "Set window option value, if option is for pane and window" +.It Li "u" Ta "Unset an option (set to default value if global) or unbind a key" +.It Li "U" Ta "Unset tagged options and unbind tagged keys" +.It Li "C-s" Ta "Search by name" +.It Li "n" Ta "Repeat last search" +.It Li "t" Ta "Toggle if item is tagged" +.It Li "T" Ta "Tag no items" +.It Li "C-t" Ta "Tag all items" +.It Li "f" Ta "Enter a format to filter items" +.It Li "v" Ta "Toggle option information" +.It Li "q" Ta "Exit mode" +.El +.Pp +.Fl f +specifies an initial filter: the filter is a format - if it evaluates to zero, +the item in the list is not shown, otherwise it is shown. +If a filter would lead to an empty list, it is ignored. +.Fl F +specifies the format for each item in the tree. +.Fl N +starts without the option information. +This command works only if at least one client is attached. +.It Xo .Ic display-panes .Op Fl b .Op Fl d Ar duration diff --git a/tmux.h b/tmux.h index 385ba212..1abb6ca6 100644 --- a/tmux.h +++ b/tmux.h @@ -745,6 +745,7 @@ enum style_default_type { /* Style option. */ struct style { struct grid_cell gc; + int ignore; int fill; enum style_align align; @@ -1665,6 +1666,7 @@ RB_HEAD(key_bindings, key_binding); struct key_table { const char *name; struct key_bindings key_bindings; + struct key_bindings default_key_bindings; u_int references; @@ -1719,6 +1721,9 @@ struct options_table_entry { const char *separator; const char *pattern; + + const char *text; + const char *unit; }; /* Common command usages. */ @@ -1896,6 +1901,7 @@ void notify_pane(const char *, struct window_pane *); /* options.c */ struct options *options_create(struct options *); void options_free(struct options *); +struct options *options_get_parent(struct options *); void options_set_parent(struct options *, struct options *); struct options_entry *options_first(struct options *); struct options_entry *options_next(struct options_entry *); @@ -1903,7 +1909,9 @@ struct options_entry *options_empty(struct options *, const struct options_table_entry *); struct options_entry *options_default(struct options *, const struct options_table_entry *); +char *options_default_to_string(const struct options_table_entry *); const char *options_name(struct options_entry *); +struct options *options_owner(struct options_entry *); const struct options_table_entry *options_table_entry(struct options_entry *); struct options_entry *options_get_only(struct options *, const char *); struct options_entry *options_get(struct options *, const char *); @@ -1918,9 +1926,9 @@ struct options_array_item *options_array_first(struct options_entry *); struct options_array_item *options_array_next(struct options_array_item *); u_int options_array_item_index(struct options_array_item *); union options_value *options_array_item_value(struct options_array_item *); -int options_isarray(struct options_entry *); -int options_isstring(struct options_entry *); -char *options_tostring(struct options_entry *, int, int); +int options_is_array(struct options_entry *); +int options_is_string(struct options_entry *); +char *options_to_string(struct options_entry *, int, int); char *options_parse(const char *, int *); struct options_entry *options_parse_get(struct options *, const char *, int *, int); @@ -1940,6 +1948,10 @@ int options_scope_from_flags(struct args *, int, struct cmd_find_state *, struct options **, char **); struct style *options_string_to_style(struct options *, const char *, struct format_tree *); +int options_from_string(struct options *, + const struct options_table_entry *, const char *, + const char *, int, char **); +void options_push_changes(const char *); /* options-table.c */ extern const struct options_table_entry options_table[]; @@ -2231,6 +2243,7 @@ struct key_table *key_bindings_first_table(void); struct key_table *key_bindings_next_table(struct key_table *); void key_bindings_unref_table(struct key_table *); struct key_binding *key_bindings_get(struct key_table *, key_code); +struct key_binding *key_bindings_get_default(struct key_table *, key_code); struct key_binding *key_bindings_first(struct key_table *); struct key_binding *key_bindings_next(struct key_table *, struct key_binding *); void key_bindings_add(const char *, key_code, const char *, int, @@ -2460,6 +2473,8 @@ void screen_write_start_callback(struct screen_write_ctx *, struct screen *, void screen_write_stop(struct screen_write_ctx *); void screen_write_reset(struct screen_write_ctx *); size_t printflike(1, 2) screen_write_strlen(const char *, ...); +int printflike(7, 8) screen_write_text(struct screen_write_ctx *, u_int, u_int, + u_int, int, const struct grid_cell *, const char *, ...); void printflike(3, 4) screen_write_puts(struct screen_write_ctx *, const struct grid_cell *, const char *, ...); void printflike(4, 5) screen_write_nputs(struct screen_write_ctx *, @@ -2678,19 +2693,23 @@ typedef void (*mode_tree_draw_cb)(void *, void *, struct screen_write_ctx *, u_int, u_int); typedef int (*mode_tree_search_cb)(void *, void *, const char *); typedef void (*mode_tree_menu_cb)(void *, struct client *, key_code); +typedef u_int (*mode_tree_height_cb)(void *, u_int); typedef void (*mode_tree_each_cb)(void *, void *, struct client *, key_code); u_int mode_tree_count_tagged(struct mode_tree_data *); void *mode_tree_get_current(struct mode_tree_data *); +const char *mode_tree_get_current_name(struct mode_tree_data *); void mode_tree_expand_current(struct mode_tree_data *); +void mode_tree_collapse_current(struct mode_tree_data *); void mode_tree_expand(struct mode_tree_data *, uint64_t); int mode_tree_set_current(struct mode_tree_data *, uint64_t); void mode_tree_each_tagged(struct mode_tree_data *, mode_tree_each_cb, struct client *, key_code, int); +void mode_tree_up(struct mode_tree_data *, int); void mode_tree_down(struct mode_tree_data *, int); struct mode_tree_data *mode_tree_start(struct window_pane *, struct args *, mode_tree_build_cb, mode_tree_draw_cb, mode_tree_search_cb, - mode_tree_menu_cb, void *, const struct menu_item *, const char **, - u_int, struct screen **); + mode_tree_menu_cb, mode_tree_height_cb, void *, + const struct menu_item *, const char **, u_int, struct screen **); void mode_tree_zoom(struct mode_tree_data *, struct args *); void mode_tree_build(struct mode_tree_data *); void mode_tree_free(struct mode_tree_data *); @@ -2698,6 +2717,7 @@ void mode_tree_resize(struct mode_tree_data *, u_int, u_int); struct mode_tree_item *mode_tree_add(struct mode_tree_data *, struct mode_tree_item *, void *, uint64_t, const char *, const char *, int); +void mode_tree_draw_as_parent(struct mode_tree_item *); void mode_tree_remove(struct mode_tree_data *, struct mode_tree_item *); void mode_tree_draw(struct mode_tree_data *); int mode_tree_key(struct mode_tree_data *, struct client *, key_code *, @@ -2728,6 +2748,9 @@ void window_copy_start_drag(struct client *, struct mouse_event *); char *window_copy_get_word(struct window_pane *, u_int, u_int); char *window_copy_get_line(struct window_pane *, u_int); +/* window-option.c */ +extern const struct window_mode window_customize_mode; + /* names.c */ void check_window_name(struct window *); char *default_window_name(struct window *); diff --git a/window-buffer.c b/window-buffer.c index 74be73a4..6156e6b4 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -298,8 +298,8 @@ window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs, data->command = xstrdup(args->argv[0]); data->data = mode_tree_start(wp, args, window_buffer_build, - window_buffer_draw, window_buffer_search, window_buffer_menu, data, - window_buffer_menu_items, window_buffer_sort_list, + window_buffer_draw, window_buffer_search, window_buffer_menu, NULL, + data, window_buffer_menu_items, window_buffer_sort_list, nitems(window_buffer_sort_list), &s); mode_tree_zoom(data->data, args); diff --git a/window-client.c b/window-client.c index cd424dd7..159efa5a 100644 --- a/window-client.c +++ b/window-client.c @@ -271,7 +271,7 @@ window_client_init(struct window_mode_entry *wme, data->command = xstrdup(args->argv[0]); data->data = mode_tree_start(wp, args, window_client_build, - window_client_draw, NULL, window_client_menu, data, + window_client_draw, NULL, window_client_menu, NULL, data, window_client_menu_items, window_client_sort_list, nitems(window_client_sort_list), &s); mode_tree_zoom(data->data, args); diff --git a/window-customize.c b/window-customize.c new file mode 100644 index 00000000..2d82897f --- /dev/null +++ b/window-customize.c @@ -0,0 +1,1430 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2020 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include + +#include "tmux.h" + +static struct screen *window_customize_init(struct window_mode_entry *, + struct cmd_find_state *, struct args *); +static void window_customize_free(struct window_mode_entry *); +static void window_customize_resize(struct window_mode_entry *, + u_int, u_int); +static void window_customize_key(struct window_mode_entry *, + struct client *, struct session *, + struct winlink *, key_code, struct mouse_event *); + +#define WINDOW_CUSTOMIZE_DEFAULT_FORMAT \ + "#{?is_option," \ + "#{?option_is_global,,#[reverse](#{option_scope})#[default] }" \ + "#[ignore]" \ + "#{option_value}#{?option_unit, #{option_unit},}" \ + "," \ + "#{key}" \ + "}" + +static const struct menu_item window_customize_menu_items[] = { + { "Select", '\r', NULL }, + { "Expand", KEYC_RIGHT, NULL }, + { "", KEYC_NONE, NULL }, + { "Tag", 't', NULL }, + { "Tag All", '\024', NULL }, + { "Tag None", 'T', NULL }, + { "", KEYC_NONE, NULL }, + { "Cancel", 'q', NULL }, + + { NULL, KEYC_NONE, NULL } +}; + +const struct window_mode window_customize_mode = { + .name = "options-mode", + .default_format = WINDOW_CUSTOMIZE_DEFAULT_FORMAT, + + .init = window_customize_init, + .free = window_customize_free, + .resize = window_customize_resize, + .key = window_customize_key, +}; + +enum window_customize_scope { + WINDOW_CUSTOMIZE_NONE, + WINDOW_CUSTOMIZE_KEY, + WINDOW_CUSTOMIZE_SERVER, + WINDOW_CUSTOMIZE_GLOBAL_SESSION, + WINDOW_CUSTOMIZE_SESSION, + WINDOW_CUSTOMIZE_GLOBAL_WINDOW, + WINDOW_CUSTOMIZE_WINDOW, + WINDOW_CUSTOMIZE_PANE +}; + +struct window_customize_itemdata { + struct window_customize_modedata *data; + enum window_customize_scope scope; + + char *table; + key_code key; + + struct options *oo; + char *name; + int idx; +}; + +struct window_customize_modedata { + struct window_pane *wp; + int dead; + int references; + + struct mode_tree_data *data; + char *format; + int hide_global; + + struct window_customize_itemdata **item_list; + u_int item_size; + + struct cmd_find_state fs; +}; + +static uint64_t +window_customize_get_tag(struct options_entry *o, int idx, + const struct options_table_entry *oe) +{ + uint64_t offset; + + if (oe == NULL) + return ((uint64_t)o); + offset = ((char *)oe - (char *)options_table) / sizeof *options_table; + return ((2ULL << 62)|(offset << 32)|((idx + 1) << 1)|1); +} + +static struct options * +window_customize_get_tree(enum window_customize_scope scope, + struct cmd_find_state *fs) +{ + switch (scope) { + case WINDOW_CUSTOMIZE_NONE: + case WINDOW_CUSTOMIZE_KEY: + return (NULL); + case WINDOW_CUSTOMIZE_SERVER: + return (global_options); + case WINDOW_CUSTOMIZE_GLOBAL_SESSION: + return (global_s_options); + case WINDOW_CUSTOMIZE_SESSION: + return (fs->s->options); + case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + return (global_w_options); + case WINDOW_CUSTOMIZE_WINDOW: + return (fs->w->options); + case WINDOW_CUSTOMIZE_PANE: + return (fs->wp->options); + } + return (NULL); +} + +static int +window_customize_check_item(struct window_customize_modedata *data, + struct window_customize_itemdata *item, struct cmd_find_state *fsp) +{ + struct cmd_find_state fs; + + if (fsp == NULL) + fsp = &fs; + + if (cmd_find_valid_state(&data->fs)) + cmd_find_copy_state(fsp, &data->fs); + else + cmd_find_from_pane(fsp, data->wp, 0); + return (item->oo == window_customize_get_tree(item->scope, fsp)); +} + +static int +window_customize_get_key(struct window_customize_itemdata *item, + struct key_table **ktp, struct key_binding **bdp) +{ + struct key_table *kt; + struct key_binding *bd; + + kt = key_bindings_get_table(item->table, 0); + if (kt == NULL) + return (0); + bd = key_bindings_get(kt, item->key); + if (bd == NULL) + return (0); + + if (ktp != NULL) + *ktp = kt; + if (bdp != NULL) + *bdp = bd; + return (1); +} + +static char * +window_customize_scope_text(enum window_customize_scope scope, + struct cmd_find_state *fs) +{ + char *s; + u_int idx; + + switch (scope) { + case WINDOW_CUSTOMIZE_NONE: + case WINDOW_CUSTOMIZE_KEY: + case WINDOW_CUSTOMIZE_SERVER: + case WINDOW_CUSTOMIZE_GLOBAL_SESSION: + case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + s = xstrdup(""); + break; + case WINDOW_CUSTOMIZE_PANE: + window_pane_index(fs->wp, &idx); + xasprintf(&s, "pane %u", idx); + break; + case WINDOW_CUSTOMIZE_SESSION: + xasprintf(&s, "session %s", fs->s->name); + break; + case WINDOW_CUSTOMIZE_WINDOW: + xasprintf(&s, "window %u", fs->wl->idx); + break; + } + return (s); +} + +static struct window_customize_itemdata * +window_customize_add_item(struct window_customize_modedata *data) +{ + struct window_customize_itemdata *item; + + data->item_list = xreallocarray(data->item_list, data->item_size + 1, + sizeof *data->item_list); + item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); + return (item); +} + +static void +window_customize_free_item(struct window_customize_itemdata *item) +{ + free(item->table); + free(item->name); + free(item); +} + +static void +window_customize_build_array(struct window_customize_modedata *data, + struct mode_tree_item *top, enum window_customize_scope scope, + struct options_entry *o, struct format_tree *ft) +{ + const struct options_table_entry *oe = options_table_entry(o); + struct options *oo = options_owner(o); + struct window_customize_itemdata *item; + struct options_array_item *ai; + char *name, *value, *text; + u_int idx; + uint64_t tag; + + ai = options_array_first(o); + while (ai != NULL) { + idx = options_array_item_index(ai); + + xasprintf(&name, "%s[%u]", options_name(o), idx); + format_add(ft, "option_name", "%s", name); + value = options_to_string(o, idx, 0); + format_add(ft, "option_value", "%s", value); + + item = window_customize_add_item(data); + item->scope = scope; + item->oo = oo; + item->name = xstrdup(options_name(o)); + item->idx = idx; + + text = format_expand(ft, data->format); + tag = window_customize_get_tag(o, idx, oe); + mode_tree_add(data->data, top, item, tag, name, text, -1); + free(text); + + free(name); + free(value); + + ai = options_array_next(ai); + } +} + +static void +window_customize_build_option(struct window_customize_modedata *data, + struct mode_tree_item *top, enum window_customize_scope scope, + struct options_entry *o, struct format_tree *ft, + const char *filter, struct cmd_find_state *fs) +{ + const struct options_table_entry *oe = options_table_entry(o); + struct options *oo = options_owner(o); + const char *name = options_name(o); + struct window_customize_itemdata *item; + char *text, *expanded, *value; + int global = 0, array = 0; + uint64_t tag; + + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK)) + return; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) + array = 1; + + if (scope == WINDOW_CUSTOMIZE_SERVER || + scope == WINDOW_CUSTOMIZE_GLOBAL_SESSION || + scope == WINDOW_CUSTOMIZE_GLOBAL_WINDOW) + global = 1; + if (data->hide_global && global) + return; + + format_add(ft, "option_name", "%s", name); + format_add(ft, "option_is_global", "%d", global); + format_add(ft, "option_is_array", "%d", array); + + text = window_customize_scope_text(scope, fs); + format_add(ft, "option_scope", "%s", text); + free(text); + + if (oe != NULL && oe->unit != NULL) + format_add(ft, "option_unit", "%s", oe->unit); + else + format_add(ft, "option_unit", "%s", ""); + + if (!array) { + value = options_to_string(o, -1, 0); + format_add(ft, "option_value", "%s", value); + free(value); + } + + if (filter != NULL) { + expanded = format_expand(ft, filter); + if (!format_true(expanded)) { + free(expanded); + return; + } + free(expanded); + } + item = window_customize_add_item(data); + item->oo = oo; + item->scope = scope; + item->name = xstrdup(name); + item->idx = -1; + + if (array) + text = NULL; + else + text = format_expand(ft, data->format); + tag = window_customize_get_tag(o, -1, oe); + top = mode_tree_add(data->data, top, item, tag, name, text, 0); + free(text); + + if (array) + window_customize_build_array(data, top, scope, o, ft); +} + +static void +window_customize_find_user_options(struct options *oo, const char ***list, + u_int *size) +{ + struct options_entry *o; + const char *name; + u_int i; + + o = options_first(oo); + while (o != NULL) { + name = options_name(o); + if (*name != '@') { + o = options_next(o); + continue; + } + for (i = 0; i < *size; i++) { + if (strcmp((*list)[i], name) == 0) + break; + } + if (i != *size) { + o = options_next(o); + continue; + } + *list = xreallocarray(*list, (*size) + 1, sizeof **list); + (*list)[(*size)++] = name; + + o = options_next(o); + } +} + +static void +window_customize_build_options(struct window_customize_modedata *data, + const char *title, uint64_t tag, + enum window_customize_scope scope0, struct options *oo0, + enum window_customize_scope scope1, struct options *oo1, + enum window_customize_scope scope2, struct options *oo2, + struct format_tree *ft, const char *filter, struct cmd_find_state *fs) +{ + struct mode_tree_item *top; + struct options_entry *o, *loop; + const char **list = NULL, *name; + u_int size = 0, i; + enum window_customize_scope scope; + + top = mode_tree_add(data->data, NULL, NULL, tag, title, NULL, 0); + + /* + * We get the options from the first tree, but build it using the + * values from the other two. Any tree can have user options so we need + * to build a separate list of them. + */ + + window_customize_find_user_options(oo0, &list, &size); + if (oo1 != NULL) + window_customize_find_user_options(oo1, &list, &size); + if (oo2 != NULL) + window_customize_find_user_options(oo2, &list, &size); + + for (i = 0; i < size; i++) { + if (oo2 != NULL) + o = options_get(oo0, list[i]); + else if (oo1 != NULL) + o = options_get(oo1, list[i]); + else + o = options_get(oo2, list[i]); + if (options_owner(o) == oo2) + scope = scope2; + else if (options_owner(o) == oo1) + scope = scope1; + else + scope = scope0; + window_customize_build_option(data, top, scope, o, ft, filter, + fs); + } + free(list); + + loop = options_first(oo0); + while (loop != NULL) { + name = options_name(loop); + if (*name == '@') { + loop = options_next(loop); + continue; + } + if (oo2 != NULL) + o = options_get(oo2, name); + else if (oo1 != NULL) + o = options_get(oo1, name); + else + o = loop; + if (options_owner(o) == oo2) + scope = scope2; + else if (options_owner(o) == oo1) + scope = scope1; + else + scope = scope0; + window_customize_build_option(data, top, scope, o, ft, filter, + fs); + loop = options_next(loop); + } +} + +static void +window_customize_build_keys(struct window_customize_modedata *data, + struct key_table *kt, struct format_tree *ft, const char *filter, + struct cmd_find_state *fs, u_int number) +{ + struct mode_tree_item *top, *child, *mti; + struct window_customize_itemdata *item; + struct key_binding *bd; + char *title, *text, *tmp, *expanded; + const char *flag; + uint64_t tag; + + tag = (1ULL << 62)|((uint64_t)number << 54)|1; + + xasprintf(&title, "Key Table - %s", kt->name); + top = mode_tree_add(data->data, NULL, NULL, tag, title, NULL, 0); + free(title); + + ft = format_create_from_state(NULL, NULL, fs); + format_add(ft, "is_option", "0"); + format_add(ft, "is_key", "1"); + + bd = key_bindings_first(kt); + while (bd != NULL) { + format_add(ft, "key", "%s", key_string_lookup_key(bd->key)); + if (bd->note != NULL) + format_add(ft, "key_note", "%s", bd->note); + if (filter != NULL) { + expanded = format_expand(ft, filter); + if (!format_true(expanded)) { + free(expanded); + continue; + } + free(expanded); + } + + item = window_customize_add_item(data); + item->scope = WINDOW_CUSTOMIZE_KEY; + item->table = xstrdup(kt->name); + item->key = bd->key; + + expanded = format_expand(ft, data->format); + child = mode_tree_add(data->data, top, item, (uint64_t)bd, + expanded, NULL, 0); + free(expanded); + + tmp = cmd_list_print(bd->cmdlist, 0); + xasprintf(&text, "#[ignore]%s", tmp); + free(tmp); + mti = mode_tree_add(data->data, child, item, + tag|(bd->key << 3)|(0 << 1)|1, "Command", text, -1); + mode_tree_draw_as_parent(mti); + free(text); + + if (bd->note != NULL) + xasprintf(&text, "#[ignore]%s", bd->note); + else + text = xstrdup(""); + mti = mode_tree_add(data->data, child, item, + tag|(bd->key << 3)|(1 << 1)|1, "Note", text, -1); + mode_tree_draw_as_parent(mti); + free(text); + + if (bd->flags & KEY_BINDING_REPEAT) + flag = "on"; + else + flag = "off"; + mti = mode_tree_add(data->data, child, item, + tag|(bd->key << 3)|(2 << 1)|1, "Repeat", flag, -1); + mode_tree_draw_as_parent(mti); + + bd = key_bindings_next(kt, bd); + } + + format_free(ft); +} + +static void +window_customize_build(void *modedata, + __unused struct mode_tree_sort_criteria *sort_crit, __unused uint64_t *tag, + const char *filter) +{ + struct window_customize_modedata *data = modedata; + struct cmd_find_state fs; + struct format_tree *ft; + u_int i; + struct key_table *kt; + + for (i = 0; i < data->item_size; i++) + window_customize_free_item(data->item_list[i]); + free(data->item_list); + data->item_list = NULL; + data->item_size = 0; + + if (cmd_find_valid_state(&data->fs)) + cmd_find_copy_state(&fs, &data->fs); + else + cmd_find_from_pane(&fs, data->wp, 0); + + ft = format_create_from_state(NULL, NULL, &fs); + format_add(ft, "is_option", "1"); + format_add(ft, "is_key", "0"); + + window_customize_build_options(data, "Server Options", + (3ULL << 62)|(OPTIONS_TABLE_SERVER << 1)|1, + WINDOW_CUSTOMIZE_SERVER, global_options, + WINDOW_CUSTOMIZE_NONE, NULL, + WINDOW_CUSTOMIZE_NONE, NULL, + ft, filter, &fs); + window_customize_build_options(data, "Session Options", + (3ULL << 62)|(OPTIONS_TABLE_SESSION << 1)|1, + WINDOW_CUSTOMIZE_GLOBAL_SESSION, global_s_options, + WINDOW_CUSTOMIZE_SESSION, fs.s->options, + WINDOW_CUSTOMIZE_NONE, NULL, + ft, filter, &fs); + window_customize_build_options(data, "Window & Pane Options", + (3ULL << 62)|(OPTIONS_TABLE_WINDOW << 1)|1, + WINDOW_CUSTOMIZE_GLOBAL_WINDOW, global_w_options, + WINDOW_CUSTOMIZE_WINDOW, fs.w->options, + WINDOW_CUSTOMIZE_PANE, fs.wp->options, + ft, filter, &fs); + + format_free(ft); + ft = format_create_from_state(NULL, NULL, &fs); + + i = 0; + kt = key_bindings_first_table(); + while (kt != NULL) { + if (!RB_EMPTY(&kt->key_bindings)) { + window_customize_build_keys(data, kt, ft, filter, &fs, + i); + if (++i == 256) + break; + } + kt = key_bindings_next_table(kt); + } + + format_free(ft); +} + +static void +window_customize_draw_key(__unused struct window_customize_modedata *data, + struct window_customize_itemdata *item, struct screen_write_ctx *ctx, + u_int sx, u_int sy) +{ + struct screen *s = ctx->s; + u_int cx = s->cx, cy = s->cy; + struct key_table *kt; + struct key_binding *bd, *default_bd; + const char *note, *period = ""; + char *cmd, *default_cmd; + + if (item == NULL || !window_customize_get_key(item, &kt, &bd)) + return; + + note = bd->note; + if (note == NULL) + note = "There is no note for this key."; + if (*note != '\0' && note[strlen (note) - 1] != '.') + period = "."; + if (!screen_write_text(ctx, cx, sx, sy, 0, &grid_default_cell, "%s%s", + note, period)) + return; + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + return; + + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "This key is in the %s table.", kt->name)) + return; + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "This key %s repeat.", + (bd->flags & KEY_BINDING_REPEAT) ? "does" : "does not")) + return; + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + return; + + cmd = cmd_list_print(bd->cmdlist, 0); + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "Command: %s", cmd)) { + free(cmd); + return; + } + default_bd = key_bindings_get_default(kt, bd->key); + if (default_bd != NULL) { + default_cmd = cmd_list_print(default_bd->cmdlist, 0); + if (strcmp(cmd, default_cmd) != 0 && + !screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "The default is: %s", default_cmd)) { + free(default_cmd); + free(cmd); + return; + } + free(default_cmd); + } + free(cmd); +} + +static void +window_customize_draw_option(struct window_customize_modedata *data, + struct window_customize_itemdata *item, struct screen_write_ctx *ctx, + u_int sx, u_int sy) +{ + struct screen *s = ctx->s; + u_int cx = s->cx, cy = s->cy; + int idx; + struct options_entry *o, *parent; + struct options *go, *wo; + const struct options_table_entry *oe; + struct grid_cell gc; + const char **choice, *text, *name; + const char *space = "", *unit = ""; + char *value = NULL, *expanded; + char *default_value = NULL; + char choices[256] = ""; + struct cmd_find_state fs; + struct format_tree *ft; + + if (!window_customize_check_item(data, item, &fs)) + return; + name = item->name; + idx = item->idx; + + o = options_get(item->oo, name); + if (o == NULL) + return; + oe = options_table_entry(o); + + if (oe != NULL && oe->unit != NULL) { + space = " "; + unit = oe->unit; + } + ft = format_create_from_state(NULL, NULL, &fs); + + if (oe == NULL) + text = "This is a user option."; + else if (oe->text == NULL) + text = "This option doesn't have a description."; + else + text = oe->text; + if (!screen_write_text(ctx, cx, sx, sy, 0, &grid_default_cell, "%s", + text)) + goto out; + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + goto out; + + if (oe == NULL) + text = "user"; + else if ((oe->scope & (OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE)) == + (OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE)) + text = "window and pane"; + else if (oe->scope & OPTIONS_TABLE_WINDOW) + text = "window"; + else if (oe->scope & OPTIONS_TABLE_SESSION) + text = "session"; + else + text = "server"; + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "This is a %s option.", text)) + goto out; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + if (idx != -1) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), + 0, &grid_default_cell, + "This is an array option, index %u.", idx)) + goto out; + } else { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), + 0, &grid_default_cell, "This is an array option.")) + goto out; + } + if (idx == -1) + goto out; + } + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + goto out; + + value = options_to_string(o, idx, 0); + if (oe != NULL && idx == -1) { + default_value = options_default_to_string(oe); + if (strcmp(default_value, value) == 0) { + free(default_value); + default_value = NULL; + } + } + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "Option value: %s%s%s", value, space, unit)) + goto out; + if (oe == NULL || oe->type == OPTIONS_TABLE_STRING) { + expanded = format_expand(ft, value); + if (strcmp(expanded, value) != 0) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), + 0, &grid_default_cell, "This expands to: %s", + expanded)) + goto out; + } + free(expanded); + } + if (oe != NULL && oe->type == OPTIONS_TABLE_CHOICE) { + for (choice = oe->choices; *choice != NULL; choice++) { + strlcat(choices, *choice, sizeof choices); + strlcat(choices, ", ", sizeof choices); + } + choices[strlen(choices) - 2] = '\0'; + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "Available values are: %s", + choices)) + goto out; + } + if (oe != NULL && oe->type == OPTIONS_TABLE_COLOUR) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 1, + &grid_default_cell, "This is a colour option: ")) + goto out; + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.fg = options_get_number(item->oo, name); + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &gc, + "EXAMPLE")) + goto out; + } + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_STYLE)) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 1, + &grid_default_cell, "This is a style option: ")) + goto out; + style_apply(&gc, item->oo, name, ft); + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &gc, + "EXAMPLE")) + goto out; + } + if (default_value != NULL) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "The default is: %s%s%s", default_value, + space, unit)) + goto out; + } + + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy > cy + sy - 1) + goto out; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + wo = NULL; + go = NULL; + } else { + switch (item->scope) { + case WINDOW_CUSTOMIZE_PANE: + wo = options_get_parent(item->oo); + go = options_get_parent(wo); + break; + case WINDOW_CUSTOMIZE_WINDOW: + case WINDOW_CUSTOMIZE_SESSION: + wo = NULL; + go = options_get_parent(item->oo); + break; + default: + wo = NULL; + go = NULL; + break; + } + } + if (wo != NULL && options_owner(o) != wo) { + parent = options_get_only(wo, name); + if (parent != NULL) { + value = options_to_string(parent, -1 , 0); + if (!screen_write_text(ctx, s->cx, sx, + sy - (s->cy - cy), 0, &grid_default_cell, + "Window value (from window %u): %s%s%s", fs.wl->idx, + value, space, unit)) + goto out; + } + } + if (go != NULL && options_owner(o) != go) { + parent = options_get_only(go, name); + if (parent != NULL) { + value = options_to_string(parent, -1 , 0); + if (!screen_write_text(ctx, s->cx, sx, + sy - (s->cy - cy), 0, &grid_default_cell, + "Global value: %s%s%s", value, space, unit)) + goto out; + } + } + +out: + free(value); + free(default_value); + format_free(ft); +} + +static void +window_customize_draw(void *modedata, void *itemdata, + struct screen_write_ctx *ctx, u_int sx, u_int sy) +{ + struct window_customize_modedata *data = modedata; + struct window_customize_itemdata *item = itemdata; + + if (item == NULL) + return; + + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_draw_key(data, item, ctx, sx, sy); + else + window_customize_draw_option(data, item, ctx, sx, sy); +} + +static void +window_customize_menu(void *modedata, struct client *c, key_code key) +{ + struct window_customize_modedata *data = modedata; + struct window_pane *wp = data->wp; + struct window_mode_entry *wme; + + wme = TAILQ_FIRST(&wp->modes); + if (wme == NULL || wme->data != modedata) + return; + window_customize_key(wme, c, NULL, NULL, key, NULL); +} + +static u_int +window_customize_height(__unused void *modedata, __unused u_int height) +{ + return (12); +} + +static struct screen * +window_customize_init(struct window_mode_entry *wme, struct cmd_find_state *fs, + struct args *args) +{ + struct window_pane *wp = wme->wp; + struct window_customize_modedata *data; + struct screen *s; + + wme->data = data = xcalloc(1, sizeof *data); + data->wp = wp; + data->references = 1; + + memcpy(&data->fs, fs, sizeof data->fs); + + if (args == NULL || !args_has(args, 'F')) + data->format = xstrdup(WINDOW_CUSTOMIZE_DEFAULT_FORMAT); + else + data->format = xstrdup(args_get(args, 'F')); + + data->data = mode_tree_start(wp, args, window_customize_build, + window_customize_draw, NULL, window_customize_menu, + window_customize_height, data, window_customize_menu_items, NULL, 0, + &s); + mode_tree_zoom(data->data, args); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + + return (s); +} + +static void +window_customize_destroy(struct window_customize_modedata *data) +{ + u_int i; + + if (--data->references != 0) + return; + + for (i = 0; i < data->item_size; i++) + window_customize_free_item(data->item_list[i]); + free(data->item_list); + + free(data->format); + + free(data); +} + +static void +window_customize_free(struct window_mode_entry *wme) +{ + struct window_customize_modedata *data = wme->data; + + if (data == NULL) + return; + + data->dead = 1; + mode_tree_free(data->data); + window_customize_destroy(data); +} + +static void +window_customize_resize(struct window_mode_entry *wme, u_int sx, u_int sy) +{ + struct window_customize_modedata *data = wme->data; + + mode_tree_resize(data->data, sx, sy); +} + +static void +window_customize_free_callback(void *modedata) +{ + window_customize_destroy(modedata); +} + +static void +window_customize_free_item_callback(void *itemdata) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + + window_customize_free_item(item); + window_customize_destroy(data); +} + +static int +window_customize_set_option_callback(struct client *c, void *itemdata, + const char *s, __unused int done) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + struct options_entry *o; + const struct options_table_entry *oe; + struct options *oo = item->oo; + const char *name = item->name; + char *cause; + int idx = item->idx; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return (0); + o = options_get(oo, name); + if (o == NULL) + return (0); + oe = options_table_entry(o); + + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + if (idx == -1) { + for (idx = 0; idx < INT_MAX; idx++) { + if (options_array_get(o, idx) == NULL) + break; + } + } + if (options_array_set(o, idx, s, 0, &cause) != 0) + goto fail; + } else { + if (options_from_string(oo, oe, name, s, 0, &cause) != 0) + goto fail; + } + + options_push_changes(item->name); + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); + +fail: + *cause = toupper((u_char)*cause); + status_message_set(c, 1, "%s", cause); + free(cause); + return (0); +} + +static void +window_customize_set_option(struct client *c, + struct window_customize_modedata *data, + struct window_customize_itemdata *item, int global, int pane) +{ + struct options_entry *o; + const struct options_table_entry *oe; + struct options *oo; + struct window_customize_itemdata *new_item; + int flag, idx = item->idx; + enum window_customize_scope scope; + u_int choice; + const char *name = item->name, *space = ""; + char *prompt, *value, *text; + struct cmd_find_state fs; + + if (item == NULL || !window_customize_check_item(data, item, &fs)) + return; + o = options_get(item->oo, name); + if (o == NULL) + return; + + oe = options_table_entry(o); + if (~oe->scope & OPTIONS_TABLE_PANE) + pane = 0; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + scope = item->scope; + oo = item->oo; + } else { + if (global) { + switch (item->scope) { + case WINDOW_CUSTOMIZE_NONE: + case WINDOW_CUSTOMIZE_KEY: + case WINDOW_CUSTOMIZE_SERVER: + case WINDOW_CUSTOMIZE_GLOBAL_SESSION: + case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + scope = item->scope; + break; + case WINDOW_CUSTOMIZE_SESSION: + scope = WINDOW_CUSTOMIZE_GLOBAL_SESSION; + break; + case WINDOW_CUSTOMIZE_WINDOW: + case WINDOW_CUSTOMIZE_PANE: + scope = WINDOW_CUSTOMIZE_GLOBAL_WINDOW; + break; + } + } else { + switch (item->scope) { + case WINDOW_CUSTOMIZE_NONE: + case WINDOW_CUSTOMIZE_KEY: + case WINDOW_CUSTOMIZE_SERVER: + case WINDOW_CUSTOMIZE_SESSION: + scope = item->scope; + break; + case WINDOW_CUSTOMIZE_WINDOW: + case WINDOW_CUSTOMIZE_PANE: + if (pane) + scope = WINDOW_CUSTOMIZE_PANE; + else + scope = WINDOW_CUSTOMIZE_WINDOW; + break; + case WINDOW_CUSTOMIZE_GLOBAL_SESSION: + scope = WINDOW_CUSTOMIZE_SESSION; + break; + case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + if (pane) + scope = WINDOW_CUSTOMIZE_PANE; + else + scope = WINDOW_CUSTOMIZE_WINDOW; + break; + } + } + if (scope == item->scope) + oo = item->oo; + else + oo = window_customize_get_tree(scope, &fs); + } + + if (oe != NULL && oe->type == OPTIONS_TABLE_FLAG) { + flag = options_get_number(oo, name); + options_set_number(oo, name, !flag); + } else if (oe != NULL && oe->type == OPTIONS_TABLE_CHOICE) { + choice = options_get_number(oo, name); + if (oe->choices[choice + 1] == NULL) + choice = 0; + else + choice++; + options_set_number(oo, name, choice); + } else { + text = window_customize_scope_text(scope, &fs); + if (*text != '\0') + space = ", for "; + else if (scope != WINDOW_CUSTOMIZE_SERVER) + space = ", global"; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + if (idx == -1) { + xasprintf(&prompt, "(%s[+]%s%s) ", name, space, + text); + } else { + xasprintf(&prompt, "(%s[%d]%s%s) ", name, idx, + space, text); + } + } else + xasprintf(&prompt, "(%s%s%s) ", name, space, text); + free(text); + + value = options_to_string(o, idx, 0); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->scope = scope; + new_item->oo = oo; + new_item->name = xstrdup(name); + new_item->idx = idx; + + data->references++; + status_prompt_set(c, prompt, value, + window_customize_set_option_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT); + + free(prompt); + free(value); + } +} + +static void +window_customize_unset_option(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct options_entry *o; + const struct options_table_entry *oe; + + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return; + + o = options_get(item->oo, item->name); + if (o == NULL) + return; + if (item->idx != -1) { + if (item == mode_tree_get_current(data->data)) + mode_tree_up(data->data, 0); + options_array_set(o, item->idx, NULL, 0, NULL); + return; + } + oe = options_table_entry(o); + if (oe != NULL && + options_owner(o) != global_options && + options_owner(o) != global_s_options && + options_owner(o) != global_w_options) + options_remove(o); + else + options_default(options_owner(o), oe); +} + +static int +window_customize_set_command_callback(struct client *c, void *itemdata, + const char *s, __unused int done) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + struct key_binding *bd; + struct cmd_parse_result *pr; + char *error; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (item == NULL || !window_customize_get_key(item, NULL, &bd)) + return (0); + + pr = cmd_parse_from_string(s, NULL); + switch (pr->status) { + case CMD_PARSE_EMPTY: + error = xstrdup("empty command"); + goto fail; + case CMD_PARSE_ERROR: + error = pr->error; + goto fail; + case CMD_PARSE_SUCCESS: + break; + } + cmd_list_free(bd->cmdlist); + bd->cmdlist = pr->cmdlist; + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); + +fail: + *error = toupper((u_char)*error); + status_message_set(c, 1, "%s", error); + free(error); + return (0); +} + +static int +window_customize_set_note_callback(__unused struct client *c, void *itemdata, + const char *s, __unused int done) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + struct key_binding *bd; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (item == NULL || !window_customize_get_key(item, NULL, &bd)) + return (0); + + free((void *)bd->note); + bd->note = xstrdup(s); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); +} + +static void +window_customize_set_key(struct client *c, + struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + key_code key = item->key; + struct key_binding *bd; + const char *s; + char *prompt, *value; + struct window_customize_itemdata *new_item; + + if (item == NULL || !window_customize_get_key(item, NULL, &bd)) + return; + + s = mode_tree_get_current_name(data->data); + if (strcmp(s, "Repeat") == 0) + bd->flags ^= KEY_BINDING_REPEAT; + else if (strcmp(s, "Command") == 0) { + xasprintf(&prompt, "(%s) ", key_string_lookup_key(key)); + value = cmd_list_print(bd->cmdlist, 0); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->scope = item->scope; + new_item->table = xstrdup(item->table); + new_item->key = key; + + data->references++; + status_prompt_set(c, prompt, value, + window_customize_set_command_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT); + free(prompt); + free(value); + } else if (strcmp(s, "Note") == 0) { + xasprintf(&prompt, "(%s) ", key_string_lookup_key(key)); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->scope = item->scope; + new_item->table = xstrdup(item->table); + new_item->key = key; + + data->references++; + status_prompt_set(c, prompt, (bd->note == NULL ? "" : bd->note), + window_customize_set_note_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT); + free(prompt); + } +} + +static void +window_customize_unset_key(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct key_table *kt; + struct key_binding *bd; + + if (item == NULL || !window_customize_get_key(item, &kt, &bd)) + return; + + if (item == mode_tree_get_current(data->data)) { + mode_tree_collapse_current(data->data); + mode_tree_up(data->data, 0); + } + key_bindings_remove(kt->name, bd->key); +} + +static void +window_customize_unset_each(void *modedata, void *itemdata, + __unused struct client *c, __unused key_code key) +{ + struct window_customize_itemdata *item = itemdata; + + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_unset_key(modedata, item); + else { + window_customize_unset_option(modedata, item); + options_push_changes(item->name); + } +} + +static int +window_customize_unset_current_callback(__unused struct client *c, + void *modedata, const char *s, __unused int done) +{ + struct window_customize_modedata *data = modedata; + struct window_customize_itemdata *item; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') + return (0); + + item = mode_tree_get_current(data->data); + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_unset_key(data, item); + else { + window_customize_unset_option(data, item); + options_push_changes(item->name); + } + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); +} + +static int +window_customize_unset_tagged_callback(struct client *c, void *modedata, + const char *s, __unused int done) +{ + struct window_customize_modedata *data = modedata; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') + return (0); + + mode_tree_each_tagged(data->data, window_customize_unset_each, c, + KEYC_NONE, 0); + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); +} + +static void +window_customize_key(struct window_mode_entry *wme, struct client *c, + __unused struct session *s, __unused struct winlink *wl, key_code key, + struct mouse_event *m) +{ + struct window_pane *wp = wme->wp; + struct window_customize_modedata *data = wme->data; + struct window_customize_itemdata *item, *new_item; + int finished; + char *prompt; + u_int tagged; + + item = mode_tree_get_current(data->data); + finished = mode_tree_key(data->data, c, &key, m, NULL, NULL); + if (item != (new_item = mode_tree_get_current(data->data))) + item = new_item; + + switch (key) { + case '\r': + case 's': + if (item == NULL) + break; + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_set_key(c, data, item); + else { + window_customize_set_option(c, data, item, 0, 1); + options_push_changes(item->name); + } + mode_tree_build(data->data); + break; + case 'w': + if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY) + break; + window_customize_set_option(c, data, item, 0, 0); + options_push_changes(item->name); + mode_tree_build(data->data); + break; + case 'S': + case 'W': + if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY) + break; + window_customize_set_option(c, data, item, 1, 0); + options_push_changes(item->name); + mode_tree_build(data->data); + break; + case 'u': + if (item == NULL) + break; + if (item->scope == WINDOW_CUSTOMIZE_KEY) { + xasprintf(&prompt, "Unbind key %s? ", + key_string_lookup_key(item->key)); + } else + xasprintf(&prompt, "Unset option %s? ", item->name); + data->references++; + status_prompt_set(c, prompt, "", + window_customize_unset_current_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT); + free(prompt); + break; + case 'U': + tagged = mode_tree_count_tagged(data->data); + if (tagged == 0) + break; + xasprintf(&prompt, "Unset or unbind %u tagged? ", tagged); + data->references++; + status_prompt_set(c, prompt, "", + window_customize_unset_tagged_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT); + free(prompt); + break; + case 'H': + data->hide_global = !data->hide_global; + mode_tree_build(data->data); + break; + } + if (finished) + window_pane_reset_mode(wp); + else { + mode_tree_draw(data->data); + wp->flags |= PANE_REDRAW; + } +} diff --git a/window-tree.c b/window-tree.c index 2eff4b8a..5731fff6 100644 --- a/window-tree.c +++ b/window-tree.c @@ -885,7 +885,7 @@ window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs, data->squash_groups = !args_has(args, 'G'); data->data = mode_tree_start(wp, args, window_tree_build, - window_tree_draw, window_tree_search, window_tree_menu, data, + window_tree_draw, window_tree_search, window_tree_menu, NULL, data, window_tree_menu_items, window_tree_sort_list, nitems(window_tree_sort_list), &s); mode_tree_zoom(data->data, args); From 592f141deef2583d6dd09f5a53a358671e7312b8 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:03:30 +0000 Subject: [PATCH 37/49] Fix next-matching-bracket logic, from Chris Barber. --- window-copy.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/window-copy.c b/window-copy.c index f500a65e..4104d2ad 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1365,9 +1365,9 @@ window_copy_cmd_next_matching_bracket(struct window_copy_cmd_state *cs) px = data->cx; py = screen_hsize(s) + data->cy - data->oy; grid_get_cell(s->grid, px, py, &gc); - if (gc.data.size != 1 || - (gc.flags & GRID_FLAG_PADDING) || - strchr(close, *gc.data.data) == NULL) + if (gc.data.size == 1 && + (~gc.flags & GRID_FLAG_PADDING) && + strchr(close, *gc.data.data) != NULL) window_copy_scroll_to(wme, sx, sy); break; } From 126bacb473f7950a156944caba42fad5f1764287 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:03:57 +0000 Subject: [PATCH 38/49] Do not loop forever when search finds an empty match, GitHub issue 2203. --- window-copy.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/window-copy.c b/window-copy.c index 4104d2ad..95b4cc6e 100644 --- a/window-copy.c +++ b/window-copy.c @@ -2445,7 +2445,8 @@ window_copy_search_lr_regex(struct grid *gd, u_int *ppx, u_int *psx, u_int py, len += gd->sx; } - if (regexec(reg, buf, 1, ®match, eflags) == 0) { + if (regexec(reg, buf, 1, ®match, eflags) == 0 && + regmatch.rm_so != regmatch.rm_eo) { foundx = first; foundy = py; window_copy_cstrtocellpos(gd, len, &foundx, &foundy, @@ -2547,6 +2548,8 @@ window_copy_last_regex(struct grid *gd, u_int py, u_int first, u_int last, foundy = py; oldx = first; while (regexec(preg, buf + px, 1, ®match, eflags) == 0) { + if (regmatch.rm_so == regmatch.rm_eo) + break; window_copy_cstrtocellpos(gd, len, &foundx, &foundy, buf + px + regmatch.rm_so); if (foundy > py || foundx >= last) From dceb6a15d04a2b2e050ab41816d0df3fe224b416 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:07:55 +0000 Subject: [PATCH 39/49] Add a -D flag to ask tmux not to daemonize, useful both for running a debugger (lldb does not have follow-fork-mode) and for running with a managed supervisor init system. GitHub issue 2190. --- proc.c | 9 +++++++-- server-client.c | 15 +++++++++++++-- server.c | 44 +++++++++++++++++++++++++++----------------- tmux.1 | 13 ++++++++++++- tmux.c | 9 +++++++-- tmux.h | 1 + tty.c | 7 ++++++- 7 files changed, 73 insertions(+), 25 deletions(-) diff --git a/proc.c b/proc.c index 3fc190a2..ce3bc8af 100644 --- a/proc.c +++ b/proc.c @@ -37,6 +37,7 @@ struct tmuxproc { void (*signalcb)(int); + struct event ev_sigint; struct event ev_sighup; struct event ev_sigchld; struct event ev_sigcont; @@ -221,10 +222,13 @@ proc_set_signals(struct tmuxproc *tp, void (*signalcb)(int)) sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_IGN; - sigaction(SIGINT, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); sigaction(SIGTSTP, &sa, NULL); + sigaction(SIGTTIN, &sa, NULL); + sigaction(SIGTTOU, &sa, NULL); + signal_set(&tp->ev_sigint, SIGINT, proc_signal_cb, tp); + signal_add(&tp->ev_sigint, NULL); signal_set(&tp->ev_sighup, SIGHUP, proc_signal_cb, tp); signal_add(&tp->ev_sighup, NULL); signal_set(&tp->ev_sigchld, SIGCHLD, proc_signal_cb, tp); @@ -251,10 +255,10 @@ proc_clear_signals(struct tmuxproc *tp, int defaults) sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_DFL; - sigaction(SIGINT, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); sigaction(SIGTSTP, &sa, NULL); + signal_del(&tp->ev_sigint); signal_del(&tp->ev_sighup); signal_del(&tp->ev_sigchld); signal_del(&tp->ev_sigcont); @@ -264,6 +268,7 @@ proc_clear_signals(struct tmuxproc *tp, int defaults) signal_del(&tp->ev_sigwinch); if (defaults) { + sigaction(SIGINT, &sa, NULL); sigaction(SIGHUP, &sa, NULL); sigaction(SIGCHLD, &sa, NULL); sigaction(SIGCONT, &sa, NULL); diff --git a/server-client.c b/server-client.c index 0bd00fc4..f42c9843 100644 --- a/server-client.c +++ b/server-client.c @@ -238,11 +238,22 @@ server_client_create(int fd) int server_client_open(struct client *c, char **cause) { + const char *ttynam = _PATH_TTY; + if (c->flags & CLIENT_CONTROL) return (0); - if (strcmp(c->ttyname, "/dev/tty") == 0) { - *cause = xstrdup("can't use /dev/tty"); + if (strcmp(c->ttyname, ttynam) == 0|| + ((isatty(STDIN_FILENO) && + (ttynam = ttyname(STDIN_FILENO)) != NULL && + strcmp(c->ttyname, ttynam) == 0) || + (isatty(STDOUT_FILENO) && + (ttynam = ttyname(STDOUT_FILENO)) != NULL && + strcmp(c->ttyname, ttynam) == 0) || + (isatty(STDERR_FILENO) && + (ttynam = ttyname(STDERR_FILENO)) != NULL && + strcmp(c->ttyname, ttynam) == 0))) { + xasprintf(cause, "can't use %s", c->ttyname); return (-1); } diff --git a/server.c b/server.c index a4216b87..f07479ae 100644 --- a/server.c +++ b/server.c @@ -161,29 +161,35 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base, struct client *c; char *cause = NULL; - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) - fatal("socketpair failed"); - server_client_flags = flags; - sigfillset(&set); sigprocmask(SIG_BLOCK, &set, &oldset); - switch (fork()) { - case -1: - fatal("fork failed"); - case 0: - break; - default: - sigprocmask(SIG_SETMASK, &oldset, NULL); - close(pair[1]); - return (pair[0]); + + if (~flags & CLIENT_NOFORK) { + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) + fatal("socketpair failed"); + + switch (fork()) { + case -1: + fatal("fork failed"); + case 0: + break; + default: + sigprocmask(SIG_SETMASK, &oldset, NULL); + close(pair[1]); + return (pair[0]); + } + close(pair[0]); + if (daemon(1, 0) != 0) + fatal("daemon failed"); } - close(pair[0]); - if (daemon(1, 0) != 0) - fatal("daemon failed"); + + server_client_flags = flags; proc_clear_signals(client, 0); + if (event_reinit(base) != 0) fatalx("event_reinit failed"); server_proc = proc_start("server"); + proc_set_signals(server_proc, server_signal); sigprocmask(SIG_SETMASK, &oldset, NULL); @@ -205,7 +211,10 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base, server_fd = server_create_socket(flags, &cause); if (server_fd != -1) server_update_socket(); - c = server_client_create(pair[1]); + if (~flags & CLIENT_NOFORK) + c = server_client_create(pair[1]); + else + options_set_number(global_options, "exit-empty", 0); if (lockfd >= 0) { unlink(lockfile); @@ -396,6 +405,7 @@ server_signal(int sig) log_debug("%s: %s", __func__, strsignal(sig)); switch (sig) { + case SIGINT: case SIGTERM: server_exit = 1; server_send_exit(); diff --git a/tmux.1 b/tmux.1 index 45c004ff..6c19283e 100644 --- a/tmux.1 +++ b/tmux.1 @@ -23,7 +23,7 @@ .Sh SYNOPSIS .Nm tmux .Bk -words -.Op Fl 2CluvV +.Op Fl 2CDluvV .Op Fl c Ar shell-command .Op Fl f Ar file .Op Fl L Ar socket-name @@ -122,6 +122,17 @@ This option is for compatibility with when .Nm is used as a login shell. +.It Fl D +Do not start the +.Nm +server as a daemon. +This also turns the +.Ic exit-empty +option off. +With +.Fl D , +.Ar command +may not be specified. .It Fl f Ar file Specify an alternative configuration file. By default, diff --git a/tmux.c b/tmux.c index 137c7eeb..cfc84cd0 100644 --- a/tmux.c +++ b/tmux.c @@ -57,7 +57,7 @@ static __dead void usage(void) { fprintf(stderr, - "usage: %s [-2CluvV] [-c shell-command] [-f file] [-L socket-name]\n" + "usage: %s [-2CDluvV] [-c shell-command] [-f file] [-L socket-name]\n" " [-S socket-path] [-T features] [command [flags]]\n", getprogname()); exit(1); @@ -336,7 +336,7 @@ main(int argc, char **argv) if (**argv == '-') flags = CLIENT_LOGIN; - while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:T:uUvV")) != -1) { + while ((opt = getopt(argc, argv, "2c:CDdf:lL:qS:T:uUvV")) != -1) { switch (opt) { case '2': tty_add_features(&feat, "256", ":,"); @@ -344,6 +344,9 @@ main(int argc, char **argv) case 'c': shell_command = optarg; break; + case 'D': + flags |= CLIENT_NOFORK; + break; case 'C': if (flags & CLIENT_CONTROL) flags |= CLIENT_CONTROLCONTROL; @@ -387,6 +390,8 @@ main(int argc, char **argv) if (shell_command != NULL && argc != 0) usage(); + if ((flags & CLIENT_NOFORK) && argc != 0) + usage(); if ((ptm_fd = getptmfd()) == -1) err(1, "getptmfd"); diff --git a/tmux.h b/tmux.h index 1abb6ca6..8ab78d7a 100644 --- a/tmux.h +++ b/tmux.h @@ -1584,6 +1584,7 @@ struct client { #define CLIENT_DEFAULTSOCKET 0x8000000 #define CLIENT_STARTSERVER 0x10000000 #define CLIENT_REDRAWPANES 0x20000000 +#define CLIENT_NOFORK 0x40000000 #define CLIENT_ALLREDRAWFLAGS \ (CLIENT_REDRAWWINDOW| \ CLIENT_REDRAWSTATUS| \ diff --git a/tty.c b/tty.c index c8efeac7..a1e2f2c6 100644 --- a/tty.c +++ b/tty.c @@ -154,16 +154,21 @@ tty_read_callback(__unused int fd, __unused short events, void *data) { struct tty *tty = data; struct client *c = tty->client; + const char *name = c->name; size_t size = EVBUFFER_LENGTH(tty->in); int nread; nread = evbuffer_read(tty->in, tty->fd, -1); if (nread == 0 || nread == -1) { + if (nread == 0) + log_debug("%s: read closed", name); + else + log_debug("%s: read error: %s", name, strerror(errno)); event_del(&tty->event_in); server_client_lost(tty->client); return; } - log_debug("%s: read %d bytes (already %zu)", c->name, nread, size); + log_debug("%s: read %d bytes (already %zu)", name, nread, size); while (tty_keys_next(tty)) ; From ff8dd150e0c59b37d9a4942e499a2a025820db8d Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:10:28 +0000 Subject: [PATCH 40/49] Add a mark in copy mode. Set with set-mark command (bound to 'X') by default and the mark and cursor position are swapped with 'jump-to-mark' (bound to M-x). The line containing the mark is shown in copy-mode-mark-style with the horizontal position in reverse. From Anindya Mukherjee in GitHub issue 2209. --- key-bindings.c | 4 ++ options-table.c | 9 ++++ tmux.1 | 10 +++++ window-copy.c | 107 ++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 121 insertions(+), 9 deletions(-) diff --git a/key-bindings.c b/key-bindings.c index ecd2f7dc..05089bab 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -426,6 +426,7 @@ key_bindings_init(void) "bind -Tcopy-mode N send -X search-reverse", "bind -Tcopy-mode R send -X rectangle-toggle", "bind -Tcopy-mode T command-prompt -1p'(jump to backward)' 'send -X jump-to-backward \"%%%\"'", + "bind -Tcopy-mode X send -X set-mark", "bind -Tcopy-mode f command-prompt -1p'(jump forward)' 'send -X jump-forward \"%%%\"'", "bind -Tcopy-mode g command-prompt -p'(goto line)' 'send -X goto-line \"%%%\"'", "bind -Tcopy-mode n send -X search-again", @@ -467,6 +468,7 @@ key_bindings_init(void) "bind -Tcopy-mode M-r send -X middle-line", "bind -Tcopy-mode M-v send -X page-up", "bind -Tcopy-mode M-w send -X copy-pipe-and-cancel", + "bind -Tcopy-mode M-x send -X jump-to-mark", "bind -Tcopy-mode 'M-{' send -X previous-paragraph", "bind -Tcopy-mode 'M-}' send -X next-paragraph", "bind -Tcopy-mode M-Up send -X halfpage-up", @@ -521,6 +523,7 @@ key_bindings_init(void) "bind -Tcopy-mode-vi T command-prompt -1p'(jump to backward)' 'send -X jump-to-backward \"%%%\"'", "bind -Tcopy-mode-vi V send -X select-line", "bind -Tcopy-mode-vi W send -X next-space", + "bind -Tcopy-mode-vi X send -X set-mark", "bind -Tcopy-mode-vi ^ send -X back-to-indentation", "bind -Tcopy-mode-vi b send -X previous-word", "bind -Tcopy-mode-vi e send -X next-word-end", @@ -554,6 +557,7 @@ key_bindings_init(void) "bind -Tcopy-mode-vi Down send -X cursor-down", "bind -Tcopy-mode-vi Left send -X cursor-left", "bind -Tcopy-mode-vi Right send -X cursor-right", + "bind -Tcopy-mode-vi M-x send -X jump-to-mark", "bind -Tcopy-mode-vi C-Up send -X scroll-up", "bind -Tcopy-mode-vi C-Down send -X scroll-down", }; diff --git a/options-table.c b/options-table.c index 55eb5b9f..b1c4ec1b 100644 --- a/options-table.c +++ b/options-table.c @@ -795,6 +795,15 @@ const struct options_table_entry options_table[] = { .text = "Style of the current search match in copy mode." }, + { .name = "copy-mode-mark-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "bg=red,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the marked line in copy mode." + }, + { .name = "main-pane-height", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, diff --git a/tmux.1 b/tmux.1 index 6c19283e..baa296fb 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1555,6 +1555,7 @@ The following commands are supported in copy mode: .It Li "jump-reverse" Ta "," Ta "," .It Li "jump-to-backward " Ta "T" Ta "" .It Li "jump-to-forward " Ta "t" Ta "" +.It Li "jump-to-mark" Ta "M-x" Ta "M-x" .It Li "middle-line" Ta "M" Ta "M-r" .It Li "next-matching-bracket" Ta "%" Ta "M-C-f" .It Li "next-paragraph" Ta "}" Ta "M-}" @@ -1585,6 +1586,7 @@ The following commands are supported in copy mode: .It Li "search-reverse" Ta "N" Ta "N" .It Li "select-line" Ta "V" Ta "" .It Li "select-word" Ta "" Ta "" +.It Li "set-mark" Ta "X" Ta "X" .It Li "start-of-line" Ta "0" Ta "C-a" .It Li "stop-selection" Ta "" Ta "" .It Li "top-line" Ta "H" Ta "M-R" @@ -3817,6 +3819,14 @@ see the .Sx STYLES section. .Pp +.It Ic copy-mode-mark-style Ar style +Set the style of the line containing the mark in copy mode. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp .It Ic copy-mode-current-match-style Ar style Set the style of the current search match in copy mode. For how to specify diff --git a/window-copy.c b/window-copy.c index 95b4cc6e..e572eaa8 100644 --- a/window-copy.c +++ b/window-copy.c @@ -131,6 +131,7 @@ static void window_copy_rectangle_toggle(struct window_mode_entry *); static void window_copy_move_mouse(struct mouse_event *); static void window_copy_drag_update(struct client *, struct mouse_event *); static void window_copy_drag_release(struct client *, struct mouse_event *); +static void window_copy_jump_to_mark(struct window_mode_entry *); const struct window_mode window_copy_mode = { .name = "copy-mode", @@ -254,6 +255,10 @@ struct window_copy_mode_data { u_int lastcx; /* position in last line w/ content */ u_int lastsx; /* size of last line w/ content */ + u_int mx; /* mark position */ + u_int my; + int showmark; + int searchtype; int searchregex; char *searchstr; @@ -424,6 +429,9 @@ window_copy_init(struct window_mode_entry *wme, data->screen.cx = data->cx; data->screen.cy = data->cy; + data->mx = data->cx; + data->my = screen_hsize(data->backing) + data->cy - data->oy; + data->showmark = 0; screen_write_start(&ctx, &data->screen); for (i = 0; i < screen_size_y(&data->screen); i++) @@ -448,6 +456,9 @@ window_copy_view_init(struct window_mode_entry *wme, data->backing = s = xmalloc(sizeof *data->backing); screen_init(s, screen_size_x(base), screen_size_y(base), UINT_MAX); + data->mx = data->cx; + data->my = screen_hsize(data->backing) + data->cy - data->oy; + data->showmark = 0; return (&data->screen); } @@ -1733,6 +1744,17 @@ window_copy_cmd_select_word(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_REDRAW); } +static enum window_copy_cmd_action +window_copy_cmd_set_mark(struct window_copy_cmd_state *cs) +{ + struct window_copy_mode_data *data = cs->wme->data; + + data->mx = data->cx; + data->my = screen_hsize(data->backing) + data->cy - data->oy; + data->showmark = 1; + return (WINDOW_COPY_CMD_REDRAW); +} + static enum window_copy_cmd_action window_copy_cmd_start_of_line(struct window_copy_cmd_state *cs) { @@ -1877,6 +1899,15 @@ window_copy_cmd_jump_to_forward(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_NOTHING); } +static enum window_copy_cmd_action +window_copy_cmd_jump_to_mark(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_jump_to_mark(wme); + return (WINDOW_COPY_CMD_NOTHING); +} + static enum window_copy_cmd_action window_copy_cmd_search_backward(struct window_copy_cmd_state *cs) { @@ -2154,6 +2185,8 @@ static const struct { window_copy_cmd_jump_to_backward }, { "jump-to-forward", 1, 1, 1, window_copy_cmd_jump_to_forward }, + { "jump-to-mark", 0, 0, 0, + window_copy_cmd_jump_to_mark }, { "middle-line", 0, 0, 1, window_copy_cmd_middle_line }, { "next-matching-bracket", 0, 0, 0, @@ -2214,6 +2247,8 @@ static const struct { window_copy_cmd_select_line }, { "select-word", 0, 0, 0, window_copy_cmd_select_word }, + { "set-mark", 0, 0, 0, + window_copy_cmd_set_mark }, { "start-of-line", 0, 0, 1, window_copy_cmd_start_of_line }, { "stop-selection", 0, 0, 0, @@ -3129,11 +3164,26 @@ window_copy_match_at_cursor(struct window_copy_mode_data *data) static void window_copy_update_style(struct window_mode_entry *wme, u_int fx, u_int fy, struct grid_cell *gc, const struct grid_cell *mgc, - const struct grid_cell *cgc) + const struct grid_cell *cgc, const struct grid_cell *mkgc) { struct window_copy_mode_data *data = wme->data; u_int mark, start, end, cy, cursor, current; u_int sx = screen_size_x(data->backing); + int inv = 0; + + if (data->showmark && fy == data->my) { + gc->attr = mkgc->attr; + if (fx == data->mx) + inv = 1; + if (inv) { + gc->fg = mkgc->bg; + gc->bg = mkgc->fg; + } + else { + gc->fg = mkgc->fg; + gc->bg = mkgc->bg; + } + } if (data->searchmark == NULL) return; @@ -3150,21 +3200,34 @@ window_copy_update_style(struct window_mode_entry *wme, u_int fx, u_int fy, window_copy_match_start_end(data, cursor, &start, &end); if (current >= start && current <= end) { gc->attr = cgc->attr; - gc->fg = cgc->fg; - gc->bg = cgc->bg; + if (inv) { + gc->fg = cgc->bg; + gc->bg = cgc->fg; + } + else { + gc->fg = cgc->fg; + gc->bg = cgc->bg; + } return; } } gc->attr = mgc->attr; - gc->fg = mgc->fg; - gc->bg = mgc->bg; + if (inv) { + gc->fg = mgc->bg; + gc->bg = mgc->fg; + } + else { + gc->fg = mgc->fg; + gc->bg = mgc->bg; + } } static void window_copy_write_one(struct window_mode_entry *wme, struct screen_write_ctx *ctx, u_int py, u_int fy, u_int nx, - const struct grid_cell *mgc, const struct grid_cell *cgc) + const struct grid_cell *mgc, const struct grid_cell *cgc, + const struct grid_cell *mkgc) { struct window_copy_mode_data *data = wme->data; struct grid *gd = data->backing->grid; @@ -3175,7 +3238,8 @@ window_copy_write_one(struct window_mode_entry *wme, for (fx = 0; fx < nx; fx++) { grid_get_cell(gd, fx, fy, &gc); if (fx + gc.data.width <= nx) { - window_copy_update_style(wme, fx, fy, &gc, mgc, cgc); + window_copy_update_style(wme, fx, fy, &gc, mgc, cgc, + mkgc); screen_write_cell(ctx, &gc); } } @@ -3189,7 +3253,7 @@ window_copy_write_line(struct window_mode_entry *wme, struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; struct options *oo = wp->window->options; - struct grid_cell gc, mgc, cgc; + struct grid_cell gc, mgc, cgc, mkgc; char hdr[512]; size_t size = 0; u_int hsize = screen_hsize(data->backing); @@ -3200,6 +3264,8 @@ window_copy_write_line(struct window_mode_entry *wme, mgc.flags |= GRID_FLAG_NOPALETTE; style_apply(&cgc, oo, "copy-mode-current-match-style", NULL); cgc.flags |= GRID_FLAG_NOPALETTE; + style_apply(&mkgc, oo, "copy-mode-mark-style", NULL); + mkgc.flags |= GRID_FLAG_NOPALETTE; if (py == 0 && s->rupper < s->rlower && !data->hide_position) { if (data->searchmark == NULL) { @@ -3233,7 +3299,7 @@ window_copy_write_line(struct window_mode_entry *wme, if (size < screen_size_x(s)) { window_copy_write_one(wme, ctx, py, hsize - data->oy + py, - screen_size_x(s) - size, &mgc, &cgc); + screen_size_x(s) - size, &mgc, &cgc, &mkgc); } if (py == data->cy && data->cx == screen_size_x(s)) { @@ -4687,3 +4753,26 @@ window_copy_drag_release(struct client *c, struct mouse_event *m) data = wme->data; evtimer_del(&data->dragtimer); } + +static void +window_copy_jump_to_mark(struct window_mode_entry *wme) +{ + struct window_copy_mode_data *data = wme->data; + u_int tmx, tmy; + + tmx = data->cx; + tmy = screen_hsize(data->backing) + data->cy - data->oy; + data->cx = data->mx; + if (data->my < screen_hsize(data->backing)) { + data->cy = 0; + data->oy = screen_hsize(data->backing) - data->my; + } else { + data->cy = data->my - screen_hsize(data->backing); + data->oy = 0; + } + data->mx = tmx; + data->my = tmy; + data->showmark = 1; + window_copy_update_selection(wme, 0, 0); + window_copy_redraw_screen(wme); +} From 72984c48347ddfd1d435f8a9ffcdf334c4b28389 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:13:09 +0000 Subject: [PATCH 41/49] Move editor stuff to common code in popup.c. --- popup.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ tmux.h | 3 ++ window-buffer.c | 71 +++++------------------------------ 3 files changed, 111 insertions(+), 61 deletions(-) diff --git a/popup.c b/popup.c index 4b47df2b..93ecd2a1 100644 --- a/popup.c +++ b/popup.c @@ -19,9 +19,11 @@ #include #include +#include #include #include #include +#include #include "tmux.h" @@ -57,6 +59,12 @@ struct popup_data { u_int lb; }; +struct popup_editor { + char *path; + popup_finish_edit_cb cb; + void *arg; +}; + static void popup_redraw_cb(const struct tty_ctx *ttyctx) { @@ -519,3 +527,93 @@ popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx, popup_draw_cb, popup_key_cb, popup_free_cb, pd); return (0); } + +static void +popup_editor_free(struct popup_editor *pe) +{ + unlink(pe->path); + free(pe->path); + free(pe); +} + +static void +popup_editor_close_cb(int status, void *arg) +{ + struct popup_editor *pe = arg; + FILE *f; + char *buf = NULL; + off_t len = 0; + + if (status != 0) { + pe->cb(NULL, 0, pe->arg); + popup_editor_free(pe); + return; + } + + f = fopen(pe->path, "r"); + if (f != NULL) { + fseeko(f, 0, SEEK_END); + len = ftello(f); + fseeko(f, 0, SEEK_SET); + + if (len == 0 || + (uintmax_t)len > (uintmax_t)SIZE_MAX || + (buf = malloc(len)) == NULL || + fread(buf, len, 1, f) != 1) { + free(buf); + buf = NULL; + len = 0; + } + fclose(f); + } + pe->cb(buf, len, pe->arg); /* callback now owns buffer */ + popup_editor_free(pe); +} + +int +popup_editor(struct client *c, const char *buf, size_t len, + popup_finish_edit_cb cb, void *arg) +{ + struct popup_editor *pe; + int fd; + FILE *f; + char *cmd; + char path[] = _PATH_TMP "tmux.XXXXXXXX"; + const char *editor; + u_int px, py, sx, sy; + + editor = options_get_string(global_options, "editor"); + if (*editor == '\0') + return (-1); + + fd = mkstemp(path); + if (fd == -1) + return (-1); + f = fdopen(fd, "w"); + if (fwrite(buf, len, 1, f) != 1) { + fclose(f); + return (-1); + } + fclose(f); + + pe = xcalloc(1, sizeof *pe); + pe->path = xstrdup(path); + pe->cb = cb; + pe->arg = arg; + + sx = c->tty.sx * 9 / 10; + sy = c->tty.sy * 9 / 10; + px = (c->tty.sx / 2) - (sx / 2); + py = (c->tty.sy / 2) - (sy / 2); + + xasprintf(&cmd, "%s %s", editor, path); + if (popup_display(POPUP_WRITEKEYS|POPUP_CLOSEEXIT, NULL, px, py, sx, sy, + 0, NULL, cmd, NULL, _PATH_TMP, c, NULL, popup_editor_close_cb, + pe) != 0) { + popup_editor_free(pe); + free(cmd); + return (-1); + } + free(cmd); + return (0); +} diff --git a/tmux.h b/tmux.h index 8ab78d7a..c2b8af40 100644 --- a/tmux.h +++ b/tmux.h @@ -2869,6 +2869,7 @@ int menu_display(struct menu *, int, struct cmdq_item *, u_int, #define POPUP_CLOSEEXIT 0x2 #define POPUP_CLOSEEXITZERO 0x4 typedef void (*popup_close_cb)(int, void *); +typedef void (*popup_finish_edit_cb)(char *, size_t, void *); u_int popup_width(struct cmdq_item *, u_int, const char **, struct client *, struct cmd_find_state *); u_int popup_height(u_int, const char **); @@ -2876,6 +2877,8 @@ int popup_display(int, struct cmdq_item *, u_int, u_int, u_int, u_int, u_int, const char **, const char *, const char *, const char *, struct client *, struct cmd_find_state *, popup_close_cb, void *); +int popup_editor(struct client *, const char *, size_t, + popup_finish_edit_cb, void *); /* style.c */ int style_parse(struct style *,const struct grid_cell *, diff --git a/window-buffer.c b/window-buffer.c index 6156e6b4..ae6f13ce 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -18,8 +18,6 @@ #include -#include -#include #include #include #include @@ -100,7 +98,6 @@ struct window_buffer_modedata { struct window_buffer_editdata { u_int wp_id; - char *path; char *name; struct paste_buffer *pb; }; @@ -366,19 +363,14 @@ window_buffer_do_paste(void *modedata, void *itemdata, struct client *c, static void window_buffer_finish_edit(struct window_buffer_editdata *ed) { - unlink(ed->path); - free(ed->path); free(ed->name); free(ed); } static void -window_buffer_edit_close_cb(int status, void *arg) +window_buffer_edit_close_cb(char *buf, size_t len, void *arg) { struct window_buffer_editdata *ed = arg; - FILE *f; - off_t len; - char *buf; size_t oldlen; const char *oldbuf; struct paste_buffer *pb; @@ -386,7 +378,7 @@ window_buffer_edit_close_cb(int status, void *arg) struct window_buffer_modedata *data; struct window_mode_entry *wme; - if (status != 0) { + if (buf == NULL || len == 0) { window_buffer_finish_edit(ed); return; } @@ -397,26 +389,13 @@ window_buffer_edit_close_cb(int status, void *arg) return; } - f = fopen(ed->path, "r"); - if (f != NULL) { - fseeko(f, 0, SEEK_END); - len = ftello(f); - fseeko(f, 0, SEEK_SET); - - if (len > 0 && - (uintmax_t)len <= (uintmax_t)SIZE_MAX && - (buf = malloc(len)) != NULL && - fread(buf, len, 1, f) == 1) { - oldbuf = paste_buffer_data(pb, &oldlen); - if (oldlen != '\0' && - oldbuf[oldlen - 1] != '\n' && - buf[len - 1] == '\n') - len--; - if (len != 0) - paste_replace(pb, buf, len); - } - fclose(f); - } + oldbuf = paste_buffer_data(pb, &oldlen); + if (oldlen != '\0' && + oldbuf[oldlen - 1] != '\n' && + buf[len - 1] == '\n') + len--; + if (len != 0) + paste_replace(pb, buf, len); wp = window_pane_find_by_id(ed->wp_id); if (wp != NULL) { @@ -436,51 +415,21 @@ window_buffer_start_edit(struct window_buffer_modedata *data, struct window_buffer_itemdata *item, struct client *c) { struct paste_buffer *pb; - int fd; - FILE *f; const char *buf; size_t len; struct window_buffer_editdata *ed; - char *cmd; - char path[] = _PATH_TMP "tmux.XXXXXXXX"; - const char *editor; - u_int px, py, sx, sy; if ((pb = paste_get_name(item->name)) == NULL) return; buf = paste_buffer_data(pb, &len); - editor = options_get_string(global_options, "editor"); - if (*editor == '\0') - return; - - fd = mkstemp(path); - if (fd == -1) - return; - f = fdopen(fd, "w"); - if (fwrite(buf, len, 1, f) != 1) { - fclose(f); - return; - } - fclose(f); - ed = xcalloc(1, sizeof *ed); ed->wp_id = data->wp->id; - ed->path = xstrdup(path); ed->name = xstrdup(paste_buffer_name(pb)); ed->pb = pb; - sx = c->tty.sx * 9 / 10; - sy = c->tty.sy * 9 / 10; - px = (c->tty.sx / 2) - (sx / 2); - py = (c->tty.sy / 2) - (sy / 2); - - xasprintf(&cmd, "%s %s", editor, path); - if (popup_display(POPUP_WRITEKEYS|POPUP_CLOSEEXIT, NULL, px, py, sx, sy, - 0, NULL, cmd, NULL, _PATH_TMP, c, NULL, window_buffer_edit_close_cb, - ed) != 0) + if (popup_editor(c, buf, len, window_buffer_edit_close_cb, ed) != 0) window_buffer_finish_edit(ed); - free(cmd); } static void From c914abfa19938fe0e41941879649b7a40e192082 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:16:07 +0000 Subject: [PATCH 42/49] Expand target from client and use it to expand the prompt. --- cmd-command-prompt.c | 6 ++++-- cmd-confirm-before.c | 6 ++++-- cmd-queue.c | 3 +-- mode-tree.c | 4 ++-- server-client.c | 7 +++---- status.c | 11 +++++++---- tmux.h | 7 ++++--- window-customize.c | 11 ++++++----- window-tree.c | 9 +++++---- 9 files changed, 36 insertions(+), 28 deletions(-) diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index b8e3bd5c..c82235c5 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -66,6 +66,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; struct cmd_command_prompt_cdata *cdata; char *prompt, *ptr, *input = NULL; @@ -125,8 +126,9 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) cdata->flags |= PROMPT_WINDOW; else if (args_has(args, 'T')) cdata->flags |= PROMPT_TARGET; - status_prompt_set(tc, prompt, input, cmd_command_prompt_callback, - cmd_command_prompt_free, cdata, cdata->flags); + status_prompt_set(tc, target, prompt, input, + cmd_command_prompt_callback, cmd_command_prompt_free, cdata, + cdata->flags); free(prompt); return (CMD_RETURN_NORMAL); diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c index 0d881178..0c562836 100644 --- a/cmd-confirm-before.c +++ b/cmd-confirm-before.c @@ -56,6 +56,7 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); struct cmd_confirm_before_data *cdata; struct client *tc = cmdq_get_target_client(item); + struct cmd_find_state *target = cmdq_get_target(item); char *cmd, *copy, *new_prompt, *ptr; const char *prompt; @@ -71,8 +72,9 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) cdata = xmalloc(sizeof *cdata); cdata->cmd = xstrdup(args->argv[0]); - status_prompt_set(tc, new_prompt, NULL, cmd_confirm_before_callback, - cmd_confirm_before_free, cdata, PROMPT_SINGLE); + status_prompt_set(tc, target, new_prompt, NULL, + cmd_confirm_before_callback, cmd_confirm_before_free, cdata, + PROMPT_SINGLE); free(new_prompt); return (CMD_RETURN_NORMAL); diff --git a/cmd-queue.c b/cmd-queue.c index a40053a6..6bc6d0d2 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -522,7 +522,7 @@ cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, const char *value; if (flag->flag == 0) { - cmd_find_clear_state(fs, 0); + cmd_find_from_client(fs, item->target_client, 0); return (CMD_RETURN_NORMAL); } @@ -610,7 +610,6 @@ cmdq_fire_command(struct cmdq_item *item) if (retval == CMD_RETURN_ERROR) goto out; - retval = entry->exec(cmd, item); if (retval == CMD_RETURN_ERROR) goto out; diff --git a/mode-tree.c b/mode-tree.c index 131830d6..a523478e 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -1125,7 +1125,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, case '/': case '\023': /* C-s */ mtd->references++; - status_prompt_set(c, "(search) ", "", + status_prompt_set(c, NULL, "(search) ", "", mode_tree_search_callback, mode_tree_search_free, mtd, PROMPT_NOFORMAT); break; @@ -1134,7 +1134,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, break; case 'f': mtd->references++; - status_prompt_set(c, "(filter) ", mtd->filter, + status_prompt_set(c, NULL, "(filter) ", mtd->filter, mode_tree_filter_callback, mode_tree_filter_free, mtd, PROMPT_NOFORMAT); break; diff --git a/server-client.c b/server-client.c index f42c9843..1e0d992d 100644 --- a/server-client.c +++ b/server-client.c @@ -213,7 +213,6 @@ server_client_create(int fd) c->queue = cmdq_new(); c->tty.fd = -1; - c->tty.sx = 80; c->tty.sy = 24; @@ -272,7 +271,7 @@ server_client_open(struct client *c, char **cause) void server_client_lost(struct client *c) { - struct client_file *cf; + struct client_file *cf, *cf1; c->flags |= CLIENT_DEAD; @@ -280,7 +279,7 @@ server_client_lost(struct client *c) status_prompt_clear(c); status_message_clear(c); - RB_FOREACH(cf, client_files, &c->files) { + RB_FOREACH_SAFE(cf, client_files, &c->files, cf1) { cf->error = EINTR; file_fire_done(cf); } @@ -2250,7 +2249,7 @@ server_client_set_flags(struct client *c, const char *flags) } -/*Get client flags. This is only flags useful to show to users. */ +/* Get client flags. This is only flags useful to show to users. */ const char * server_client_get_flags(struct client *c) { diff --git a/status.c b/status.c index b5fa0824..56af02f5 100644 --- a/status.c +++ b/status.c @@ -532,14 +532,17 @@ status_message_redraw(struct client *c) /* Enable status line prompt. */ void -status_prompt_set(struct client *c, const char *msg, const char *input, - prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags) +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) { struct format_tree *ft; char *tmp, *cp; - ft = format_create(c, NULL, FORMAT_NONE, 0); - format_defaults(ft, c, NULL, NULL, NULL); + if (fs != NULL) + ft = format_create_from_state(NULL, c, fs); + else + ft = format_create_defaults(NULL, c, NULL, NULL, NULL); if (input == NULL) input = ""; diff --git a/tmux.h b/tmux.h index c2b8af40..3ccb005a 100644 --- a/tmux.h +++ b/tmux.h @@ -1504,7 +1504,7 @@ struct client_file { client_file_cb cb; void *data; - RB_ENTRY (client_file) entry; + RB_ENTRY(client_file) entry; }; RB_HEAD(client_files, client_file); @@ -2359,8 +2359,9 @@ void printflike(3, 4) status_message_set(struct client *, int, const char *, ...); void status_message_clear(struct client *); int status_message_redraw(struct client *); -void status_prompt_set(struct client *, const char *, const char *, - prompt_input_cb, prompt_free_cb, void *, int); +void status_prompt_set(struct client *, struct cmd_find_state *, + const char *, const char *, prompt_input_cb, prompt_free_cb, + void *, int); void status_prompt_clear(struct client *); int status_prompt_redraw(struct client *); int status_prompt_key(struct client *, key_code); diff --git a/window-customize.c b/window-customize.c index 2d82897f..f444dc6d 100644 --- a/window-customize.c +++ b/window-customize.c @@ -1111,7 +1111,7 @@ window_customize_set_option(struct client *c, new_item->idx = idx; data->references++; - status_prompt_set(c, prompt, value, + status_prompt_set(c, NULL, prompt, value, window_customize_set_option_callback, window_customize_free_item_callback, new_item, PROMPT_NOFORMAT); @@ -1243,7 +1243,7 @@ window_customize_set_key(struct client *c, new_item->key = key; data->references++; - status_prompt_set(c, prompt, value, + status_prompt_set(c, NULL, prompt, value, window_customize_set_command_callback, window_customize_free_item_callback, new_item, PROMPT_NOFORMAT); @@ -1259,7 +1259,8 @@ window_customize_set_key(struct client *c, new_item->key = key; data->references++; - status_prompt_set(c, prompt, (bd->note == NULL ? "" : bd->note), + status_prompt_set(c, NULL, prompt, + (bd->note == NULL ? "" : bd->note), window_customize_set_note_callback, window_customize_free_item_callback, new_item, PROMPT_NOFORMAT); @@ -1398,7 +1399,7 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, } else xasprintf(&prompt, "Unset option %s? ", item->name); data->references++; - status_prompt_set(c, prompt, "", + status_prompt_set(c, NULL, prompt, "", window_customize_unset_current_callback, window_customize_free_callback, data, PROMPT_SINGLE|PROMPT_NOFORMAT); @@ -1410,7 +1411,7 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, break; xasprintf(&prompt, "Unset or unbind %u tagged? ", tagged); data->references++; - status_prompt_set(c, prompt, "", + status_prompt_set(c, NULL, prompt, "", window_customize_unset_tagged_callback, window_customize_free_callback, data, PROMPT_SINGLE|PROMPT_NOFORMAT); diff --git a/window-tree.c b/window-tree.c index 5731fff6..32b94e15 100644 --- a/window-tree.c +++ b/window-tree.c @@ -1234,7 +1234,7 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, if (prompt == NULL) break; data->references++; - status_prompt_set(c, prompt, "", + status_prompt_set(c, NULL, prompt, "", window_tree_kill_current_callback, window_tree_command_free, data, PROMPT_SINGLE|PROMPT_NOFORMAT); free(prompt); @@ -1245,7 +1245,7 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, break; xasprintf(&prompt, "Kill %u tagged? ", tagged); data->references++; - status_prompt_set(c, prompt, "", + status_prompt_set(c, NULL, prompt, "", window_tree_kill_tagged_callback, window_tree_command_free, data, PROMPT_SINGLE|PROMPT_NOFORMAT); free(prompt); @@ -1257,8 +1257,9 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, else xasprintf(&prompt, "(current) "); data->references++; - status_prompt_set(c, prompt, "", window_tree_command_callback, - window_tree_command_free, data, PROMPT_NOFORMAT); + status_prompt_set(c, NULL, prompt, "", + window_tree_command_callback, window_tree_command_free, + data, PROMPT_NOFORMAT); free(prompt); break; case '\r': From 303d342d5fa5903983c08e4cae429e4f9480eea3 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:20:59 +0000 Subject: [PATCH 43/49] Add a client flag 'active-pane' which stores the active pane in the client and allows it to be changed independently from the real active pane stored in the window. This is can be used with session groups which allow an independent current window (although it would be nice to have a flag for this too and remove session groups). The client active pane is only really useful interactively, many things (hooks, window-style, zooming) still use the window active pane. --- cmd-break-pane.c | 1 + cmd-find.c | 23 +++++++++--- cmd-join-pane.c | 1 + cmd-kill-pane.c | 1 + cmd-queue.c | 2 +- cmd-select-pane.c | 18 ++++++--- cmd-split-window.c | 1 + cmd-swap-pane.c | 3 ++ screen-redraw.c | 15 ++++---- server-client.c | 91 +++++++++++++++++++++++++++++++++++++++++++++- server-fn.c | 2 + spawn.c | 1 + tmux.1 | 9 +++++ tmux.h | 17 ++++++++- tty.c | 5 +-- 15 files changed, 164 insertions(+), 26 deletions(-) diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 87892d73..9483aa7e 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -89,6 +89,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) } TAILQ_REMOVE(&w->panes, wp, entry); + server_client_remove_pane(wp); window_lost_pane(w, wp); layout_close_pane(wp); diff --git a/cmd-find.c b/cmd-find.c index 9c8bcbc1..9f04c4a8 100644 --- a/cmd-find.c +++ b/cmd-find.c @@ -588,22 +588,22 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane) return (-1); return (0); } else if (strcmp(pane, "{up-of}") == 0) { - fs->wp = window_pane_find_up(fs->w->active); + fs->wp = window_pane_find_up(fs->current->wp); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{down-of}") == 0) { - fs->wp = window_pane_find_down(fs->w->active); + fs->wp = window_pane_find_down(fs->current->wp); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{left-of}") == 0) { - fs->wp = window_pane_find_left(fs->w->active); + fs->wp = window_pane_find_left(fs->current->wp); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{right-of}") == 0) { - fs->wp = window_pane_find_right(fs->w->active); + fs->wp = window_pane_find_right(fs->current->wp); if (fs->wp == NULL) return (-1); return (0); @@ -615,7 +615,7 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane) n = strtonum(pane + 1, 1, INT_MAX, NULL); else n = 1; - wp = fs->w->active; + wp = fs->current->wp; if (pane[0] == '+') fs->wp = window_pane_next_by_number(fs->w, wp, n); else @@ -867,7 +867,18 @@ cmd_find_from_client(struct cmd_find_state *fs, struct client *c, int flags) /* If this is an attached client, all done. */ if (c->session != NULL) { - cmd_find_from_session(fs, c->session, flags); + cmd_find_clear_state(fs, flags); + + fs->wp = server_client_get_pane(c); + if (fs->wp == NULL) { + cmd_find_from_session(fs, c->session, flags); + return (0); + } + fs->s = c->session; + fs->wl = fs->s->curw; + fs->w = fs->wl->window; + + cmd_find_log_state(__func__, fs); return (0); } cmd_find_clear_state(fs, flags); diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 3efe769b..9802083d 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -136,6 +136,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) layout_close_pane(src_wp); + server_client_remove_pane(src_wp); window_lost_pane(src_w, src_wp); TAILQ_REMOVE(&src_w->panes, src_wp, entry); diff --git a/cmd-kill-pane.c b/cmd-kill-pane.c index 2302d7bb..3bf6e26e 100644 --- a/cmd-kill-pane.c +++ b/cmd-kill-pane.c @@ -54,6 +54,7 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item) TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) { if (loopwp == wp) continue; + server_client_remove_pane(loopwp); layout_close_pane(loopwp); window_remove_pane(wl->window, loopwp); } diff --git a/cmd-queue.c b/cmd-queue.c index 6bc6d0d2..5620fdad 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -809,7 +809,7 @@ cmdq_print(struct cmdq_item *item, const char *fmt, ...) } file_print(c, "%s\n", msg); } else { - wp = c->session->curw->window->active; + wp = server_client_get_pane(c); wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode != &window_view_mode) { window_pane_set_mode(wp, NULL, &window_view_mode, NULL, diff --git a/cmd-select-pane.c b/cmd-select-pane.c index 224370ab..3b639e06 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -87,10 +87,11 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) const struct cmd_entry *entry = cmd_get_entry(self); struct cmd_find_state *current = cmdq_get_current(item); struct cmd_find_state *target = cmdq_get_target(item); + struct client *c = cmdq_get_client(item); struct winlink *wl = target->wl; struct window *w = wl->window; struct session *s = target->s; - struct window_pane *wp = target->wp, *lastwp, *markedwp; + struct window_pane *wp = target->wp, *activewp, *lastwp, *markedwp; struct options *oo = wp->options; char *title; const char *style; @@ -201,16 +202,21 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } - if (wp == w->active) + if (c->session != NULL && (c->flags & CLIENT_ACTIVEPANE)) + activewp = server_client_get_pane(c); + else + activewp = w->active; + if (wp == activewp) return (CMD_RETURN_NORMAL); if (window_push_zoom(w, args_has(args, 'Z'))) server_redraw_window(w); window_redraw_active_switch(w, wp); - if (window_set_active_pane(w, wp, 1)) { + if (c->session != NULL && (c->flags & CLIENT_ACTIVEPANE)) + server_client_set_pane(c, wp); + else if (window_set_active_pane(w, wp, 1)) cmd_find_from_winlink_pane(current, wl, wp, 0); - cmdq_insert_hook(s, item, current, "after-select-pane"); - cmd_select_pane_redraw(w); - } + cmdq_insert_hook(s, item, current, "after-select-pane"); + cmd_select_pane_redraw(w); if (window_pop_zoom(w)) server_redraw_window(w); diff --git a/cmd-split-window.c b/cmd-split-window.c index c0e0e22a..c9d92fae 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -160,6 +160,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } if (input && window_pane_start_input(new_wp, item, &cause) != 0) { + server_client_remove_pane(new_wp); layout_close_pane(new_wp); window_remove_pane(wp->window, new_wp); cmdq_error(item, "%s", cause); diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 021ac224..dd981b9a 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -79,6 +79,9 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) if (src_wp == dst_wp) goto out; + server_client_remove_pane(src_wp); + server_client_remove_pane(dst_wp); + tmp_wp = TAILQ_PREV(dst_wp, window_panes, entry); TAILQ_REMOVE(&dst_w->panes, dst_wp, entry); TAILQ_REPLACE(&src_w->panes, src_wp, dst_wp, entry); diff --git a/screen-redraw.c b/screen-redraw.c index 0f83479c..ffa7aecf 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -242,7 +242,7 @@ screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, struct window_pane **wpp) { struct window *w = c->session->curw->window; - struct window_pane *wp; + struct window_pane *wp, *active; int border; u_int right, line; @@ -254,7 +254,7 @@ screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, return (screen_redraw_type_of_cell(c, px, py, pane_status)); if (pane_status != PANE_STATUS_OFF) { - wp = w->active; + active = wp = server_client_get_pane(c); do { if (!window_pane_visible(wp)) goto next1; @@ -272,10 +272,10 @@ screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, wp = TAILQ_NEXT(wp, entry); if (wp == NULL) wp = TAILQ_FIRST(&w->panes); - } while (wp != w->active); + } while (wp != active); } - wp = w->active; + active = wp = server_client_get_pane(c); do { if (!window_pane_visible(wp)) goto next2; @@ -296,7 +296,7 @@ screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, wp = TAILQ_NEXT(wp, entry); if (wp == NULL) wp = TAILQ_FIRST(&w->panes); - } while (wp != w->active); + } while (wp != active); return (CELL_OUTSIDE); } @@ -330,7 +330,7 @@ screen_redraw_make_pane_status(struct client *c, struct window *w, ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS); format_defaults(ft, c, c->session, c->session->curw, wp); - if (wp == w->active) + if (wp == server_client_get_pane(c)) style_apply(&gc, w->options, "pane-active-border-style", ft); else style_apply(&gc, w->options, "pane-border-style", ft); @@ -558,6 +558,7 @@ screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x, struct client *c = ctx->c; struct session *s = c->session; struct window *w = s->curw->window; + struct window_pane *active = server_client_get_pane(c); struct options *oo = w->options; struct grid_cell *gc; struct format_tree *ft; @@ -569,7 +570,7 @@ screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x, ft = format_create_defaults(NULL, c, s, s->curw, wp); gc = &wp->border_gc; - if (screen_redraw_check_is(x, y, ctx->pane_status, w->active)) { + if (screen_redraw_check_is(x, y, ctx->pane_status, active)) { style_apply(gc, oo, "pane-active-border-style", ft); gc->attr |= GRID_ATTR_CHARSET; } else { diff --git a/server-client.c b/server-client.c index 1e0d992d..3ce393cd 100644 --- a/server-client.c +++ b/server-client.c @@ -56,6 +56,19 @@ static void server_client_dispatch_read_data(struct client *, static void server_client_dispatch_read_done(struct client *, struct imsg *); +/* Compare client windows. */ +static int +server_client_window_cmp(struct client_window *cw1, + struct client_window *cw2) +{ + if (cw1->window < cw2->window) + return (-1); + if (cw1->window > cw2->window) + return (1); + return (0); +} +RB_GENERATE(client_windows, client_window, entry, server_client_window_cmp); + /* Number of attached clients. */ u_int server_client_how_many(void) @@ -211,6 +224,7 @@ server_client_create(int fd) c->cwd = NULL; c->queue = cmdq_new(); + RB_INIT(&c->windows); c->tty.fd = -1; c->tty.sx = 80; @@ -272,6 +286,7 @@ void server_client_lost(struct client *c) { struct client_file *cf, *cf1; + struct client_window *cw, *cw1; c->flags |= CLIENT_DEAD; @@ -283,6 +298,10 @@ server_client_lost(struct client *c) cf->error = EINTR; file_fire_done(cf); } + RB_FOREACH_SAFE(cw, client_windows, &c->windows, cw1) { + RB_REMOVE(client_windows, &c->windows, cw); + free(cw); + } TAILQ_REMOVE(&clients, c, entry); log_debug("lost client %p", c); @@ -1126,7 +1145,7 @@ server_client_key_callback(struct cmdq_item *item, void *data) /* Find affected pane. */ if (!KEYC_IS_MOUSE(key) || cmd_find_from_mouse(&fs, m, 0) != 0) - cmd_find_from_session(&fs, s, 0); + cmd_find_from_client(&fs, c, 0); wp = fs.wp; /* Forward mouse keys if disabled. */ @@ -1535,7 +1554,7 @@ server_client_reset_state(struct client *c) { struct tty *tty = &c->tty; struct window *w = c->session->curw->window; - struct window_pane *wp = w->active, *loop; + struct window_pane *wp = server_client_get_pane(c), *loop; struct screen *s = NULL; struct options *oo = c->session->options; int mode = 0, cursor, flags; @@ -2236,6 +2255,8 @@ server_client_set_flags(struct client *c, const char *flags) flag = CLIENT_READONLY; else if (strcmp(next, "ignore-size") == 0) flag = CLIENT_IGNORESIZE; + else if (strcmp(next, "active-pane") == 0) + flag = CLIENT_ACTIVEPANE; else continue; @@ -2266,6 +2287,8 @@ server_client_get_flags(struct client *c) strlcat(s, "no-output,", sizeof s); if (c->flags & CLIENT_READONLY) strlcat(s, "read-only,", sizeof s); + if (c->flags & CLIENT_ACTIVEPANE) + strlcat(s, "active-pane,", sizeof s); if (c->flags & CLIENT_SUSPENDED) strlcat(s, "suspended,", sizeof s); if (c->flags & CLIENT_UTF8) @@ -2274,3 +2297,67 @@ server_client_get_flags(struct client *c) s[strlen(s) - 1] = '\0'; return (s); } + +/* Get client window. */ +static struct client_window * +server_client_get_client_window(struct client *c, u_int id) +{ + struct client_window cw = { .window = id }; + + return (RB_FIND(client_windows, &c->windows, &cw)); +} + +/* Get client active pane. */ +struct window_pane * +server_client_get_pane(struct client *c) +{ + struct session *s = c->session; + struct client_window *cw; + + if (s == NULL) + return (NULL); + + if (~c->flags & CLIENT_ACTIVEPANE) + return (s->curw->window->active); + cw = server_client_get_client_window(c, s->curw->window->id); + if (cw == NULL) + return (s->curw->window->active); + return (cw->pane); +} + +/* Set client active pane. */ +void +server_client_set_pane(struct client *c, struct window_pane *wp) +{ + struct session *s = c->session; + struct client_window *cw; + + if (s == NULL) + return; + + cw = server_client_get_client_window(c, s->curw->window->id); + if (cw == NULL) { + cw = xcalloc(1, sizeof *cw); + cw->window = s->curw->window->id; + RB_INSERT(client_windows, &c->windows, cw); + } + cw->pane = wp; + log_debug("%s pane now %%%u", c->name, wp->id); +} + +/* Remove pane from client lists. */ +void +server_client_remove_pane(struct window_pane *wp) +{ + struct client *c; + struct window *w = wp->window; + struct client_window *cw; + + TAILQ_FOREACH(c, &clients, entry) { + cw = server_client_get_client_window(c, w->id); + if (cw != NULL && cw->pane == wp) { + RB_REMOVE(client_windows, &c->windows, cw); + free(cw); + } + } +} diff --git a/server-fn.c b/server-fn.c index fde1d8e8..d66aed0b 100644 --- a/server-fn.c +++ b/server-fn.c @@ -187,6 +187,7 @@ server_kill_pane(struct window_pane *wp) recalculate_sizes(); } else { server_unzoom_window(w); + server_client_remove_pane(wp); layout_close_pane(wp); window_remove_pane(w, wp); server_redraw_window(w); @@ -348,6 +349,7 @@ server_destroy_pane(struct window_pane *wp, int notify) notify_pane("pane-exited", wp); server_unzoom_window(w); + server_client_remove_pane(wp); layout_close_pane(wp); window_remove_pane(w, wp); diff --git a/spawn.c b/spawn.c index f05887b2..01f19097 100644 --- a/spawn.c +++ b/spawn.c @@ -362,6 +362,7 @@ spawn_pane(struct spawn_context *sc, char **cause) xasprintf(cause, "fork failed: %s", strerror(errno)); new_wp->fd = -1; if (~sc->flags & SPAWN_RESPAWN) { + server_client_remove_pane(new_wp); layout_close_pane(new_wp); window_remove_pane(w, new_wp); } diff --git a/tmux.1 b/tmux.1 index baa296fb..0b03becc 100644 --- a/tmux.1 +++ b/tmux.1 @@ -986,6 +986,8 @@ the client is read-only the client does not affect the size of other clients .It no-output the client does not receive pane output in control mode +.It active-pane +the client has an independent active pane .El .Pp A leading @@ -1000,6 +1002,13 @@ When a client is read-only, only keys bound to the or .Ic switch-client commands have any effect. +A client with the +.Ar active-pane +flag allows the active pane to be selected independently of the window's active +pane used by clients without the flag. +This only affects the cursor position and commands issued from the client; +other features such as hooks and styles continue to use the window's active +pane. .Pp If no server is started, .Ic attach-session diff --git a/tmux.h b/tmux.h index 3ccb005a..3bd37f05 100644 --- a/tmux.h +++ b/tmux.h @@ -1508,6 +1508,14 @@ struct client_file { }; RB_HEAD(client_files, client_file); +/* Client window. */ +struct client_window { + u_int window; + struct window_pane *pane; + RB_ENTRY(client_window) entry; +}; +RB_HEAD(client_windows, client_window); + /* Client connection. */ typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); typedef void (*prompt_free_cb)(void *); @@ -1521,6 +1529,8 @@ struct client { struct tmuxpeer *peer; struct cmdq_list *queue; + struct client_windows windows; + pid_t pid; int fd; struct event event; @@ -1585,6 +1595,7 @@ struct client { #define CLIENT_STARTSERVER 0x10000000 #define CLIENT_REDRAWPANES 0x20000000 #define CLIENT_NOFORK 0x40000000 +#define CLIENT_ACTIVEPANE 0x80000000ULL #define CLIENT_ALLREDRAWFLAGS \ (CLIENT_REDRAWWINDOW| \ CLIENT_REDRAWSTATUS| \ @@ -1600,7 +1611,7 @@ struct client { (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ CLIENT_DETACHING) - int flags; + uint64_t flags; struct key_table *keytable; uint64_t redraw_panes; @@ -2299,6 +2310,7 @@ void server_add_accept(int); void printflike(1, 2) server_add_message(const char *, ...); /* server-client.c */ +RB_PROTOTYPE(client_windows, client_window, entry, server_client_window_cmp); u_int server_client_how_many(void); void server_client_set_overlay(struct client *, u_int, overlay_check_cb, overlay_mode_cb, overlay_draw_cb, overlay_key_cb, @@ -2321,6 +2333,9 @@ void server_client_push_stderr(struct client *); const char *server_client_get_cwd(struct client *, struct session *); void server_client_set_flags(struct client *, const char *); const char *server_client_get_flags(struct client *); +struct window_pane *server_client_get_pane(struct client *); +void server_client_set_pane(struct client *, struct window_pane *); +void server_client_remove_pane(struct window_pane *); /* server-fn.c */ void server_redraw_client(struct client *); diff --git a/tty.c b/tty.c index a1e2f2c6..a4f2acee 100644 --- a/tty.c +++ b/tty.c @@ -693,8 +693,7 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) } 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)) + if (s->cstyle == 0 && tty_term_has(tty->term, TTYC_SE)) tty_putcode(tty, TTYC_SE); else tty_putcode1(tty, TTYC_SS, s->cstyle); @@ -792,7 +791,7 @@ tty_window_offset1(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy) { struct client *c = tty->client; struct window *w = c->session->curw->window; - struct window_pane *wp = w->active; + struct window_pane *wp = server_client_get_pane(c); u_int cx, cy, lines; lines = status_line_size(c); From ecbdcc256fd2c69c60c9d900a28922914d6b9896 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:22:01 +0000 Subject: [PATCH 44/49] Add screen write flags instead of individual bits and fix line length calculation with padding. --- grid.c | 4 +++- screen-write.c | 4 ++-- tmux.h | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/grid.c b/grid.c index 2437e2ea..06a82522 100644 --- a/grid.c +++ b/grid.c @@ -1391,7 +1391,9 @@ grid_line_length(struct grid *gd, u_int py) px = gd->sx; while (px > 0) { grid_get_cell(gd, px - 1, py, &gc); - if (gc.data.size != 1 || *gc.data.data != ' ') + if ((gc.flags & GRID_FLAG_PADDING) || + gc.data.size != 1 || + *gc.data.data != ' ') break; px--; } diff --git a/screen-write.c b/screen-write.c index 9571cbec..062a2929 100644 --- a/screen-write.c +++ b/screen-write.c @@ -184,10 +184,10 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, } if (ctx->wp != NULL && - !ctx->sync && + (~ctx->flags & SCREEN_WRITE_SYNC) && (sync || ctx->wp != ctx->wp->window->active)) { tty_write(tty_cmd_syncstart, ttyctx); - ctx->sync = 1; + ctx->flags |= SCREEN_WRITE_SYNC; } } diff --git a/tmux.h b/tmux.h index 3bd37f05..921a51f9 100644 --- a/tmux.h +++ b/tmux.h @@ -796,7 +796,9 @@ typedef void (*screen_write_init_ctx_cb)(struct screen_write_ctx *, struct screen_write_ctx { struct window_pane *wp; struct screen *s; - int sync; + + int flags; +#define SCREEN_WRITE_SYNC 0x1 screen_write_init_ctx_cb init_ctx_cb; void *arg; From e2a26740b9880d0066c8a04ca2d7202e7f99bd07 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:26:34 +0000 Subject: [PATCH 45/49] Add an option to set the pane border lines style from a choice of single lines (ACS or UTF-8), double or heavy (UTF-8), simple (plain ASCII) or number (the pane numbers). Lines that won't work on a non-UTF-8 terminal are translated back into ACS when they are output. --- format-draw.c | 3 +- options-table.c | 11 +++++ screen-redraw.c | 129 +++++++++++++++++++++++++++++++++++------------- tmux.1 | 22 +++++++++ tmux.h | 11 +++++ tty-acs.c | 85 ++++++++++++++++++++++++++++--- tty.c | 34 +++++++------ 7 files changed, 239 insertions(+), 56 deletions(-) diff --git a/format-draw.c b/format-draw.c index bd32b2a8..ec98ba95 100644 --- a/format-draw.c +++ b/format-draw.c @@ -600,7 +600,8 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, /* If this style pushed or popped the default, update it. */ if (sy.default_type == STYLE_DEFAULT_PUSH) { - memcpy(¤t_default, &saved_sy.gc, sizeof current_default); + memcpy(¤t_default, &saved_sy.gc, + sizeof current_default); sy.default_type = STYLE_DEFAULT_BASE; } else if (sy.default_type == STYLE_DEFAULT_POP) { memcpy(¤t_default, base, sizeof current_default); diff --git a/options-table.c b/options-table.c index b1c4ec1b..4ac0d0c3 100644 --- a/options-table.c +++ b/options-table.c @@ -60,6 +60,9 @@ static const char *options_table_visual_bell_list[] = { static const char *options_table_pane_status_list[] = { "off", "top", "bottom", NULL }; +static const char *options_table_pane_lines_list[] = { + "single", "double", "heavy", "simple", "number", NULL +}; static const char *options_table_set_clipboard_list[] = { "off", "external", "on", NULL }; @@ -904,6 +907,14 @@ const struct options_table_entry options_table[] = { .text = "Format of text in the pane status lines." }, + { .name = "pane-border-lines", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_WINDOW, + .choices = options_table_pane_lines_list, + .default_num = PANE_LINES_SINGLE, + .text = "Type of the pane type lines." + }, + { .name = "pane-border-status", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, diff --git a/screen-redraw.c b/screen-redraw.c index ffa7aecf..002970e9 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -45,8 +45,36 @@ static void screen_redraw_draw_pane(struct screen_redraw_ctx *, #define CELL_BORDERS " xqlkmjwvtun~" -const struct grid_cell screen_redraw_border_cell = { - { { ' ' }, 0, 1, 1 }, GRID_ATTR_CHARSET, 0, 8, 8, 0 +static const struct utf8_data screen_redraw_double_borders[] = { + { "", 0, 0, 0 }, + { "\342\225\221", 0, 3, 1 }, /* U+2551 */ + { "\342\225\220", 0, 3, 1 }, /* U+2550 */ + { "\342\225\224", 0, 3, 1 }, /* U+2554 */ + { "\342\225\227", 0, 3, 1 }, /* U+2557 */ + { "\342\225\232", 0, 3, 1 }, /* U+255A */ + { "\342\225\235", 0, 3, 1 }, /* U+255D */ + { "\342\225\246", 0, 3, 1 }, /* U+2566 */ + { "\342\225\251", 0, 3, 1 }, /* U+2569 */ + { "\342\225\240", 0, 3, 1 }, /* U+2560 */ + { "\342\225\243", 0, 3, 1 }, /* U+2563 */ + { "\342\225\254", 0, 3, 1 }, /* U+256C */ + { "\302\267", 0, 2, 1 } /* U+00B7 */ +}; + +static const struct utf8_data screen_redraw_heavy_borders[] = { + { "", 0, 0, 0 }, + { "\342\224\203", 0, 3, 1 }, /* U+2503 */ + { "\342\224\201", 0, 3, 1 }, /* U+2501 */ + { "\342\224\223", 0, 3, 1 }, /* U+2513 */ + { "\342\224\217", 0, 3, 1 }, /* U+250F */ + { "\342\224\227", 0, 3, 1 }, /* U+2517 */ + { "\342\224\233", 0, 3, 1 }, /* U+251B */ + { "\342\224\263", 0, 3, 1 }, /* U+2533 */ + { "\342\224\273", 0, 3, 1 }, /* U+253B */ + { "\342\224\243", 0, 3, 1 }, /* U+2523 */ + { "\342\224\253", 0, 3, 1 }, /* U+252B */ + { "\342\225\213", 0, 3, 1 }, /* U+254B */ + { "\302\267", 0, 2, 1 } /* U+00B7 */ }; enum screen_redraw_border_type { @@ -55,6 +83,45 @@ enum screen_redraw_border_type { SCREEN_REDRAW_BORDER }; +/* Get cell border character. */ +static void +screen_redraw_border_set(struct window_pane *wp, int pane_lines, int cell_type, + struct grid_cell *gc) +{ + u_int idx; + + switch (pane_lines) { + case PANE_LINES_NUMBER: + if (cell_type == CELL_OUTSIDE) { + gc->attr |= GRID_ATTR_CHARSET; + utf8_set(&gc->data, CELL_BORDERS[CELL_OUTSIDE]); + break; + } + gc->attr &= ~GRID_ATTR_CHARSET; + if (wp != NULL && window_pane_index(wp, &idx) == 0) + utf8_set(&gc->data, '0' + (idx % 10)); + else + utf8_set(&gc->data, '*'); + break; + case PANE_LINES_DOUBLE: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, &screen_redraw_double_borders[cell_type]); + break; + case PANE_LINES_HEAVY: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, &screen_redraw_heavy_borders[cell_type]); + break; + case PANE_LINES_SIMPLE: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_set(&gc->data, " |-+++++++++."[cell_type]); + break; + default: + gc->attr |= GRID_ATTR_CHARSET; + utf8_set(&gc->data, CELL_BORDERS[cell_type]); + break; + } +} + /* Return if window has only two panes. */ static int screen_redraw_two_panes(struct window *w, int direction) @@ -317,7 +384,7 @@ screen_redraw_check_is(u_int px, u_int py, int pane_status, /* Update pane status. */ static int screen_redraw_make_pane_status(struct client *c, struct window *w, - struct window_pane *wp) + struct window_pane *wp, int pane_lines) { struct grid_cell gc; const char *fmt; @@ -348,9 +415,9 @@ screen_redraw_make_pane_status(struct client *c, struct window *w, screen_write_start(&ctx, &wp->status_screen); - gc.attr |= GRID_ATTR_CHARSET; + screen_redraw_border_set(wp, pane_lines, CELL_TOPBOTTOM, &gc); for (i = 0; i < width; i++) - screen_write_putc(&ctx, &gc, 'q'); + screen_write_cell(&ctx, &gc); gc.attr &= ~GRID_ATTR_CHARSET; screen_write_cursormove(&ctx, 0, 0, 0); @@ -436,7 +503,7 @@ screen_redraw_update(struct client *c, int flags) struct window *w = c->session->curw->window; struct window_pane *wp; struct options *wo = w->options; - int redraw; + int redraw, lines; if (c->message_string != NULL) redraw = status_message_redraw(c); @@ -451,9 +518,10 @@ screen_redraw_update(struct client *c, int flags) flags |= CLIENT_REDRAWOVERLAY; if (options_get_number(wo, "pane-border-status") != PANE_STATUS_OFF) { + lines = options_get_number(wo, "pane-border-lines"); redraw = 0; TAILQ_FOREACH(wp, &w->panes, entry) { - if (screen_redraw_make_pane_status(c, w, wp)) + if (screen_redraw_make_pane_status(c, w, wp, lines)) redraw = 1; } if (redraw) @@ -483,6 +551,7 @@ screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx) ctx->statuslines = lines; ctx->pane_status = options_get_number(wo, "pane-border-status"); + ctx->pane_lines = options_get_number(wo, "pane-border-lines"); tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, &ctx->sx, &ctx->sy); @@ -560,7 +629,6 @@ screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x, struct window *w = s->curw->window; struct window_pane *active = server_client_get_pane(c); struct options *oo = w->options; - struct grid_cell *gc; struct format_tree *ft; if (wp->border_gc_set) @@ -568,18 +636,13 @@ screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x, wp->border_gc_set = 1; ft = format_create_defaults(NULL, c, s, s->curw, wp); - gc = &wp->border_gc; - - if (screen_redraw_check_is(x, y, ctx->pane_status, active)) { - style_apply(gc, oo, "pane-active-border-style", ft); - gc->attr |= GRID_ATTR_CHARSET; - } else { - style_apply(gc, oo, "pane-border-style", ft); - gc->attr |= GRID_ATTR_CHARSET; - } - + if (screen_redraw_check_is(x, y, ctx->pane_status, active)) + style_apply(&wp->border_gc, oo, "pane-active-border-style", ft); + else + style_apply(&wp->border_gc, oo, "pane-border-style", ft); format_free(ft); - return (gc); + + return (&wp->border_gc); } /* Draw a border cell. */ @@ -590,39 +653,37 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) struct session *s = c->session; struct tty *tty = &c->tty; struct window_pane *wp; - u_int type, x = ctx->ox + i, y = ctx->oy + j; + u_int cell_type, x = ctx->ox + i, y = ctx->oy + j; int pane_status = ctx->pane_status; - const struct grid_cell *gc; - struct grid_cell copy; + struct grid_cell gc; + const struct grid_cell *tmp; if (c->overlay_check != NULL && !c->overlay_check(c, x, y)) return; - type = screen_redraw_check_cell(c, x, y, pane_status, &wp); - if (type == CELL_INSIDE) + cell_type = screen_redraw_check_cell(c, x, y, pane_status, &wp); + if (cell_type == CELL_INSIDE) return; if (wp == NULL) - gc = &screen_redraw_border_cell; + memcpy(&gc, &grid_default_cell, sizeof gc); else { - gc = screen_redraw_draw_borders_style(ctx, x, y, wp); - if (gc == NULL) + tmp = screen_redraw_draw_borders_style(ctx, x, y, wp); + if (tmp == NULL) return; + memcpy(&gc, tmp, sizeof gc); if (server_is_marked(s, s->curw, marked_pane.wp) && - screen_redraw_check_is(x, y, pane_status, marked_pane.wp)) { - memcpy(©, gc, sizeof copy); - copy.attr ^= GRID_ATTR_REVERSE; - gc = © - } + screen_redraw_check_is(x, y, pane_status, marked_pane.wp)) + gc.attr ^= GRID_ATTR_REVERSE; } + screen_redraw_border_set(wp, ctx->pane_lines, cell_type, &gc); - tty_attributes(tty, gc, &grid_default_cell, NULL); if (ctx->statustop) tty_cursor(tty, i, ctx->statuslines + j); else tty_cursor(tty, i, j); - tty_putc(tty, CELL_BORDERS[type]); + tty_cell(tty, &gc, &grid_default_cell, NULL); } /* Draw the borders. */ diff --git a/tmux.1 b/tmux.1 index 0b03becc..421f8889 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3924,6 +3924,28 @@ but set the starting index for pane numbers. .It Ic pane-border-format Ar format Set the text shown in pane border status lines. .Pp +.It Ic pane-border-lines Ar type +Set the type of characters used for drawing pane borders. +.Ar type +may be one of: +.Bl -tag -width Ds +.It single +single lines using ACS or UTF-8 characters +.It double +double lines using UTF-8 characters +.It heavy +heavy lines using UTF-8 characters +.It simple +simple ASCII characters +.It number +the pane number +.El +.Pp +.Ql double +and +.Ql heavy +will fall back to standard ACS line drawing when UTF-8 is not supported. +.Pp .It Xo Ic pane-border-status .Op Ic off | top | bottom .Xc diff --git a/tmux.h b/tmux.h index 921a51f9..a413e0cf 100644 --- a/tmux.h +++ b/tmux.h @@ -820,6 +820,7 @@ struct screen_redraw_ctx { int statustop; int pane_status; + int pane_lines; u_int sx; u_int sy; @@ -1050,6 +1051,13 @@ TAILQ_HEAD(winlink_stack, winlink); #define PANE_STATUS_TOP 1 #define PANE_STATUS_BOTTOM 2 +/* Pane border lines option. */ +#define PANE_LINES_SINGLE 0 +#define PANE_LINES_DOUBLE 1 +#define PANE_LINES_HEAVY 2 +#define PANE_LINES_SIMPLE 3 +#define PANE_LINES_NUMBER 4 + /* Layout direction. */ enum layout_type { LAYOUT_LEFTRIGHT, @@ -2030,6 +2038,8 @@ void tty_putcode_ptr2(struct tty *, enum tty_code_code, const void *, void tty_puts(struct tty *, const char *); void tty_putc(struct tty *, u_char); void tty_putn(struct tty *, const void *, size_t, u_int); +void tty_cell(struct tty *, const struct grid_cell *, + const struct grid_cell *, int *); int tty_init(struct tty *, struct client *, int); void tty_resize(struct tty *); void tty_set_size(struct tty *, u_int, u_int, u_int, u_int); @@ -2103,6 +2113,7 @@ void tty_default_features(int *, const char *, u_int); /* tty-acs.c */ int tty_acs_needed(struct tty *); const char *tty_acs_get(struct tty *, u_char); +int tty_acs_reverse_get(struct tty *, const char *, size_t); /* tty-keys.c */ void tty_keys_build(struct tty *); diff --git a/tty-acs.c b/tty-acs.c index 3e811103..63eccb93 100644 --- a/tty-acs.c +++ b/tty-acs.c @@ -19,11 +19,10 @@ #include #include +#include #include "tmux.h" -static int tty_acs_cmp(const void *, const void *); - /* Table mapping ACS entries to UTF-8. */ struct tty_acs_entry { u_char key; @@ -68,14 +67,65 @@ static const struct tty_acs_entry tty_acs_table[] = { { '~', "\302\267" } /* bullet */ }; +/* Table mapping UTF-8 to ACS entries. */ +struct tty_acs_reverse_entry { + const char *string; + u_char key; +}; +static const struct tty_acs_reverse_entry tty_acs_reverse2[] = { + { "\302\267", '~' } +}; +static const struct tty_acs_reverse_entry tty_acs_reverse3[] = { + { "\342\224\200", 'q' }, + { "\342\224\201", 'q' }, + { "\342\224\202", 'x' }, + { "\342\224\203", 'x' }, + { "\342\224\214", 'l' }, + { "\342\224\217", 'k' }, + { "\342\224\220", 'k' }, + { "\342\224\223", 'l' }, + { "\342\224\224", 'm' }, + { "\342\224\227", 'm' }, + { "\342\224\230", 'j' }, + { "\342\224\233", 'j' }, + { "\342\224\234", 't' }, + { "\342\224\243", 't' }, + { "\342\224\244", 'u' }, + { "\342\224\253", 'u' }, + { "\342\224\263", 'w' }, + { "\342\224\264", 'v' }, + { "\342\224\273", 'v' }, + { "\342\224\274", 'n' }, + { "\342\225\213", 'n' }, + { "\342\225\220", 'q' }, + { "\342\225\221", 'x' }, + { "\342\225\224", 'l' }, + { "\342\225\227", 'k' }, + { "\342\225\232", 'm' }, + { "\342\225\235", 'j' }, + { "\342\225\240", 't' }, + { "\342\225\243", 'u' }, + { "\342\225\246", 'w' }, + { "\342\225\251", 'v' }, + { "\342\225\254", 'n' }, +}; + static int tty_acs_cmp(const void *key, const void *value) { const struct tty_acs_entry *entry = value; - u_char ch; + int test = *(u_char *)key; - ch = *(u_char *) key; - return (ch - entry->key); + return (test - entry->key); +} + +static int +tty_acs_reverse_cmp(const void *key, const void *value) +{ + const struct tty_acs_reverse_entry *entry = value; + const char *test = key; + + return (strcmp(test, entry->string)); } /* Should this terminal use ACS instead of UTF-8 line drawing? */ @@ -104,11 +154,11 @@ tty_acs_needed(struct tty *tty) return (1); } -/* Retrieve ACS to output as a string. */ +/* Retrieve ACS to output as UTF-8. */ const char * tty_acs_get(struct tty *tty, u_char ch) { - struct tty_acs_entry *entry; + const struct tty_acs_entry *entry; /* Use the ACS set instead of UTF-8 if needed. */ if (tty_acs_needed(tty)) { @@ -124,3 +174,24 @@ tty_acs_get(struct tty *tty, u_char ch) return (NULL); return (entry->string); } + +/* Reverse UTF-8 into ACS. */ +int +tty_acs_reverse_get(__unused struct tty *tty, const char *s, size_t slen) +{ + const struct tty_acs_reverse_entry *table, *entry; + u_int items; + + if (slen == 2) { + table = tty_acs_reverse2; + items = nitems(tty_acs_reverse2); + } else if (slen == 3) { + table = tty_acs_reverse3; + items = nitems(tty_acs_reverse3); + } else + return (-1); + entry = bsearch(s, table, items, sizeof table[0], tty_acs_reverse_cmp); + if (entry == NULL) + return (-1); + return (entry->key); +} diff --git a/tty.c b/tty.c index a4f2acee..99996dfa 100644 --- a/tty.c +++ b/tty.c @@ -65,8 +65,6 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); static void tty_repeat_space(struct tty *, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); -static void tty_cell(struct tty *, const struct grid_cell *, - const struct grid_cell *, int *); static void tty_default_attributes(struct tty *, const struct grid_cell *, int *, u_int); @@ -1243,7 +1241,7 @@ static const struct grid_cell * tty_check_codeset(struct tty *tty, const struct grid_cell *gc) { static struct grid_cell new; - u_int n; + int c; /* Characters less than 0x7f are always fine, no matter what. */ if (gc->data.size == 1 && *gc->data.data < 0x7f) @@ -1252,14 +1250,21 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc) /* UTF-8 terminal and a UTF-8 character - fine. */ if (tty->client->flags & CLIENT_UTF8) return (gc); + memcpy(&new, gc, sizeof new); + + /* See if this can be mapped to an ACS character. */ + c = tty_acs_reverse_get(tty, gc->data.data, gc->data.size); + if (c != -1) { + utf8_set(&new.data, c); + new.attr |= GRID_ATTR_CHARSET; + return (&new); + } /* Replace by the right number of underscores. */ - n = gc->data.width; - if (n > UTF8_SIZE) - n = UTF8_SIZE; - memcpy(&new, gc, sizeof new); - new.data.size = n; - memset(new.data.data, '_', n); + new.data.size = gc->data.width; + if (new.data.size > UTF8_SIZE) + new.data.size = UTF8_SIZE; + memset(new.data.data, '_', new.data.size); return (&new); } @@ -1924,7 +1929,7 @@ tty_cmd_syncstart(struct tty *tty, __unused const struct tty_ctx *ctx) tty_sync_start(tty); } -static void +void tty_cell(struct tty *tty, const struct grid_cell *gc, const struct grid_cell *defaults, int *palette) { @@ -1940,12 +1945,13 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, if (gc->flags & GRID_FLAG_PADDING) return; - /* Set the attributes. */ - tty_attributes(tty, gc, defaults, palette); - - /* Get the cell and if ASCII write with putc to do ACS translation. */ + /* Check the output codeset and apply attributes. */ gcp = tty_check_codeset(tty, gc); + tty_attributes(tty, gcp, defaults, palette); + + /* If it is a single character, write with putc to handle ACS. */ if (gcp->data.size == 1) { + tty_attributes(tty, gcp, defaults, palette); if (*gcp->data.data < 0x20 || *gcp->data.data == 0x7f) return; tty_putc(tty, *gcp->data.data); From 292b335ca5b594729cf9ff79f0f4273c725537a4 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:35:13 +0000 Subject: [PATCH 46/49] Separate key flags and modifiers, log key flags, make the "xterm" flag more explicit and fix M- keys with a leading escape. --- cmd-list-keys.c | 10 ++++----- cmd-queue.c | 2 +- cmd-send-keys.c | 4 +--- key-bindings.c | 4 ++-- key-string.c | 51 +++++++++++++++++++++++++++++++--------------- menu.c | 4 ++-- mode-tree.c | 6 +++--- options.c | 4 ++-- popup.c | 4 ++-- status.c | 12 +++++------ window-customize.c | 8 ++++---- 11 files changed, 63 insertions(+), 46 deletions(-) diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 60ef73af..51c90dfe 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -73,7 +73,7 @@ cmd_list_keys_get_width(const char *tablename, key_code only) bd = key_bindings_next(table, bd); continue; } - width = utf8_cstrwidth(key_string_lookup_key(bd->key)); + width = utf8_cstrwidth(key_string_lookup_key(bd->key, 0)); if (width > keywidth) keywidth = width; @@ -106,7 +106,7 @@ cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args, continue; } found = 1; - key = key_string_lookup_key(bd->key); + key = key_string_lookup_key(bd->key, 0); if (bd->note == NULL || *bd->note == '\0') note = cmd_list_print(bd->cmdlist, 1); @@ -135,7 +135,7 @@ cmd_list_keys_get_prefix(struct args *args, key_code *prefix) *prefix = options_get_number(global_s_options, "prefix"); if (!args_has(args, 'P')) { if (*prefix != KEYC_NONE) - xasprintf(&s, "%s ", key_string_lookup_key(*prefix)); + xasprintf(&s, "%s ", key_string_lookup_key(*prefix, 0)); else s = xstrdup(""); } else @@ -221,7 +221,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) bd = key_bindings_next(table, bd); continue; } - key = args_escape(key_string_lookup_key(bd->key)); + key = args_escape(key_string_lookup_key(bd->key, 0)); if (bd->flags & KEY_BINDING_REPEAT) repeat = 1; @@ -255,7 +255,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) continue; } found = 1; - key = args_escape(key_string_lookup_key(bd->key)); + key = args_escape(key_string_lookup_key(bd->key, 0)); if (!repeat) r = ""; diff --git a/cmd-queue.c b/cmd-queue.c index 5620fdad..b0c70428 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -547,7 +547,7 @@ cmdq_add_message(struct cmdq_item *item) if (c != NULL) { name = c->name; if (c->session != NULL && state->event.key != KEYC_NONE) { - key = key_string_lookup_key(state->event.key); + key = key_string_lookup_key(state->event.key, 0); server_add_message("%s key %s: %s", name, key, tmp); } else server_add_message("%s command: %s", name, tmp); diff --git a/cmd-send-keys.c b/cmd-send-keys.c index afaf0a81..a9ecc807 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -71,15 +71,13 @@ cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after, wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode->key_table == NULL) { - if (options_get_number(wp->window->options, "xterm-keys")) - key |= KEYC_XTERM; if (window_pane_key(wp, tc, s, wl, key, NULL) != 0) return (NULL); return (item); } table = key_bindings_get_table(wme->mode->key_table(wme), 1); - bd = key_bindings_get(table, key & ~KEYC_XTERM); + bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS); if (bd != NULL) { table->references++; after = key_bindings_dispatch(bd, after, tc, NULL, target); diff --git a/key-bindings.c b/key-bindings.c index 05089bab..59cfbb0d 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -190,7 +190,7 @@ key_bindings_add(const char *name, key_code key, const char *note, int repeat, table = key_bindings_get_table(name, 1); - bd = key_bindings_get(table, key & ~KEYC_XTERM); + bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS); if (bd != NULL) { RB_REMOVE(key_bindings, &table->key_bindings, bd); key_bindings_free(bd); @@ -217,7 +217,7 @@ key_bindings_remove(const char *name, key_code key) if (table == NULL) return; - bd = key_bindings_get(table, key & ~KEYC_XTERM); + bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS); if (bd == NULL) return; diff --git a/key-string.c b/key-string.c index 2a0602b2..65f1afe5 100644 --- a/key-string.c +++ b/key-string.c @@ -143,7 +143,7 @@ key_string_get_modifiers(const char **string) break; case 'M': case 'm': - modifiers |= KEYC_ESCAPE; + modifiers |= KEYC_META; break; case 'S': case 's': @@ -212,7 +212,7 @@ key_string_lookup_string(const char *string) return (KEYC_UNKNOWN); if (utf8_combine(&ud, &wc) != UTF8_DONE) return (KEYC_UNKNOWN); - return (wc | modifiers); + return (wc|modifiers); } /* Otherwise look the key up in the table. */ @@ -236,14 +236,15 @@ key_string_lookup_string(const char *string) modifiers &= ~KEYC_CTRL; } - return (key | modifiers); + return (key|modifiers); } /* Convert a key code into string format, with prefix if necessary. */ const char * -key_string_lookup_key(key_code key) +key_string_lookup_key(key_code key, int with_flags) { - static char out[32]; + key_code saved = key; + static char out[64]; char tmp[8]; const char *s; u_int i; @@ -255,25 +256,27 @@ key_string_lookup_key(key_code key) /* Literal keys are themselves. */ if (key & KEYC_LITERAL) { snprintf(out, sizeof out, "%c", (int)(key & 0xff)); - return (out); + goto out; } /* Display C-@ as C-Space. */ - if ((key & KEYC_MASK_KEY) == 0) - key = ' ' | KEYC_CTRL | (key & KEYC_MASK_MOD); + if ((key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)) == 0) + key = ' '|KEYC_CTRL; /* Fill in the modifiers. */ if (key & KEYC_CTRL) strlcat(out, "C-", sizeof out); - if (key & KEYC_ESCAPE) + if (key & KEYC_META) strlcat(out, "M-", sizeof out); if (key & KEYC_SHIFT) strlcat(out, "S-", sizeof out); key &= KEYC_MASK_KEY; /* Handle no key. */ - if (key == KEYC_NONE) - return ("None"); + if (key == KEYC_NONE) { + s = "None"; + goto append; + } /* Handle special keys. */ if (key == KEYC_UNKNOWN) { @@ -331,7 +334,7 @@ key_string_lookup_key(key_code key) if (key >= KEYC_USER && key < KEYC_USER + KEYC_NUSER) { snprintf(tmp, sizeof tmp, "User%u", (u_int)(key - KEYC_USER)); strlcat(out, tmp, sizeof out); - return (out); + goto out; } /* Try the key against the string table. */ @@ -341,7 +344,7 @@ key_string_lookup_key(key_code key) } if (i != nitems(key_string_table)) { strlcat(out, key_string_table[i].string, sizeof out); - return (out); + goto out; } /* Is this a UTF-8 key? */ @@ -350,14 +353,14 @@ key_string_lookup_key(key_code key) off = strlen(out); memcpy(out + off, ud.data, ud.size); out[off + ud.size] = '\0'; - return (out); + goto out; } } /* Invalid keys are errors. */ if (key > 255) { snprintf(out, sizeof out, "Invalid#%llx", key); - return (out); + goto out; } /* Check for standard or control key. */ @@ -375,9 +378,25 @@ key_string_lookup_key(key_code key) xsnprintf(tmp, sizeof tmp, "\\%llo", key); strlcat(out, tmp, sizeof out); - return (out); + goto out; append: strlcat(out, s, sizeof out); + +out: + if (with_flags && (saved & KEYC_MASK_FLAGS) != 0) { + strlcat(out, "[", sizeof out); + if (saved & KEYC_LITERAL) + strlcat(out, "L", sizeof out); + if (saved & KEYC_KEYPAD) + strlcat(out, "K", sizeof out); + if (saved & KEYC_CURSOR) + strlcat(out, "C", sizeof out); + if (saved & KEYC_IMPLIED_META) + strlcat(out, "I", sizeof out); + if (saved & KEYC_BUILD_MODIFIERS) + strlcat(out, "B", sizeof out); + strlcat(out, "]", sizeof out); + } return (out); } diff --git a/menu.c b/menu.c index 62010a58..7f6933c5 100644 --- a/menu.c +++ b/menu.c @@ -81,7 +81,7 @@ menu_add_item(struct menu *menu, const struct menu_item *item, return; } if (*s != '-' && item->key != KEYC_UNKNOWN && item->key != KEYC_NONE) { - key = key_string_lookup_key(item->key); + key = key_string_lookup_key(item->key, 0); xasprintf(&name, "%s#[default] #[align=right](%s)", s, key); } else xasprintf(&name, "%s", s); @@ -226,7 +226,7 @@ menu_key_cb(struct client *c, struct key_event *event) goto chosen; } } - switch (event->key) { + switch (event->key & ~KEYC_MASK_FLAGS) { case KEYC_UP: case 'k': if (old == -1) diff --git a/mode-tree.c b/mode-tree.c index a523478e..993070ec 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -987,7 +987,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, choice = -1; if (*key >= '0' && *key <= '9') choice = (*key) - '0'; - else if (((*key) & KEYC_MASK_MOD) == KEYC_ESCAPE) { + else if (((*key) & KEYC_MASK_MODIFIERS) == KEYC_META) { tmp = (*key) & KEYC_MASK_KEY; if (tmp >= 'a' && tmp <= 'z') choice = 10 + (tmp - 'a'); @@ -1111,12 +1111,12 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mode_tree_build(mtd); } break; - case '-'|KEYC_ESCAPE: + case '-'|KEYC_META: TAILQ_FOREACH(mti, &mtd->children, entry) mti->expanded = 0; mode_tree_build(mtd); break; - case '+'|KEYC_ESCAPE: + case '+'|KEYC_META: TAILQ_FOREACH(mti, &mtd->children, entry) mti->expanded = 1; mode_tree_build(mtd); diff --git a/options.c b/options.c index 58b7f7c1..6ed38bcd 100644 --- a/options.c +++ b/options.c @@ -130,7 +130,7 @@ options_value_to_string(struct options_entry *o, union options_value *ov, xasprintf(&s, "%lld", ov->number); break; case OPTIONS_TABLE_KEY: - s = xstrdup(key_string_lookup_key(ov->number)); + s = xstrdup(key_string_lookup_key(ov->number, 0)); break; case OPTIONS_TABLE_COLOUR: s = xstrdup(colour_tostring(ov->number)); @@ -283,7 +283,7 @@ options_default_to_string(const struct options_table_entry *oe) xasprintf(&s, "%lld", oe->default_num); break; case OPTIONS_TABLE_KEY: - s = xstrdup(key_string_lookup_key(oe->default_num)); + s = xstrdup(key_string_lookup_key(oe->default_num, 0)); break; case OPTIONS_TABLE_COLOUR: s = xstrdup(colour_tostring(oe->default_num)); diff --git a/popup.c b/popup.c index 93ecd2a1..87658a6f 100644 --- a/popup.c +++ b/popup.c @@ -329,7 +329,7 @@ popup_key_cb(struct client *c, struct key_event *event) bufferevent_write(job_get_event(pd->job), buf, len); return (0); } - input_key(NULL, &pd->s, job_get_event(pd->job), event->key); + input_key(&pd->s, job_get_event(pd->job), event->key); return (0); } @@ -341,7 +341,7 @@ popup_key_cb(struct client *c, struct key_event *event) format_defaults(ft, c, fs->s, fs->wl, fs->wp); else format_defaults(ft, c, NULL, NULL, NULL); - format_add(ft, "popup_key", "%s", key_string_lookup_key(event->key)); + format_add(ft, "popup_key", "%s", key_string_lookup_key(event->key, 0)); if (KEYC_IS_MOUSE(event->key)) { format_add(ft, "popup_mouse", "1"); format_add(ft, "popup_mouse_x", "%u", m->x - pd->px); diff --git a/status.c b/status.c index 56af02f5..93ac70df 100644 --- a/status.c +++ b/status.c @@ -827,7 +827,7 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) return (1); case 'b': case 'B': - *new_key = 'b'|KEYC_ESCAPE; + *new_key = 'b'|KEYC_META; return (1); case 'd': *new_key = '\025'; @@ -836,7 +836,7 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) case 'E': case 'w': case 'W': - *new_key = 'f'|KEYC_ESCAPE; + *new_key = 'f'|KEYC_META; return (1); case 'p': *new_key = '\031'; /* C-y */ @@ -1023,7 +1023,7 @@ status_prompt_key(struct client *c, key_code key) int keys; if (c->prompt_flags & PROMPT_KEY) { - keystring = key_string_lookup_key(key); + keystring = key_string_lookup_key(key, 0); c->prompt_inputcb(c, c->prompt_data, keystring, 1); status_prompt_clear(c); return (0); @@ -1039,7 +1039,7 @@ status_prompt_key(struct client *c, key_code key) free(s); return (1); } - key &= ~KEYC_XTERM; + key &= ~KEYC_MASK_FLAGS; keys = options_get_number(c->session->options, "status-keys"); if (keys == MODEKEY_VI) { @@ -1158,7 +1158,7 @@ process_key: c->prompt_index = idx; goto changed; - case 'f'|KEYC_ESCAPE: + case 'f'|KEYC_META: case KEYC_RIGHT|KEYC_CTRL: ws = options_get_string(oo, "word-separators"); @@ -1182,7 +1182,7 @@ process_key: c->prompt_index--; goto changed; - case 'b'|KEYC_ESCAPE: + case 'b'|KEYC_META: case KEYC_LEFT|KEYC_CTRL: ws = options_get_string(oo, "word-separators"); diff --git a/window-customize.c b/window-customize.c index f444dc6d..093ebbe4 100644 --- a/window-customize.c +++ b/window-customize.c @@ -460,7 +460,7 @@ window_customize_build_keys(struct window_customize_modedata *data, bd = key_bindings_first(kt); while (bd != NULL) { - format_add(ft, "key", "%s", key_string_lookup_key(bd->key)); + format_add(ft, "key", "%s", key_string_lookup_key(bd->key, 0)); if (bd->note != NULL) format_add(ft, "key_note", "%s", bd->note); if (filter != NULL) { @@ -1233,7 +1233,7 @@ window_customize_set_key(struct client *c, if (strcmp(s, "Repeat") == 0) bd->flags ^= KEY_BINDING_REPEAT; else if (strcmp(s, "Command") == 0) { - xasprintf(&prompt, "(%s) ", key_string_lookup_key(key)); + xasprintf(&prompt, "(%s) ", key_string_lookup_key(key, 0)); value = cmd_list_print(bd->cmdlist, 0); new_item = xcalloc(1, sizeof *new_item); @@ -1250,7 +1250,7 @@ window_customize_set_key(struct client *c, free(prompt); free(value); } else if (strcmp(s, "Note") == 0) { - xasprintf(&prompt, "(%s) ", key_string_lookup_key(key)); + xasprintf(&prompt, "(%s) ", key_string_lookup_key(key, 0)); new_item = xcalloc(1, sizeof *new_item); new_item->data = data; @@ -1395,7 +1395,7 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, break; if (item->scope == WINDOW_CUSTOMIZE_KEY) { xasprintf(&prompt, "Unbind key %s? ", - key_string_lookup_key(item->key)); + key_string_lookup_key(item->key, 0)); } else xasprintf(&prompt, "Unset option %s? ", item->name); data->references++; From 0ab82d95314e7a26a48452c77ad710f3aff97dd7 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:44:54 +0000 Subject: [PATCH 47/49] Add a terminal feature for enable/disable extended keys (supported by xterm and mintty) and add an option to make tmux send it. Only forward extended keys if the application has requested them, even though we use the CSI u sequence and xterm uses CSI 27 ~ - this is what mintty does as well. --- input-keys.c | 565 ++++++++++++++++++++++++++++++++++++------------ input.c | 19 +- options-table.c | 13 +- tmux.1 | 18 +- tty-features.c | 17 +- tty-keys.c | 476 ++++++++++++++++++++++++++-------------- tty-term.c | 2 + tty.c | 21 +- 8 files changed, 802 insertions(+), 329 deletions(-) diff --git a/input-keys.c b/input-keys.c index e9c595b4..19134c1a 100644 --- a/input-keys.c +++ b/input-keys.c @@ -33,112 +33,340 @@ static void input_key_mouse(struct window_pane *, struct mouse_event *); -struct input_key_ent { - key_code key; - const char *data; +/* Entry in the key tree. */ +struct input_key_entry { + key_code key; + const char *data; - int flags; -#define INPUTKEY_KEYPAD 0x1 /* keypad key */ -#define INPUTKEY_CURSOR 0x2 /* cursor key */ + RB_ENTRY(input_key_entry) entry; }; +RB_HEAD(input_key_tree, input_key_entry); -static const struct input_key_ent input_keys[] = { +/* Tree of input keys. */ +static int input_key_cmp(struct input_key_entry *, + struct input_key_entry *); +RB_GENERATE_STATIC(input_key_tree, input_key_entry, entry, input_key_cmp); +struct input_key_tree input_key_tree = RB_INITIALIZER(&input_key_tree); + +/* List of default keys, the tree is built from this. */ +static struct input_key_entry input_key_defaults[] = { /* Paste keys. */ - { KEYC_PASTE_START, "\033[200~", 0 }, - { KEYC_PASTE_END, "\033[201~", 0 }, + { .key = KEYC_PASTE_START, + .data = "\033[200~" + }, + { .key = KEYC_PASTE_END, + .data = "\033[201~" + }, /* Function keys. */ - { KEYC_F1, "\033OP", 0 }, - { KEYC_F2, "\033OQ", 0 }, - { KEYC_F3, "\033OR", 0 }, - { KEYC_F4, "\033OS", 0 }, - { KEYC_F5, "\033[15~", 0 }, - { KEYC_F6, "\033[17~", 0 }, - { KEYC_F7, "\033[18~", 0 }, - { KEYC_F8, "\033[19~", 0 }, - { KEYC_F9, "\033[20~", 0 }, - { KEYC_F10, "\033[21~", 0 }, - { KEYC_F11, "\033[23~", 0 }, - { KEYC_F12, "\033[24~", 0 }, - { KEYC_F1|KEYC_SHIFT, "\033[25~", 0 }, - { KEYC_F2|KEYC_SHIFT, "\033[26~", 0 }, - { KEYC_F3|KEYC_SHIFT, "\033[28~", 0 }, - { KEYC_F4|KEYC_SHIFT, "\033[29~", 0 }, - { KEYC_F5|KEYC_SHIFT, "\033[31~", 0 }, - { KEYC_F6|KEYC_SHIFT, "\033[32~", 0 }, - { KEYC_F7|KEYC_SHIFT, "\033[33~", 0 }, - { KEYC_F8|KEYC_SHIFT, "\033[34~", 0 }, - { KEYC_IC, "\033[2~", 0 }, - { KEYC_DC, "\033[3~", 0 }, - { KEYC_HOME, "\033[1~", 0 }, - { KEYC_END, "\033[4~", 0 }, - { KEYC_NPAGE, "\033[6~", 0 }, - { KEYC_PPAGE, "\033[5~", 0 }, - { KEYC_BTAB, "\033[Z", 0 }, + { .key = KEYC_F1, + .data = "\033OP" + }, + { .key = KEYC_F2, + .data = "\033OQ" + }, + { .key = KEYC_F3, + .data = "\033OR" + }, + { .key = KEYC_F4, + .data = "\033OS" + }, + { .key = KEYC_F5, + .data = "\033[15~" + }, + { .key = KEYC_F6, + .data = "\033[17~" + }, + { .key = KEYC_F7, + .data = "\033[18~" + }, + { .key = KEYC_F8, + .data = "\033[19~" + }, + { .key = KEYC_F9, + .data = "\033[20~" + }, + { .key = KEYC_F10, + .data = "\033[21~" + }, + { .key = KEYC_F11, + .data = "\033[23~" + }, + { .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~" + }, + { .key = KEYC_DC, + .data = "\033[3~" + }, + { .key = KEYC_HOME, + .data = "\033[1~" + }, + { .key = KEYC_END, + .data = "\033[4~" + }, + { .key = KEYC_NPAGE, + .data = "\033[6~" + }, + { .key = KEYC_PPAGE, + .data = "\033[5~" + }, + { .key = KEYC_BTAB, + .data = "\033[Z" + }, - /* - * Arrow keys. Cursor versions must come first. The codes are toggled - * between CSI and SS3 versions when ctrl is pressed. - */ - { KEYC_UP|KEYC_CTRL, "\033[A", INPUTKEY_CURSOR }, - { KEYC_DOWN|KEYC_CTRL, "\033[B", INPUTKEY_CURSOR }, - { KEYC_RIGHT|KEYC_CTRL, "\033[C", INPUTKEY_CURSOR }, - { KEYC_LEFT|KEYC_CTRL, "\033[D", INPUTKEY_CURSOR }, + /* Arrow keys. */ + { .key = KEYC_UP|KEYC_CURSOR, + .data = "\033OA" + }, + { .key = KEYC_DOWN|KEYC_CURSOR, + .data = "\033OB" + }, + { .key = KEYC_RIGHT|KEYC_CURSOR, + .data = "\033OC" + }, + { .key = KEYC_LEFT|KEYC_CURSOR, + .data = "\033OD" + }, + { .key = KEYC_UP, + .data = "\033[A" + }, + { .key = KEYC_DOWN, + .data = "\033[B" + }, + { .key = KEYC_RIGHT, + .data = "\033[C" + }, + { .key = KEYC_LEFT, + .data = "\033[D" + }, - { KEYC_UP, "\033OA", INPUTKEY_CURSOR }, - { KEYC_DOWN, "\033OB", INPUTKEY_CURSOR }, - { KEYC_RIGHT, "\033OC", INPUTKEY_CURSOR }, - { KEYC_LEFT, "\033OD", INPUTKEY_CURSOR }, + /* Keypad keys. */ + { .key = KEYC_KP_SLASH|KEYC_KEYPAD, + .data = "\033Oo" + }, + { .key = KEYC_KP_STAR|KEYC_KEYPAD, + .data = "\033Oj" + }, + { .key = KEYC_KP_MINUS|KEYC_KEYPAD, + .data = "\033Om" + }, + { .key = KEYC_KP_SEVEN|KEYC_KEYPAD, + .data = "\033Ow" + }, + { .key = KEYC_KP_EIGHT|KEYC_KEYPAD, + .data = "\033Ox" + }, + { .key = KEYC_KP_NINE|KEYC_KEYPAD, + .data = "\033Oy" + }, + { .key = KEYC_KP_PLUS|KEYC_KEYPAD, + .data = "\033Ok" + }, + { .key = KEYC_KP_FOUR|KEYC_KEYPAD, + .data = "\033Ot" + }, + { .key = KEYC_KP_FIVE|KEYC_KEYPAD, + .data = "\033Ou" + }, + { .key = KEYC_KP_SIX|KEYC_KEYPAD, + .data = "\033Ov" + }, + { .key = KEYC_KP_ONE|KEYC_KEYPAD, + .data = "\033Oq" + }, + { .key = KEYC_KP_TWO|KEYC_KEYPAD, + .data = "\033Or" + }, + { .key = KEYC_KP_THREE|KEYC_KEYPAD, + .data = "\033Os" + }, + { .key = KEYC_KP_ENTER|KEYC_KEYPAD, + .data = "\033OM" + }, + { .key = KEYC_KP_ZERO|KEYC_KEYPAD, + .data = "\033Op" + }, + { .key = KEYC_KP_PERIOD|KEYC_KEYPAD, + .data = "\033On" + }, + { .key = KEYC_KP_SLASH, + .data = "/" + }, + { .key = KEYC_KP_STAR, + .data = "*" + }, + { .key = KEYC_KP_MINUS, + .data = "-" + }, + { .key = KEYC_KP_SEVEN, + .data = "7" + }, + { .key = KEYC_KP_EIGHT, + .data = "8" + }, + { .key = KEYC_KP_NINE, + .data = "9" + }, + { .key = KEYC_KP_PLUS, + .data = "+" + }, + { .key = KEYC_KP_FOUR, + .data = "4" + }, + { .key = KEYC_KP_FIVE, + .data = "5" + }, + { .key = KEYC_KP_SIX, + .data = "6" + }, + { .key = KEYC_KP_ONE, + .data = "1" + }, + { .key = KEYC_KP_TWO, + .data = "2" + }, + { .key = KEYC_KP_THREE, + .data = "3" + }, + { .key = KEYC_KP_ENTER, + .data = "\n" + }, + { .key = KEYC_KP_ZERO, + .data = "0" + }, + { .key = KEYC_KP_PERIOD, + .data = "." + }, - { KEYC_UP|KEYC_CTRL, "\033OA", 0 }, - { KEYC_DOWN|KEYC_CTRL, "\033OB", 0 }, - { KEYC_RIGHT|KEYC_CTRL, "\033OC", 0 }, - { KEYC_LEFT|KEYC_CTRL, "\033OD", 0 }, - - { KEYC_UP, "\033[A", 0 }, - { KEYC_DOWN, "\033[B", 0 }, - { KEYC_RIGHT, "\033[C", 0 }, - { KEYC_LEFT, "\033[D", 0 }, - - /* Keypad keys. Keypad versions must come first. */ - { KEYC_KP_SLASH, "\033Oo", INPUTKEY_KEYPAD }, - { KEYC_KP_STAR, "\033Oj", INPUTKEY_KEYPAD }, - { KEYC_KP_MINUS, "\033Om", INPUTKEY_KEYPAD }, - { KEYC_KP_SEVEN, "\033Ow", INPUTKEY_KEYPAD }, - { KEYC_KP_EIGHT, "\033Ox", INPUTKEY_KEYPAD }, - { KEYC_KP_NINE, "\033Oy", INPUTKEY_KEYPAD }, - { KEYC_KP_PLUS, "\033Ok", INPUTKEY_KEYPAD }, - { KEYC_KP_FOUR, "\033Ot", INPUTKEY_KEYPAD }, - { KEYC_KP_FIVE, "\033Ou", INPUTKEY_KEYPAD }, - { KEYC_KP_SIX, "\033Ov", INPUTKEY_KEYPAD }, - { KEYC_KP_ONE, "\033Oq", INPUTKEY_KEYPAD }, - { KEYC_KP_TWO, "\033Or", INPUTKEY_KEYPAD }, - { KEYC_KP_THREE, "\033Os", INPUTKEY_KEYPAD }, - { KEYC_KP_ENTER, "\033OM", INPUTKEY_KEYPAD }, - { KEYC_KP_ZERO, "\033Op", INPUTKEY_KEYPAD }, - { KEYC_KP_PERIOD, "\033On", INPUTKEY_KEYPAD }, - - { KEYC_KP_SLASH, "/", 0 }, - { KEYC_KP_STAR, "*", 0 }, - { KEYC_KP_MINUS, "-", 0 }, - { KEYC_KP_SEVEN, "7", 0 }, - { KEYC_KP_EIGHT, "8", 0 }, - { KEYC_KP_NINE, "9", 0 }, - { KEYC_KP_PLUS, "+", 0 }, - { KEYC_KP_FOUR, "4", 0 }, - { KEYC_KP_FIVE, "5", 0 }, - { KEYC_KP_SIX, "6", 0 }, - { KEYC_KP_ONE, "1", 0 }, - { KEYC_KP_TWO, "2", 0 }, - { KEYC_KP_THREE, "3", 0 }, - { KEYC_KP_ENTER, "\n", 0 }, - { KEYC_KP_ZERO, "0", 0 }, - { KEYC_KP_PERIOD, ".", 0 }, + /* Keys with an embedded modifier. */ + { .key = KEYC_F1|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_P" + }, + { .key = KEYC_F2|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_Q" + }, + { .key = KEYC_F3|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_R" + }, + { .key = KEYC_F4|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_S" + }, + { .key = KEYC_F5|KEYC_BUILD_MODIFIERS, + .data = "\033[15;_~" + }, + { .key = KEYC_F6|KEYC_BUILD_MODIFIERS, + .data = "\033[17;_~" + }, + { .key = KEYC_F7|KEYC_BUILD_MODIFIERS, + .data = "\033[18;_~" + }, + { .key = KEYC_F8|KEYC_BUILD_MODIFIERS, + .data = "\033[19;_~" + }, + { .key = KEYC_F9|KEYC_BUILD_MODIFIERS, + .data = "\033[20;_~" + }, + { .key = KEYC_F10|KEYC_BUILD_MODIFIERS, + .data = "\033[21;_~" + }, + { .key = KEYC_F11|KEYC_BUILD_MODIFIERS, + .data = "\033[23;_~" + }, + { .key = KEYC_F12|KEYC_BUILD_MODIFIERS, + .data = "\033[24;_~" + }, + { .key = KEYC_UP|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_A" + }, + { .key = KEYC_DOWN|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_B" + }, + { .key = KEYC_RIGHT|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_C" + }, + { .key = KEYC_LEFT|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_D" + }, + { .key = KEYC_HOME|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_H" + }, + { .key = KEYC_END|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_F" + }, + { .key = KEYC_PPAGE|KEYC_BUILD_MODIFIERS, + .data = "\033[5;_~" + }, + { .key = KEYC_NPAGE|KEYC_BUILD_MODIFIERS, + .data = "\033[6;_~" + }, + { .key = KEYC_IC|KEYC_BUILD_MODIFIERS, + .data = "\033[2;_~" + }, + { .key = KEYC_DC|KEYC_BUILD_MODIFIERS, + .data = "\033[3;_~" } }; +static const key_code input_key_modifiers[] = { + 0, + 0, + KEYC_SHIFT, + KEYC_META|KEYC_IMPLIED_META, + KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META, + KEYC_CTRL, + KEYC_SHIFT|KEYC_CTRL, + KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL, + KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL +}; + +/* Input key comparison function. */ +static int +input_key_cmp(struct input_key_entry *ike1, struct input_key_entry *ike2) +{ + if (ike1->key < ike2->key) + return (-1); + if (ike1->key > ike2->key) + return (1); + return (0); +} + +/* Look for key in tree. */ +static struct input_key_entry * +input_key_get (key_code key) +{ + struct input_key_entry entry = { .key = key }; + + return (RB_FIND(input_key_tree, &input_key_tree, &entry)); +} /* Split a character into two UTF-8 bytes. */ static size_t -input_split2(u_int c, u_char *dst) +input_key_split2(u_int c, u_char *dst) { if (c > 0x7f) { dst[0] = (c >> 6) | 0xc0; @@ -149,32 +377,65 @@ input_split2(u_int c, u_char *dst) return (1); } +/* Build input key tree. */ +void +input_key_build(void) +{ + struct input_key_entry *ike, *new; + u_int i, j; + char *data; + key_code key; + + for (i = 0; i < nitems(input_key_defaults); i++) { + ike = &input_key_defaults[i]; + if (~ike->key & KEYC_BUILD_MODIFIERS) { + RB_INSERT(input_key_tree, &input_key_tree, ike); + continue; + } + + for (j = 2; j < nitems(input_key_modifiers); j++) { + key = (ike->key & ~KEYC_BUILD_MODIFIERS); + data = xstrdup(ike->data); + data[strcspn(data, "_")] = '0' + j; + + new = xcalloc(1, sizeof *new); + new->key = key|input_key_modifiers[j]; + new->data = data; + RB_INSERT(input_key_tree, &input_key_tree, new); + } + } + + RB_FOREACH(ike, input_key_tree, &input_key_tree) { + log_debug("%s: 0x%llx (%s) is %s", __func__, ike->key, + key_string_lookup_key(ike->key, 1), ike->data); + } +} + /* Translate a key code into an output key sequence for a pane. */ int input_key_pane(struct window_pane *wp, key_code key, struct mouse_event *m) { - log_debug("writing key 0x%llx (%s) to %%%u", key, - key_string_lookup_key(key), wp->id); + if (log_get_level() != 0) { + log_debug("writing key 0x%llx (%s) to %%%u", key, + key_string_lookup_key(key, 1), wp->id); + } if (KEYC_IS_MOUSE(key)) { if (m != NULL && m->wp != -1 && (u_int)m->wp == wp->id) input_key_mouse(wp, m); return (0); } - return (input_key(wp, wp->screen, wp->event, key)); + return (input_key(wp->screen, wp->event, key)); } /* Translate a key code into an output key sequence. */ int -input_key(struct window_pane *wp, struct screen *s, struct bufferevent *bev, - key_code key) +input_key(struct screen *s, struct bufferevent *bev, key_code key) { - const struct input_key_ent *ike; - u_int i; - size_t dlen; - char *out; - key_code justkey, newkey; - struct utf8_data ud; + struct input_key_entry *ike; + key_code justkey, newkey, outkey; + struct utf8_data ud; + char tmp[64], modifier; /* Mouse keys need a pane. */ if (KEYC_IS_MOUSE(key)) @@ -192,16 +453,16 @@ input_key(struct window_pane *wp, struct screen *s, struct bufferevent *bev, newkey = options_get_number(global_options, "backspace"); if (newkey >= 0x7f) newkey = '\177'; - key = newkey|(key & KEYC_MASK_MOD); + key = newkey|(key & (KEYC_MASK_MODIFIERS|KEYC_MASK_FLAGS)); } /* * If this is a normal 7-bit key, just send it, with a leading escape * if necessary. If it is a UTF-8 key, split it and send it. */ - justkey = (key & ~(KEYC_XTERM|KEYC_ESCAPE)); + justkey = (key & ~(KEYC_META|KEYC_IMPLIED_META)); if (justkey <= 0x7f) { - if (key & KEYC_ESCAPE) + if (key & KEYC_META) bufferevent_write(bev, "\033", 1); ud.data[0] = justkey; bufferevent_write(bev, &ud.data[0], 1); @@ -210,51 +471,69 @@ input_key(struct window_pane *wp, struct screen *s, struct bufferevent *bev, if (justkey > 0x7f && justkey < KEYC_BASE) { if (utf8_split(justkey, &ud) != UTF8_DONE) return (-1); - if (key & KEYC_ESCAPE) + if (key & KEYC_META) bufferevent_write(bev, "\033", 1); bufferevent_write(bev, ud.data, ud.size); return (0); } /* - * Then try to look this up as an xterm key, if the flag to output them - * is set. + * Look up in the tree. If not in application keypad or cursor mode, + * remove the flags from the key. */ - if (wp == NULL || options_get_number(wp->window->options, "xterm-keys")) { - if ((out = xterm_keys_lookup(key)) != NULL) { - bufferevent_write(bev, out, strlen(out)); - free(out); - return (0); - } + if (~s->mode & MODE_KKEYPAD) + key &= ~KEYC_KEYPAD; + if (~s->mode & MODE_KCURSOR) + key &= ~KEYC_CURSOR; + ike = input_key_get(key); + if (ike == NULL && (key & KEYC_META) && (~key & KEYC_IMPLIED_META)) + ike = input_key_get(key & ~KEYC_META); + if (ike != NULL) { + log_debug("found key 0x%llx: \"%s\"", key, ike->data); + if (key & KEYC_META && (~key & KEYC_IMPLIED_META)) + bufferevent_write(bev, "\033", 1); + bufferevent_write(bev, ike->data, strlen(ike->data)); + return (0); } - key &= ~KEYC_XTERM; - /* Otherwise look the key up in the table. */ - for (i = 0; i < nitems(input_keys); i++) { - ike = &input_keys[i]; - - if ((ike->flags & INPUTKEY_KEYPAD) && (~s->mode & MODE_KKEYPAD)) - continue; - if ((ike->flags & INPUTKEY_CURSOR) && (~s->mode & MODE_KCURSOR)) - continue; - - if ((key & KEYC_ESCAPE) && (ike->key | KEYC_ESCAPE) == key) - break; - if (ike->key == key) - break; + /* No builtin key sequence; construct an extended key sequence. */ + if (~s->mode & MODE_KEXTENDED) { + if ((key & KEYC_MASK_MODIFIERS) == KEYC_CTRL && + (key & KEYC_MASK_KEY) < KEYC_BASE) + return (input_key(s, bev, key & ~KEYC_CTRL)); + goto missing; } - if (i == nitems(input_keys)) { - log_debug("key 0x%llx missing", key); - return (-1); + outkey = (key & KEYC_MASK_KEY); + switch (key & KEYC_MASK_MODIFIERS) { + case KEYC_SHIFT: + modifier = '2'; + break; + case KEYC_META: + modifier = '3'; + break; + case KEYC_SHIFT|KEYC_META: + modifier = '4'; + break; + case KEYC_CTRL: + modifier = '5'; + break; + case KEYC_SHIFT|KEYC_CTRL: + modifier = '6'; + break; + case KEYC_META|KEYC_CTRL: + modifier = '7'; + break; + case KEYC_SHIFT|KEYC_META|KEYC_CTRL: + modifier = '8'; + break; } - dlen = strlen(ike->data); - log_debug("found key 0x%llx: \"%s\"", key, ike->data); - - /* Prefix a \033 for escape. */ - if (key & KEYC_ESCAPE) - bufferevent_write(bev, "\033", 1); - bufferevent_write(bev, ike->data, dlen); + xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", outkey, modifier); + bufferevent_write(bev, tmp, strlen(tmp)); return (0); + +missing: + log_debug("key 0x%llx missing", key); + return (-1); } /* Get mouse event string. */ @@ -309,9 +588,9 @@ input_key_get_mouse(struct screen *s, struct mouse_event *m, u_int x, u_int y, if (m->b > 0x7ff - 32 || x > 0x7ff - 33 || y > 0x7ff - 33) return (0); len = xsnprintf(buf, sizeof buf, "\033[M"); - len += input_split2(m->b + 32, &buf[len]); - len += input_split2(x + 33, &buf[len]); - len += input_split2(y + 33, &buf[len]); + len += input_key_split2(m->b + 32, &buf[len]); + len += input_key_split2(x + 33, &buf[len]); + len += input_key_split2(y + 33, &buf[len]); } else { if (m->b > 223) return (0); diff --git a/input.c b/input.c index 03b81c69..44b1b948 100644 --- a/input.c +++ b/input.c @@ -241,6 +241,8 @@ enum input_csi_type { INPUT_CSI_HPA, INPUT_CSI_ICH, INPUT_CSI_IL, + INPUT_CSI_MODOFF, + INPUT_CSI_MODSET, INPUT_CSI_RCP, INPUT_CSI_REP, INPUT_CSI_RM, @@ -289,7 +291,9 @@ static const struct input_table_entry input_csi_table[] = { { 'l', "", INPUT_CSI_RM }, { 'l', "?", INPUT_CSI_RM_PRIVATE }, { 'm', "", INPUT_CSI_SGR }, + { 'm', ">", INPUT_CSI_MODSET }, { 'n', "", INPUT_CSI_DSR }, + { 'n', ">", INPUT_CSI_MODOFF }, { 'q', " ", INPUT_CSI_DECSCUSR }, { 'q', ">", INPUT_CSI_XDA }, { 'r', "", INPUT_CSI_DECSTBM }, @@ -1380,6 +1384,19 @@ input_csi_dispatch(struct input_ctx *ictx) if (n != -1 && m != -1) screen_write_cursormove(sctx, m - 1, n - 1, 1); break; + case INPUT_CSI_MODSET: + n = input_get(ictx, 0, 0, 0); + m = input_get(ictx, 1, 0, 0); + if (n == 0 || (n == 4 && m == 0)) + screen_write_mode_clear(sctx, MODE_KEXTENDED); + else if (n == 4 && (m == 1 || m == 2)) + screen_write_mode_set(sctx, MODE_KEXTENDED); + break; + case INPUT_CSI_MODOFF: + n = input_get(ictx, 0, 0, 0); + if (n == 4) + screen_write_mode_clear(sctx, MODE_KEXTENDED); + break; case INPUT_CSI_WINOPS: input_csi_dispatch_winops(ictx); break; @@ -1593,7 +1610,7 @@ input_csi_dispatch(struct input_ctx *ictx) break; case INPUT_CSI_XDA: n = input_get(ictx, 0, 0, 0); - if (n != 0) + if (n == 0) input_reply(ictx, "\033P>|tmux %s\033\\", getversion()); break; diff --git a/options-table.c b/options-table.c index 4ac0d0c3..87670b12 100644 --- a/options-table.c +++ b/options-table.c @@ -252,6 +252,14 @@ const struct options_table_entry options_table[] = { "clients." }, + { .name = "extended-keys", + .type = OPTIONS_TABLE_FLAG, + .scope = OPTIONS_TABLE_SERVER, + .default_num = 0, + .text = "Whether to request extended key sequences from terminals " + "that support it." + }, + { .name = "focus-events", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, @@ -1053,11 +1061,12 @@ const struct options_table_entry options_table[] = { "bottom." }, - { .name = "xterm-keys", + { .name = "xterm-keys", /* no longer used */ .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, .default_num = 1, - .text = "Whether xterm-style function key sequences should be sent." + .text = "Whether xterm-style function key sequences should be sent. " + "This option is no longer used." }, /* Hook options. */ diff --git a/tmux.1 b/tmux.1 index 421f8889..9f687bd2 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3225,6 +3225,12 @@ sessions. .Op Ic on | off .Xc If enabled, the server will exit when there are no attached clients. +.It Xo Ic extended-keys +.Op Ic on | off +.Xc +When enabled, extended keys are requested from the terminal and if supported +are recognised by +.Nm . .It Xo Ic focus-events .Op Ic on | off .Xc @@ -4054,16 +4060,6 @@ option. .Xc If this option is set, searches will wrap around the end of the pane contents. The default is on. -.Pp -.It Xo Ic xterm-keys -.Op Ic on | off -.Xc -If this option is set, -.Nm -will generate -.Xr xterm 1 -style -function key sequences; these have a number included to indicate modifiers such -as Shift, Alt or Ctrl. .El .Pp Available pane options are: @@ -5754,6 +5750,8 @@ Disable and enable bracketed paste. These are set automatically if the .Em XT capability is present. +.It Em \&Dseks , \&Eneks +Disable and enable extended keys. .It Em \&Dsfcs , \&Enfcs Disable and enable focus reporting. These are set automatically if the diff --git a/tty-features.c b/tty-features.c index 30d3d1a0..26344b90 100644 --- a/tty-features.c +++ b/tty-features.c @@ -190,6 +190,18 @@ static const struct tty_feature tty_feature_sync = { 0 }; +/* Terminal supports extended keys. */ +static const char *tty_feature_extkeys_capabilities[] = { + "Eneks=\\E[>4;1m", + "Dseks=\\E[>4m", + NULL +}; +static const struct tty_feature tty_feature_extkeys = { + "extkeys", + tty_feature_extkeys_capabilities, + 0 +}; + /* Terminal supports DECSLRM margins. */ static const char *tty_feature_margins_capabilities[] = { "Enmg=\\E[?69h", @@ -218,6 +230,7 @@ static const struct tty_feature *tty_features[] = { &tty_feature_ccolour, &tty_feature_clipboard, &tty_feature_cstyle, + &tty_feature_extkeys, &tty_feature_focus, &tty_feature_margins, &tty_feature_overline, @@ -321,7 +334,7 @@ tty_default_features(int *feat, const char *name, u_int version) } table[] = { #define TTY_FEATURES_BASE_MODERN_XTERM "256,RGB,bpaste,clipboard,strikethrough,title" { .name = "mintty", - .features = TTY_FEATURES_BASE_MODERN_XTERM ",ccolour,cstyle,margins,overline" + .features = TTY_FEATURES_BASE_MODERN_XTERM ",ccolour,cstyle,extkeys,margins,overline" }, { .name = "tmux", .features = TTY_FEATURES_BASE_MODERN_XTERM ",ccolour,cstyle,focus,overline,usstyle" @@ -333,7 +346,7 @@ tty_default_features(int *feat, const char *name, u_int version) .features = TTY_FEATURES_BASE_MODERN_XTERM ",cstyle,margins,sync" }, { .name = "XTerm", - .features = TTY_FEATURES_BASE_MODERN_XTERM ",ccolour,cstyle,focus,margins,rectfill" + .features = TTY_FEATURES_BASE_MODERN_XTERM ",ccolour,cstyle,extkeys,focus,margins,rectfill" } }; u_int i; diff --git a/tty-keys.c b/tty-keys.c index dc064a17..5a2b3817 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -21,6 +21,7 @@ #include +#include #include #include #include @@ -46,6 +47,8 @@ static struct tty_key *tty_keys_find(struct tty *, const char *, size_t, static int tty_keys_next1(struct tty *, const char *, size_t, key_code *, size_t *, int); static void tty_keys_callback(int, short, void *); +static int tty_keys_extended_key(struct tty *, const char *, size_t, + size_t *, key_code *); static int tty_keys_mouse(struct tty *, const char *, size_t, size_t *, struct mouse_event *); static int tty_keys_clipboard(struct tty *, const char *, size_t, @@ -69,33 +72,33 @@ static const struct tty_default_key_raw tty_default_raw_keys[] = { * put the terminal into keypad_xmit mode. Translation of numbers * mode/applications mode is done in input-keys.c. */ - { "\033Oo", KEYC_KP_SLASH }, - { "\033Oj", KEYC_KP_STAR }, - { "\033Om", KEYC_KP_MINUS }, - { "\033Ow", KEYC_KP_SEVEN }, - { "\033Ox", KEYC_KP_EIGHT }, - { "\033Oy", KEYC_KP_NINE }, - { "\033Ok", KEYC_KP_PLUS }, - { "\033Ot", KEYC_KP_FOUR }, - { "\033Ou", KEYC_KP_FIVE }, - { "\033Ov", KEYC_KP_SIX }, - { "\033Oq", KEYC_KP_ONE }, - { "\033Or", KEYC_KP_TWO }, - { "\033Os", KEYC_KP_THREE }, - { "\033OM", KEYC_KP_ENTER }, - { "\033Op", KEYC_KP_ZERO }, - { "\033On", KEYC_KP_PERIOD }, + { "\033Oo", KEYC_KP_SLASH|KEYC_KEYPAD }, + { "\033Oj", KEYC_KP_STAR|KEYC_KEYPAD }, + { "\033Om", KEYC_KP_MINUS|KEYC_KEYPAD }, + { "\033Ow", KEYC_KP_SEVEN|KEYC_KEYPAD }, + { "\033Ox", KEYC_KP_EIGHT|KEYC_KEYPAD }, + { "\033Oy", KEYC_KP_NINE|KEYC_KEYPAD }, + { "\033Ok", KEYC_KP_PLUS|KEYC_KEYPAD }, + { "\033Ot", KEYC_KP_FOUR|KEYC_KEYPAD }, + { "\033Ou", KEYC_KP_FIVE|KEYC_KEYPAD }, + { "\033Ov", KEYC_KP_SIX|KEYC_KEYPAD }, + { "\033Oq", KEYC_KP_ONE|KEYC_KEYPAD }, + { "\033Or", KEYC_KP_TWO|KEYC_KEYPAD }, + { "\033Os", KEYC_KP_THREE|KEYC_KEYPAD }, + { "\033OM", KEYC_KP_ENTER|KEYC_KEYPAD }, + { "\033Op", KEYC_KP_ZERO|KEYC_KEYPAD }, + { "\033On", KEYC_KP_PERIOD|KEYC_KEYPAD }, /* Arrow keys. */ - { "\033OA", KEYC_UP }, - { "\033OB", KEYC_DOWN }, - { "\033OC", KEYC_RIGHT }, - { "\033OD", KEYC_LEFT }, + { "\033OA", KEYC_UP|KEYC_CURSOR }, + { "\033OB", KEYC_DOWN|KEYC_CURSOR }, + { "\033OC", KEYC_RIGHT|KEYC_CURSOR }, + { "\033OD", KEYC_LEFT|KEYC_CURSOR }, - { "\033[A", KEYC_UP }, - { "\033[B", KEYC_DOWN }, - { "\033[C", KEYC_RIGHT }, - { "\033[D", KEYC_LEFT }, + { "\033[A", KEYC_UP|KEYC_CURSOR }, + { "\033[B", KEYC_DOWN|KEYC_CURSOR }, + { "\033[C", KEYC_RIGHT|KEYC_CURSOR }, + { "\033[D", KEYC_LEFT|KEYC_CURSOR }, /* Other (xterm) "cursor" keys. */ { "\033OH", KEYC_HOME }, @@ -182,11 +185,59 @@ static const struct tty_default_key_raw tty_default_raw_keys[] = { { "\033[201~", KEYC_PASTE_END }, }; +/* Default xterm keys. */ +struct tty_default_key_xterm { + const char *template; + key_code key; +}; +static const struct tty_default_key_xterm tty_default_xterm_keys[] = { + { "\033[1;_P", KEYC_F1 }, + { "\033O1;_P", KEYC_F1 }, + { "\033O_P", KEYC_F1 }, + { "\033[1;_Q", KEYC_F2 }, + { "\033O1;_Q", KEYC_F2 }, + { "\033O_Q", KEYC_F2 }, + { "\033[1;_R", KEYC_F3 }, + { "\033O1;_R", KEYC_F3 }, + { "\033O_R", KEYC_F3 }, + { "\033[1;_S", KEYC_F4 }, + { "\033O1;_S", KEYC_F4 }, + { "\033O_S", KEYC_F4 }, + { "\033[15;_~", KEYC_F5 }, + { "\033[17;_~", KEYC_F6 }, + { "\033[18;_~", KEYC_F7 }, + { "\033[19;_~", KEYC_F8 }, + { "\033[20;_~", KEYC_F9 }, + { "\033[21;_~", KEYC_F10 }, + { "\033[23;_~", KEYC_F11 }, + { "\033[24;_~", KEYC_F12 }, + { "\033[1;_A", KEYC_UP }, + { "\033[1;_B", KEYC_DOWN }, + { "\033[1;_C", KEYC_RIGHT }, + { "\033[1;_D", KEYC_LEFT }, + { "\033[1;_H", KEYC_HOME }, + { "\033[1;_F", KEYC_END }, + { "\033[5;_~", KEYC_PPAGE }, + { "\033[6;_~", KEYC_NPAGE }, + { "\033[2;_~", KEYC_IC }, + { "\033[3;_~", KEYC_DC }, +}; +static const key_code tty_default_xterm_modifiers[] = { + 0, + 0, + KEYC_SHIFT, + KEYC_META|KEYC_IMPLIED_META, + KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META, + KEYC_CTRL, + KEYC_SHIFT|KEYC_CTRL, + KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL, + KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL +}; + /* - * Default terminfo(5) keys. Any keys that have builtin modifiers - * (that is, where the key itself contains the modifiers) has the - * KEYC_XTERM flag set so a leading escape is not treated as meta (and - * probably removed). + * Default terminfo(5) keys. Any keys that have builtin modifiers (that is, + * where the key itself contains the modifiers) has the KEYC_XTERM flag set so + * a leading escape is not treated as meta (and probably removed). */ struct tty_default_key_code { enum tty_code_code code; @@ -207,61 +258,61 @@ static const struct tty_default_key_code tty_default_code_keys[] = { { TTYC_KF11, KEYC_F11 }, { TTYC_KF12, KEYC_F12 }, - { TTYC_KF13, KEYC_F1|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF14, KEYC_F2|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF15, KEYC_F3|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF16, KEYC_F4|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF17, KEYC_F5|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF18, KEYC_F6|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF19, KEYC_F7|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF20, KEYC_F8|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF21, KEYC_F9|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF22, KEYC_F10|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF23, KEYC_F11|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF24, KEYC_F12|KEYC_SHIFT|KEYC_XTERM }, + { TTYC_KF13, KEYC_F1|KEYC_SHIFT }, + { TTYC_KF14, KEYC_F2|KEYC_SHIFT }, + { TTYC_KF15, KEYC_F3|KEYC_SHIFT }, + { TTYC_KF16, KEYC_F4|KEYC_SHIFT }, + { TTYC_KF17, KEYC_F5|KEYC_SHIFT }, + { TTYC_KF18, KEYC_F6|KEYC_SHIFT }, + { TTYC_KF19, KEYC_F7|KEYC_SHIFT }, + { TTYC_KF20, KEYC_F8|KEYC_SHIFT }, + { TTYC_KF21, KEYC_F9|KEYC_SHIFT }, + { TTYC_KF22, KEYC_F10|KEYC_SHIFT }, + { TTYC_KF23, KEYC_F11|KEYC_SHIFT }, + { TTYC_KF24, KEYC_F12|KEYC_SHIFT }, - { TTYC_KF25, KEYC_F1|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF26, KEYC_F2|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF27, KEYC_F3|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF28, KEYC_F4|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF29, KEYC_F5|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF30, KEYC_F6|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF31, KEYC_F7|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF32, KEYC_F8|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF33, KEYC_F9|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF34, KEYC_F10|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF35, KEYC_F11|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF36, KEYC_F12|KEYC_CTRL|KEYC_XTERM }, + { TTYC_KF25, KEYC_F1|KEYC_CTRL }, + { TTYC_KF26, KEYC_F2|KEYC_CTRL }, + { TTYC_KF27, KEYC_F3|KEYC_CTRL }, + { TTYC_KF28, KEYC_F4|KEYC_CTRL }, + { TTYC_KF29, KEYC_F5|KEYC_CTRL }, + { TTYC_KF30, KEYC_F6|KEYC_CTRL }, + { TTYC_KF31, KEYC_F7|KEYC_CTRL }, + { TTYC_KF32, KEYC_F8|KEYC_CTRL }, + { TTYC_KF33, KEYC_F9|KEYC_CTRL }, + { TTYC_KF34, KEYC_F10|KEYC_CTRL }, + { TTYC_KF35, KEYC_F11|KEYC_CTRL }, + { TTYC_KF36, KEYC_F12|KEYC_CTRL }, - { TTYC_KF37, KEYC_F1|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF38, KEYC_F2|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF39, KEYC_F3|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF40, KEYC_F4|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF41, KEYC_F5|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF42, KEYC_F6|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF43, KEYC_F7|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF44, KEYC_F8|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF45, KEYC_F9|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF46, KEYC_F10|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF47, KEYC_F11|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF48, KEYC_F12|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, + { TTYC_KF37, KEYC_F1|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF38, KEYC_F2|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF39, KEYC_F3|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF40, KEYC_F4|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF41, KEYC_F5|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF42, KEYC_F6|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF43, KEYC_F7|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF44, KEYC_F8|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF45, KEYC_F9|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF46, KEYC_F10|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF47, KEYC_F11|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF48, KEYC_F12|KEYC_SHIFT|KEYC_CTRL }, - { TTYC_KF49, KEYC_F1|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF50, KEYC_F2|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF51, KEYC_F3|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF52, KEYC_F4|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF53, KEYC_F5|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF54, KEYC_F6|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF55, KEYC_F7|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF56, KEYC_F8|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF57, KEYC_F9|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF58, KEYC_F10|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF59, KEYC_F11|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF60, KEYC_F12|KEYC_ESCAPE|KEYC_XTERM }, + { TTYC_KF49, KEYC_F1|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF50, KEYC_F2|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF51, KEYC_F3|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF52, KEYC_F4|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF53, KEYC_F5|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF54, KEYC_F6|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF55, KEYC_F7|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF56, KEYC_F8|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF57, KEYC_F9|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF58, KEYC_F10|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF59, KEYC_F11|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF60, KEYC_F12|KEYC_META|KEYC_IMPLIED_META }, - { TTYC_KF61, KEYC_F1|KEYC_ESCAPE|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF62, KEYC_F2|KEYC_ESCAPE|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF63, KEYC_F3|KEYC_ESCAPE|KEYC_SHIFT|KEYC_XTERM }, + { TTYC_KF61, KEYC_F1|KEYC_META|KEYC_IMPLIED_META|KEYC_SHIFT }, + { TTYC_KF62, KEYC_F2|KEYC_META|KEYC_IMPLIED_META|KEYC_SHIFT }, + { TTYC_KF63, KEYC_F3|KEYC_META|KEYC_IMPLIED_META|KEYC_SHIFT }, { TTYC_KICH1, KEYC_IC }, { TTYC_KDCH1, KEYC_DC }, @@ -272,74 +323,74 @@ static const struct tty_default_key_code tty_default_code_keys[] = { { TTYC_KCBT, KEYC_BTAB }, /* Arrow keys from terminfo. */ - { TTYC_KCUU1, KEYC_UP }, - { TTYC_KCUD1, KEYC_DOWN }, - { TTYC_KCUB1, KEYC_LEFT }, - { TTYC_KCUF1, KEYC_RIGHT }, + { TTYC_KCUU1, KEYC_UP|KEYC_CURSOR }, + { TTYC_KCUD1, KEYC_DOWN|KEYC_CURSOR }, + { TTYC_KCUB1, KEYC_LEFT|KEYC_CURSOR }, + { TTYC_KCUF1, KEYC_RIGHT|KEYC_CURSOR }, /* Key and modifier capabilities. */ - { TTYC_KDC2, KEYC_DC|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KDC3, KEYC_DC|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KDC4, KEYC_DC|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KDC5, KEYC_DC|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KDC6, KEYC_DC|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KDC7, KEYC_DC|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KIND, KEYC_DOWN|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KDN2, KEYC_DOWN|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KDN3, KEYC_DOWN|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KDN4, KEYC_DOWN|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KDN5, KEYC_DOWN|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KDN6, KEYC_DOWN|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KDN7, KEYC_DOWN|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KEND2, KEYC_END|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KEND3, KEYC_END|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KEND4, KEYC_END|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KEND5, KEYC_END|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KEND6, KEYC_END|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KEND7, KEYC_END|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KHOM2, KEYC_HOME|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KHOM3, KEYC_HOME|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KHOM4, KEYC_HOME|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KHOM5, KEYC_HOME|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KHOM6, KEYC_HOME|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KHOM7, KEYC_HOME|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KIC2, KEYC_IC|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KIC3, KEYC_IC|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KIC4, KEYC_IC|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KIC5, KEYC_IC|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KIC6, KEYC_IC|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KIC7, KEYC_IC|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KLFT2, KEYC_LEFT|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KLFT3, KEYC_LEFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KLFT4, KEYC_LEFT|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KLFT5, KEYC_LEFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KLFT6, KEYC_LEFT|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KLFT7, KEYC_LEFT|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KNXT2, KEYC_NPAGE|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KNXT3, KEYC_NPAGE|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KNXT4, KEYC_NPAGE|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KNXT5, KEYC_NPAGE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KNXT6, KEYC_NPAGE|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KNXT7, KEYC_NPAGE|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KPRV2, KEYC_PPAGE|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KPRV3, KEYC_PPAGE|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KPRV4, KEYC_PPAGE|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KPRV5, KEYC_PPAGE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KPRV6, KEYC_PPAGE|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KPRV7, KEYC_PPAGE|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KRIT2, KEYC_RIGHT|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KRIT3, KEYC_RIGHT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KRIT4, KEYC_RIGHT|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KRIT5, KEYC_RIGHT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KRIT6, KEYC_RIGHT|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KRIT7, KEYC_RIGHT|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KRI, KEYC_UP|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KUP2, KEYC_UP|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KUP3, KEYC_UP|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KUP4, KEYC_UP|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KUP5, KEYC_UP|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KUP6, KEYC_UP|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KUP7, KEYC_UP|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, + { TTYC_KDC2, KEYC_DC|KEYC_SHIFT }, + { TTYC_KDC3, KEYC_DC|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KDC4, KEYC_DC|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KDC5, KEYC_DC|KEYC_CTRL }, + { TTYC_KDC6, KEYC_DC|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KDC7, KEYC_DC|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KIND, KEYC_DOWN|KEYC_SHIFT }, + { TTYC_KDN2, KEYC_DOWN|KEYC_SHIFT }, + { TTYC_KDN3, KEYC_DOWN|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KDN4, KEYC_DOWN|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KDN5, KEYC_DOWN|KEYC_CTRL }, + { TTYC_KDN6, KEYC_DOWN|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KDN7, KEYC_DOWN|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KEND2, KEYC_END|KEYC_SHIFT }, + { TTYC_KEND3, KEYC_END|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KEND4, KEYC_END|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KEND5, KEYC_END|KEYC_CTRL }, + { TTYC_KEND6, KEYC_END|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KEND7, KEYC_END|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KHOM2, KEYC_HOME|KEYC_SHIFT }, + { TTYC_KHOM3, KEYC_HOME|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KHOM4, KEYC_HOME|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KHOM5, KEYC_HOME|KEYC_CTRL }, + { TTYC_KHOM6, KEYC_HOME|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KHOM7, KEYC_HOME|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KIC2, KEYC_IC|KEYC_SHIFT }, + { TTYC_KIC3, KEYC_IC|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KIC4, KEYC_IC|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KIC5, KEYC_IC|KEYC_CTRL }, + { TTYC_KIC6, KEYC_IC|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KIC7, KEYC_IC|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KLFT2, KEYC_LEFT|KEYC_SHIFT }, + { TTYC_KLFT3, KEYC_LEFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KLFT4, KEYC_LEFT|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KLFT5, KEYC_LEFT|KEYC_CTRL }, + { TTYC_KLFT6, KEYC_LEFT|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KLFT7, KEYC_LEFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KNXT2, KEYC_NPAGE|KEYC_SHIFT }, + { TTYC_KNXT3, KEYC_NPAGE|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KNXT4, KEYC_NPAGE|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KNXT5, KEYC_NPAGE|KEYC_CTRL }, + { TTYC_KNXT6, KEYC_NPAGE|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KNXT7, KEYC_NPAGE|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KPRV2, KEYC_PPAGE|KEYC_SHIFT }, + { TTYC_KPRV3, KEYC_PPAGE|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KPRV4, KEYC_PPAGE|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KPRV5, KEYC_PPAGE|KEYC_CTRL }, + { TTYC_KPRV6, KEYC_PPAGE|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KPRV7, KEYC_PPAGE|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KRIT2, KEYC_RIGHT|KEYC_SHIFT }, + { TTYC_KRIT3, KEYC_RIGHT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KRIT4, KEYC_RIGHT|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KRIT5, KEYC_RIGHT|KEYC_CTRL }, + { TTYC_KRIT6, KEYC_RIGHT|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KRIT7, KEYC_RIGHT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KRI, KEYC_UP|KEYC_SHIFT }, + { TTYC_KUP2, KEYC_UP|KEYC_SHIFT }, + { TTYC_KUP3, KEYC_UP|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KUP4, KEYC_UP|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KUP5, KEYC_UP|KEYC_CTRL }, + { TTYC_KUP6, KEYC_UP|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KUP7, KEYC_UP|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, }; /* Add key to tree. */ @@ -350,7 +401,7 @@ tty_keys_add(struct tty *tty, const char *s, key_code key) size_t size; const char *keystr; - keystr = key_string_lookup_key(key); + keystr = key_string_lookup_key(key, 1); if ((tk = tty_keys_find(tty, s, strlen(s), &size)) == NULL) { log_debug("new key %s: 0x%llx (%s)", s, key, keystr); tty_keys_add1(&tty->key_tree, s, key); @@ -403,17 +454,30 @@ void tty_keys_build(struct tty *tty) { const struct tty_default_key_raw *tdkr; + const struct tty_default_key_xterm *tdkx; const struct tty_default_key_code *tdkc; - u_int i; + u_int i, j; const char *s; struct options_entry *o; struct options_array_item *a; union options_value *ov; + char copy[16]; + key_code key; if (tty->key_tree != NULL) tty_keys_free(tty); tty->key_tree = NULL; + for (i = 0; i < nitems(tty_default_xterm_keys); i++) { + tdkx = &tty_default_xterm_keys[i]; + for (j = 2; j < nitems(tty_default_xterm_modifiers); j++) { + strlcpy(copy, tdkx->template, sizeof copy); + copy[strcspn(copy, "_")] = '0' + j; + + key = tdkx->key|tty_default_xterm_modifiers[j]; + tty_keys_add(tty, copy, key); + } + } for (i = 0; i < nitems(tty_default_raw_keys); i++) { tdkr = &tty_default_raw_keys[i]; @@ -516,7 +580,6 @@ tty_keys_next1(struct tty *tty, const char *buf, size_t len, key_code *key, enum utf8_state more; u_int i; wchar_t wc; - int n; log_debug("%s: next key is %zu (%.*s) (expired=%d)", c->name, len, (int)len, buf, expired); @@ -534,13 +597,6 @@ tty_keys_next1(struct tty *tty, const char *buf, size_t len, key_code *key, return (0); } - /* Is this an an xterm(1) key? */ - n = xterm_keys_find(buf, len, size, key); - if (n == 0) - return (0); - if (n == 1 && !expired) - return (1); - /* Is this valid UTF-8? */ more = utf8_open(&ud, (u_char)*buf); if (more == UTF8_MORE) { @@ -637,6 +693,16 @@ tty_keys_next(struct tty *tty) goto partial_key; } + /* Is this an extended key press? */ + switch (tty_keys_extended_key(tty, buf, len, &size, &key)) { + case 0: /* yes */ + goto complete_key; + case -1: /* no, or not valid */ + break; + case 1: /* partial */ + goto partial_key; + } + first_key: /* Try to lookup complete key. */ n = tty_keys_next1(tty, buf, len, &key, &size, expired); @@ -653,7 +719,7 @@ first_key: /* Look for a key without the escape. */ n = tty_keys_next1(tty, buf + 1, len - 1, &key, &size, expired); if (n == 0) { /* found */ - if (key & KEYC_XTERM) { + if (key & KEYC_IMPLIED_META) { /* * We want the escape key as well as the xterm * key, because the xterm sequence implicitly @@ -665,7 +731,7 @@ first_key: size = 1; goto complete_key; } - key |= KEYC_ESCAPE; + key |= KEYC_META; size++; goto complete_key; } @@ -678,7 +744,7 @@ first_key: * escape). So pass it through even if the timer has not expired. */ if (*buf == '\033' && len >= 2) { - key = (u_char)buf[1] | KEYC_ESCAPE; + key = (u_char)buf[1] | KEYC_META; size = 2; } else { key = (u_char)buf[0]; @@ -723,7 +789,7 @@ complete_key: */ bspace = tty->tio.c_cc[VERASE]; if (bspace != _POSIX_VDISABLE && (key & KEYC_MASK_KEY) == bspace) - key = (key & KEYC_MASK_MOD) | KEYC_BSPACE; + key = (key & KEYC_MASK_MODIFIERS)|KEYC_BSPACE; /* Remove data from buffer. */ evbuffer_drain(tty->in, size); @@ -774,6 +840,96 @@ tty_keys_callback(__unused int fd, __unused short events, void *data) } } +/* + * Handle extended key input. This has two forms: \033[27;m;k~ and \033[k;mu, + * where k is key as a number and m is a modifier. Returns 0 for success, -1 + * for failure, 1 for partial; + */ +static int +tty_keys_extended_key(struct tty *tty, const char *buf, size_t len, + size_t *size, key_code *key) +{ + struct client *c = tty->client; + size_t end; + u_int number, modifiers; + char tmp[64]; + + *size = 0; + + /* First two bytes are always \033[. */ + if (buf[0] != '\033') + return (-1); + if (len == 1) + return (1); + if (buf[1] != '[') + return (-1); + if (len == 2) + return (1); + + /* + * Look for a terminator. Stop at either '~' or anything that isn't a + * number or ';'. + */ + for (end = 2; end < len && end != sizeof tmp; end++) { + if (buf[end] == '~') + break; + if (!isdigit((u_char)buf[end]) && buf[end] != ';') + break; + } + if (end == len) + return (1); + if (end == sizeof tmp || (buf[end] != '~' && buf[end] != 'u')) + return (-1); + + /* Copy to the buffer. */ + memcpy(tmp, buf + 2, end); + tmp[end] = '\0'; + + /* Try to parse either form of key. */ + if (buf[end] == '~') { + if (sscanf(tmp, "27;%u;%u", &modifiers, &number) != 2) + return (-1); + } else { + if (sscanf(tmp ,"%u;%u", &number, &modifiers) != 2) + return (-1); + } + *size = end + 1; + + /* Store the key and modifiers. */ + *key = number; + switch (modifiers) { + case 2: + (*key) |= KEYC_SHIFT; + break; + case 3: + (*key) |= (KEYC_META|KEYC_IMPLIED_META); + break; + case 4: + (*key) |= (KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META); + break; + case 5: + (*key) |= KEYC_CTRL; + break; + case 6: + (*key) |= (KEYC_SHIFT|KEYC_CTRL); + break; + case 7: + (*key) |= (KEYC_META|KEYC_CTRL); + break; + case 8: + (*key) |= (KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL); + break; + default: + *key = KEYC_NONE; + break; + } + if (log_get_level() != 0) { + log_debug("%s: extended key %.*s is %llx (%s)", c->name, + (int)*size, buf, *key, key_string_lookup_key(*key, 1)); + } + return (0); +} + /* * Handle mouse key input. Returns 0 for success, -1 for failure, 1 for partial * (probably a mouse sequence but need more data). @@ -1137,7 +1293,7 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, else if (strncmp(tmp, "tmux ", 5) == 0) tty_default_features(&c->term_features, "tmux", 0); else if (strncmp(tmp, "XTerm(", 6) == 0) - tty_default_features(&c->term_features, "xterm", 0); + tty_default_features(&c->term_features, "XTerm", 0); else if (strncmp(tmp, "mintty ", 7) == 0) tty_default_features(&c->term_features, "mintty", 0); log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf); diff --git a/tty-term.c b/tty-term.c index 67ca1bac..76c4fb57 100644 --- a/tty-term.c +++ b/tty-term.c @@ -83,6 +83,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_DIM] = { TTYCODE_STRING, "dim" }, [TTYC_DL1] = { TTYCODE_STRING, "dl1" }, [TTYC_DL] = { TTYCODE_STRING, "dl" }, + [TTYC_DSEKS] = { TTYCODE_STRING, "Dseks" }, [TTYC_DSFCS] = { TTYCODE_STRING, "Dsfcs" }, [TTYC_DSBP] = { TTYCODE_STRING, "Dsbp" }, [TTYC_DSMG] = { TTYCODE_STRING, "Dsmg" }, @@ -93,6 +94,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_EL] = { TTYCODE_STRING, "el" }, [TTYC_ENACS] = { TTYCODE_STRING, "enacs" }, [TTYC_ENBP] = { TTYCODE_STRING, "Enbp" }, + [TTYC_ENEKS] = { TTYCODE_STRING, "Eneks" }, [TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" }, [TTYC_ENMG] = { TTYCODE_STRING, "Enmg" }, [TTYC_FSL] = { TTYCODE_STRING, "fsl" }, diff --git a/tty.c b/tty.c index 99996dfa..770c8b78 100644 --- a/tty.c +++ b/tty.c @@ -286,6 +286,8 @@ tty_start_timer_callback(__unused int fd, __unused short events, void *data) struct client *c = tty->client; log_debug("%s: start timer fired", c->name); + if ((tty->flags & (TTY_HAVEDA|TTY_HAVEXDA)) == 0) + tty_update_features(tty); tty->flags |= (TTY_HAVEDA|TTY_HAVEXDA); } @@ -329,13 +331,6 @@ tty_start_tty(struct tty *tty) tty_puts(tty, "\033[?1006l\033[?1005l"); } - if (options_get_number(global_options, "focus-events")) { - tty->flags |= TTY_FOCUS; - tty_raw(tty, tty_term_string(tty->term, TTYC_ENFCS)); - } - if (tty->term->flags & TERM_VT100LIKE) - tty_puts(tty, "\033[?7727h"); - evtimer_set(&tty->start_timer, tty_start_timer_callback, tty); evtimer_add(&tty->start_timer, &tv); @@ -415,12 +410,10 @@ tty_stop_tty(struct tty *tty) tty_raw(tty, "\033[?1006l\033[?1005l"); } - if (tty->flags & TTY_FOCUS) { - tty->flags &= ~TTY_FOCUS; - tty_raw(tty, tty_term_string(tty->term, TTYC_DSFCS)); - } if (tty->term->flags & TERM_VT100LIKE) tty_raw(tty, "\033[?7727l"); + tty_raw(tty, tty_term_string(tty->term, TTYC_DSFCS)); + tty_raw(tty, tty_term_string(tty->term, TTYC_DSEKS)); if (tty_use_margin(tty)) tty_raw(tty, tty_term_string(tty->term, TTYC_DSMG)); @@ -471,6 +464,12 @@ tty_update_features(struct tty *tty) if (tty_use_margin(tty)) tty_putcode(tty, TTYC_ENMG); + if (options_get_number(global_options, "extended-keys")) + tty_puts(tty, tty_term_string(tty->term, TTYC_ENEKS)); + if (options_get_number(global_options, "focus-events")) + tty_raw(tty, tty_term_string(tty->term, TTYC_ENFCS)); + if (tty->term->flags & TERM_VT100LIKE) + tty_puts(tty, "\033[?7727h"); } void From 844b363baf64fbaff91cf8fa01d4fd782e7274a8 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:45:55 +0000 Subject: [PATCH 48/49] On select-window, make this client the latest client for the window. --- cmd-select-window.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd-select-window.c b/cmd-select-window.c index 377e3633..c85f36be 100644 --- a/cmd-select-window.c +++ b/cmd-select-window.c @@ -85,6 +85,7 @@ static enum cmd_retval cmd_select_window_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); + struct client *c = cmdq_get_client(item); struct cmd_find_state *current = cmdq_get_current(item); struct cmd_find_state *target = cmdq_get_target(item); struct winlink *wl = target->wl; @@ -141,6 +142,8 @@ cmd_select_window_exec(struct cmd *self, struct cmdq_item *item) } cmdq_insert_hook(s, item, current, "after-select-window"); } + if (c->session != NULL) + s->curw->window->latest = c; recalculate_sizes(); return (CMD_RETURN_NORMAL); From 574a9e4b6c7a419b60668c30916327aa7e65d2c8 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:50:55 +0000 Subject: [PATCH 49/49] Move lazy resize from the pane to the window, there is no point in resizing the window unless it is the current window, and if we do and don't resize the pane until later there are problems if the size changes from A to B then back to A. --- cmd-set-option.c | 1 - cmd-show-messages.c | 2 +- resize.c | 25 +++++++++++++---- server-client.c | 68 ++++++++++++++++++++++++--------------------- tmux.h | 57 ++++++++++++++++++++++--------------- window.c | 11 +++++--- 6 files changed, 98 insertions(+), 66 deletions(-) diff --git a/cmd-set-option.c b/cmd-set-option.c index c6f83796..36579f29 100644 --- a/cmd-set-option.c +++ b/cmd-set-option.c @@ -18,7 +18,6 @@ #include -#include #include #include diff --git a/cmd-show-messages.c b/cmd-show-messages.c index deb0487c..ef5acf44 100644 --- a/cmd-show-messages.c +++ b/cmd-show-messages.c @@ -27,7 +27,7 @@ #include "tmux.h" /* - * Show client message log. + * Show message log. */ #define SHOW_MESSAGES_TEMPLATE \ diff --git a/resize.c b/resize.c index 15d146d8..68717e35 100644 --- a/resize.c +++ b/resize.c @@ -61,6 +61,7 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) tty_update_window_offset(w); server_redraw_window(w); notify_window("window-layout-changed", w); + w->flags &= ~WINDOW_RESIZE; } static int @@ -346,16 +347,30 @@ recalculate_size(struct window *w) changed = 0; break; } - if (changed && w->sx == sx && w->sy == sy) - changed = 0; + if (w->flags & WINDOW_RESIZE) { + if (changed && w->new_sx == sx && w->new_sy == sy) + changed = 0; + } else { + if (changed && w->sx == sx && w->sy == sy) + changed = 0; + } if (!changed) { tty_update_window_offset(w); return; } - log_debug("%s: @%u changed to %u,%u (%ux%u)", __func__, w->id, sx, sy, - xpixel, ypixel); - resize_window(w, sx, sy, xpixel, ypixel); + log_debug("%s: @%u new size %u,%u", __func__, w->id, sx, sy); + if (type == WINDOW_SIZE_MANUAL) + resize_window(w, sx, sy, xpixel, ypixel); + else { + w->new_sx = sx; + w->new_sy = sy; + w->new_xpixel = xpixel; + w->new_ypixel = ypixel; + + w->flags |= WINDOW_RESIZE; + tty_update_window_offset(w); + } } void diff --git a/server-client.c b/server-client.c index 3ce393cd..3cd2083d 100644 --- a/server-client.c +++ b/server-client.c @@ -33,8 +33,9 @@ #include "tmux.h" static void server_client_free(int, short, void *); -static void server_client_check_focus(struct window_pane *); -static void server_client_check_resize(struct window_pane *); +static void server_client_check_pane_focus(struct window_pane *); +static void server_client_check_pane_resize(struct window_pane *); +static void server_client_check_window_resize(struct window *); static key_code server_client_check_mouse(struct client *, struct key_event *); static void server_client_repeat_timer(int, short, void *); static void server_client_click_timer(int, short, void *); @@ -1035,14 +1036,14 @@ have_event: out: /* Apply modifiers if any. */ if (b & MOUSE_MASK_META) - key |= KEYC_ESCAPE; + key |= KEYC_META; if (b & MOUSE_MASK_CTRL) key |= KEYC_CTRL; if (b & MOUSE_MASK_SHIFT) key |= KEYC_SHIFT; if (log_get_level() != 0) - log_debug("mouse key is %s", key_string_lookup_key (key)); + log_debug("mouse key is %s", key_string_lookup_key (key, 1)); return (key); } @@ -1174,7 +1175,7 @@ table_changed: * The prefix always takes precedence and forces a switch to the prefix * table, unless we are already there. */ - key0 = (key & ~KEYC_XTERM); + key0 = (key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)); if ((key0 == (key_code)options_get_number(s->options, "prefix") || key0 == (key_code)options_get_number(s->options, "prefix2")) && strcmp(table->name, "prefix") != 0) { @@ -1341,10 +1342,13 @@ server_client_loop(void) struct client *c; struct window *w; struct window_pane *wp; - struct winlink *wl; - struct session *s; - int focus, attached, resize; + int focus; + /* Check for window resize. This is done before redrawing. */ + RB_FOREACH(w, windows, &windows) + server_client_check_window_resize(w); + + /* Check clients. */ TAILQ_FOREACH(c, &clients, entry) { server_client_check_exit(c); if (c->session != NULL) { @@ -1356,34 +1360,14 @@ server_client_loop(void) /* * Any windows will have been redrawn as part of clients, so clear * their flags now. Also check pane focus and resize. - * - * As an optimization, panes in windows that are in an attached session - * but not the current window are not resized (this reduces the amount - * of work needed when, for example, resizing an X terminal a - * lot). Windows in no attached session are resized immediately since - * that is likely to have come from a command like split-window and be - * what the user wanted. */ focus = options_get_number(global_options, "focus-events"); RB_FOREACH(w, windows, &windows) { - attached = resize = 0; - TAILQ_FOREACH(wl, &w->winlinks, wentry) { - s = wl->session; - if (s->attached != 0) - attached = 1; - if (s->attached != 0 && s->curw == wl) { - resize = 1; - break; - } - } - if (!attached) - resize = 1; TAILQ_FOREACH(wp, &w->panes, entry) { if (wp->fd != -1) { if (focus) - server_client_check_focus(wp); - if (resize) - server_client_check_resize(wp); + server_client_check_pane_focus(wp); + server_client_check_pane_resize(wp); } wp->flags &= ~PANE_REDRAW; } @@ -1391,6 +1375,26 @@ server_client_loop(void) } } +/* Check if window needs to be resized. */ +static void +server_client_check_window_resize(struct window *w) +{ + struct winlink *wl; + + if (~w->flags & WINDOW_RESIZE) + return; + + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (wl->session->attached != 0 && wl->session->curw == wl) + break; + } + if (wl == NULL) + return; + + log_debug("%s: resizing window @%u", __func__, w->id); + resize_window(w, w->new_sx, w->new_sy, w->new_xpixel, w->new_ypixel); +} + /* Check if we need to force a resize. */ static int server_client_resize_force(struct window_pane *wp) @@ -1472,7 +1476,7 @@ server_client_resize_event(__unused int fd, __unused short events, void *data) /* Check if pane should be resized. */ static void -server_client_check_resize(struct window_pane *wp) +server_client_check_pane_resize(struct window_pane *wp) { if (~wp->flags & PANE_RESIZE) return; @@ -1490,7 +1494,7 @@ server_client_check_resize(struct window_pane *wp) /* Check whether pane should be focused. */ static void -server_client_check_focus(struct window_pane *wp) +server_client_check_pane_focus(struct window_pane *wp) { struct client *c; int push; diff --git a/tmux.h b/tmux.h index a413e0cf..cdc57474 100644 --- a/tmux.h +++ b/tmux.h @@ -112,25 +112,31 @@ struct winlink; #define VISUAL_BOTH 2 /* Special key codes. */ -#define KEYC_NONE 0x00ff000000000ULL -#define KEYC_UNKNOWN 0x00fe000000000ULL -#define KEYC_BASE 0x0001000000000ULL -#define KEYC_USER 0x0002000000000ULL +#define KEYC_NONE 0x00ff000000000ULL +#define KEYC_UNKNOWN 0x00fe000000000ULL +#define KEYC_BASE 0x0001000000000ULL +#define KEYC_USER 0x0002000000000ULL /* Key modifier bits. */ -#define KEYC_ESCAPE 0x0100000000000ULL -#define KEYC_CTRL 0x0200000000000ULL -#define KEYC_SHIFT 0x0400000000000ULL -#define KEYC_XTERM 0x0800000000000ULL -#define KEYC_LITERAL 0x1000000000000ULL +#define KEYC_META 0x00100000000000ULL +#define KEYC_CTRL 0x00200000000000ULL +#define KEYC_SHIFT 0x00400000000000ULL + +/* Key flag bits. */ +#define KEYC_LITERAL 0x01000000000000ULL +#define KEYC_KEYPAD 0x02000000000000ULL +#define KEYC_CURSOR 0x04000000000000ULL +#define KEYC_IMPLIED_META 0x08000000000000ULL +#define KEYC_BUILD_MODIFIERS 0x10000000000000ULL + +/* Masks for key bits. */ +#define KEYC_MASK_MODIFIERS 0x00f00000000000ULL +#define KEYC_MASK_FLAGS 0xff000000000000ULL +#define KEYC_MASK_KEY 0x000fffffffffffULL /* Available user keys. */ #define KEYC_NUSER 1000 -/* Mask to obtain key w/o modifiers. */ -#define KEYC_MASK_MOD 0xff00000000000ULL -#define KEYC_MASK_KEY 0x00fffffffffffULL - /* Is this a mouse key? */ #define KEYC_IS_MOUSE(key) (((key) & KEYC_MASK_KEY) >= KEYC_MOUSE && \ ((key) & KEYC_MASK_KEY) < KEYC_BSPACE) @@ -284,6 +290,7 @@ enum tty_code_code { TTYC_DL, TTYC_DL1, TTYC_DSBP, + TTYC_DSEKS, TTYC_DSFCS, TTYC_DSMG, TTYC_E3, @@ -293,6 +300,7 @@ enum tty_code_code { TTYC_EL1, TTYC_ENACS, TTYC_ENBP, + TTYC_ENEKS, TTYC_ENFCS, TTYC_ENMG, TTYC_FSL, @@ -569,8 +577,8 @@ struct msg_write_close { #define MODE_CURSOR 0x1 #define MODE_INSERT 0x2 #define MODE_KCURSOR 0x4 -#define MODE_KKEYPAD 0x8 /* set = application, clear = number */ -#define MODE_WRAP 0x10 /* whether lines wrap */ +#define MODE_KKEYPAD 0x8 +#define MODE_WRAP 0x10 #define MODE_MOUSE_STANDARD 0x20 #define MODE_MOUSE_BUTTON 0x40 #define MODE_BLINKING 0x80 @@ -581,6 +589,7 @@ struct msg_write_close { #define MODE_MOUSE_ALL 0x1000 #define MODE_ORIGIN 0x2000 #define MODE_CRLF 0x4000 +#define MODE_KEXTENDED 0x8000 #define ALL_MODES 0xffffff #define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) @@ -1001,12 +1010,18 @@ struct window { u_int xpixel; u_int ypixel; + u_int new_sx; + u_int new_sy; + u_int new_xpixel; + u_int new_ypixel; + int flags; #define WINDOW_BELL 0x1 #define WINDOW_ACTIVITY 0x2 #define WINDOW_SILENCE 0x4 #define WINDOW_ZOOMED 0x8 #define WINDOW_WASZOOMED 0x10 +#define WINDOW_RESIZE 0x20 #define WINDOW_ALERTFLAGS (WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_SILENCE) int alerts_queued; @@ -1278,7 +1293,7 @@ struct tty { /* 0x8 unused */ #define TTY_STARTED 0x10 #define TTY_OPENED 0x20 -#define TTY_FOCUS 0x40 +/* 0x40 unused */ #define TTY_BLOCK 0x80 #define TTY_HAVEDA 0x100 #define TTY_HAVEXDA 0x200 @@ -2282,7 +2297,7 @@ struct cmdq_item *key_bindings_dispatch(struct key_binding *, /* key-string.c */ key_code key_string_lookup_string(const char *); -const char *key_string_lookup_key(key_code); +const char *key_string_lookup_key(key_code, int); /* alerts.c */ void alerts_reset_all(void); @@ -2415,16 +2430,12 @@ void input_parse_screen(struct input_ctx *, struct screen *, screen_write_init_ctx_cb, void *, u_char *, size_t); /* input-key.c */ +void input_key_build(void); int input_key_pane(struct window_pane *, key_code, struct mouse_event *); -int input_key(struct window_pane *, struct screen *, struct bufferevent *, - key_code); +int input_key(struct screen *, struct bufferevent *, key_code); int input_key_get_mouse(struct screen *, struct mouse_event *, u_int, u_int, const char **, size_t *); -/* xterm-keys.c */ -char *xterm_keys_lookup(key_code); -int xterm_keys_find(const char *, size_t, size_t *, key_code *); - /* colour.c */ int colour_find_rgb(u_char, u_char, u_char); int colour_join_rgb(u_char, u_char, u_char); diff --git a/window.c b/window.c index b28a2257..2427bd6e 100644 --- a/window.c +++ b/window.c @@ -440,13 +440,15 @@ window_pane_send_resize(struct window_pane *wp, int yadjust) { struct window *w = wp->window; struct winsize ws; + u_int sy = wp->sy + yadjust; if (wp->fd == -1) return; + log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, wp->sx, sy); memset(&ws, 0, sizeof ws); ws.ws_col = wp->sx; - ws.ws_row = wp->sy + yadjust; + ws.ws_row = sy; ws.ws_xpixel = w->xpixel * ws.ws_col; ws.ws_ypixel = w->ypixel * ws.ws_row; if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) @@ -991,7 +993,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); - wp->flags |= (PANE_RESIZE|PANE_RESIZED); } @@ -1135,8 +1136,10 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s, wme = TAILQ_FIRST(&wp->modes); if (wme != NULL) { - if (wme->mode->key != NULL && c != NULL) - wme->mode->key(wme, c, s, wl, (key & ~KEYC_XTERM), m); + if (wme->mode->key != NULL && c != NULL) { + key &= ~KEYC_MASK_FLAGS; + wme->mode->key(wme, c, s, wl, key, m); + } return (0); }