summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rw-r--r--cmd-capture-pane.c6
-rw-r--r--cmd-display-panes.c6
-rw-r--r--grid.c13
-rw-r--r--hyperlinks.c225
-rw-r--r--input.c45
-rw-r--r--screen-redraw.c2
-rw-r--r--screen.c14
-rw-r--r--server.c1
-rw-r--r--style.c2
-rw-r--r--tmux.111
-rw-r--r--tmux.h23
-rw-r--r--tty-features.c16
-rw-r--r--tty-term.c1
-rw-r--r--tty.c114
15 files changed, 432 insertions, 48 deletions
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 <author@will.party>
+ * Copyright (c) 2022 Jeff Chiang <pobomp@gmail.com>
+ *
+ * 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 <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <vis.h>
+
+#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,