From d0d2c39decd1c342f2ffdb360e5d6b509b9bb34e Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 6 Jul 2022 07:36:36 +0000 Subject: [PATCH] Support hyperlinks with capture-pane -e and add a mouse_hyperlink format, GitHub issue 3247 from Jeff Chiang. --- cmd-capture-pane.c | 2 +- format.c | 39 +++++++++++++++++++++++++ grid-view.c | 2 +- grid.c | 72 ++++++++++++++++++++++++++++++++++++++++------ hyperlinks.c | 7 +++-- key-bindings.c | 3 ++ tmux.1 | 1 + tmux.h | 6 ++-- tty.c | 2 +- 9 files changed, 118 insertions(+), 16 deletions(-) diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index 04a88172..422f87d6 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -177,7 +177,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, buf = NULL; for (i = top; i <= bottom; i++) { line = grid_string_cells(gd, 0, i, sx, &gc, with_codes, - escape_c0, !join_lines && !no_trim); + escape_c0, !join_lines && !no_trim, wp->screen); linelen = strlen(line); buf = cmd_capture_pane_append(buf, len, line, linelen); diff --git a/format.c b/format.c index b97c842c..edf4217c 100644 --- a/format.c +++ b/format.c @@ -1145,6 +1145,25 @@ format_cb_mouse_word(struct format_tree *ft) return (format_grid_word(gd, x, gd->hsize + y)); } +/* Callback for mouse_hyperlink. */ +static void * +format_cb_mouse_hyperlink(struct format_tree *ft) +{ + struct window_pane *wp; + struct grid *gd; + u_int x, y; + + if (!ft->m.valid) + return (NULL); + wp = cmd_mouse_pane(&ft->m, NULL, NULL); + if (wp == NULL) + return (NULL); + if (cmd_mouse_at(wp, &ft->m, &x, &y, 0) != 0) + return (NULL); + gd = wp->base.grid; + return (format_grid_hyperlink(gd, x, gd->hsize + y, wp->screen)); +} + /* Callback for mouse_line. */ static void * format_cb_mouse_line(struct format_tree *ft) @@ -2789,6 +2808,9 @@ static const struct format_table_entry format_table[] = { { "mouse_button_flag", FORMAT_TABLE_STRING, format_cb_mouse_button_flag }, + { "mouse_hyperlink", FORMAT_TABLE_STRING, + format_cb_mouse_hyperlink + }, { "mouse_line", FORMAT_TABLE_STRING, format_cb_mouse_line }, @@ -5064,3 +5086,20 @@ format_grid_line(struct grid *gd, u_int y) } return (s); } + +/* Return hyperlink at given coordinates. Caller frees. */ +char * +format_grid_hyperlink(struct grid *gd, u_int x, u_int y, struct screen* s) +{ + const char *uri; + struct grid_cell gc; + + grid_get_cell(gd, x, y, &gc); + if (gc.flags & GRID_FLAG_PADDING) + return (NULL); + if (s->hyperlinks == NULL || gc.link == 0) + return (NULL); + if (!hyperlinks_get(s->hyperlinks, gc.link, &uri, NULL, NULL)) + return (NULL); + return (xstrdup(uri)); +} diff --git a/grid-view.c b/grid-view.c index f230d3c8..689ac4e4 100644 --- a/grid-view.c +++ b/grid-view.c @@ -231,5 +231,5 @@ grid_view_string_cells(struct grid *gd, u_int px, u_int py, u_int nx) px = grid_view_x(gd, px); py = grid_view_y(gd, py); - return (grid_string_cells(gd, px, py, nx, NULL, 0, 0, 0)); + return (grid_string_cells(gd, px, py, nx, NULL, 0, 0, 0, NULL)); } diff --git a/grid.c b/grid.c index 828def68..b1afd398 100644 --- a/grid.c +++ b/grid.c @@ -885,18 +885,47 @@ grid_string_cells_add_code(char *buf, size_t len, u_int n, int *s, int *newc, } } +static int +grid_string_cells_add_hyperlink(char *buf, size_t len, const char *id, + const char *uri, int escape_c0) +{ + char *tmp; + + if (strlen(uri) + strlen(id) + 17 >= len) + return (0); + + if (escape_c0) + strlcat(buf, "\\033]8;", len); + else + strlcat(buf, "\033]8;", len); + if (*id != '\0') { + xasprintf(&tmp, "id=%s;", id); + strlcat(buf, tmp, len); + free(tmp); + } else + strlcat(buf, ";", len); + strlcat(buf, uri, len); + if (escape_c0) + strlcat(buf, "\\033\\\\", len); + else + strlcat(buf, "\033\\", len); + return (1); +} + /* * Returns ANSI code to set particular attributes (colour, bold and so on) * given a current state. */ static void grid_string_cells_code(const struct grid_cell *lastgc, - const struct grid_cell *gc, char *buf, size_t len, int escape_c0) + const struct grid_cell *gc, char *buf, size_t len, int escape_c0, + struct screen *sc, int *has_link) { - int oldc[64], newc[64], s[128]; - size_t noldc, nnewc, n, i; - u_int attr = gc->attr, lastattr = lastgc->attr; - char tmp[64]; + int oldc[64], newc[64], s[128]; + size_t noldc, nnewc, n, i; + u_int attr = gc->attr, lastattr = lastgc->attr; + char tmp[64]; + const char *uri, *id; struct { u_int mask; @@ -986,19 +1015,32 @@ grid_string_cells_code(const struct grid_cell *lastgc, else strlcat(buf, "\017", len); /* SI */ } + + /* Add hyperlink if changed. */ + if (sc != NULL && sc->hyperlinks != NULL && lastgc->link != gc->link) { + if (hyperlinks_get(sc->hyperlinks, gc->link, &uri, &id, NULL)) { + *has_link = grid_string_cells_add_hyperlink(buf, len, + id, uri, escape_c0); + } else if (*has_link) { + grid_string_cells_add_hyperlink(buf, len, "", "", + escape_c0); + *has_link = 0; + } + } } /* Convert cells into a string. */ char * grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, - struct grid_cell **lastgc, int with_codes, int escape_c0, int trim) + struct grid_cell **lastgc, int with_codes, int escape_c0, int trim, + struct screen *s) { struct grid_cell gc; static struct grid_cell lastgc1; const char *data; - char *buf, code[128]; + char *buf, code[8192]; size_t len, off, size, codelen; - u_int xx; + u_int xx, has_link = 0; const struct grid_line *gl; if (lastgc != NULL && *lastgc == NULL) { @@ -1020,7 +1062,7 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, if (with_codes) { grid_string_cells_code(*lastgc, &gc, code, sizeof code, - escape_c0); + escape_c0, s, &has_link); codelen = strlen(code); memcpy(*lastgc, &gc, sizeof **lastgc); } else @@ -1046,6 +1088,18 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, off += size; } + if (has_link) { + grid_string_cells_add_hyperlink(code, sizeof code, "", "", + escape_c0); + codelen = strlen(code); + while (len < off + size + codelen + 1) { + buf = xreallocarray(buf, 2, len); + len *= 2; + } + memcpy(buf + off, code, codelen); + off += codelen; + } + if (trim) { while (off > 0 && buf[off - 1] == ' ') off--; diff --git a/hyperlinks.c b/hyperlinks.c index 3dd3efa9..18163cb2 100644 --- a/hyperlinks.c +++ b/hyperlinks.c @@ -179,7 +179,7 @@ hyperlinks_put(struct hyperlinks *hl, const char *uri_in, /* Get hyperlink by inner number. */ int hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out, - const char **external_id_out) + const char **internal_id_out, const char **external_id_out) { struct hyperlinks_uri find, *hlu; @@ -188,7 +188,10 @@ hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out, hlu = RB_FIND(hyperlinks_by_inner_tree, &hl->by_inner, &find); if (hlu == NULL) return (0); - *external_id_out = hlu->external_id; + if (internal_id_out != NULL) + *internal_id_out = hlu->internal_id; + if (external_id_out != NULL) + *external_id_out = hlu->external_id; *uri_out = hlu->uri; return (1); } diff --git a/key-bindings.c b/key-bindings.c index 95171966..bcc6004d 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -54,6 +54,9 @@ " '#{?mouse_word,Copy #[underscore]#{=/9/...:mouse_word},}' 'c' {copy-mode -q; set-buffer -- \"#{q:mouse_word}\"}" \ " '#{?mouse_line,Copy Line,}' 'l' {copy-mode -q; set-buffer -- \"#{q:mouse_line}\"}" \ " ''" \ + " '#{?mouse_hyperlink,Type #[underscore]#{=/9/...:mouse_hyperlink},}' 'C-h' {copy-mode -q; send-keys -l -- \"#{q:mouse_hyperlink}\"}" \ + " '#{?mouse_hyperlink,Copy #[underscore]#{=/9/...:mouse_hyperlink},}' 'h' {copy-mode -q; set-buffer -- \"#{q:mouse_hyperlink}\"}" \ + " ''" \ " 'Horizontal Split' 'h' {split-window -h}" \ " 'Vertical Split' 'v' {split-window -v}" \ " ''" \ diff --git a/tmux.1 b/tmux.1 index c7ff6cea..ee6740ac 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5160,6 +5160,7 @@ The following variables are available, where appropriate: .It Li "mouse_all_flag" Ta "" Ta "Pane mouse all flag" .It Li "mouse_any_flag" Ta "" Ta "Pane mouse any flag" .It Li "mouse_button_flag" Ta "" Ta "Pane mouse button flag" +.It Li "mouse_hyperlink" Ta "" Ta "Hyperlink under mouse, if any" .It Li "mouse_line" Ta "" Ta "Line under mouse, if any" .It Li "mouse_sgr_flag" Ta "" Ta "Pane mouse SGR flag" .It Li "mouse_standard_flag" Ta "" Ta "Pane mouse standard flag" diff --git a/tmux.h b/tmux.h index 71245ffc..3137ca3b 100644 --- a/tmux.h +++ b/tmux.h @@ -2126,6 +2126,8 @@ void format_defaults_paste_buffer(struct format_tree *, struct paste_buffer *); void format_lost_client(struct client *); char *format_grid_word(struct grid *, u_int, u_int); +char *format_grid_hyperlink(struct grid *, u_int, u_int, + struct screen *); char *format_grid_line(struct grid *, u_int); /* format-draw.c */ @@ -2772,7 +2774,7 @@ void grid_clear_lines(struct grid *, u_int, u_int, u_int); void grid_move_lines(struct grid *, u_int, u_int, u_int, u_int); void grid_move_cells(struct grid *, u_int, u_int, u_int, u_int, u_int); char *grid_string_cells(struct grid *, u_int, u_int, u_int, - struct grid_cell **, int, int, int); + struct grid_cell **, int, int, int, struct screen *); void grid_duplicate_lines(struct grid *, u_int, struct grid *, u_int, u_int); void grid_reflow(struct grid *, u_int); @@ -3312,7 +3314,7 @@ uid_t server_acl_get_uid(struct server_acl_user *); u_int hyperlinks_put(struct hyperlinks *, const char *, const char *); int hyperlinks_get(struct hyperlinks *, u_int, - const char **, const char **); + const char **, const char **, const char **); struct hyperlinks *hyperlinks_init(void); void hyperlinks_reset(struct hyperlinks *); void hyperlinks_free(struct hyperlinks *); diff --git a/tty.c b/tty.c index 78961e47..1f373821 100644 --- a/tty.c +++ b/tty.c @@ -2501,7 +2501,7 @@ tty_hyperlink(struct tty *tty, const struct grid_cell *gc, if (hl == NULL) return; - if (gc->link == 0 || !hyperlinks_get(hl, gc->link, &uri, &id)) + if (gc->link == 0 || !hyperlinks_get(hl, gc->link, &uri, NULL, &id)) tty_putcode_ptr2(tty, TTYC_HLS, "", ""); else tty_putcode_ptr2(tty, TTYC_HLS, id, uri);