diff --git a/mode-key.c b/mode-key.c index aed161bb..aaa5e0d0 100644 --- a/mode-key.c +++ b/mode-key.c @@ -140,12 +140,14 @@ const struct mode_key_cmdstr mode_key_cmdstr_copy[] = { { MODEKEYCOPY_RECTANGLETOGGLE, "rectangle-toggle" }, { MODEKEYCOPY_MIDDLELINE, "middle-line" }, { MODEKEYCOPY_NEXTPAGE, "page-down" }, + { MODEKEYCOPY_NEXTPARAGRAPH, "next-paragraph" }, { MODEKEYCOPY_NEXTSPACE, "next-space" }, { MODEKEYCOPY_NEXTSPACEEND, "next-space-end" }, { MODEKEYCOPY_NEXTWORD, "next-word" }, { MODEKEYCOPY_NEXTWORDEND, "next-word-end" }, { MODEKEYCOPY_OTHEREND, "other-end" }, { MODEKEYCOPY_PREVIOUSPAGE, "page-up" }, + { MODEKEYCOPY_PREVIOUSPARAGRAPH, "previous-paragraph" }, { MODEKEYCOPY_PREVIOUSSPACE, "previous-space" }, { MODEKEYCOPY_PREVIOUSWORD, "previous-word" }, { MODEKEYCOPY_RIGHT, "cursor-right" }, @@ -335,6 +337,8 @@ const struct mode_key_entry mode_key_vi_copy[] = { { 'q', 0, MODEKEYCOPY_CANCEL }, { 'v', 0, MODEKEYCOPY_RECTANGLETOGGLE }, { 'w', 0, MODEKEYCOPY_NEXTWORD }, + { '{', 0, MODEKEYCOPY_PREVIOUSPARAGRAPH }, + { '}', 0, MODEKEYCOPY_NEXTPARAGRAPH }, { KEYC_BSPACE, 0, MODEKEYCOPY_LEFT }, { KEYC_DOWN | KEYC_CTRL, 0, MODEKEYCOPY_SCROLLDOWN }, { KEYC_DOWN, 0, MODEKEYCOPY_DOWN }, @@ -483,6 +487,8 @@ const struct mode_key_entry mode_key_emacs_copy[] = { { 't', 0, MODEKEYCOPY_JUMPTO }, { 'v' | KEYC_ESCAPE, 0, MODEKEYCOPY_PREVIOUSPAGE }, { 'w' | KEYC_ESCAPE, 0, MODEKEYCOPY_COPYSELECTION }, + { '{' | KEYC_ESCAPE, 0, MODEKEYCOPY_PREVIOUSPARAGRAPH }, + { '}' | KEYC_ESCAPE, 0, MODEKEYCOPY_NEXTPARAGRAPH }, { KEYC_DOWN | KEYC_CTRL, 0, MODEKEYCOPY_SCROLLDOWN }, { KEYC_DOWN | KEYC_ESCAPE, 0, MODEKEYCOPY_HALFPAGEDOWN }, { KEYC_DOWN, 0, MODEKEYCOPY_DOWN }, diff --git a/tmux.1 b/tmux.1 index 776b6faf..b0d5afae 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1029,6 +1029,7 @@ The following keys are supported as appropriate for the mode: .It Li "Jump to backward" Ta "T" Ta "" .It Li "Jump to forward" Ta "t" Ta "" .It Li "Next page" Ta "C-f" Ta "Page down" +.It Li "Next paragraph" Ta "}" Ta "M-}" .It Li "Next space" Ta "W" Ta "" .It Li "Next space, end of word" Ta "E" Ta "" .It Li "Next word" Ta "w" Ta "" @@ -1036,6 +1037,7 @@ The following keys are supported as appropriate for the mode: .It Li "Other end of selection" Ta "o" Ta "" .It Li "Paste buffer" Ta "p" Ta "C-y" .It Li "Previous page" Ta "C-b" Ta "Page up" +.It Li "Previous paragraph" Ta "{" Ta "M-{" .It Li "Previous space" Ta "B" Ta "" .It Li "Previous word" Ta "b" Ta "M-b" .It Li "Quit mode" Ta "q" Ta "Escape" diff --git a/tmux.h b/tmux.h index aa3e4967..b0b13f5c 100644 --- a/tmux.h +++ b/tmux.h @@ -538,12 +538,14 @@ enum mode_key_cmd { MODEKEYCOPY_LEFT, MODEKEYCOPY_MIDDLELINE, MODEKEYCOPY_NEXTPAGE, + MODEKEYCOPY_NEXTPARAGRAPH, MODEKEYCOPY_NEXTSPACE, MODEKEYCOPY_NEXTSPACEEND, MODEKEYCOPY_NEXTWORD, MODEKEYCOPY_NEXTWORDEND, MODEKEYCOPY_OTHEREND, MODEKEYCOPY_PREVIOUSPAGE, + MODEKEYCOPY_PREVIOUSPARAGRAPH, MODEKEYCOPY_PREVIOUSSPACE, MODEKEYCOPY_PREVIOUSWORD, MODEKEYCOPY_RECTANGLETOGGLE, diff --git a/tty-keys.c b/tty-keys.c index 105f99f7..efbc1f28 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -33,14 +33,17 @@ * into a ternary tree. */ -void tty_keys_add1(struct tty_key **, const char *, key_code); -void tty_keys_add(struct tty *, const char *, key_code); -void tty_keys_free1(struct tty_key *); -struct tty_key *tty_keys_find1(struct tty_key *, const char *, size_t, +static void tty_keys_add1(struct tty_key **, const char *, key_code); +static void tty_keys_add(struct tty *, const char *, key_code); +static void tty_keys_free1(struct tty_key *); +static struct tty_key *tty_keys_find1(struct tty_key *, const char *, size_t, size_t *); -struct tty_key *tty_keys_find(struct tty *, const char *, size_t, size_t *); -void tty_keys_callback(int, short, void *); -int tty_keys_mouse(struct tty *, const char *, size_t, size_t *); +static struct tty_key *tty_keys_find(struct tty *, const char *, size_t, + size_t *); +static int tty_keys_next1(struct tty *, const char *, size_t, key_code *, + size_t *); +static void tty_keys_callback(int, short, void *); +static int tty_keys_mouse(struct tty *, const char *, size_t, size_t *); /* Default raw keys. */ struct tty_default_key_raw { @@ -316,7 +319,7 @@ const struct tty_default_key_code tty_default_code_keys[] = { }; /* Add key to tree. */ -void +static void tty_keys_add(struct tty *tty, const char *s, key_code key) { struct tty_key *tk; @@ -334,7 +337,7 @@ tty_keys_add(struct tty *tty, const char *s, key_code key) } /* Add next node to the tree. */ -void +static void tty_keys_add1(struct tty_key **tkp, const char *s, key_code key) { struct tty_key *tk; @@ -409,7 +412,7 @@ tty_keys_free(struct tty *tty) } /* Free a single key. */ -void +static void tty_keys_free1(struct tty_key *tk) { if (tk->next != NULL) @@ -422,7 +425,7 @@ tty_keys_free1(struct tty_key *tk) } /* Lookup a key in the tree. */ -struct tty_key * +static struct tty_key * tty_keys_find(struct tty *tty, const char *buf, size_t len, size_t *size) { *size = 0; @@ -430,7 +433,7 @@ tty_keys_find(struct tty *tty, const char *buf, size_t len, size_t *size) } /* Find the next node. */ -struct tty_key * +static struct tty_key * tty_keys_find1(struct tty_key *tk, const char *buf, size_t len, size_t *size) { /* If the node is NULL, this is the end of the tree. No match. */ @@ -460,6 +463,56 @@ tty_keys_find1(struct tty_key *tk, const char *buf, size_t len, size_t *size) return (tty_keys_find1(tk, buf, len, size)); } +/* Look up part of the next key. */ +static int +tty_keys_next1(struct tty *tty, const char *buf, size_t len, key_code *key, + size_t *size) +{ + struct tty_key *tk, *tk1; + struct utf8_data ud; + enum utf8_state more; + u_int i; + wchar_t wc; + + log_debug("next key is %zu (%.*s)", len, (int)len, buf); + + /* Empty buffer is a partial key. */ + if (len == 0) + return (1); + + /* Is this a known key? */ + tk = tty_keys_find(tty, buf, len, size); + if (tk != NULL) { + tk1 = tk; + do + log_debug("keys in list: %#llx", tk->key); + while ((tk1 = tk1->next) != NULL); + *key = tk->key; + return (tk->next != NULL); + } + + /* Is this valid UTF-8? */ + more = utf8_open(&ud, (u_char)*buf); + if (more == UTF8_MORE) { + *size = ud.size; + if (len < ud.size) + return (1); + for (i = 1; i < ud.size; i++) + more = utf8_append(&ud, (u_char)buf[i]); + if (more != UTF8_DONE) + return (0); + + if (utf8_combine(&ud, &wc) != UTF8_DONE) + return (0); + *key = wc; + + log_debug("UTF-8 key %.*s %#llx", (int)ud.size, buf, *key); + return (0); + } + + return (-1); +} + /* * Process at least one key in the buffer and invoke tty->key_callback. Return * 0 if there are no further keys, or 1 if there could be more in the buffer. @@ -467,17 +520,12 @@ tty_keys_find1(struct tty_key *tk, const char *buf, size_t len, size_t *size) key_code tty_keys_next(struct tty *tty) { - struct tty_key *tk; - struct timeval tv; - const char *buf; - size_t len, size; - cc_t bspace; - int delay, expired = 0; - key_code key; - struct utf8_data ud; - enum utf8_state more; - u_int i; - wchar_t wc; + struct timeval tv; + const char *buf; + size_t len, size; + cc_t bspace; + int delay, expired = 0; + key_code key; /* Get key buffer. */ buf = EVBUFFER_DATA(tty->event->input); @@ -485,7 +533,7 @@ tty_keys_next(struct tty *tty) if (len == 0) return (0); - log_debug("keys are %zu (%.*s)", len, (int) len, buf); + log_debug("keys are %zu (%.*s)", len, (int)len, buf); /* Is this a mouse key press? */ switch (tty_keys_mouse(tty, buf, len, &size)) { @@ -501,68 +549,65 @@ tty_keys_next(struct tty *tty) goto partial_key; } - /* Look for matching key string and return if found. */ - tk = tty_keys_find(tty, buf, len, &size); - if (tk != NULL) { - if (tk->next != NULL) +first_key: + /* If escape is at the start, try without it. */ + if (*buf == '\033') { + switch (tty_keys_next1 (tty, buf + 1, len - 1, &key, &size)) { + case 0: /* found */ + if (key != KEYC_UNKNOWN) + key |= KEYC_ESCAPE; + size++; /* include escape */ + goto complete_key; + case -1: /* not found */ + break; + case 1: + if (expired) + goto complete_key; goto partial_key; - key = tk->key; - goto complete_key; + } } - /* Try to parse a key with an xterm-style modifier. */ + /* Try with the escape. */ + switch (tty_keys_next1 (tty, buf, len, &key, &size)) { + case 0: /* found */ + goto complete_key; + case -1: /* not found */ + break; + case 1: + if (expired) + goto complete_key; + goto partial_key; + } + + /* Is this an an xterm(1) key? */ switch (xterm_keys_find(buf, len, &size, &key)) { case 0: /* found */ goto complete_key; case -1: /* not found */ break; case 1: + if (expired) + break; goto partial_key; } -first_key: - /* Is this a meta key? */ - if (len >= 2 && buf[0] == '\033') { - if (buf[1] != '\033') { - key = buf[1] | KEYC_ESCAPE; + /* + * If this starts with escape and is at least two keys, it must be + * complete even if the timer has not expired, because otherwise + * tty_keys_next1 would have found a partial key. If just an escape + * alone, it needs to wait for the timer first. + */ + if (*buf == '\033') { + if (len >= 2) { + key = (u_char)buf[1] | KEYC_ESCAPE; size = 2; goto complete_key; } - - tk = tty_keys_find(tty, buf + 1, len - 1, &size); - if (tk != NULL && (!expired || tk->next == NULL)) { - size++; /* include escape */ - if (tk->next != NULL) - goto partial_key; - key = tk->key; - if (key != KEYC_UNKNOWN) - key |= KEYC_ESCAPE; - goto complete_key; - } - } - - /* Is this valid UTF-8? */ - if ((more = utf8_open(&ud, (u_char)*buf) == UTF8_MORE)) { - size = ud.size; - if (len < size) { - if (expired) - goto discard_key; + if (!expired) goto partial_key; - } - for (i = 1; i < size; i++) - more = utf8_append(&ud, (u_char)buf[i]); - if (more != UTF8_DONE) - goto discard_key; - - if (utf8_combine(&ud, &wc) != UTF8_DONE) - goto discard_key; - key = wc; - - log_debug("UTF-8 key %.*s %#llx", (int)size, buf, key); - goto complete_key; } - /* No key found, take first. */ + /* No longer key found, use the first character. */ key = (u_char)*buf; size = 1; @@ -578,7 +623,7 @@ first_key: goto complete_key; partial_key: - log_debug("partial key %.*s", (int) len, buf); + log_debug("partial key %.*s", (int)len, buf); /* If timer is going, check for expiration. */ if (tty->flags & TTY_TIMER) { @@ -640,7 +685,7 @@ discard_key: } /* Key timer callback. */ -void +static void tty_keys_callback(__unused int fd, __unused short events, void *data) { struct tty *tty = data; @@ -655,7 +700,7 @@ tty_keys_callback(__unused int fd, __unused short events, void *data) * Handle mouse key input. Returns 0 for success, -1 for failure, 1 for partial * (probably a mouse sequence but need more data). */ -int +static int tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size) { struct mouse_event *m = &tty->mouse; diff --git a/utf8.c b/utf8.c index 54cea671..87dce597 100644 --- a/utf8.c +++ b/utf8.c @@ -118,6 +118,14 @@ utf8_width(wchar_t wc) width = wcwidth(wc); if (width < 0 || width > 0xff) { log_debug("Unicode %04x, wcwidth() %d", wc, width); + + /* + * Many platforms have no width for relatively common + * characters (wcwidth() returns -1); assume width 1 in this + * case and hope for the best. + */ + if (width < 0) + return (1); return (-1); } return (width); diff --git a/window-copy.c b/window-copy.c index 5c907c3d..a97d6dbe 100644 --- a/window-copy.c +++ b/window-copy.c @@ -27,6 +27,8 @@ struct screen *window_copy_init(struct window_pane *); void window_copy_free(struct window_pane *); void window_copy_pagedown(struct window_pane *); +void window_copy_next_paragraph(struct window_pane *); +void window_copy_previous_paragraph(struct window_pane *); void window_copy_resize(struct window_pane *, u_int, u_int); void window_copy_key(struct window_pane *, struct client *, struct session *, key_code, struct mouse_event *); @@ -403,6 +405,44 @@ window_copy_pagedown(struct window_pane *wp) window_copy_redraw_screen(wp); } +void +window_copy_previous_paragraph(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + u_int ox, oy; + + oy = screen_hsize(data->backing) + data->cy - data->oy; + ox = window_copy_find_length(wp, oy); + + while (oy > 0 && window_copy_find_length(wp, oy) == 0) + oy--; + + while (oy > 0 && window_copy_find_length(wp, oy) > 0) + oy--; + + window_copy_scroll_to(wp, 0, oy); +} + +void +window_copy_next_paragraph(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + u_int maxy, ox, oy; + + oy = screen_hsize(data->backing) + data->cy - data->oy; + maxy = screen_hsize(data->backing) + screen_size_y(s) - 1; + + while (oy < maxy && window_copy_find_length(wp, oy) == 0) + oy++; + + while (oy < maxy && window_copy_find_length(wp, oy) > 0) + oy++; + + ox = window_copy_find_length(wp, oy); + window_copy_scroll_to(wp, ox, oy); +} + void window_copy_resize(struct window_pane *wp, u_int sx, u_int sy) { @@ -548,6 +588,14 @@ window_copy_key(struct window_pane *wp, struct client *c, struct session *sess, for (; np != 0; np--) window_copy_pagedown(wp); break; + case MODEKEYCOPY_PREVIOUSPARAGRAPH: + for (; np != 0; np--) + window_copy_previous_paragraph(wp); + break; + case MODEKEYCOPY_NEXTPARAGRAPH: + for (; np != 0; np--) + window_copy_next_paragraph(wp); + break; case MODEKEYCOPY_HALFPAGEUP: n = screen_size_y(s) / 2; for (; np != 0; np--) {