diff --git a/Makefile.am b/Makefile.am index fb94e820..14d8874b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -150,6 +150,7 @@ dist_tmux_SOURCES = \ grid-reader.c \ grid-view.c \ grid.c \ + hyperlinks.c \ input-keys.c \ input.c \ job.c \ diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index a3e84c46..04a88172 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -53,8 +53,8 @@ const struct cmd_entry cmd_clear_history_entry = { .name = "clear-history", .alias = "clearhist", - .args = { "t:", 0, 0, NULL }, - .usage = CMD_TARGET_PANE_USAGE, + .args = { "Ht:", 0, 0, NULL }, + .usage = "[-H] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -204,6 +204,8 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item) if (cmd_get_entry(self) == &cmd_clear_history_entry) { window_pane_reset_mode_all(wp); grid_clear_history(wp->base.grid); + if (args_has(args, 'H')) + screen_reset_hyperlinks(wp->screen); return (CMD_RETURN_NORMAL); } diff --git a/cmd-display-panes.c b/cmd-display-panes.c index 5773a2d0..06f6dc27 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -144,7 +144,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, llen = 0; if (sx < len * 6 || sy < 5) { - tty_attributes(tty, &fgc, &grid_default_cell, NULL); + tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL); if (sx >= len + llen + 1) { len += llen + 1; tty_cursor(tty, xoff + px - len / 2, yoff + py); @@ -161,7 +161,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, px -= len * 3; py -= 2; - tty_attributes(tty, &bgc, &grid_default_cell, NULL); + tty_attributes(tty, &bgc, &grid_default_cell, NULL, NULL); for (ptr = buf; *ptr != '\0'; ptr++) { if (*ptr < '0' || *ptr > '9') continue; @@ -179,7 +179,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, if (sy <= 6) goto out; - tty_attributes(tty, &fgc, &grid_default_cell, NULL); + tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL); if (rlen != 0 && sx >= rlen) { tty_cursor(tty, xoff + sx - rlen, yoff); tty_putn(tty, rbuf, rlen, rlen); diff --git a/grid.c b/grid.c index ba251e70..828def68 100644 --- a/grid.c +++ b/grid.c @@ -37,7 +37,7 @@ /* Default grid cell data. */ const struct grid_cell grid_default_cell = { - { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 + { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 }; /* @@ -45,12 +45,12 @@ const struct grid_cell grid_default_cell = { * appears in the grid - because of this, they are always extended cells. */ static const struct grid_cell grid_padding_cell = { - { { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 0 + { { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 0, 0 }; /* Cleared grid cell data. */ static const struct grid_cell grid_cleared_cell = { - { { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 0 + { { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 0, 0 }; static const struct grid_cell_entry grid_cleared_entry = { GRID_FLAG_CLEARED, { .data = { 0, 8, 8, ' ' } } @@ -90,6 +90,8 @@ grid_need_extended_cell(const struct grid_cell_entry *gce, return (1); if (gc->us != 0) /* only supports 256 or RGB */ return (1); + if (gc->link != 0) + return (1); return (0); } @@ -131,6 +133,7 @@ grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, gee->fg = gc->fg; gee->bg = gc->bg; gee->us = gc->us; + gee->link = gc->link; return (gee); } @@ -231,6 +234,8 @@ grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2) return (0); if (gc1->attr != gc2->attr || gc1->flags != gc2->flags) return (0); + if (gc1->link != gc2->link) + return (0); return (1); } @@ -509,6 +514,7 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) gc->fg = gee->fg; gc->bg = gee->bg; gc->us = gee->us; + gc->link = gee->link; utf8_to_data(gee->data, &gc->data); } return; @@ -524,6 +530,7 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) gc->bg |= COLOUR_FLAG_256; gc->us = 0; utf8_set(&gc->data, gce->data.data); + gc->link = 0; } /* Get cell for reading. */ diff --git a/hyperlinks.c b/hyperlinks.c new file mode 100644 index 00000000..3dd3efa9 --- /dev/null +++ b/hyperlinks.c @@ -0,0 +1,225 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2021 Will + * Copyright (c) 2022 Jeff Chiang + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include + +#include "tmux.h" + +/* + * OSC 8 hyperlinks, described at: + * + * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda + * + * Each hyperlink and ID combination is assigned a number ("inner" in this + * file) which is stored in an extended grid cell and maps into a tree here. + * + * Each URI has one inner number and one external ID (which tmux uses to send + * the hyperlink to the terminal) and one internal ID (which is received from + * the sending application inside tmux). + * + * Anonymous hyperlinks are each unique and are not reused even if they have + * the same URI (terminals will not want to tie them together). + */ + +#define MAX_HYPERLINKS 5000 + +static uint64_t hyperlinks_next_external_id = 1; +static u_int global_hyperlinks_count; + +struct hyperlinks_uri { + struct hyperlinks *tree; + + u_int inner; + const char *internal_id; + const char *external_id; + const char *uri; + + TAILQ_ENTRY(hyperlinks_uri) list_entry; + RB_ENTRY(hyperlinks_uri) by_inner_entry; + RB_ENTRY(hyperlinks_uri) by_uri_entry; /* by internal ID and URI */ +}; +RB_HEAD(hyperlinks_by_uri_tree, hyperlinks_uri); +RB_HEAD(hyperlinks_by_inner_tree, hyperlinks_uri); + +TAILQ_HEAD(hyperlinks_list, hyperlinks_uri); +static struct hyperlinks_list global_hyperlinks = + TAILQ_HEAD_INITIALIZER(global_hyperlinks); + +struct hyperlinks { + u_int next_inner; + struct hyperlinks_by_inner_tree by_inner; + struct hyperlinks_by_uri_tree by_uri; +}; + +static int +hyperlinks_by_uri_cmp(struct hyperlinks_uri *left, struct hyperlinks_uri *right) +{ + int r; + + if (*left->internal_id == '\0' || *right->internal_id == '\0') { + /* + * If both URIs are anonymous, use the inner for comparison so + * that they do not match even if the URI is the same - each + * anonymous URI should be unique. + */ + if (*left->internal_id != '\0') + return (-1); + if (*right->internal_id != '\0') + return (1); + return (left->inner - right->inner); + } + + r = strcmp(left->internal_id, right->internal_id); + if (r != 0) + return (r); + return (strcmp(left->uri, right->uri)); +} +RB_PROTOTYPE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, + hyperlinks_by_uri_cmp); +RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, + hyperlinks_by_uri_cmp); + +static int +hyperlinks_by_inner_cmp(struct hyperlinks_uri *left, + struct hyperlinks_uri *right) +{ + return (left->inner - right->inner); +} +RB_PROTOTYPE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, + hyperlinks_by_inner_cmp); +RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, + hyperlinks_by_inner_cmp); + +/* Remove a hyperlink. */ +static void +hyperlinks_remove(struct hyperlinks_uri *hlu) +{ + struct hyperlinks *hl = hlu->tree; + + TAILQ_REMOVE(&global_hyperlinks, hlu, list_entry); + global_hyperlinks_count--; + + RB_REMOVE(hyperlinks_by_inner_tree, &hl->by_inner, hlu); + RB_REMOVE(hyperlinks_by_uri_tree, &hl->by_uri, hlu); + + free((void *)hlu->internal_id); + free((void *)hlu->external_id); + free((void *)hlu->uri); + free(hlu); +} + +/* Store a new hyperlink or return if it already exists. */ +u_int +hyperlinks_put(struct hyperlinks *hl, const char *uri_in, + const char *internal_id_in) +{ + struct hyperlinks_uri find, *hlu; + char *uri, *internal_id, *external_id; + + /* + * Anonymous URI are stored with an empty internal ID and the tree + * comparator will make sure they never match each other (so each + * anonymous URI is unique). + */ + if (internal_id_in == NULL) + internal_id_in = ""; + + utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE); + utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE); + + if (*internal_id_in != '\0') { + find.uri = uri; + find.internal_id = internal_id; + + hlu = RB_FIND(hyperlinks_by_uri_tree, &hl->by_uri, &find); + if (hlu != NULL) { + free (uri); + free (internal_id); + return (hlu->inner); + } + } + xasprintf(&external_id, "tmux%llX", hyperlinks_next_external_id++); + + hlu = xcalloc(1, sizeof *hlu); + hlu->inner = hl->next_inner++; + hlu->internal_id = internal_id; + hlu->external_id = external_id; + hlu->uri = uri; + hlu->tree = hl; + RB_INSERT(hyperlinks_by_uri_tree, &hl->by_uri, hlu); + RB_INSERT(hyperlinks_by_inner_tree, &hl->by_inner, hlu); + + TAILQ_INSERT_TAIL(&global_hyperlinks, hlu, list_entry); + if (++global_hyperlinks_count == MAX_HYPERLINKS) + hyperlinks_remove(TAILQ_FIRST(&global_hyperlinks)); + + return (hlu->inner); +} + +/* Get hyperlink by inner number. */ +int +hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out, + const char **external_id_out) +{ + struct hyperlinks_uri find, *hlu; + + find.inner = inner; + + hlu = RB_FIND(hyperlinks_by_inner_tree, &hl->by_inner, &find); + if (hlu == NULL) + return (0); + *external_id_out = hlu->external_id; + *uri_out = hlu->uri; + return (1); +} + +/* Initialize hyperlink set. */ +struct hyperlinks * +hyperlinks_init(void) +{ + struct hyperlinks *hl; + + hl = xcalloc(1, sizeof *hl); + hl->next_inner = 1; + RB_INIT(&hl->by_uri); + RB_INIT(&hl->by_inner); + return (hl); +} + +/* Free all hyperlinks but not the set itself. */ +void +hyperlinks_reset(struct hyperlinks *hl) +{ + struct hyperlinks_uri *hlu, *hlu1; + + RB_FOREACH_SAFE(hlu, hyperlinks_by_inner_tree, &hl->by_inner, hlu1) + hyperlinks_remove(hlu); +} + +/* Free hyperlink set. */ +void +hyperlinks_free(struct hyperlinks *hl) +{ + hyperlinks_reset(hl); + free(hl); +} diff --git a/input.c b/input.c index d4ffe784..4252428b 100644 --- a/input.c +++ b/input.c @@ -135,6 +135,7 @@ static void input_set_state(struct input_ctx *, static void input_reset_cell(struct input_ctx *); static void input_osc_4(struct input_ctx *, const char *); +static void input_osc_8(struct input_ctx *, const char *); static void input_osc_10(struct input_ctx *, const char *); static void input_osc_11(struct input_ctx *, const char *); static void input_osc_12(struct input_ctx *, const char *); @@ -2318,6 +2319,9 @@ input_exit_osc(struct input_ctx *ictx) } } break; + case 8: + input_osc_8(ictx, p); + break; case 10: input_osc_10(ictx, p); break; @@ -2562,6 +2566,47 @@ input_osc_4(struct input_ctx *ictx, const char *p) free(copy); } +/* Handle the OSC 8 sequence for embedding hyperlinks. */ +static void +input_osc_8(struct input_ctx *ictx, const char *p) +{ + struct hyperlinks *hl = ictx->ctx.s->hyperlinks; + struct grid_cell *gc = &ictx->cell.cell; + const char *start, *end, *uri; + char *id = NULL; + + for (start = p; (end = strpbrk(start, ":;")) != NULL; start = end + 1) { + if (end - start >= 4 && strncmp(start, "id=", 3) == 0) { + if (id != NULL) + goto bad; + id = xstrndup(start + 3, end - start - 3); + } + + /* The first ; is the end of parameters and start of the URI. */ + if (*end == ';') + break; + } + if (end == NULL || *end != ';') + goto bad; + uri = end + 1; + if (*uri == '\0') { + gc->link = 0; + free(id); + return; + } + gc->link = hyperlinks_put(hl, uri, id); + if (id == NULL) + log_debug("hyperlink (anonymous) %s = %u", uri, gc->link); + else + log_debug("hyperlink (id=%s) %s = %u", id, uri, gc->link); + free(id); + return; + +bad: + log_debug("bad OSC 8 %s", p); + free(id); +} + /* Handle the OSC 10 sequence for setting and querying foreground colour. */ static void input_osc_10(struct input_ctx *ictx, const char *p) diff --git a/screen-redraw.c b/screen-redraw.c index c4906ab8..470135cc 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -738,7 +738,7 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) } } - tty_cell(tty, &gc, &grid_default_cell, NULL); + tty_cell(tty, &gc, &grid_default_cell, NULL, NULL); if (isolates) tty_puts(tty, START_ISOLATE); } diff --git a/screen.c b/screen.c index eceef642..12ca4d17 100644 --- a/screen.c +++ b/screen.c @@ -89,6 +89,7 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) s->sel = NULL; s->write_list = NULL; + s->hyperlinks = NULL; screen_reinit(s); } @@ -118,6 +119,17 @@ screen_reinit(struct screen *s) screen_clear_selection(s); screen_free_titles(s); + screen_reset_hyperlinks(s); +} + +/* Reset hyperlinks of a screen. */ +void +screen_reset_hyperlinks(struct screen *s) +{ + if (s->hyperlinks == NULL) + s->hyperlinks = hyperlinks_init(); + else + hyperlinks_reset(s->hyperlinks); } /* Destroy a screen. */ @@ -136,6 +148,8 @@ screen_free(struct screen *s) grid_destroy(s->saved_grid); grid_destroy(s->grid); + if (s->hyperlinks != NULL) + hyperlinks_free(s->hyperlinks); screen_free_titles(s); } diff --git a/server.c b/server.c index 06da2a8d..6fef468b 100644 --- a/server.c +++ b/server.c @@ -213,7 +213,6 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base, RB_INIT(&sessions); key_bindings_init(); TAILQ_INIT(&message_log); - gettimeofday(&start_time, NULL); #ifdef HAVE_SYSTEMD diff --git a/style.c b/style.c index 89a4e63a..8407dc68 100644 --- a/style.c +++ b/style.c @@ -30,7 +30,7 @@ /* Default style. */ static struct style style_default = { - { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 }, + { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 }, 0, 8, diff --git a/tmux.1 b/tmux.1 index fd6b1717..fd01853e 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3659,6 +3659,8 @@ Allows setting the cursor style. Supports extended keys. .It focus Supports focus reporting. +.It hyperlinks +Supports OSC 8 hyperlinks. .It ignorefkeys Ignore function keys from .Xr terminfo 5 @@ -6125,9 +6127,14 @@ a format for each shortcut key; both are evaluated once for each line. starts without the preview. This command works only if at least one client is attached. .Tg clearhist -.It Ic clear-history Op Fl t Ar target-pane +.It Xo Ic clear-history +.Op Fl H +.Op Fl t Ar target-pane +.Xc .D1 Pq alias: Ic clearhist Remove and free the history for the specified pane. +.Fl H +also removes all hyperlinks. .Tg deleteb .It Ic delete-buffer Op Fl b Ar buffer-name .D1 Pq alias: Ic deleteb @@ -6415,6 +6422,8 @@ Disable and enable focus reporting. These are set automatically if the .Em XT capability is present. +.It Em \&Hls +Set or clear a hyperlink annotation. .It Em \&Rect Tell .Nm diff --git a/tmux.h b/tmux.h index 05235b85..0e922dca 100644 --- a/tmux.h +++ b/tmux.h @@ -50,6 +50,8 @@ struct control_state; struct environ; struct format_job_tree; struct format_tree; +struct hyperlinks_uri; +struct hyperlinks; struct input_ctx; struct job; struct menu_data; @@ -366,6 +368,7 @@ enum tty_code_code { TTYC_ENFCS, TTYC_ENMG, TTYC_FSL, + TTYC_HLS, TTYC_HOME, TTYC_HPA, TTYC_ICH, @@ -690,6 +693,7 @@ struct grid_cell { int fg; int bg; int us; + u_int link; }; /* Grid extended cell entry. */ @@ -700,6 +704,7 @@ struct grid_extd_entry { int fg; int bg; int us; + u_int link; } __packed; /* Grid cell entry. */ @@ -851,6 +856,8 @@ struct screen { struct screen_sel *sel; struct screen_write_cline *write_list; + + struct hyperlinks *hyperlinks; }; /* Screen write context. */ @@ -2247,7 +2254,8 @@ void tty_update_window_offset(struct window *); void tty_update_client_offset(struct client *); void tty_raw(struct tty *, const char *); void tty_attributes(struct tty *, const struct grid_cell *, - const struct grid_cell *, struct colour_palette *); + const struct grid_cell *, struct colour_palette *, + struct hyperlinks *); void tty_reset(struct tty *); void tty_region_off(struct tty *); void tty_margin_off(struct tty *); @@ -2264,7 +2272,8 @@ void tty_puts(struct tty *, const char *); void tty_putc(struct tty *, u_char); void tty_putn(struct tty *, const void *, size_t, u_int); void tty_cell(struct tty *, const struct grid_cell *, - const struct grid_cell *, struct colour_palette *); + const struct grid_cell *, struct colour_palette *, + struct hyperlinks *); int tty_init(struct tty *, struct client *); void tty_resize(struct tty *); void tty_set_size(struct tty *, u_int, u_int, u_int, u_int); @@ -2895,6 +2904,7 @@ void screen_init(struct screen *, u_int, u_int, u_int); void screen_reinit(struct screen *); void screen_free(struct screen *); void screen_reset_tabs(struct screen *); +void screen_reset_hyperlinks(struct screen *); void screen_set_cursor_style(u_int, enum screen_cursor_style *, int *); void screen_set_cursor_colour(struct screen *, int); int screen_set_title(struct screen *, const char *); @@ -3301,4 +3311,13 @@ void server_acl_user_deny_write(uid_t); int server_acl_join(struct client *); uid_t server_acl_get_uid(struct server_acl_user *); +/* hyperlink.c */ +u_int hyperlinks_put(struct hyperlinks *, const char *, + const char *); +int hyperlinks_get(struct hyperlinks *, u_int, + const char **, const char **); +struct hyperlinks *hyperlinks_init(void); +void hyperlinks_reset(struct hyperlinks *); +void hyperlinks_free(struct hyperlinks *); + #endif /* TMUX_H */ diff --git a/tty-features.c b/tty-features.c index 477925e3..396a351e 100644 --- a/tty-features.c +++ b/tty-features.c @@ -87,6 +87,17 @@ static const struct tty_feature tty_feature_clipboard = { 0 }; +/* Terminal supports OSC 8 hyperlinks. */ +static const char *tty_feature_hyperlinks_capabilities[] = { + "*:Hls=\\E]8;%?%p1%l%tid=%p1%s%;;%p2%s\\E\\\\", + NULL +}; +static const struct tty_feature tty_feature_hyperlinks = { + "hyperlinks", + tty_feature_hyperlinks_capabilities, + 0 +}; + /* * Terminal supports RGB colour. This replaces setab and setaf also since * terminals with RGB have versions that do not allow setting colours from the @@ -330,6 +341,7 @@ static const struct tty_feature *tty_features[] = { &tty_feature_bpaste, &tty_feature_ccolour, &tty_feature_clipboard, + &tty_feature_hyperlinks, &tty_feature_cstyle, &tty_feature_extkeys, &tty_feature_focus, @@ -444,14 +456,14 @@ tty_default_features(int *feat, const char *name, u_int version) }, { .name = "tmux", .features = TTY_FEATURES_BASE_MODERN_XTERM - ",ccolour,cstyle,focus,overline,usstyle" + ",ccolour,cstyle,focus,overline,usstyle,hyperlinks" }, { .name = "rxvt-unicode", .features = "256,bpaste,ccolour,cstyle,mouse,title,ignorefkeys" }, { .name = "iTerm2", .features = TTY_FEATURES_BASE_MODERN_XTERM - ",cstyle,extkeys,margins,usstyle,sync,osc7" + ",cstyle,extkeys,margins,usstyle,sync,osc7,hyperlinks" }, { .name = "XTerm", /* diff --git a/tty-term.c b/tty-term.c index fdf0c4fa..4b02e2c5 100644 --- a/tty-term.c +++ b/tty-term.c @@ -103,6 +103,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" }, [TTYC_ENMG] = { TTYCODE_STRING, "Enmg" }, [TTYC_FSL] = { TTYCODE_STRING, "fsl" }, + [TTYC_HLS] = { TTYCODE_STRING, "Hls" }, [TTYC_HOME] = { TTYCODE_STRING, "home" }, [TTYC_HPA] = { TTYCODE_STRING, "hpa" }, [TTYC_ICH1] = { TTYCODE_STRING, "ich1" }, diff --git a/tty.c b/tty.c index 7e0a6a3e..78961e47 100644 --- a/tty.c +++ b/tty.c @@ -69,7 +69,7 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, static void tty_repeat_space(struct tty *, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); static void tty_default_attributes(struct tty *, const struct grid_cell *, - struct colour_palette *, u_int); + struct colour_palette *, u_int, struct hyperlinks *); static int tty_check_overlay(struct tty *, u_int, u_int); static void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, struct overlay_ranges *); @@ -1455,7 +1455,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, tty_term_has(tty->term, TTYC_EL1) && !tty_fake_bce(tty, defaults, 8) && c->overlay_check == NULL) { - tty_default_attributes(tty, defaults, palette, 8); + tty_default_attributes(tty, defaults, palette, 8, + s->hyperlinks); tty_cursor(tty, nx - 1, aty); tty_putcode(tty, TTYC_EL1); cleared = 1; @@ -1480,9 +1481,11 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, gcp->fg != last.fg || gcp->bg != last.bg || gcp->us != last.us || + gcp->link != last.link || ux + width + gcp->data.width > nx || (sizeof buf) - len < gcp->data.size)) { - tty_attributes(tty, &last, defaults, palette); + tty_attributes(tty, &last, defaults, palette, + s->hyperlinks); if (last.flags & GRID_FLAG_CLEARED) { log_debug("%s: %zu cleared", __func__, len); tty_clear_line(tty, defaults, aty, atx + ux, @@ -1515,7 +1518,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, ux += gcp->data.width; } else if (hidden != 0 || ux + gcp->data.width > nx) { if (~gcp->flags & GRID_FLAG_PADDING) { - tty_attributes(tty, &last, defaults, palette); + tty_attributes(tty, &last, defaults, palette, + s->hyperlinks); for (j = 0; j < OVERLAY_MAX_RANGES; j++) { if (r.nx[j] == 0) continue; @@ -1532,7 +1536,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, } } } else if (gcp->attr & GRID_ATTR_CHARSET) { - tty_attributes(tty, &last, defaults, palette); + tty_attributes(tty, &last, defaults, palette, + s->hyperlinks); tty_cursor(tty, atx + ux, aty); for (j = 0; j < gcp->data.size; j++) tty_putc(tty, gcp->data.data[j]); @@ -1544,7 +1549,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, } } if (len != 0 && ((~last.flags & GRID_FLAG_CLEARED) || last.bg != 8)) { - tty_attributes(tty, &last, defaults, palette); + tty_attributes(tty, &last, defaults, palette, s->hyperlinks); if (last.flags & GRID_FLAG_CLEARED) { log_debug("%s: %zu cleared (end)", __func__, len); tty_clear_line(tty, defaults, aty, atx + ux, width, @@ -1560,7 +1565,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, if (!cleared && ux < nx) { log_debug("%s: %u to end of line (%zu cleared)", __func__, nx - ux, len); - tty_default_attributes(tty, defaults, palette, 8); + tty_default_attributes(tty, defaults, palette, 8, + s->hyperlinks); tty_clear_line(tty, defaults, aty, atx + ux, nx - ux, 8); } @@ -1646,7 +1652,8 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); @@ -1668,7 +1675,8 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); @@ -1678,7 +1686,8 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx) { - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->num, ctx->bg); } @@ -1700,7 +1709,8 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); @@ -1727,7 +1737,8 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); @@ -1740,7 +1751,8 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearline(struct tty *tty, const struct tty_ctx *ctx) { - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->sx, ctx->bg); } @@ -1750,7 +1762,8 @@ tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx) { u_int nx = ctx->sx - ctx->ocx; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, nx, ctx->bg); } @@ -1758,7 +1771,8 @@ tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearstartofline(struct tty *tty, const struct tty_ctx *ctx) { - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->ocx + 1, ctx->bg); } @@ -1784,7 +1798,8 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1815,7 +1830,8 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1855,7 +1871,8 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1895,7 +1912,8 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1914,7 +1932,8 @@ tty_cmd_clearendofscreen(struct tty *tty, const struct tty_ctx *ctx) { u_int px, py, nx, ny; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1938,7 +1957,8 @@ tty_cmd_clearstartofscreen(struct tty *tty, const struct tty_ctx *ctx) { u_int px, py, nx, ny; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1962,7 +1982,8 @@ tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx) { u_int px, py, nx, ny; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1985,7 +2006,8 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_attributes(tty, &grid_default_cell, &ctx->defaults, ctx->palette); + tty_attributes(tty, &grid_default_cell, &ctx->defaults, ctx->palette, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -2031,7 +2053,8 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette); + tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette, + ctx->s->hyperlinks); } void @@ -2062,7 +2085,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette); + tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette, ctx->s->hyperlinks); /* Get tty position from pane position for overlay check. */ px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2136,7 +2159,8 @@ tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx) void tty_cell(struct tty *tty, const struct grid_cell *gc, - const struct grid_cell *defaults, struct colour_palette *palette) + const struct grid_cell *defaults, struct colour_palette *palette, + struct hyperlinks *hl) { const struct grid_cell *gcp; @@ -2152,11 +2176,11 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, /* Check the output codeset and apply attributes. */ gcp = tty_check_codeset(tty, gc); - tty_attributes(tty, gcp, defaults, palette); + tty_attributes(tty, gcp, defaults, palette, hl); /* If it is a single character, write with putc to handle ACS. */ if (gcp->data.size == 1) { - tty_attributes(tty, gcp, defaults, palette); + tty_attributes(tty, gcp, defaults, palette, hl); if (*gcp->data.data < 0x20 || *gcp->data.data == 0x7f) return; tty_putc(tty, *gcp->data.data); @@ -2173,6 +2197,8 @@ tty_reset(struct tty *tty) struct grid_cell *gc = &tty->cell; if (!grid_cells_equal(gc, &grid_default_cell)) { + if (gc->link != 0) + tty_putcode_ptr2(tty, TTYC_HLS, "", ""); if ((gc->attr & GRID_ATTR_CHARSET) && tty_acs_needed(tty)) tty_putcode(tty, TTYC_RMACS); tty_putcode(tty, TTYC_SGR0); @@ -2462,9 +2488,29 @@ out: tty->cy = cy; } +static void +tty_hyperlink(struct tty *tty, const struct grid_cell *gc, + struct hyperlinks *hl) +{ + const char *uri, *id; + + if (gc->link == tty->cell.link) + return; + tty->cell.link = gc->link; + + if (hl == NULL) + return; + + if (gc->link == 0 || !hyperlinks_get(hl, gc->link, &uri, &id)) + tty_putcode_ptr2(tty, TTYC_HLS, "", ""); + else + tty_putcode_ptr2(tty, TTYC_HLS, id, uri); +} + void tty_attributes(struct tty *tty, const struct grid_cell *gc, - const struct grid_cell *defaults, struct colour_palette *palette) + const struct grid_cell *defaults, struct colour_palette *palette, + struct hyperlinks *hl) { struct grid_cell *tc = &tty->cell, gc2; int changed; @@ -2482,7 +2528,8 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, if (gc2.attr == tty->last_cell.attr && gc2.fg == tty->last_cell.fg && gc2.bg == tty->last_cell.bg && - gc2.us == tty->last_cell.us) + gc2.us == tty->last_cell.us && + gc2.link == tty->last_cell.link) return; /* @@ -2559,6 +2606,9 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, if ((changed & GRID_ATTR_CHARSET) && tty_acs_needed(tty)) tty_putcode(tty, TTYC_SMACS); + /* Set hyperlink if any. */ + tty_hyperlink(tty, gc, hl); + memcpy(&tty->last_cell, &gc2, sizeof tty->last_cell); } @@ -2924,13 +2974,13 @@ tty_default_colours(struct grid_cell *gc, struct window_pane *wp) static void tty_default_attributes(struct tty *tty, const struct grid_cell *defaults, - struct colour_palette *palette, u_int bg) + struct colour_palette *palette, u_int bg, struct hyperlinks *hl) { struct grid_cell gc; memcpy(&gc, &grid_default_cell, sizeof gc); gc.bg = bg; - tty_attributes(tty, &gc, defaults, palette); + tty_attributes(tty, &gc, defaults, palette, hl); } static void