Display hyperlinks in copy mode and add copy_cursor_hyperlink format to
[tmux-openbsd.git] / hyperlinks.c
blobc8fd0ca2febee5a9136e18a92e1f7529d13fb687
1 /* $OpenBSD$ */
3 /*
4 * Copyright (c) 2021 Will <author@will.party>
5 * Copyright (c) 2022 Jeff Chiang <pobomp@gmail.com>
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
16 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
17 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 #include <sys/types.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <vis.h>
26 #include "tmux.h"
29 * OSC 8 hyperlinks, described at:
31 * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
33 * Each hyperlink and ID combination is assigned a number ("inner" in this
34 * file) which is stored in an extended grid cell and maps into a tree here.
36 * Each URI has one inner number and one external ID (which tmux uses to send
37 * the hyperlink to the terminal) and one internal ID (which is received from
38 * the sending application inside tmux).
40 * Anonymous hyperlinks are each unique and are not reused even if they have
41 * the same URI (terminals will not want to tie them together).
44 #define MAX_HYPERLINKS 5000
46 static long long hyperlinks_next_external_id = 1;
47 static u_int global_hyperlinks_count;
49 struct hyperlinks_uri {
50 struct hyperlinks *tree;
52 u_int inner;
53 const char *internal_id;
54 const char *external_id;
55 const char *uri;
57 TAILQ_ENTRY(hyperlinks_uri) list_entry;
58 RB_ENTRY(hyperlinks_uri) by_inner_entry;
59 RB_ENTRY(hyperlinks_uri) by_uri_entry; /* by internal ID and URI */
61 RB_HEAD(hyperlinks_by_uri_tree, hyperlinks_uri);
62 RB_HEAD(hyperlinks_by_inner_tree, hyperlinks_uri);
64 TAILQ_HEAD(hyperlinks_list, hyperlinks_uri);
65 static struct hyperlinks_list global_hyperlinks =
66 TAILQ_HEAD_INITIALIZER(global_hyperlinks);
68 struct hyperlinks {
69 u_int next_inner;
70 struct hyperlinks_by_inner_tree by_inner;
71 struct hyperlinks_by_uri_tree by_uri;
72 u_int references;
75 static int
76 hyperlinks_by_uri_cmp(struct hyperlinks_uri *left, struct hyperlinks_uri *right)
78 int r;
80 if (*left->internal_id == '\0' || *right->internal_id == '\0') {
82 * If both URIs are anonymous, use the inner for comparison so
83 * that they do not match even if the URI is the same - each
84 * anonymous URI should be unique.
86 if (*left->internal_id != '\0')
87 return (-1);
88 if (*right->internal_id != '\0')
89 return (1);
90 return (left->inner - right->inner);
93 r = strcmp(left->internal_id, right->internal_id);
94 if (r != 0)
95 return (r);
96 return (strcmp(left->uri, right->uri));
98 RB_PROTOTYPE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry,
99 hyperlinks_by_uri_cmp);
100 RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry,
101 hyperlinks_by_uri_cmp);
103 static int
104 hyperlinks_by_inner_cmp(struct hyperlinks_uri *left,
105 struct hyperlinks_uri *right)
107 return (left->inner - right->inner);
109 RB_PROTOTYPE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry,
110 hyperlinks_by_inner_cmp);
111 RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry,
112 hyperlinks_by_inner_cmp);
114 /* Remove a hyperlink. */
115 static void
116 hyperlinks_remove(struct hyperlinks_uri *hlu)
118 struct hyperlinks *hl = hlu->tree;
120 TAILQ_REMOVE(&global_hyperlinks, hlu, list_entry);
121 global_hyperlinks_count--;
123 RB_REMOVE(hyperlinks_by_inner_tree, &hl->by_inner, hlu);
124 RB_REMOVE(hyperlinks_by_uri_tree, &hl->by_uri, hlu);
126 free((void *)hlu->internal_id);
127 free((void *)hlu->external_id);
128 free((void *)hlu->uri);
129 free(hlu);
132 /* Store a new hyperlink or return if it already exists. */
133 u_int
134 hyperlinks_put(struct hyperlinks *hl, const char *uri_in,
135 const char *internal_id_in)
137 struct hyperlinks_uri find, *hlu;
138 char *uri, *internal_id, *external_id;
141 * Anonymous URI are stored with an empty internal ID and the tree
142 * comparator will make sure they never match each other (so each
143 * anonymous URI is unique).
145 if (internal_id_in == NULL)
146 internal_id_in = "";
148 utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE);
149 utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE);
151 if (*internal_id_in != '\0') {
152 find.uri = uri;
153 find.internal_id = internal_id;
155 hlu = RB_FIND(hyperlinks_by_uri_tree, &hl->by_uri, &find);
156 if (hlu != NULL) {
157 free (uri);
158 free (internal_id);
159 return (hlu->inner);
162 xasprintf(&external_id, "tmux%llX", hyperlinks_next_external_id++);
164 hlu = xcalloc(1, sizeof *hlu);
165 hlu->inner = hl->next_inner++;
166 hlu->internal_id = internal_id;
167 hlu->external_id = external_id;
168 hlu->uri = uri;
169 hlu->tree = hl;
170 RB_INSERT(hyperlinks_by_uri_tree, &hl->by_uri, hlu);
171 RB_INSERT(hyperlinks_by_inner_tree, &hl->by_inner, hlu);
173 TAILQ_INSERT_TAIL(&global_hyperlinks, hlu, list_entry);
174 if (++global_hyperlinks_count == MAX_HYPERLINKS)
175 hyperlinks_remove(TAILQ_FIRST(&global_hyperlinks));
177 return (hlu->inner);
180 /* Get hyperlink by inner number. */
182 hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out,
183 const char **internal_id_out, const char **external_id_out)
185 struct hyperlinks_uri find, *hlu;
187 find.inner = inner;
189 hlu = RB_FIND(hyperlinks_by_inner_tree, &hl->by_inner, &find);
190 if (hlu == NULL)
191 return (0);
192 if (internal_id_out != NULL)
193 *internal_id_out = hlu->internal_id;
194 if (external_id_out != NULL)
195 *external_id_out = hlu->external_id;
196 *uri_out = hlu->uri;
197 return (1);
200 /* Initialize hyperlink set. */
201 struct hyperlinks *
202 hyperlinks_init(void)
204 struct hyperlinks *hl;
206 hl = xcalloc(1, sizeof *hl);
207 hl->next_inner = 1;
208 RB_INIT(&hl->by_uri);
209 RB_INIT(&hl->by_inner);
210 hl->references = 1;
211 return (hl);
214 /* Copy hyperlink set. */
215 struct hyperlinks *
216 hyperlinks_copy(struct hyperlinks *hl)
218 hl->references++;
219 return (hl);
222 /* Free all hyperlinks but not the set itself. */
223 void
224 hyperlinks_reset(struct hyperlinks *hl)
226 struct hyperlinks_uri *hlu, *hlu1;
228 RB_FOREACH_SAFE(hlu, hyperlinks_by_inner_tree, &hl->by_inner, hlu1)
229 hyperlinks_remove(hlu);
232 /* Free hyperlink set. */
233 void
234 hyperlinks_free(struct hyperlinks *hl)
236 if (--hl->references == 0) {
237 hyperlinks_reset(hl);
238 free(hl);