diff --git a/CHANGES b/CHANGES index cd3a1b8a..d6806e49 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,32 @@ CHANGES FROM 3.0 to X.X +* Add support for adding a note to a key binding (with bind-key -N) and use + this to add descriptions to the default key bindings. A new -N flag to + list-keys shows key bindings with notes. Change the default ? binding to use + this to show a readable summary of keys. Also extend command-prompt to return + the name of the key pressed and add a default binding (/) to show the note + for the next key pressed. + +* Add support for the iTerm2 DSR 1337 sequence to get the terminal version. + +* Treat plausible but invalid keys (like C-BSpace) as literal like any other + unrecognised string passed to send-keys + +* Detect iTerm2 and enable use of DECSLRM (much faster with horizontally split + windows). + +* Add -Z to default switch-client command in tree mode. + +* Add ~ to quoted characters for %%%. + +* Document client exit messages in the manual page. + +* Do not let read-only clients limit the size, unless all clients are + read-only. + +* Add a number of new formats to inspect what sessions and clients a window is + present or active in. + * Change file reading and writing to go through the client if necessary. This fixes commands like "tmux loadb /dev/fd/X". Also modify source-file to support "-" for standard input, like load-buffer and save-buffer. diff --git a/Makefile.am b/Makefile.am index 6902477e..082a467c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -11,8 +11,9 @@ EXTRA_DIST = \ dist_EXTRA_tmux_SOURCES = compat/*.[ch] # Preprocessor flags. -AM_CPPFLAGS += @XOPEN_DEFINES@ -AM_CPPFLAGS += -DTMUX_CONF="\"$(sysconfdir)/tmux.conf:~/.tmux.conf:~/.config/tmux/tmux.conf\"" +AM_CPPFLAGS += @XOPEN_DEFINES@ \ + -DTMUX_VERSION="\"@VERSION@\"" \ + -DTMUX_CONF="\"$(sysconfdir)/tmux.conf:~/.tmux.conf:~/.config/tmux/tmux.conf\"" # Additional object files. LDADD = $(LIBOBJS) diff --git a/cfg.c b/cfg.c index ddc112a2..933eda4e 100644 --- a/cfg.c +++ b/cfg.c @@ -184,9 +184,9 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags, new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); if (item != NULL) - cmdq_insert_after(item, new_item0); + new_item0 = cmdq_insert_after(item, new_item0); else - cmdq_append(NULL, new_item0); + new_item0 = cmdq_append(NULL, new_item0); cmd_list_free(pr->cmdlist); if (new_item != NULL) @@ -230,9 +230,9 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path, new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); if (item != NULL) - cmdq_insert_after(item, new_item0); + new_item0 = cmdq_insert_after(item, new_item0); else - cmdq_append(NULL, new_item0); + new_item0 = cmdq_append(NULL, new_item0); cmd_list_free(pr->cmdlist); if (new_item != NULL) diff --git a/client.c b/client.c index 26c392b1..91e084a5 100644 --- a/client.c +++ b/client.c @@ -865,6 +865,12 @@ client_dispatch_wait(struct imsg *imsg) case MSG_WRITE_CLOSE: client_write_close(data, datalen); break; + case MSG_OLDSTDERR: + case MSG_OLDSTDIN: + case MSG_OLDSTDOUT: + fprintf(stderr, "server version is too old for client\n"); + proc_exit(client_proc); + break; } } diff --git a/cmd-bind-key.c b/cmd-bind-key.c index 2af15d29..27f75dd0 100644 --- a/cmd-bind-key.c +++ b/cmd-bind-key.c @@ -33,8 +33,8 @@ const struct cmd_entry cmd_bind_key_entry = { .name = "bind-key", .alias = "bind", - .args = { "cnrT:", 2, -1 }, - .usage = "[-cnr] [-T key-table] key " + .args = { "cnrN:T:", 2, -1 }, + .usage = "[-cnr] [-T key-table] [-N note] key " "command [arguments]", .flags = CMD_AFTERHOOK, @@ -46,10 +46,10 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; key_code key; - const char *tablename; + const char *tablename, *note; struct cmd_parse_result *pr; char **argv = args->argv; - int argc = args->argc; + int argc = args->argc, repeat; key = key_string_lookup_string(argv[0]); if (key == KEYC_NONE || key == KEYC_UNKNOWN) { @@ -63,6 +63,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item) tablename = "root"; else tablename = "prefix"; + repeat = args_has(args, 'r'); if (argc == 2) pr = cmd_parse_from_string(argv[1], NULL); @@ -79,6 +80,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item) case CMD_PARSE_SUCCESS: break; } - key_bindings_add(tablename, key, args_has(args, 'r'), pr->cmdlist); + note = args_get(args, 'N'); + key_bindings_add(tablename, key, note, repeat, pr->cmdlist); return (CMD_RETURN_NORMAL); } diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index 603ddb0a..9f0ea19f 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 = { "1iI:Np:t:", 0, 1 }, - .usage = "[-1Ni] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " " + .args = { "1kiI:Np:t:", 0, 1 }, + .usage = "[-1kiN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " " "[template]", .flags = 0, @@ -122,6 +122,8 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) cdata->flags |= PROMPT_NUMERIC; else if (args_has(args, 'i')) cdata->flags |= PROMPT_INCREMENTAL; + else if (args_has(args, 'k')) + cdata->flags |= PROMPT_KEY; status_prompt_set(c, prompt, input, cmd_command_prompt_callback, cmd_command_prompt_free, cdata, cdata->flags); free(prompt); diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 67207291..4b767e3e 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -52,7 +52,7 @@ const struct cmd_entry cmd_move_pane_entry = { .args = { "bdhvp:l:s:t:", 0, 0 }, .usage = "[-bdhv] [-p percentage|-l size] " CMD_SRCDST_PANE_USAGE, - .source = { 's', CMD_FIND_PANE, 0 }, + .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED }, .target = { 't', CMD_FIND_PANE, 0 }, .flags = 0, diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 8636b70a..e9e75a72 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -36,8 +36,8 @@ const struct cmd_entry cmd_list_keys_entry = { .name = "list-keys", .alias = "lsk", - .args = { "T:", 0, 0 }, - .usage = "[-T key-table]", + .args = { "1NP:T:", 0, 1 }, + .usage = "[-1N] [-P prefix-string] [-T key-table] [key]", .flags = CMD_STARTSERVER|CMD_AFTERHOOK, .exec = cmd_list_keys_exec @@ -54,6 +54,88 @@ const struct cmd_entry cmd_list_commands_entry = { .exec = cmd_list_keys_exec }; +static u_int +cmd_list_keys_get_width(const char *tablename, key_code only) +{ + struct key_table *table; + struct key_binding *bd; + u_int width, keywidth = 0; + + table = key_bindings_get_table(tablename, 0); + if (table == NULL) + return (0); + bd = key_bindings_first(table); + while (bd != NULL) { + if ((only != KEYC_UNKNOWN && bd->key != only) || + KEYC_IS_MOUSE(bd->key) || + bd->note == NULL) { + bd = key_bindings_next(table, bd); + continue; + } + width = utf8_cstrwidth(key_string_lookup_key(bd->key)); + if (width > keywidth) + keywidth = width; + + bd = key_bindings_next(table, bd); + } + return (keywidth); +} + +static int +cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args, + const char *tablename, u_int keywidth, key_code only, const char *prefix) +{ + struct client *c = cmd_find_client(item, NULL, 1); + struct key_table *table; + struct key_binding *bd; + const char *key; + char *tmp; + int found = 0; + + table = key_bindings_get_table(tablename, 0); + if (table == NULL) + return (0); + bd = key_bindings_first(table); + while (bd != NULL) { + if ((only != KEYC_UNKNOWN && bd->key != only) || + KEYC_IS_MOUSE(bd->key) || + bd->note == NULL) { + bd = key_bindings_next(table, bd); + continue; + } + found = 1; + key = key_string_lookup_key(bd->key); + + tmp = utf8_padcstr(key, keywidth + 1); + if (args_has(args, '1') && c != NULL) + status_message_set(c, "%s%s%s", prefix, tmp, bd->note); + else + cmdq_print(item, "%s%s%s", prefix, tmp, bd->note); + free(tmp); + + if (args_has(args, '1')) + break; + bd = key_bindings_next(table, bd); + } + return (found); +} + +static char * +cmd_list_keys_get_prefix(struct args *args, key_code *prefix) +{ + char *s; + + *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)); + else + s = xstrdup(""); + } else + s = xstrdup(args_get(args, 'P')); + return (s); +} + static enum cmd_retval cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) { @@ -61,19 +143,63 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) struct key_table *table; struct key_binding *bd; const char *tablename, *r; - char *key, *cp, *tmp; - int repeat, width, tablewidth, keywidth; + char *key, *cp, *tmp, *start, *empty; + key_code prefix, only = KEYC_UNKNOWN; + int repeat, width, tablewidth, keywidth, found = 0; size_t tmpsize, tmpused, cplen; if (self->entry == &cmd_list_commands_entry) return (cmd_list_keys_commands(self, item)); + if (args->argc != 0) { + only = key_string_lookup_string(args->argv[0]); + if (only == KEYC_UNKNOWN) { + cmdq_error(item, "invalid key: %s", args->argv[0]); + return (CMD_RETURN_ERROR); + } + } + tablename = args_get(args, 'T'); if (tablename != NULL && key_bindings_get_table(tablename, 0) == NULL) { cmdq_error(item, "table %s doesn't exist", tablename); return (CMD_RETURN_ERROR); } + if (args_has(args, 'N')) { + if (tablename == NULL) { + start = cmd_list_keys_get_prefix(args, &prefix); + keywidth = cmd_list_keys_get_width("root", only); + if (prefix != KEYC_NONE) { + width = cmd_list_keys_get_width("prefix", only); + if (width == 0) + prefix = KEYC_NONE; + else if (width > keywidth) + keywidth = width; + } + empty = utf8_padcstr("", utf8_cstrwidth(start)); + + found = cmd_list_keys_print_notes(item, args, "root", + keywidth, only, empty); + if (prefix != KEYC_NONE) { + if (cmd_list_keys_print_notes(item, args, + "prefix", keywidth, only, start)) + found = 1; + } + free(empty); + } else { + if (args_has(args, 'P')) + start = xstrdup(args_get(args, 'P')); + else + start = xstrdup(""); + keywidth = cmd_list_keys_get_width(tablename, only); + found = cmd_list_keys_print_notes(item, args, tablename, + keywidth, only, start); + + } + free(start); + goto out; + } + repeat = 0; tablewidth = keywidth = 0; table = key_bindings_first_table (); @@ -84,6 +210,10 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) } bd = key_bindings_first(table); while (bd != NULL) { + if (only != KEYC_UNKNOWN && bd->key != only) { + bd = key_bindings_next(table, bd); + continue; + } key = args_escape(key_string_lookup_key(bd->key)); if (bd->flags & KEY_BINDING_REPEAT) @@ -113,6 +243,11 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) } bd = key_bindings_first(table); while (bd != NULL) { + if (only != KEYC_UNKNOWN && bd->key != only) { + bd = key_bindings_next(table, bd); + continue; + } + found = 1; key = args_escape(key_string_lookup_key(bd->key)); if (!repeat) @@ -162,13 +297,18 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) free(tmp); +out: + if (only != KEYC_UNKNOWN && !found) { + cmdq_error(item, "unknown key: %s", args->argv[0]); + return (CMD_RETURN_ERROR); + } return (CMD_RETURN_NORMAL); } static enum cmd_retval cmd_list_keys_commands(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = self->args; const struct cmd_entry **entryp; const struct cmd_entry *entry; struct format_tree *ft; diff --git a/cmd-parse.y b/cmd-parse.y index 97b50f57..2375370b 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -133,7 +133,12 @@ statements : statement '\n' free($2); } -statement : condition +statement : /* empty */ + { + $$ = xmalloc (sizeof *$$); + TAILQ_INIT($$); + } + | condition { struct cmd_parse_state *ps = &parse_state; @@ -144,11 +149,6 @@ statement : condition cmd_parse_free_commands($1); } } - | assignment - { - $$ = xmalloc (sizeof *$$); - TAILQ_INIT($$); - } | commands { struct cmd_parse_state *ps = &parse_state; @@ -194,8 +194,10 @@ expanded : format free($1); } -assignment : /* empty */ - | EQUALS +optional_assignment : /* empty */ + | assignment + +assignment : EQUALS { struct cmd_parse_state *ps = &parse_state; int flags = ps->input->flags; @@ -339,7 +341,8 @@ commands : command struct cmd_parse_state *ps = &parse_state; $$ = cmd_parse_new_commands(); - if (ps->scope == NULL || ps->scope->flag) + if ($1->name != NULL && + (ps->scope == NULL || ps->scope->flag)) TAILQ_INSERT_TAIL($$, $1, entry); else cmd_parse_free_command($1); @@ -358,7 +361,8 @@ commands : command { struct cmd_parse_state *ps = &parse_state; - if (ps->scope == NULL || ps->scope->flag) { + if ($3->name != NULL && + (ps->scope == NULL || ps->scope->flag)) { $$ = $1; TAILQ_INSERT_TAIL($$, $3, entry); } else { @@ -372,7 +376,15 @@ commands : command $$ = $1; } -command : assignment TOKEN +command : assignment + { + struct cmd_parse_state *ps = &parse_state; + + $$ = xcalloc(1, sizeof *$$); + $$->name = NULL; + $$->line = ps->input->line; + } + | optional_assignment TOKEN { struct cmd_parse_state *ps = &parse_state; @@ -381,7 +393,7 @@ command : assignment TOKEN $$->line = ps->input->line; } - | assignment TOKEN arguments + | optional_assignment TOKEN arguments { struct cmd_parse_state *ps = &parse_state; diff --git a/cmd-queue.c b/cmd-queue.c index 69e4f6b2..59c7a35c 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -53,12 +53,16 @@ cmdq_get(struct client *c) } /* Append an item. */ -void +struct cmdq_item * cmdq_append(struct client *c, struct cmdq_item *item) { struct cmdq_list *queue = cmdq_get(c); struct cmdq_item *next; + TAILQ_FOREACH(next, queue, entry) { + log_debug("%s %s: queue %s (%u)", __func__, cmdq_name(c), + next->name, next->group); + } do { next = item->next; item->next = NULL; @@ -73,16 +77,21 @@ cmdq_append(struct client *c, struct cmdq_item *item) item = next; } while (item != NULL); + return (TAILQ_LAST(queue, cmdq_list)); } /* Insert an item. */ -void +struct cmdq_item * cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) { struct client *c = after->client; struct cmdq_list *queue = after->queue; struct cmdq_item *next; + TAILQ_FOREACH(next, queue, entry) { + log_debug("%s %s: queue %s (%u)", __func__, cmdq_name(c), + next->name, next->group); + } do { next = item->next; item->next = after->next; @@ -100,6 +109,7 @@ cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) after = item; item = next; } while (item != NULL); + return (after); } /* Insert a hook. */ @@ -143,11 +153,10 @@ cmdq_insert_hook(struct session *s, struct cmdq_item *item, new_item = cmdq_get_command(cmdlist, fs, NULL, CMDQ_NOHOOKS); cmdq_format(new_item, "hook", "%s", name); - if (item != NULL) { - cmdq_insert_after(item, new_item); - item = new_item; - } else - cmdq_append(NULL, new_item); + if (item != NULL) + item = cmdq_insert_after(item, new_item); + else + item = cmdq_append(NULL, new_item); a = options_array_next(a); } @@ -542,7 +551,10 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...) msg = utf8_sanitize(tmp); free(tmp); } - file_error(c, "%s\n", msg); + if (c->flags & CLIENT_CONTROL) + file_print(c, "%s\n", msg); + else + file_error(c, "%s\n", msg); c->retval = 1; } else { *msg = toupper((u_char) *msg); diff --git a/cmd-send-keys.c b/cmd-send-keys.c index ddbab6f7..cc04a73f 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -60,6 +60,9 @@ static struct cmdq_item * cmd_send_keys_inject_key(struct client *c, struct cmd_find_state *fs, struct cmdq_item *item, key_code key) { + struct session *s = fs->s; + struct winlink *wl = fs->wl; + struct window_pane *wp = fs->wp; struct window_mode_entry *wme; struct key_table *table; struct key_binding *bd; @@ -68,7 +71,8 @@ cmd_send_keys_inject_key(struct client *c, struct cmd_find_state *fs, if (wme == NULL || wme->mode->key_table == NULL) { if (options_get_number(fs->wp->window->options, "xterm-keys")) key |= KEYC_XTERM; - window_pane_key(fs->wp, item->client, fs->s, fs->wl, key, NULL); + if (window_pane_key(wp, item->client, s, wl, key, NULL) != 0) + return (NULL); return (item); } table = key_bindings_get_table(wme->mode->key_table(wme), 1); @@ -87,6 +91,7 @@ cmd_send_keys_inject_string(struct client *c, struct cmd_find_state *fs, struct cmdq_item *item, struct args *args, int i) { const char *s = args->argv[i]; + struct cmdq_item *new_item; struct utf8_data *ud, *uc; wchar_t wc; key_code key; @@ -104,8 +109,11 @@ cmd_send_keys_inject_string(struct client *c, struct cmd_find_state *fs, literal = args_has(args, 'l'); if (!literal) { key = key_string_lookup_string(s); - if (key != KEYC_NONE && key != KEYC_UNKNOWN) - return (cmd_send_keys_inject_key(c, fs, item, key)); + if (key != KEYC_NONE && key != KEYC_UNKNOWN) { + new_item = cmd_send_keys_inject_key(c, fs, item, key); + if (new_item != NULL) + return (new_item); + } literal = 1; } if (literal) { diff --git a/cmd-source-file.c b/cmd-source-file.c index bdcdd222..afe45a54 100644 --- a/cmd-source-file.c +++ b/cmd-source-file.c @@ -113,6 +113,7 @@ cmd_source_file_done(struct client *c, const char *path, int error, static void cmd_source_file_add(struct cmd_source_file_data *cdata, const char *path) { + log_debug("%s: %s", __func__, path); cdata->files = xreallocarray(cdata->files, cdata->nfiles + 1, sizeof *cdata->files); cdata->files[cdata->nfiles++] = xstrdup(path); @@ -123,7 +124,6 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; struct cmd_source_file_data *cdata; - int flags = 0; struct client *c = item->client; enum cmd_retval retval = CMD_RETURN_NORMAL; char *pattern, *cwd; @@ -159,7 +159,7 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) if ((result = glob(pattern, 0, NULL, &g)) != 0) { if (result != GLOB_NOMATCH || - (~flags & CMD_PARSE_QUIET)) { + (~cdata->flags & CMD_PARSE_QUIET)) { if (result == GLOB_NOMATCH) error = strerror(ENOENT); else if (result == GLOB_NOSPACE) diff --git a/cmd.c b/cmd.c index f77176c9..380eedcf 100644 --- a/cmd.c +++ b/cmd.c @@ -660,7 +660,7 @@ char * cmd_template_replace(const char *template, const char *s, int idx) { char ch, *buf; - const char *ptr, *cp, quote[] = "\"\\$;"; + const char *ptr, *cp, quote[] = "\"\\$;~"; int replaced, quoted; size_t len; diff --git a/compat.h b/compat.h index 794ac67d..70801d0d 100644 --- a/compat.h +++ b/compat.h @@ -171,8 +171,12 @@ void warnx(const char *, ...); #endif #ifndef FNM_CASEFOLD +#ifdef FNM_IGNORECASE +#define FNM_CASEFOLD FNM_IGNORECASE +#else #define FNM_CASEFOLD 0 #endif +#endif #ifndef INFTIM #define INFTIM -1 diff --git a/format-draw.c b/format-draw.c index 6cced9fd..bb16e0dd 100644 --- a/format-draw.c +++ b/format-draw.c @@ -849,8 +849,10 @@ format_trim_left(const char *expanded, u_int limit) out += ud.size; } width += ud.width; - } else + } else { cp -= ud.have; + cp++; + } } else if (*cp > 0x1f && *cp < 0x7f) { if (width + 1 <= limit) *out++ = *cp; @@ -896,8 +898,10 @@ format_trim_right(const char *expanded, u_int limit) out += ud.size; } width += ud.width; - } else + } else { cp -= ud.have; + cp++; + } } else if (*cp > 0x1f && *cp < 0x7f) { if (width >= skip) *out++ = *cp; diff --git a/format.c b/format.c index ee507340..0d3e26d7 100644 --- a/format.c +++ b/format.c @@ -456,6 +456,35 @@ format_cb_pid(__unused struct format_tree *ft, struct format_entry *fe) xasprintf(&fe->value, "%ld", (long)getpid()); } +/* Callback for session_attached_list. */ +static void +format_cb_session_attached_list(struct format_tree *ft, struct format_entry *fe) +{ + struct session *s = ft->s; + struct client *loop; + struct evbuffer *buffer; + int size; + + if (s == NULL) + return; + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->session == s) { + if (EVBUFFER_LENGTH(buffer) > 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%s", loop->name); + } + } + + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + evbuffer_free(buffer); +} + /* Callback for session_alerts. */ static void format_cb_session_alerts(struct format_tree *ft, struct format_entry *fe) @@ -528,6 +557,128 @@ format_cb_window_stack_index(struct format_tree *ft, struct format_entry *fe) fe->value = xstrdup("0"); } +/* Callback for window_linked_sessions_list. */ +static void +format_cb_window_linked_sessions_list(struct format_tree *ft, + struct format_entry *fe) +{ + struct window *w = ft->wl->window; + struct winlink *wl; + struct evbuffer *buffer; + int size; + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (EVBUFFER_LENGTH(buffer) > 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%s", wl->session->name); + } + + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + evbuffer_free(buffer); +} + +/* Callback for window_active_sessions. */ +static void +format_cb_window_active_sessions(struct format_tree *ft, + struct format_entry *fe) +{ + struct window *w = ft->wl->window; + struct winlink *wl; + u_int n = 0; + + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (wl->session->curw == wl) + n++; + } + + xasprintf(&fe->value, "%u", n); +} + +/* Callback for window_active_sessions_list. */ +static void +format_cb_window_active_sessions_list(struct format_tree *ft, + struct format_entry *fe) +{ + struct window *w = ft->wl->window; + struct winlink *wl; + struct evbuffer *buffer; + int size; + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (wl->session->curw == wl) { + if (EVBUFFER_LENGTH(buffer) > 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%s", wl->session->name); + } + } + + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + evbuffer_free(buffer); +} + +/* Callback for window_active_clients. */ +static void +format_cb_window_active_clients(struct format_tree *ft, struct format_entry *fe) +{ + struct window *w = ft->wl->window; + struct client *loop; + struct session *client_session; + u_int n = 0; + + TAILQ_FOREACH(loop, &clients, entry) { + client_session = loop->session; + if (client_session == NULL) + continue; + + if (w == client_session->curw->window) + n++; + } + + xasprintf(&fe->value, "%u", n); +} + +/* Callback for window_active_clients_list. */ +static void +format_cb_window_active_clients_list(struct format_tree *ft, + struct format_entry *fe) +{ + struct window *w = ft->wl->window; + struct client *loop; + struct session *client_session; + struct evbuffer *buffer; + int size; + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + + TAILQ_FOREACH(loop, &clients, entry) { + client_session = loop->session; + if (client_session == NULL) + continue; + + if (w == client_session->curw->window) { + if (EVBUFFER_LENGTH(buffer) > 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%s", loop->name); + } + } + + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + evbuffer_free(buffer); +} + /* Callback for window_layout. */ static void format_cb_window_layout(struct format_tree *ft, struct format_entry *fe) @@ -677,11 +828,52 @@ format_cb_session_group_list(struct format_tree *ft, struct format_entry *fe) buffer = evbuffer_new(); if (buffer == NULL) fatalx("out of memory"); + TAILQ_FOREACH(loop, &sg->sessions, gentry) { if (EVBUFFER_LENGTH(buffer) > 0) evbuffer_add(buffer, ",", 1); evbuffer_add_printf(buffer, "%s", loop->name); } + + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + evbuffer_free(buffer); +} + +/* Callback for session_group_attached_list. */ +static void +format_cb_session_group_attached_list(struct format_tree *ft, + struct format_entry *fe) +{ + struct session *s = ft->s, *client_session, *session_loop; + struct session_group *sg; + struct client *loop; + struct evbuffer *buffer; + int size; + + if (s == NULL) + return; + sg = session_group_contains(s); + if (sg == NULL) + return; + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + + TAILQ_FOREACH(loop, &clients, entry) { + client_session = loop->session; + if (client_session == NULL) + continue; + TAILQ_FOREACH(session_loop, &sg->sessions, gentry) { + if (session_loop == client_session){ + if (EVBUFFER_LENGTH(buffer) > 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%s", loop->name); + } + } + } + if ((size = EVBUFFER_LENGTH(buffer)) != 0) xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); evbuffer_free(buffer); @@ -929,7 +1121,7 @@ format_create(struct client *c, struct cmdq_item *item, int tag, int flags) ft->flags = flags; ft->time = time(NULL); - format_add(ft, "version", "%s", VERSION); + format_add(ft, "version", "%s", getversion()); format_add_cb(ft, "host", format_cb_host); format_add_cb(ft, "host_short", format_cb_host_short); format_add_cb(ft, "pid", format_cb_pid); @@ -974,12 +1166,12 @@ format_each(struct format_tree *ft, void (*cb)(const char *, const char *, void *), void *arg) { struct format_entry *fe; - static char s[64]; + char s[64]; RB_FOREACH(fe, format_entry_tree, &ft->tree) { if (fe->t != 0) { xsnprintf(s, sizeof s, "%lld", (long long)fe->t); - cb(fe->key, fe->value, s); + cb(fe->key, s, arg); } else { if (fe->value == NULL && fe->cb != NULL) { fe->cb(ft, fe); @@ -1022,8 +1214,7 @@ format_add(struct format_tree *ft, const char *key, const char *fmt, ...) static void format_add_tv(struct format_tree *ft, const char *key, struct timeval *tv) { - struct format_entry *fe; - struct format_entry *fe_now; + struct format_entry *fe, *fe_now; fe = xmalloc(sizeof *fe); fe->key = xstrdup(key); @@ -2141,8 +2332,14 @@ format_defaults_session(struct format_tree *ft, struct session *s) format_add(ft, "session_group", "%s", sg->name); format_add(ft, "session_group_size", "%u", session_group_count (sg)); + format_add(ft, "session_group_attached", "%u", + session_group_attached_count (sg)); + format_add(ft, "session_group_many_attached", "%u", + session_group_attached_count (sg) > 1); format_add_cb(ft, "session_group_list", format_cb_session_group_list); + format_add_cb(ft, "session_group_attached_list", + format_cb_session_group_attached_list); } format_add_tv(ft, "session_created", &s->creation_time); @@ -2151,6 +2348,8 @@ format_defaults_session(struct format_tree *ft, struct session *s) format_add(ft, "session_attached", "%u", s->attached); format_add(ft, "session_many_attached", "%d", s->attached > 1); + format_add_cb(ft, "session_attached_list", + format_cb_session_attached_list); format_add_cb(ft, "session_alerts", format_cb_session_alerts); format_add_cb(ft, "session_stack", format_cb_session_stack); @@ -2163,7 +2362,6 @@ format_defaults_client(struct format_tree *ft, struct client *c) struct session *s; const char *name; struct tty *tty = &c->tty; - const char *types[] = TTY_TYPES; if (ft->s == NULL) ft->s = c->session; @@ -2181,8 +2379,6 @@ format_defaults_client(struct format_tree *ft, struct client *c) if (tty->term_name != NULL) format_add(ft, "client_termname", "%s", tty->term_name); - if (tty->term_name != NULL) - format_add(ft, "client_termtype", "%s", types[tty->term_type]); format_add_tv(ft, "client_created", &c->creation_time); format_add_tv(ft, "client_activity", &c->activity_time); @@ -2265,6 +2461,14 @@ format_defaults_winlink(struct format_tree *ft, struct winlink *wl) format_add_cb(ft, "window_stack_index", format_cb_window_stack_index); format_add(ft, "window_flags", "%s", window_printable_flags(wl)); format_add(ft, "window_active", "%d", wl == s->curw); + format_add_cb(ft, "window_active_sessions", + format_cb_window_active_sessions); + format_add_cb(ft, "window_active_sessions_list", + format_cb_window_active_sessions_list); + format_add_cb(ft, "window_active_clients", + format_cb_window_active_clients); + format_add_cb(ft, "window_active_clients_list", + format_cb_window_active_clients_list); format_add(ft, "window_start_flag", "%d", !!(wl == RB_MIN(winlinks, &s->windows))); @@ -2285,6 +2489,11 @@ format_defaults_winlink(struct format_tree *ft, struct winlink *wl) format_add(ft, "window_last_flag", "%d", !!(wl == TAILQ_FIRST(&s->lastw))); format_add(ft, "window_linked", "%d", session_is_linked(s, wl->window)); + + format_add_cb(ft, "window_linked_sessions_list", + format_cb_window_linked_sessions_list); + format_add(ft, "window_linked_sessions", "%u", + wl->window->references); } /* Set default format keys for a window pane. */ diff --git a/input-keys.c b/input-keys.c index b0ea5104..69c5199e 100644 --- a/input-keys.c +++ b/input-keys.c @@ -149,7 +149,7 @@ input_split2(u_int c, u_char *dst) } /* Translate a key code into an output key sequence. */ -void +int input_key(struct window_pane *wp, key_code key, struct mouse_event *m) { const struct input_key_ent *ike; @@ -166,14 +166,14 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) if (KEYC_IS_MOUSE(key)) { if (m != NULL && m->wp != -1 && (u_int)m->wp == wp->id) input_key_mouse(wp, m); - return; + return (0); } /* Literal keys go as themselves (can't be more than eight bits). */ if (key & KEYC_LITERAL) { ud.data[0] = (u_char)key; bufferevent_write(wp->event, &ud.data[0], 1); - return; + return (0); } /* Is this backspace? */ @@ -194,15 +194,15 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) bufferevent_write(wp->event, "\033", 1); ud.data[0] = justkey; bufferevent_write(wp->event, &ud.data[0], 1); - return; + return (0); } if (justkey > 0x7f && justkey < KEYC_BASE) { if (utf8_split(justkey, &ud) != UTF8_DONE) - return; + return (-1); if (key & KEYC_ESCAPE) bufferevent_write(wp->event, "\033", 1); bufferevent_write(wp->event, ud.data, ud.size); - return; + return (0); } /* @@ -213,7 +213,7 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) if ((out = xterm_keys_lookup(key)) != NULL) { bufferevent_write(wp->event, out, strlen(out)); free(out); - return; + return (0); } } key &= ~KEYC_XTERM; @@ -236,7 +236,7 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) } if (i == nitems(input_keys)) { log_debug("key 0x%llx missing", key); - return; + return (-1); } dlen = strlen(ike->data); log_debug("found key 0x%llx: \"%s\"", key, ike->data); @@ -245,6 +245,7 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) if (key & KEYC_ESCAPE) bufferevent_write(wp->event, "\033", 1); bufferevent_write(wp->event, ike->data, dlen); + return (0); } /* Translate mouse and output. */ diff --git a/input.c b/input.c index b8d2f102..6cb67f06 100644 --- a/input.c +++ b/input.c @@ -20,6 +20,7 @@ #include +#include #include #include #include @@ -772,6 +773,7 @@ input_save_state(struct input_ctx *ictx) ictx->old_mode = s->mode; } +/* Restore screen state. */ static void input_restore_state(struct input_ctx *ictx) { @@ -1301,6 +1303,7 @@ 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); @@ -1432,6 +1435,13 @@ 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; diff --git a/key-bindings.c b/key-bindings.c index d4fada6a..4387c011 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -85,6 +85,15 @@ key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2) return (0); } +static void +key_bindings_free(struct key_table *table, struct key_binding *bd) +{ + RB_REMOVE(key_bindings, &table->key_bindings, bd); + cmd_list_free(bd->cmdlist); + free((void *)bd->note); + free(bd); +} + struct key_table * key_bindings_get_table(const char *name, int create) { @@ -126,11 +135,8 @@ key_bindings_unref_table(struct key_table *table) if (--table->references != 0) return; - RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) { - RB_REMOVE(key_bindings, &table->key_bindings, bd); - cmd_list_free(bd->cmdlist); - free(bd); - } + RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) + key_bindings_free(table, bd); free((void *)table->name); free(table); @@ -158,24 +164,22 @@ key_bindings_next(__unused struct key_table *table, struct key_binding *bd) } void -key_bindings_add(const char *name, key_code key, int repeat, +key_bindings_add(const char *name, key_code key, const char *note, int repeat, struct cmd_list *cmdlist) { struct key_table *table; - struct key_binding bd_find, *bd; + struct key_binding *bd; table = key_bindings_get_table(name, 1); - bd_find.key = (key & ~KEYC_XTERM); - bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find); - if (bd != NULL) { - RB_REMOVE(key_bindings, &table->key_bindings, bd); - cmd_list_free(bd->cmdlist); - free(bd); - } + bd = key_bindings_get(table, key & ~KEYC_XTERM); + if (bd != NULL) + key_bindings_free(table, bd); bd = xcalloc(1, sizeof *bd); bd->key = key; + if (note != NULL) + bd->note = xstrdup(note); RB_INSERT(key_bindings, &table->key_bindings, bd); if (repeat) @@ -187,20 +191,16 @@ void key_bindings_remove(const char *name, key_code key) { struct key_table *table; - struct key_binding bd_find, *bd; + struct key_binding *bd; table = key_bindings_get_table(name, 0); if (table == NULL) return; - bd_find.key = (key & ~KEYC_XTERM); - bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find); + bd = key_bindings_get(table, key & ~KEYC_XTERM); if (bd == NULL) return; - - RB_REMOVE(key_bindings, &table->key_bindings, bd); - cmd_list_free(bd->cmdlist); - free(bd); + key_bindings_free(table, bd); if (RB_EMPTY(&table->key_bindings)) { RB_REMOVE(key_tables, &key_tables, table); @@ -229,87 +229,88 @@ void key_bindings_init(void) { static const char *defaults[] = { - "bind C-b send-prefix", - "bind C-o rotate-window", - "bind C-z suspend-client", - "bind Space next-layout", - "bind ! break-pane", - "bind '\"' split-window", - "bind '#' list-buffers", - "bind '$' command-prompt -I'#S' \"rename-session -- '%%'\"", - "bind % split-window -h", - "bind & confirm-before -p\"kill-window #W? (y/n)\" kill-window", - "bind \"'\" command-prompt -pindex \"select-window -t ':%%'\"", - "bind ( switch-client -p", - "bind ) switch-client -n", - "bind , command-prompt -I'#W' \"rename-window -- '%%'\"", - "bind - delete-buffer", - "bind . command-prompt \"move-window -t '%%'\"", - "bind 0 select-window -t:=0", - "bind 1 select-window -t:=1", - "bind 2 select-window -t:=2", - "bind 3 select-window -t:=3", - "bind 4 select-window -t:=4", - "bind 5 select-window -t:=5", - "bind 6 select-window -t:=6", - "bind 7 select-window -t:=7", - "bind 8 select-window -t:=8", - "bind 9 select-window -t:=9", - "bind : command-prompt", - "bind \\; last-pane", - "bind = choose-buffer -Z", - "bind ? list-keys", - "bind D choose-client -Z", - "bind E select-layout -E", - "bind L switch-client -l", - "bind M select-pane -M", - "bind [ copy-mode", - "bind ] paste-buffer", - "bind c new-window", - "bind d detach-client", - "bind f command-prompt \"find-window -Z -- '%%'\"", - "bind i display-message", - "bind l last-window", - "bind m select-pane -m", - "bind n next-window", - "bind o select-pane -t:.+", - "bind p previous-window", - "bind q display-panes", - "bind r refresh-client", - "bind s choose-tree -Zs", - "bind t clock-mode", - "bind w choose-tree -Zw", - "bind x confirm-before -p\"kill-pane #P? (y/n)\" kill-pane", - "bind z resize-pane -Z", - "bind '{' swap-pane -U", - "bind '}' swap-pane -D", - "bind '~' show-messages", - "bind PPage copy-mode -u", - "bind -r Up select-pane -U", - "bind -r Down select-pane -D", - "bind -r Left select-pane -L", - "bind -r Right select-pane -R", - "bind M-1 select-layout even-horizontal", - "bind M-2 select-layout even-vertical", - "bind M-3 select-layout main-horizontal", - "bind M-4 select-layout main-vertical", - "bind M-5 select-layout tiled", - "bind M-n next-window -a", - "bind M-o rotate-window -D", - "bind M-p previous-window -a", - "bind -r S-Up refresh-client -U 10", - "bind -r S-Down refresh-client -D 10", - "bind -r S-Left refresh-client -L 10", - "bind -r S-Right refresh-client -R 10", - "bind -r DC refresh-client -c", - "bind -r M-Up resize-pane -U 5", - "bind -r M-Down resize-pane -D 5", - "bind -r M-Left resize-pane -L 5", - "bind -r M-Right resize-pane -R 5", - "bind -r C-Up resize-pane -U", - "bind -r C-Down resize-pane -D", - "bind -r C-Left resize-pane -L", - "bind -r C-Right resize-pane -R", + "bind -N 'Send the prefix key' C-b send-prefix", + "bind -N 'Rotate through the panes' C-o rotate-window", + "bind -N 'Suspend the current client' C-z suspend-client", + "bind -N 'Select next layout' Space next-layout", + "bind -N 'Break pane to a new window' ! break-pane", + "bind -N 'Split window vertically' '\"' split-window", + "bind -N 'List all paste buffers' '#' list-buffers", + "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 '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 '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", + "bind -N 'Select window 2' 2 select-window -t:=2", + "bind -N 'Select window 3' 3 select-window -t:=3", + "bind -N 'Select window 4' 4 select-window -t:=4", + "bind -N 'Select window 5' 5 select-window -t:=5", + "bind -N 'Select window 6' 6 select-window -t:=6", + "bind -N 'Select window 7' 7 select-window -t:=7", + "bind -N 'Select window 8' 8 select-window -t:=8", + "bind -N 'Select window 9' 9 select-window -t:=9", + "bind -N 'Prompt for a command' : command-prompt", + "bind -N 'Move to the previously active pane' \\; last-pane", + "bind -N 'Choose a paste buffer from a list' = choose-buffer -Z", + "bind -N 'List key bindings' ? list-keys -N", + "bind -N 'Choose a client from a list' D choose-client -Z", + "bind -N 'Spread panes out evenly' E select-layout -E", + "bind -N 'Switch to the last client' L switch-client -l", + "bind -N 'Clear the marked pane' M select-pane -M", + "bind -N 'Enter copy mode' [ copy-mode", + "bind -N 'Paste the most recent paste buffer' ] paste-buffer", + "bind -N 'Create a new window' c new-window", + "bind -N 'Detach the current client' d detach-client", + "bind -N 'Search for a pane' f command-prompt \"find-window -Z -- '%%'\"", + "bind -N 'Display window information' i display-message", + "bind -N 'Select the previously current window' l last-window", + "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 'Select the previous pane' p previous-window", + "bind -N 'Display pane numbers' q display-panes", + "bind -N 'Redraw the current client' r refresh-client", + "bind -N 'Choose a session from a list' s choose-tree -Zs", + "bind -N 'Show a clock' t clock-mode", + "bind -N 'Choose a window from a list' w choose-tree -Zw", + "bind -N 'Kill the active pane' x confirm-before -p\"kill-pane #P? (y/n)\" kill-pane", + "bind -N 'Zoom the active pane' z resize-pane -Z", + "bind -N 'Swap the active pane with the pane above' '{' swap-pane -U", + "bind -N 'Swap the active pane with the pane below' '}' swap-pane -D", + "bind -N 'Show messages' '~' show-messages", + "bind -N 'Enter copy mode and scroll up' PPage copy-mode -u", + "bind -N 'Select the pane above the active pane' -r Up select-pane -U", + "bind -N 'Select the pane below the active pane' -r Down select-pane -D", + "bind -N 'Select the pane to the left of the active pane' -r Left select-pane -L", + "bind -N 'Select the pane to the right of the active pane' -r Right select-pane -R", + "bind -N 'Set the even-horizontal layout' M-1 select-layout even-horizontal", + "bind -N 'Set the even-vertical layout' M-2 select-layout even-vertical", + "bind -N 'Set the main-horizontal layout' M-3 select-layout main-horizontal", + "bind -N 'Set the main-vertical layout' M-4 select-layout main-vertical", + "bind -N 'Select the tiled layout' M-5 select-layout tiled", + "bind -N 'Select the next window with an alert' M-n next-window -a", + "bind -N 'Rotate through the panes in reverse' M-o rotate-window -D", + "bind -N 'Select the previous window with an alert' M-p previous-window -a", + "bind -N 'Move the visible part of the window up' -r S-Up refresh-client -U 10", + "bind -N 'Move the visible part of the window down' -r S-Down refresh-client -D 10", + "bind -N 'Move the visible part of the window left' -r S-Left refresh-client -L 10", + "bind -N 'Move the visible part of the window right' -r S-Right refresh-client -R 10", + "bind -N 'Reset so the visible part of the window follows the cursor' -r DC refresh-client -c", + "bind -N 'Resize the pane up by 5' -r M-Up resize-pane -U 5", + "bind -N 'Resize the pane down by 5' -r M-Down resize-pane -D 5", + "bind -N 'Resize the pane left by 5' -r M-Left resize-pane -L 5", + "bind -N 'Resize the pane right by 5' -r M-Right resize-pane -R 5", + "bind -N 'Resize the pane up' -r C-Up resize-pane -U", + "bind -N 'Resize the pane down' -r C-Down resize-pane -D", + "bind -N 'Resize the pane left' -r C-Left resize-pane -L", + "bind -N 'Resize the pane right' -r C-Right resize-pane -R", "bind -n MouseDown1Pane select-pane -t=\\; send-keys -M", "bind -n MouseDrag1Border resize-pane -M", @@ -520,8 +521,8 @@ key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item, new_item->shared->flags |= CMDQ_SHARED_REPEAT; } if (item != NULL) - cmdq_insert_after(item, new_item); + new_item = cmdq_insert_after(item, new_item); else - cmdq_append(c, new_item); + new_item = cmdq_append(c, new_item); return (new_item); } diff --git a/notify.c b/notify.c index 8bd8f98f..200e23d6 100644 --- a/notify.c +++ b/notify.c @@ -89,9 +89,7 @@ notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne) new_item = cmdq_get_command(cmdlist, &fs, NULL, CMDQ_NOHOOKS); cmdq_format(new_item, "hook", "%s", ne->name); notify_hook_formats(new_item, s, w, ne->pane); - - cmdq_insert_after(item, new_item); - item = new_item; + item = cmdq_insert_after(item, new_item); a = options_array_next(a); } diff --git a/proc.c b/proc.c index b10d4850..111c3730 100644 --- a/proc.c +++ b/proc.c @@ -181,7 +181,7 @@ proc_start(const char *name) memset(&u, 0, sizeof u); log_debug("%s started (%ld): version %s, socket %s, protocol %d", name, - (long)getpid(), VERSION, socket_path, PROTOCOL_VERSION); + (long)getpid(), getversion(), socket_path, PROTOCOL_VERSION); log_debug("on %s %s %s; libevent %s (%s)", u.sysname, u.release, u.version, event_get_version(), event_get_method()); diff --git a/resize.c b/resize.c index 054b025f..96d733f0 100644 --- a/resize.c +++ b/resize.c @@ -66,10 +66,26 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) static int ignore_client_size(struct client *c) { + struct client *loop; + if (c->session == NULL) return (1); if (c->flags & CLIENT_NOSIZEFLAGS) return (1); + if (c->flags & CLIENT_READONLY) { + /* + * Ignore readonly clients if there are any attached clients + * that aren't readonly. + */ + TAILQ_FOREACH (loop, &clients, entry) { + if (loop->session == NULL) + continue; + if (loop->flags & CLIENT_NOSIZEFLAGS) + continue; + if (~loop->flags & CLIENT_READONLY) + return (1); + } + } if ((c->flags & CLIENT_CONTROL) && (~c->flags & CLIENT_SIZECHANGED)) return (1); return (0); @@ -363,14 +379,15 @@ recalculate_sizes(void) * client. */ TAILQ_FOREACH(c, &clients, entry) { + s = c->session; + if (s != NULL && !(c->flags & CLIENT_UNATTACHEDFLAGS)) + s->attached++; if (ignore_client_size(c)) continue; - s = c->session; if (c->tty.sy <= s->statuslines || (c->flags & CLIENT_CONTROL)) c->flags |= CLIENT_STATUSOFF; else c->flags &= ~CLIENT_STATUSOFF; - s->attached++; } /* Walk each window and adjust the size. */ diff --git a/server-client.c b/server-client.c index 27d80cbd..5527b6e8 100644 --- a/server-client.c +++ b/server-client.c @@ -1032,7 +1032,7 @@ server_client_key_callback(struct cmdq_item *item, void *data) key_code key0; /* Check the client is good to accept input. */ - if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0) + if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) goto out; wl = s->curw; @@ -1219,7 +1219,7 @@ server_client_handle_key(struct client *c, struct key_event *event) struct cmdq_item *item; /* Check the client is good to accept input. */ - if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0) + if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return (0); /* diff --git a/session.c b/session.c index b361bde3..eddafa2c 100644 --- a/session.c +++ b/session.c @@ -568,7 +568,20 @@ session_group_count(struct session_group *sg) n = 0; TAILQ_FOREACH(s, &sg->sessions, gentry) - n++; + n++; + return (n); +} + +/* Count number of clients attached to sessions in session group. */ +u_int +session_group_attached_count(struct session_group *sg) +{ + struct session *s; + u_int n; + + n = 0; + TAILQ_FOREACH(s, &sg->sessions, gentry) + n += s->attached; return (n); } diff --git a/spawn.c b/spawn.c index 51e4ae78..d49d3adb 100644 --- a/spawn.c +++ b/spawn.c @@ -78,6 +78,8 @@ spawn_log(const char *from, struct spawn_context *sc) struct winlink * spawn_window(struct spawn_context *sc, char **cause) { + struct cmdq_item *item = sc->item; + struct client *c = item->client; struct session *s = sc->s; struct window *w; struct window_pane *wp; @@ -180,7 +182,8 @@ spawn_window(struct spawn_context *sc, char **cause) /* Set the name of the new window. */ if (~sc->flags & SPAWN_RESPAWN) { if (sc->name != NULL) { - w->name = xstrdup(sc->name); + w->name = format_single(item, sc->name, c, s, NULL, + NULL); options_set_number(w->options, "automatic-rename", 0); } else w->name = xstrdup(default_window_name(w)); @@ -220,6 +223,17 @@ spawn_pane(struct spawn_context *sc, char **cause) spawn_log(__func__, sc); + /* + * Work out the current working directory. If respawning, use + * the pane's stored one unless specified. + */ + if (sc->cwd != NULL) + cwd = format_single(item, sc->cwd, c, s, NULL, NULL); + else if (~sc->flags & SPAWN_RESPAWN) + cwd = xstrdup(server_client_get_cwd(c, s)); + else + cwd = NULL; + /* * If we are respawning then get rid of the old process. Otherwise * either create a new cell or assign to the one we are given. @@ -230,6 +244,7 @@ spawn_pane(struct spawn_context *sc, char **cause) window_pane_index(sc->wp0, &idx); xasprintf(cause, "pane %s:%d.%u still active", s->name, sc->wl->idx, idx); + free(cwd); return (NULL); } if (sc->wp0->fd != -1) { @@ -250,8 +265,8 @@ spawn_pane(struct spawn_context *sc, char **cause) } /* - * Now we have a pane with nothing running in it ready for the new - * process. Work out the command and arguments. + * Now we have a pane with nothing running in it ready for the new process. + * Work out the command and arguments and store the working directory. */ if (sc->argc == 0 && (~sc->flags & SPAWN_RESPAWN)) { cmd = options_get_string(s->options, "default-command"); @@ -266,6 +281,10 @@ spawn_pane(struct spawn_context *sc, char **cause) argc = sc->argc; argv = sc->argv; } + if (cwd != NULL) { + free(new_wp->cwd); + new_wp->cwd = cwd; + } /* * Replace the stored arguments if there are new ones. If not, the @@ -277,21 +296,6 @@ spawn_pane(struct spawn_context *sc, char **cause) new_wp->argv = cmd_copy_argv(argc, argv); } - /* - * Work out the current working directory. If respawning, use - * the pane's stored one unless specified. - */ - if (sc->cwd != NULL) - cwd = format_single(item, sc->cwd, c, s, NULL, NULL); - else if (~sc->flags & SPAWN_RESPAWN) - cwd = xstrdup(server_client_get_cwd(c, s)); - else - cwd = NULL; - if (cwd != NULL) { - free(new_wp->cwd); - new_wp->cwd = cwd; - } - /* Create an environment for this pane. */ child = environ_for_session(s, 0); if (sc->environ != NULL) diff --git a/status.c b/status.c index 0f96f0d3..33f6c47a 100644 --- a/status.c +++ b/status.c @@ -915,11 +915,17 @@ status_prompt_key(struct client *c, key_code key) { struct options *oo = c->session->options; char *s, *cp, word[64], prefix = '='; - const char *histstr, *ws = NULL; + const char *histstr, *ws = NULL, *keystring; size_t size, n, off, idx, used; struct utf8_data tmp, *first, *last, *ud; int keys; + if (c->prompt_flags & PROMPT_KEY) { + keystring = key_string_lookup_key(key); + c->prompt_inputcb(c, c->prompt_data, keystring, 1); + status_prompt_clear(c); + return (0); + } size = utf8_strlen(c->prompt_buffer); if (c->prompt_flags & PROMPT_NUMERIC) { diff --git a/tmux.1 b/tmux.1 index aac4625c..59cac594 100644 --- a/tmux.1 +++ b/tmux.1 @@ -214,7 +214,6 @@ was given) and off. Report the .Nm version. -.Pp .It Ar command Op Ar flags This specifies one of a set of commands used to control .Nm , @@ -556,7 +555,7 @@ Braces may be enclosed inside braces, for example: .Bd -literal -offset indent bind x if-shell "true" { if-shell "true" { - display "true!" + display "true!" } } .Ed @@ -938,7 +937,9 @@ If is specified, any other clients attached to the session are detached. If .Fl x -is given, send SIGHUP to the parent process of the client as well as +is given, send +.Dv SIGHUP +to the parent process of the client as well as detaching the client, typically causing it to exit. .Fl r signifies the client is read-only (only keys bound to the @@ -989,7 +990,9 @@ option kills all but the client given with .Fl t . If .Fl P -is given, send SIGHUP to the parent process of the client, typically causing it +is given, send +.Dv SIGHUP +to the parent process of the client, typically causing it to exit. With .Fl E , @@ -1288,6 +1291,17 @@ shows the parsed commands and line numbers if possible. Start the .Nm server, if not already running, without creating any sessions. +.Pp +Note that as by default the +.Nm +server will exit with no sessions, this is only useful if a session is created in +.Pa ~/.tmux.conf , +.Ic exit-empty +is turned off, or another command is run as part of the same command sequence. +For example: +.Bd -literal -offset indent +$ tmux start \\; show -g +.Ed .It Xo Ic suspend-client .Op Fl t Ar target-client .Xc @@ -1336,7 +1350,8 @@ is used, option will not be applied. .Pp .Fl T -sets the client's key table; the next key from the client will be interpreted from +sets the client's key table; the next key from the client will be interpreted +from .Ar key-table . This may be used to configure multiple prefix keys, or to bind commands to sequences of keys. @@ -2534,10 +2549,11 @@ This is similar to except the source and destination windows are swapped. It is an error if no window exists at .Ar src-window . +If +.Fl d +is given, the new window does not become the current window. .Pp -Like -.Ic swap-pane , -if +If .Fl s is omitted and a marked pane is present (see .Ic select-pane @@ -2613,6 +2629,7 @@ Commands related to key bindings are as follows: .Bl -tag -width Ds .It Xo Ic bind-key .Op Fl nr +.Op Fl N Ar note .Op Fl T Ar key-table .Ar key Ar command Op Ar arguments .Xc @@ -2660,22 +2677,46 @@ The flag indicates this key may repeat, see the .Ic repeat-time option. +.Fl N +attaches a note to the key (shown with +.Ic list-keys +.Fl N ) . .Pp To view the default bindings and possible commands, see the .Ic list-keys command. .It Xo Ic list-keys -.Op Fl T Ar key-table +.Op Fl 1N +.Op Fl P Ar prefix-string Fl T Ar key-table +.Op key .Xc .D1 (alias: Ic lsk ) List all key bindings. -Without -.Fl T -all key tables are printed. +By default this shows all keys or any bindings for +.Ar key +in the syntax of the +.Ic bind-key +command. +.Fl N +instead show keys and attached notes, i +.Ar key-table +if given or in the +.Em root +and +.Em prefix +key tables by default. +.Fl P +specifies a prefix to print before each key. With +.Fl 1 +only the first matching key and note is shown. +.Pp +Without +.Fl N , .Fl T -only -.Ar key-table . +prints only keys in +.Ar key-table , +otherwise all key tables are printed. .It Xo Ic send-keys .Op Fl FHlMRX .Op Fl N Ar repeat-count @@ -4228,7 +4269,6 @@ 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_termtype" Ta "" Ta "Terminal type of client" .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" @@ -4316,10 +4356,14 @@ The following variables are available, where appropriate: .It Li "session_activity" Ta "" Ta "Time of session last activity" .It Li "session_alerts" Ta "" Ta "List of window indexes with alerts" .It Li "session_attached" Ta "" Ta "Number of clients session is attached to" +.It Li "session_attached_list" Ta "" Ta "List of clients session is attached to" .It Li "session_created" Ta "" Ta "Time session created" .It Li "session_format" Ta "" Ta "1 if format is for a session" .It Li "session_group" Ta "" Ta "Name of session group" +.It Li "session_group_attached" Ta "" Ta "Number of clients sessions in group are attached to" +.It Li "session_group_attached_list" Ta "" Ta "List of clients sessions in group are attached to" .It Li "session_group_list" Ta "" Ta "List of sessions in group" +.It Li "session_group_many_attached" Ta "" Ta "1 if multiple clients attached to sessions in group" .It Li "session_group_size" Ta "" Ta "Size of session group" .It Li "session_grouped" Ta "" Ta "1 if session in a group" .It Li "session_id" Ta "" Ta "Unique session ID" @@ -4332,6 +4376,10 @@ The following variables are available, where appropriate: .It Li "start_time" Ta "" Ta "Server start time" .It Li "version" Ta "" Ta "Server version" .It Li "window_active" Ta "" Ta "1 if window active" +.It Li "window_active_clients" Ta "" Ta "Number of clients viewing this window" +.It Li "window_active_clients_list" Ta "" Ta "List of clients viewing this window" +.It Li "window_active_sessions" Ta "" Ta "Number of sessions on which this window is active" +.It Li "window_active_sessions_list" Ta "" Ta "List of sessions on which this window is active" .It Li "window_activity" Ta "" Ta "Time of window last activity" .It Li "window_activity_flag" Ta "" Ta "1 if window has activity" .It Li "window_bell_flag" Ta "" Ta "1 if window has bell" @@ -4347,6 +4395,8 @@ The following variables are available, where appropriate: .It Li "window_last_flag" Ta "" Ta "1 if window is the last used" .It Li "window_layout" Ta "" Ta "Window layout description, ignoring zoomed window panes" .It Li "window_linked" Ta "" Ta "1 if window is linked across sessions" +.It Li "window_linked_sessions" Ta "" Ta "Number of sessions this window is linked to" +.It Li "window_linked_sessions_list" Ta "" Ta "List of sessions this window is linked to" .It Li "window_marked_flag" Ta "" Ta "1 if window contains the marked pane" .It Li "window_name" Ta "#W" Ta "Name of window" .It Li "window_offset_x" Ta "" Ta "X offset into window if larger than client" @@ -4367,7 +4417,7 @@ interface, for example .Ic status-style for the status line. In addition, embedded styles may be specified in format options, such as -.Ic status-left-format , +.Ic status-left , by enclosing them in .Ql #[ and @@ -4686,7 +4736,7 @@ session option. Commands related to the status line are as follows: .Bl -tag -width Ds .It Xo Ic command-prompt -.Op Fl 1Ni +.Op Fl 1ikN .Op Fl I Ar inputs .Op Fl p Ar prompts .Op Fl t Ar target-client @@ -4736,6 +4786,10 @@ but any quotation marks are escaped. .Fl 1 makes the prompt only accept one key press, in this case the resulting input is a single character. +.Fl k +is like +.Fl 1 +but the key press is translated to a key name. .Fl N makes the prompt only accept numeric key presses. .Fl i @@ -4899,7 +4953,7 @@ When the option is reached, the oldest automatically named buffer is deleted. Explicitly named buffers are not subject to .Ic buffer-limit -and may be deleted with +and may be deleted with the .Ic delete-buffer command. .Pp @@ -5145,6 +5199,37 @@ channel are made to wait until the channel is unlocked with .Ic wait-for .Fl U . .El +.Sh EXIT MESSAGES +When a +.Nm +client detaches, it prints a message. +This may be one of: +.Bl -tag -width Ds +.It [detached (from session ...)] +The client was detached normally. +.It [detached and SIGHUP] +The client was detached and its parent sent the +.Dv SIGHUP +signal (for example with +.Ic detach-client +.Fl P ) . +.It [lost tty] +The client's +.Xr tty 4 +or +.Xr pty 4 +was unexpectedly destroyed. +.It [terminated] +The client was killed with +.Dv SIGTERM . +.It [exited] +The server exited when it had no sessions. +.It [server exited] +The server exited when it received +.Dv SIGTERM . +.It [server exited unexpectedly] +The server crashed or otherwise exited without telling the client the reason. +.El .Sh TERMINFO EXTENSIONS .Nm understands some unofficial extensions to diff --git a/tmux.c b/tmux.c index c8a5a44e..3c1feccc 100644 --- a/tmux.c +++ b/tmux.c @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -209,6 +210,12 @@ find_home(void) return (home); } +const char * +getversion(void) +{ + return TMUX_VERSION; +} + int main(int argc, char **argv) { @@ -235,7 +242,7 @@ main(int argc, char **argv) flags = 0; label = path = NULL; - while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUVv")) != -1) { + while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUvV")) != -1) { switch (opt) { case '2': flags |= CLIENT_256COLOURS; @@ -249,12 +256,12 @@ main(int argc, char **argv) else flags |= CLIENT_CONTROL; break; - case 'V': - printf("%s %s\n", getprogname(), VERSION); - exit(0); case 'f': set_cfg_file(optarg); break; + case 'V': + printf("%s %s\n", getprogname(), getversion()); + exit(0); case 'l': flags |= CLIENT_LOGIN; break; diff --git a/tmux.h b/tmux.h index 22d74935..030f9af7 100644 --- a/tmux.h +++ b/tmux.h @@ -1171,8 +1171,11 @@ struct tty_term { struct tty_code *codes; #define TERM_256COLOURS 0x1 -#define TERM_EARLYWRAP 0x2 -#define TERM_SIXEL 0x4 +#define TERM_NOXENL 0x2 +#define TERM_DECSLRM 0x4 +#define TERM_DECFRA 0x8 +#define TERM_RGBCOLOURS 0x10 +#define TERM_SIXEL 0x20 int flags; LIST_ENTRY(tty_term) entry; @@ -1181,6 +1184,7 @@ LIST_HEAD(tty_terms, tty_term); struct tty { struct client *client; + struct event start_timer; u_int sx; u_int sy; @@ -1229,22 +1233,14 @@ struct tty { #define TTY_OPENED 0x20 #define TTY_FOCUS 0x40 #define TTY_BLOCK 0x80 -#define TTY_NOBLOCK 0x100 +#define TTY_HAVEDA 0x100 +#define TTY_HAVEDSR 0x200 +#define TTY_NOBLOCK 0x400 int flags; struct tty_term *term; char *term_name; int term_flags; - enum { - TTY_VT100, - TTY_VT101, - TTY_VT102, - TTY_VT220, - TTY_VT320, - TTY_VT420, - TTY_VT520, - TTY_UNKNOWN - } term_type; u_int mouse_last_x; u_int mouse_last_y; @@ -1258,15 +1254,6 @@ struct tty { struct event key_timer; struct tty_key *key_tree; }; -#define TTY_TYPES \ - { "VT100", \ - "VT101", \ - "VT102", \ - "VT220", \ - "VT320", \ - "VT420", \ - "VT520", \ - "Unknown" } /* TTY command context. */ struct tty_ctx { @@ -1602,6 +1589,10 @@ struct client { CLIENT_REDRAWSTATUSALWAYS| \ CLIENT_REDRAWBORDERS| \ CLIENT_REDRAWOVERLAY) +#define CLIENT_UNATTACHEDFLAGS \ + (CLIENT_DEAD| \ + CLIENT_SUSPENDED| \ + CLIENT_DETACHING) #define CLIENT_NOSIZEFLAGS \ (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ @@ -1628,6 +1619,7 @@ struct client { #define PROMPT_NUMERIC 0x2 #define PROMPT_INCREMENTAL 0x4 #define PROMPT_NOFORMAT 0x8 +#define PROMPT_KEY 0x10 int prompt_flags; struct session *session; @@ -1655,6 +1647,7 @@ TAILQ_HEAD(clients, client); struct key_binding { key_code key; struct cmd_list *cmdlist; + const char *note; int flags; #define KEY_BINDING_REPEAT 0x1 @@ -1782,6 +1775,7 @@ int areshell(const char *); void setblocking(int, int); const char *find_cwd(void); const char *find_home(void); +const char *getversion(void); /* proc.c */ struct imsg; @@ -1997,7 +1991,7 @@ void tty_draw_line(struct tty *, struct window_pane *, struct screen *, int tty_open(struct tty *, char **); void tty_close(struct tty *); void tty_free(struct tty *); -void tty_set_type(struct tty *, int, int); +void tty_set_flags(struct tty *, int); void tty_write(void (*)(struct tty *, const struct tty_ctx *), struct tty_ctx *); void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *); @@ -2141,8 +2135,8 @@ struct cmdq_item *cmdq_get_command(struct cmd_list *, struct cmd_find_state *, #define cmdq_get_callback(cb, data) cmdq_get_callback1(#cb, cb, data) struct cmdq_item *cmdq_get_callback1(const char *, cmdq_cb, void *); struct cmdq_item *cmdq_get_error(const char *); -void cmdq_insert_after(struct cmdq_item *, struct cmdq_item *); -void cmdq_append(struct client *, struct cmdq_item *); +struct cmdq_item *cmdq_insert_after(struct cmdq_item *, struct cmdq_item *); +struct cmdq_item *cmdq_append(struct client *, struct cmdq_item *); void cmdq_insert_hook(struct session *, struct cmdq_item *, struct cmd_find_state *, const char *, ...); void cmdq_continue(struct cmdq_item *); @@ -2167,7 +2161,8 @@ void key_bindings_unref_table(struct key_table *); struct key_binding *key_bindings_get(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, int, struct cmd_list *); +void key_bindings_add(const char *, key_code, const char *, int, + struct cmd_list *); void key_bindings_remove(const char *, key_code); void key_bindings_remove_table(const char *); void key_bindings_init(void); @@ -2298,7 +2293,7 @@ void input_parse(struct window_pane *); void input_parse_buffer(struct window_pane *, u_char *, size_t); /* input-key.c */ -void input_key(struct window_pane *, key_code, struct mouse_event *); +int input_key(struct window_pane *, key_code, struct mouse_event *); /* xterm-keys.c */ char *xterm_keys_lookup(key_code); @@ -2522,7 +2517,7 @@ int window_pane_set_mode(struct window_pane *, struct args *); void window_pane_reset_mode(struct window_pane *); void window_pane_reset_mode_all(struct window_pane *); -void window_pane_key(struct window_pane *, struct client *, +int window_pane_key(struct window_pane *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); int window_pane_visible(struct window_pane *); @@ -2697,6 +2692,7 @@ void session_group_add(struct session_group *, struct session *); void session_group_synchronize_to(struct session *); void session_group_synchronize_from(struct session *); u_int session_group_count(struct session_group *); +u_int session_group_attached_count(struct session_group *); void session_renumber_windows(struct session *); /* utf8.c */ diff --git a/tty-keys.c b/tty-keys.c index e644addb..e6f00b2e 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -52,6 +52,8 @@ 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 *, + size_t, size_t *); /* Default raw keys. */ struct tty_default_key_raw { @@ -607,6 +609,17 @@ 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)) { + case 0: /* yes */ + key = KEYC_UNKNOWN; + goto complete_key; + case -1: /* no, or not valid */ + break; + case 1: /* partial */ + goto partial_key; + } + /* Is this a mouse key press? */ switch (tty_keys_mouse(tty, buf, len, &size, &m)) { case 0: /* yes */ @@ -1001,13 +1014,14 @@ static int tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, size_t *size) { - struct client *c = tty->client; - u_int i, n = 0; - char tmp[64], *endptr, p[32] = { 0 }, *cp, *next; - static const char *types[] = TTY_TYPES; - int type, flags = 0; + struct client *c = tty->client; + u_int i, n = 0; + char tmp[64], *endptr, p[32] = { 0 }, *cp, *next; + int flags = 0; *size = 0; + if (tty->flags & TTY_HAVEDA) + return (-1); /* First three bytes are always \033[?. */ if (buf[0] != '\033') @@ -1038,44 +1052,81 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, cp = tmp; while ((next = strsep(&cp, ";")) != NULL) { p[n] = strtoul(next, &endptr, 10); - if (*endptr != '\0' && *endptr != ';') + if (*endptr != '\0') p[n] = 0; n++; } - /* Store terminal type. */ - type = TTY_UNKNOWN; + /* Set terminal flags. */ switch (p[0]) { - case 1: - if (p[1] == 2) - type = TTY_VT100; - else if (p[1] == 0) - type = TTY_VT101; - break; - case 6: - type = TTY_VT102; - break; - case 62: - type = TTY_VT220; - break; - case 63: - type = TTY_VT320; - break; - case 64: - type = TTY_VT420; - break; - case 65: - type = TTY_VT520; + case 64: /* VT420 */ + flags |= (TERM_DECFRA|TERM_DECSLRM); break; } - for (i = 2; i < n; i++) { + for (i = 1; i < n; i++) { log_debug("%s: DA feature: %d", c->name, p[i]); if (p[i] == 4) flags |= TERM_SIXEL; } tty_set_type(tty, type, flags); - log_debug("%s: received DA %.*s (%s)", c->name, (int)*size, buf, - types[type]); + tty_set_flags(tty, flags); + tty->flags |= TTY_HAVEDA; + + return (0); +} + +/* + * Handle device status report 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) +{ + struct client *c = tty->client; + u_int i; + char tmp[64]; + int flags = 0; + + *size = 0; + if (tty->flags & TTY_HAVEDSR) + return (-1); + + /* First three 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); + if (buf[2] != 'I' && buf[2] != 'T') + return (-1); + if (len == 3) + 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) + return (1); + tmp[i] = buf[2 + i]; + } + if (i == (sizeof tmp) - 1) + return (-1); + tmp[i] = '\0'; + *size = 3 + i; + + /* Set terminal flags. */ + if (strncmp(tmp, "ITERM2 ", 7) == 0) + flags |= (TERM_DECSLRM|TERM_256COLOURS|TERM_RGBCOLOURS); + if (strncmp(tmp, "TMUX ", 5) == 0) + flags |= (TERM_256COLOURS|TERM_RGBCOLOURS); + log_debug("%s: received DSR %.*s", c->name, (int)*size, buf); + + tty_set_flags(tty, flags); + tty->flags |= TTY_HAVEDSR; + return (0); } diff --git a/tty-term.c b/tty-term.c index ce3d690a..4bf58a97 100644 --- a/tty-term.c +++ b/tty-term.c @@ -529,11 +529,16 @@ tty_term_find(char *name, int fd, char **cause) goto error; } - /* Figure out if we have 256 colours (or more). */ - if (tty_term_number(term, TTYC_COLORS) >= 256 || - tty_term_has(term, TTYC_RGB)) + /* Set flag if terminal has 256 colours. */ + if (tty_term_number(term, TTYC_COLORS) >= 256) term->flags |= TERM_256COLOURS; + /* Set flag 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))) + term->flags |= TERM_RGBCOLOURS; + /* * Terminals without xenl (eat newline glitch) wrap at at $COLUMNS - 1 * rather than $COLUMNS (the cursor can never be beyond $COLUMNS - 1). @@ -545,7 +550,7 @@ tty_term_find(char *name, int fd, char **cause) * do the best possible. */ if (!tty_term_flag(term, TTYC_XENL)) - term->flags |= TERM_EARLYWRAP; + term->flags |= TERM_NOXENL; /* Generate ACS table. If none is present, use nearest ASCII. */ memset(term->acs, 0, sizeof term->acs); @@ -568,22 +573,7 @@ tty_term_find(char *name, int fd, char **cause) code->type = TTYCODE_STRING; } - /* - * On terminals with RGB colour (Tc or RGB), fill in setrgbf and - * setrgbb if they are missing. - */ - if ((tty_term_flag(term, TTYC_TC) || tty_term_flag(term, TTYC_RGB)) && - !tty_term_has(term, TTYC_SETRGBF) && - !tty_term_has(term, TTYC_SETRGBB)) { - code = &term->codes[TTYC_SETRGBF]; - code->value.string = xstrdup("\033[38;2;%p1%d;%p2%d;%p3%dm"); - code->type = TTYCODE_STRING; - code = &term->codes[TTYC_SETRGBB]; - code->value.string = xstrdup("\033[48;2;%p1%d;%p2%d;%p3%dm"); - code->type = TTYCODE_STRING; - } - - /* Log it. */ + /* Log the capabilities. */ for (i = 0; i < tty_term_ncodes(); i++) log_debug("%s%s", name, tty_term_describe(term, i)); diff --git a/tty.c b/tty.c index fba071b4..1a6c89aa 100644 --- a/tty.c +++ b/tty.c @@ -74,7 +74,7 @@ static void tty_default_attributes(struct tty *, struct window_pane *, u_int); #define tty_use_margin(tty) \ - ((tty)->term_type == TTY_VT420) + ((tty->term->flags|tty->term_flags) & TERM_DECSLRM) #define tty_pane_full_width(tty, ctx) \ ((ctx)->xoff == 0 && screen_size_x((ctx)->wp->screen) >= (tty)->sx) @@ -115,9 +115,7 @@ tty_init(struct tty *tty, struct client *c, int fd, char *term) tty->ccolour = xstrdup(""); tty->flags = 0; - tty->term_flags = 0; - tty->term_type = TTY_UNKNOWN; return (0); } @@ -292,11 +290,22 @@ tty_open(struct tty *tty, char **cause) return (0); } +static void +tty_start_timer_callback(__unused int fd, __unused short events, void *data) +{ + struct tty *tty = data; + struct client *c = tty->client; + + log_debug("%s: start timer fired", c->name); + tty->flags |= (TTY_HAVEDA|TTY_HAVEDSR); +} + void tty_start_tty(struct tty *tty) { struct client *c = tty->client; struct termios tio; + struct timeval tv = { .tv_sec = 1 }; if (tty->fd != -1 && tcgetattr(tty->fd, &tty->tio) == 0) { setblocking(tty->fd, 0); @@ -334,8 +343,15 @@ tty_start_tty(struct tty *tty) tty->flags |= TTY_FOCUS; tty_puts(tty, "\033[?1004h"); } - tty_puts(tty, "\033[c"); - } + if (~tty->flags & TTY_HAVEDA) + tty_puts(tty, "\033[c"); + if (~tty->flags & TTY_HAVEDSR) + tty_puts(tty, "\033[1337n"); + } else + tty->flags |= (TTY_HAVEDA|TTY_HAVEDSR); + + evtimer_set(&tty->start_timer, tty_start_timer_callback, tty); + evtimer_add(&tty->start_timer, &tv); tty->flags |= TTY_STARTED; tty_invalidate(tty); @@ -357,6 +373,8 @@ tty_stop_tty(struct tty *tty) return; tty->flags &= ~TTY_STARTED; + evtimer_del(&tty->start_timer); + event_del(&tty->timer); tty->flags &= ~TTY_BLOCK; @@ -443,9 +461,8 @@ tty_free(struct tty *tty) } void -tty_set_type(struct tty *tty, int type, int flags) +tty_set_flags(struct tty *tty, int flags) { - tty->term_type = type; tty->term_flags |= flags; if (tty_use_margin(tty)) @@ -549,7 +566,7 @@ tty_putc(struct tty *tty, u_char ch) { const char *acs; - if ((tty->term->flags & TERM_EARLYWRAP) && + if ((tty->term->flags & TERM_NOXENL) && ch >= 0x20 && ch != 0x7f && tty->cy == tty->sy - 1 && tty->cx + 1 >= tty->sx) @@ -575,7 +592,7 @@ tty_putc(struct tty *tty, u_char ch) * where we think it should be after a line wrap - this * means it works on sensible terminals as well. */ - if (tty->term->flags & TERM_EARLYWRAP) + if (tty->term->flags & TERM_NOXENL) tty_putcode2(tty, TTYC_CUP, tty->cy, tty->cx); } else tty->cx++; @@ -585,7 +602,7 @@ tty_putc(struct tty *tty, u_char ch) void tty_putn(struct tty *tty, const void *buf, size_t len, u_int width) { - if ((tty->term->flags & TERM_EARLYWRAP) && + if ((tty->term->flags & TERM_NOXENL) && tty->cy == tty->sy - 1 && tty->cx + len >= tty->sx) len = tty->sx - tty->cx - 1; @@ -1135,7 +1152,8 @@ tty_clear_area(struct tty *tty, struct window_pane *wp, u_int py, u_int ny, * background colour isn't default (because it doesn't work * after SGR 0). */ - if (tty->term_type == TTY_VT420 && !COLOUR_DEFAULT(bg)) { + if (((tty->term->flags|tty->term_flags) & TERM_DECFRA) && + !COLOUR_DEFAULT(bg)) { xsnprintf(tmp, sizeof tmp, "\033[32;%u;%u;%u;%u$x", py + 1, px + 1, py + ny, px + nx); tty_puts(tty, tmp); @@ -1830,7 +1848,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) ctx->xoff + ctx->ocx + ctx->num > ctx->ox + ctx->sx)) { if (!ctx->wrapped || !tty_pane_full_width(tty, ctx) || - (tty->term->flags & TERM_EARLYWRAP) || + (tty->term->flags & TERM_NOXENL) || ctx->xoff + ctx->ocx != 0 || ctx->yoff + ctx->ocy != tty->cy + 1 || tty->cx < tty->sx || @@ -1892,7 +1910,7 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, struct window_pane *wp) const struct grid_cell *gcp; /* Skip last character if terminal is stupid. */ - if ((tty->term->flags & TERM_EARLYWRAP) && + if ((tty->term->flags & TERM_NOXENL) && tty->cy == tty->sy - 1 && tty->cx == tty->sx - 1) return; @@ -2051,7 +2069,7 @@ tty_cursor_pane_unless_wrap(struct tty *tty, const struct tty_ctx *ctx, { if (!ctx->wrapped || !tty_pane_full_width(tty, ctx) || - (tty->term->flags & TERM_EARLYWRAP) || + (tty->term->flags & TERM_NOXENL) || ctx->xoff + cx != 0 || ctx->yoff + cy != tty->cy + 1 || tty->cx < tty->sx || @@ -2388,11 +2406,10 @@ tty_check_fg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) /* Is this a 24-bit colour? */ if (gc->fg & COLOUR_FLAG_RGB) { /* Not a 24-bit terminal? Translate to 256-colour palette. */ - if (!tty_term_has(tty->term, TTYC_SETRGBF)) { - colour_split_rgb(gc->fg, &r, &g, &b); - gc->fg = colour_find_rgb(r, g, b); - } else + if ((tty->term->flags|tty->term_flags) & TERM_RGBCOLOURS) return; + colour_split_rgb(gc->fg, &r, &g, &b); + gc->fg = colour_find_rgb(r, g, b); } /* How many colours does this terminal have? */ @@ -2438,11 +2455,10 @@ tty_check_bg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) /* Is this a 24-bit colour? */ if (gc->bg & COLOUR_FLAG_RGB) { /* Not a 24-bit terminal? Translate to 256-colour palette. */ - if (!tty_term_has(tty->term, TTYC_SETRGBB)) { - colour_split_rgb(gc->bg, &r, &g, &b); - gc->bg = colour_find_rgb(r, g, b); - } else + if ((tty->term->flags|tty->term_flags) & TERM_RGBCOLOURS) return; + colour_split_rgb(gc->bg, &r, &g, &b); + gc->bg = colour_find_rgb(r, g, b); } /* How many colours does this terminal have? */ @@ -2619,15 +2635,14 @@ tty_try_colour(struct tty *tty, int colour, const char *type) } if (colour & COLOUR_FLAG_RGB) { + colour_split_rgb(colour & 0xffffff, &r, &g, &b); if (*type == '3') { if (!tty_term_has(tty->term, TTYC_SETRGBF)) - return (-1); - colour_split_rgb(colour & 0xffffff, &r, &g, &b); + goto fallback_rgb; tty_putcode3(tty, TTYC_SETRGBF, r, g, b); } else { if (!tty_term_has(tty->term, TTYC_SETRGBB)) - return (-1); - colour_split_rgb(colour & 0xffffff, &r, &g, &b); + goto fallback_rgb; tty_putcode3(tty, TTYC_SETRGBB, r, g, b); } return (0); @@ -2640,6 +2655,12 @@ fallback_256: log_debug("%s: 256 colour fallback: %s", tty->client->name, s); tty_puts(tty, s); return (0); + +fallback_rgb: + xsnprintf(s, sizeof s, "\033[%s;2;%d;%d;%dm", type, r, g, b); + log_debug("%s: RGB colour fallback: %s", tty->client->name, s); + tty_puts(tty, s); + return (0); } static void diff --git a/window-client.c b/window-client.c index 22a0f2e2..4688cbf3 100644 --- a/window-client.c +++ b/window-client.c @@ -210,7 +210,7 @@ window_client_draw(__unused void *modedata, void *itemdata, struct window_pane *wp; u_int cx = s->cx, cy = s->cy, lines, at; - if (c->session == NULL || (c->flags & (CLIENT_DEAD|CLIENT_DETACHING))) + if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return; wp = c->session->curw->window->active; diff --git a/window-copy.c b/window-copy.c index 4787fb27..6e80daad 100644 --- a/window-copy.c +++ b/window-copy.c @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -57,18 +58,29 @@ static int window_copy_search_lr(struct grid *, struct grid *, u_int *, u_int, u_int, u_int, int); static int window_copy_search_rl(struct grid *, struct grid *, u_int *, u_int, u_int, u_int, int); +static int window_copy_search_lr_regex(struct grid *, struct grid *, + u_int *, u_int *, u_int, u_int, u_int, int); +static int window_copy_search_rl_regex(struct grid *, struct grid *, + u_int *, u_int *, u_int, u_int, u_int, int); +static int window_copy_last_regex(struct grid *gd, u_int py, u_int first, + u_int last, u_int len, u_int *ppx, u_int *psx, + const char *buf, const regex_t *preg, int eflags); +static char *window_copy_stringify(struct grid *, u_int, u_int, u_int, + char *, u_int *); +static void window_copy_cstrtocellpos(struct grid *, u_int, u_int *, u_int *, + const char *str); static int window_copy_search_marks(struct window_mode_entry *, - struct screen *); + struct screen *, int); static void window_copy_clear_marks(struct window_mode_entry *); static void window_copy_move_left(struct screen *, u_int *, u_int *, int); static void window_copy_move_right(struct screen *, u_int *, u_int *, int); static int window_copy_is_lowercase(const char *); static int window_copy_search_jump(struct window_mode_entry *, struct grid *, struct grid *, u_int, u_int, u_int, int, int, - int); -static int window_copy_search(struct window_mode_entry *, int); -static int window_copy_search_up(struct window_mode_entry *); -static int window_copy_search_down(struct window_mode_entry *); + int, int); +static int window_copy_search(struct window_mode_entry *, int, int); +static int window_copy_search_up(struct window_mode_entry *, int); +static int window_copy_search_down(struct window_mode_entry *, int); static void window_copy_goto_line(struct window_mode_entry *, const char *); static void window_copy_update_cursor(struct window_mode_entry *, u_int, u_int); @@ -622,7 +634,7 @@ window_copy_resize(struct window_mode_entry *wme, u_int sx, u_int sy) screen_write_stop(&ctx); if (search) - window_copy_search_marks(wme, NULL); + window_copy_search_marks(wme, NULL, 1); data->searchx = data->cx; data->searchy = data->cy; data->searcho = data->oy; @@ -1458,10 +1470,10 @@ window_copy_cmd_search_again(struct window_copy_cmd_state *cs) if (data->searchtype == WINDOW_COPY_SEARCHUP) { for (; np != 0; np--) - window_copy_search_up(wme); + window_copy_search_up(wme, 1); } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) { for (; np != 0; np--) - window_copy_search_down(wme); + window_copy_search_down(wme, 1); } return (WINDOW_COPY_CMD_NOTHING); } @@ -1475,10 +1487,10 @@ window_copy_cmd_search_reverse(struct window_copy_cmd_state *cs) if (data->searchtype == WINDOW_COPY_SEARCHUP) { for (; np != 0; np--) - window_copy_search_down(wme); + window_copy_search_down(wme, 1); } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) { for (; np != 0; np--) - window_copy_search_up(wme); + window_copy_search_up(wme, 1); } return (WINDOW_COPY_CMD_NOTHING); } @@ -1696,7 +1708,7 @@ window_copy_cmd_search_backward(struct window_copy_cmd_state *cs) if (data->searchstr != NULL) { data->searchtype = WINDOW_COPY_SEARCHUP; for (; np != 0; np--) - window_copy_search_up(wme); + window_copy_search_up(wme, 1); } return (WINDOW_COPY_CMD_NOTHING); } @@ -1731,7 +1743,7 @@ window_copy_cmd_search_forward(struct window_copy_cmd_state *cs) if (data->searchstr != NULL) { data->searchtype = WINDOW_COPY_SEARCHDOWN; for (; np != 0; np--) - window_copy_search_down(wme); + window_copy_search_down(wme, 1); } return (WINDOW_COPY_CMD_NOTHING); } @@ -1767,7 +1779,7 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs) data->searchtype = WINDOW_COPY_SEARCHUP; free(data->searchstr); data->searchstr = xstrdup(argument); - if (!window_copy_search_up(wme)) { + if (!window_copy_search_up(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); } @@ -1776,7 +1788,7 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs) data->searchtype = WINDOW_COPY_SEARCHDOWN; free(data->searchstr); data->searchstr = xstrdup(argument); - if (!window_copy_search_down(wme)) { + if (!window_copy_search_down(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); } @@ -1816,7 +1828,7 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs) data->searchtype = WINDOW_COPY_SEARCHDOWN; free(data->searchstr); data->searchstr = xstrdup(argument); - if (!window_copy_search_down(wme)) { + if (!window_copy_search_down(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); } @@ -1825,7 +1837,7 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs) data->searchtype = WINDOW_COPY_SEARCHUP; free(data->searchstr); data->searchstr = xstrdup(argument); - if (!window_copy_search_up(wme)) { + if (!window_copy_search_up(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); } @@ -2152,6 +2164,297 @@ window_copy_search_rl(struct grid *gd, return (0); } +static int +window_copy_search_lr_regex(struct grid *gd, struct grid *sgd, + u_int *ppx, u_int *psx, u_int py, u_int first, u_int last, int cis) +{ + int cflags = REG_EXTENDED, eflags = 0; + u_int endline, foundx, foundy, len, pywrap, size = 1; + u_int ssize = 1; + char *buf, *sbuf; + regex_t reg; + regmatch_t regmatch; + struct grid_line *gl; + + /* + * This can happen during search if the last match was the last + * character on a line. + */ + if (first >= last) + return (0); + + sbuf = xmalloc(ssize); + sbuf[0] = '\0'; + sbuf = window_copy_stringify(sgd, 0, 0, sgd->sx, sbuf, &ssize); + if (sbuf == NULL) + return (0); + + /* Set flags for regex search. */ + if (cis) + cflags |= REG_ICASE; + if (regcomp(®, sbuf, cflags) != 0) { + free(sbuf); + return (0); + } + if (first != 0) + eflags |= REG_NOTBOL; + + /* Need to look at the entire string. */ + buf = xmalloc(size); + buf[0] = '\0'; + buf = window_copy_stringify(gd, py, first, gd->sx, buf, &size); + len = gd->sx - first; + endline = gd->hsize + gd->sy - 1; + pywrap = py; + while (buf != NULL && pywrap <= endline) { + gl = grid_get_line(gd, pywrap); + if (~gl->flags & GRID_LINE_WRAPPED) + break; + pywrap++; + buf = window_copy_stringify(gd, pywrap, 0, gd->sx, buf, &size); + len += gd->sx; + } + + if (regexec(®, buf, 1, ®match, eflags) == 0) { + foundx = first; + foundy = py; + window_copy_cstrtocellpos(gd, len, &foundx, &foundy, + buf + regmatch.rm_so); + if (foundy == py && foundx < last) { + *ppx = foundx; + len -= foundx - first; + window_copy_cstrtocellpos(gd, len, &foundx, &foundy, + buf + regmatch.rm_eo); + *psx = foundx; + while (foundy > py) { + *psx += gd->sx; + foundy--; + } + *psx -= *ppx; + regfree(®); + free(sbuf); + free(buf); + return (1); + } + } + + regfree(®); + free(sbuf); + free(buf); + *ppx = 0; + *psx = 0; + return (0); +} + +static int +window_copy_search_rl_regex(struct grid *gd, struct grid *sgd, + u_int *ppx, u_int *psx, u_int py, u_int first, u_int last, int cis) +{ + int cflags = REG_EXTENDED, eflags = 0; + u_int endline, len, pywrap, size = 1, ssize = 1; + char *buf, *sbuf; + regex_t reg; + struct grid_line *gl; + + sbuf = xmalloc(ssize); + sbuf[0] = '\0'; + sbuf = window_copy_stringify(sgd, 0, 0, sgd->sx, sbuf, &ssize); + if (sbuf == NULL) + return (0); + + /* Set flags for regex search. */ + if (cis) + cflags |= REG_ICASE; + if (regcomp(®, sbuf, cflags) != 0) { + free(sbuf); + return (0); + } + if (first != 0) + eflags |= REG_NOTBOL; + + /* Need to look at the entire string. */ + buf = xmalloc(size); + buf[0] = '\0'; + buf = window_copy_stringify(gd, py, first, gd->sx, buf, &size); + len = gd->sx - first; + endline = gd->hsize + gd->sy - 1; + pywrap = py; + while (buf != NULL && (pywrap <= endline)) { + gl = grid_get_line(gd, pywrap); + if (~gl->flags & GRID_LINE_WRAPPED) + break; + pywrap++; + buf = window_copy_stringify(gd, pywrap, 0, gd->sx, buf, &size); + len += gd->sx; + } + + if (window_copy_last_regex(gd, py, first, last, len, ppx, psx, buf, + ®, eflags)) + { + regfree(®); + free(sbuf); + free(buf); + return (1); + } + + regfree(®); + free(sbuf); + free(buf); + *ppx = 0; + *psx = 0; + return (0); +} + +/* Find last match in given range. */ +static int +window_copy_last_regex(struct grid *gd, u_int py, u_int first, u_int last, + u_int len, u_int *ppx, u_int *psx, const char *buf, const regex_t *preg, + int eflags) +{ + u_int foundx, foundy, oldx, px = 0, savepx, savesx = 0; + regmatch_t regmatch; + + foundx = first; + foundy = py; + oldx = first; + while (regexec(preg, buf + px, 1, ®match, eflags) == 0) { + window_copy_cstrtocellpos(gd, len, &foundx, &foundy, + buf + px + regmatch.rm_so); + if (foundy > py || foundx >= last) + break; + len -= foundx - oldx; + savepx = foundx; + window_copy_cstrtocellpos(gd, len, &foundx, &foundy, + buf + px + regmatch.rm_eo); + if (foundy > py || foundx >= last) { + *ppx = savepx; + *psx = foundx; + while (foundy > py) { + *psx += gd->sx; + foundy--; + } + *psx -= *ppx; + return (1); + } else { + savesx = foundx - savepx; + len -= savesx; + oldx = foundx; + } + px += regmatch.rm_eo; + } + + if (savesx > 0) { + *ppx = savepx; + *psx = savesx; + return (1); + } else { + *ppx = 0; + *psx = 0; + return (0); + } +} + +/* Stringify line and append to input buffer. Caller frees. */ +static char * +window_copy_stringify(struct grid *gd, u_int py, u_int first, u_int last, + char *buf, u_int *size) +{ + u_int ax, bx, newsize; + struct grid_cell gc; + + bx = *size - 1; + newsize = *size; + for (ax = first; ax < last; ax++) { + grid_get_cell(gd, ax, py, &gc); + newsize += gc.data.size; + buf = xrealloc(buf, newsize); + memcpy(buf + bx, gc.data.data, gc.data.size); + bx += gc.data.size; + } + + buf[newsize - 1] = '\0'; + *size = newsize; + return (buf); +} + +/* Map start of C string containing UTF-8 data to grid cell position. */ +static void +window_copy_cstrtocellpos(struct grid *gd, u_int ncells, u_int *ppx, u_int *ppy, + const char *str) +{ + u_int cell, ccell, px, pywrap; + int match; + const char *cstr; + char *celldata, **cells; + struct grid_cell gc; + + /* Set up staggered array of cell contents. This speeds up search. */ + cells = xreallocarray(NULL, ncells, sizeof cells[0]); + + /* Populate the array of cell data. */ + cell = 0; + px = *ppx; + pywrap = *ppy; + while (cell < ncells) { + grid_get_cell(gd, px, pywrap, &gc); + celldata = xmalloc(gc.data.size + 1); + memcpy(celldata, gc.data.data, gc.data.size); + celldata[gc.data.size] = '\0'; + cells[cell] = celldata; + cell++; + px = (px + 1) % gd->sx; + if (px == 0) + pywrap++; + } + + /* Locate starting cell. */ + cell = 0; + while (cell < ncells) { + ccell = cell; + cstr = str; + match = 1; + while (ccell < ncells) { + /* Anchor found to the end. */ + if (*cstr == '\0') { + match = 0; + break; + } + + celldata = cells[ccell]; + while (*celldata != '\0' && *cstr != '\0') { + if (*celldata++ != *cstr++) { + match = 0; + break; + } + } + + if (!match) + break; + ccell++; + } + + if (match) + break; + cell++; + } + + /* If not found this will be one past the end. */ + px = *ppx + cell; + pywrap = *ppy; + while (px >= gd->sx) { + px -= gd->sx; + pywrap++; + } + + *ppx = px; + *ppy = pywrap; + + /* Free cell data. */ + for (cell = 0; cell < ncells; cell++) + free(cells[cell]); + free(cells); +} + static void window_copy_move_left(struct screen *s, u_int *fx, u_int *fy, int wrapflag) { @@ -2206,24 +2509,31 @@ window_copy_is_lowercase(const char *ptr) static int window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, struct grid *sgd, u_int fx, u_int fy, u_int endline, int cis, int wrap, - int direction) + int direction, int regex) { - u_int i, px; - int found; + u_int i, px, sx; + int found = 0; - found = 0; if (direction) { for (i = fy; i <= endline; i++) { - found = window_copy_search_lr(gd, sgd, &px, i, fx, - gd->sx, cis); + if (regex) + found = window_copy_search_lr_regex(gd, sgd, + &px, &sx, i, fx, gd->sx, cis); + else + found = window_copy_search_lr(gd, sgd, + &px, i, fx, gd->sx, cis); if (found) break; fx = 0; } } else { for (i = fy + 1; endline < i; i--) { - found = window_copy_search_rl(gd, sgd, &px, i - 1, 0, - fx + 1, cis); + if (regex) + found = window_copy_search_rl_regex(gd, sgd, + &px, &sx, i - 1, 0, fx + 1, cis); + else + found = window_copy_search_rl(gd, sgd, + &px, i - 1, 0, fx + 1, cis); if (found) { i--; break; @@ -2240,7 +2550,7 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, return (window_copy_search_jump(wme, gd, sgd, direction ? 0 : gd->sx - 1, direction ? 0 : gd->hsize + gd->sy - 1, fy, cis, 0, - direction)); + direction, regex)); } return (0); } @@ -2250,7 +2560,7 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, * down. */ static int -window_copy_search(struct window_mode_entry *wme, int direction) +window_copy_search(struct window_mode_entry *wme, int direction, int regex) { struct window_pane *wp = wme->wp; struct window_copy_mode_data *data = wme->data; @@ -2281,10 +2591,11 @@ window_copy_search(struct window_mode_entry *wme, int direction) window_copy_move_left(s, &fx, &fy, wrapflag); endline = 0; } - found = window_copy_search_jump(wme, gd, ss.grid, fx, fy, endline, cis, - wrapflag, direction); - if (window_copy_search_marks(wme, &ss)) + found = window_copy_search_jump(wme, gd, ss.grid, fx, fy, endline, cis, + wrapflag, direction, regex); + + if (window_copy_search_marks(wme, &ss, regex)) window_copy_redraw_screen(wme); screen_free(&ss); @@ -2292,7 +2603,8 @@ window_copy_search(struct window_mode_entry *wme, int direction) } static int -window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp) +window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, + int regex) { struct window_copy_mode_data *data = wme->data; struct screen *s = data->backing, ss; @@ -2320,10 +2632,19 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp) for (py = 0; py < gd->hsize + gd->sy; py++) { px = 0; for (;;) { - found = window_copy_search_lr(gd, ssp->grid, &px, py, - px, gd->sx, cis); - if (!found) - break; + if (regex) { + found = window_copy_search_lr_regex(gd, + ssp->grid, &px, &width, py, px, + gd->sx, cis); + if (!found) + break; + } + else { + found = window_copy_search_lr(gd, ssp->grid, + &px, py, px, gd->sx, cis); + if (!found) + break; + } nfound++; if (px == data->cx && py == gd->hsize + data->cy - data->oy) @@ -2357,15 +2678,15 @@ window_copy_clear_marks(struct window_mode_entry *wme) } static int -window_copy_search_up(struct window_mode_entry *wme) +window_copy_search_up(struct window_mode_entry *wme, int regex) { - return (window_copy_search(wme, 0)); + return (window_copy_search(wme, 0, regex)); } static int -window_copy_search_down(struct window_mode_entry *wme) +window_copy_search_down(struct window_mode_entry *wme, int regex) { - return (window_copy_search(wme, 1)); + return (window_copy_search(wme, 1, regex)); } static void diff --git a/window-tree.c b/window-tree.c index 7ba14054..009e93c6 100644 --- a/window-tree.c +++ b/window-tree.c @@ -33,7 +33,7 @@ static void window_tree_key(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); -#define WINDOW_TREE_DEFAULT_COMMAND "switch-client -t '%%'" +#define WINDOW_TREE_DEFAULT_COMMAND "switch-client -Zt '%%'" #define WINDOW_TREE_DEFAULT_FORMAT \ "#{?pane_format," \ diff --git a/window.c b/window.c index 96632058..015738c9 100644 --- a/window.c +++ b/window.c @@ -1239,7 +1239,7 @@ window_pane_reset_mode_all(struct window_pane *wp) window_pane_reset_mode(wp); } -void +int window_pane_key(struct window_pane *wp, struct client *c, struct session *s, struct winlink *wl, key_code key, struct mouse_event *m) { @@ -1247,23 +1247,24 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s, struct window_pane *wp2; if (KEYC_IS_MOUSE(key) && m == NULL) - return; + return (-1); wme = TAILQ_FIRST(&wp->modes); if (wme != NULL) { wp->modelast = time(NULL); if (wme->mode->key != NULL) wme->mode->key(wme, c, s, wl, (key & ~KEYC_XTERM), m); - return; + return (0); } if (wp->fd == -1 || wp->flags & PANE_INPUTOFF) - return; + return (0); - input_key(wp, key, m); + if (input_key(wp, key, m) != 0) + return (-1); if (KEYC_IS_MOUSE(key)) - return; + return (0); if (options_get_number(wp->window->options, "synchronize-panes")) { TAILQ_FOREACH(wp2, &wp->window->panes, entry) { if (wp2 != wp && @@ -1274,6 +1275,7 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s, input_key(wp2, key, NULL); } } + return (0); } int