Line | Count | Source |
1 | | /* $OpenBSD: hyperlinks.c,v 1.5 2026/06/29 16:44:06 nicm Exp $ */ |
2 | | |
3 | | /* |
4 | | * Copyright (c) 2021 Will <author@will.party> |
5 | | * Copyright (c) 2022 Jeff Chiang <pobomp@gmail.com> |
6 | | * |
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. |
10 | | * |
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. |
18 | | */ |
19 | | |
20 | | #include <sys/types.h> |
21 | | |
22 | | #include <stdlib.h> |
23 | | #include <string.h> |
24 | | |
25 | | #include "tmux.h" |
26 | | |
27 | | /* |
28 | | * OSC 8 hyperlinks, described at: |
29 | | * |
30 | | * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda |
31 | | * |
32 | | * Each hyperlink and ID combination is assigned a number ("inner" in this |
33 | | * file) which is stored in an extended grid cell and maps into a tree here. |
34 | | * |
35 | | * Each URI has one inner number and one external ID (which tmux uses to send |
36 | | * the hyperlink to the terminal) and one internal ID (which is received from |
37 | | * the sending application inside tmux). |
38 | | * |
39 | | * Anonymous hyperlinks are each unique and are not reused even if they have |
40 | | * the same URI (terminals will not want to tie them together). |
41 | | */ |
42 | | |
43 | 6.78k | #define MAX_HYPERLINKS 5000 |
44 | 6.93k | #define MAX_HYPERLINK_URI 1024 |
45 | | |
46 | | static long long hyperlinks_next_external_id = 1; |
47 | | static u_int global_hyperlinks_count; |
48 | | |
49 | | struct hyperlinks_uri { |
50 | | struct hyperlinks *tree; |
51 | | |
52 | | u_int inner; |
53 | | const char *internal_id; |
54 | | const char *external_id; |
55 | | const char *uri; |
56 | | |
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 */ |
60 | | }; |
61 | | RB_HEAD(hyperlinks_by_uri_tree, hyperlinks_uri); |
62 | | RB_HEAD(hyperlinks_by_inner_tree, hyperlinks_uri); |
63 | | |
64 | | TAILQ_HEAD(hyperlinks_list, hyperlinks_uri); |
65 | | static struct hyperlinks_list global_hyperlinks = |
66 | | TAILQ_HEAD_INITIALIZER(global_hyperlinks); |
67 | | |
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; |
73 | | }; |
74 | | |
75 | | static int |
76 | | hyperlinks_by_uri_cmp(struct hyperlinks_uri *left, struct hyperlinks_uri *right) |
77 | 37.9k | { |
78 | 37.9k | int r; |
79 | | |
80 | 37.9k | if (*left->internal_id == '\0' || *right->internal_id == '\0') { |
81 | | /* |
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. |
85 | | */ |
86 | 26.2k | if (*left->internal_id != '\0') |
87 | 3.22k | return (-1); |
88 | 23.0k | if (*right->internal_id != '\0') |
89 | 352 | return (1); |
90 | 22.6k | return (left->inner - right->inner); |
91 | 23.0k | } |
92 | | |
93 | 11.6k | r = strcmp(left->internal_id, right->internal_id); |
94 | 11.6k | if (r != 0) |
95 | 9.81k | return (r); |
96 | 1.85k | return (strcmp(left->uri, right->uri)); |
97 | 11.6k | } |
98 | | RB_PROTOTYPE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, |
99 | | hyperlinks_by_uri_cmp); |
100 | 79.6k | RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, hyperlinks.c:hyperlinks_by_uri_tree_RB_FIND Line | Count | Source | 100 | | RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, |
hyperlinks.c:hyperlinks_by_uri_tree_RB_INSERT Line | Count | Source | 100 | | RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, |
hyperlinks.c:hyperlinks_by_uri_tree_RB_REMOVE Line | Count | Source | 100 | | RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, |
hyperlinks.c:hyperlinks_by_uri_tree_RB_REMOVE_COLOR Line | Count | Source | 100 | | RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, |
|
101 | 79.6k | hyperlinks_by_uri_cmp); |
102 | 79.6k | |
103 | 79.6k | static int |
104 | 79.6k | hyperlinks_by_inner_cmp(struct hyperlinks_uri *left, |
105 | 79.6k | struct hyperlinks_uri *right) |
106 | 79.6k | { |
107 | 31.5k | return (left->inner - right->inner); |
108 | 31.5k | } |
109 | | RB_PROTOTYPE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, |
110 | | hyperlinks_by_inner_cmp); |
111 | 98.2k | RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, hyperlinks.c:hyperlinks_by_inner_tree_RB_INSERT Line | Count | Source | 111 | | RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, |
hyperlinks.c:hyperlinks_by_inner_tree_RB_REMOVE Line | Count | Source | 111 | | RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, |
hyperlinks.c:hyperlinks_by_inner_tree_RB_REMOVE_COLOR Line | Count | Source | 111 | | RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, |
Unexecuted instantiation: hyperlinks.c:hyperlinks_by_inner_tree_RB_FIND hyperlinks.c:hyperlinks_by_inner_tree_RB_MINMAX Line | Count | Source | 111 | | RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, |
|
112 | 98.2k | hyperlinks_by_inner_cmp); |
113 | 98.2k | |
114 | 98.2k | /* Remove a hyperlink. */ |
115 | 98.2k | static void |
116 | 98.2k | hyperlinks_remove(struct hyperlinks_uri *hlu) |
117 | 98.2k | { |
118 | 6.76k | struct hyperlinks *hl = hlu->tree; |
119 | | |
120 | 6.76k | TAILQ_REMOVE(&global_hyperlinks, hlu, list_entry); |
121 | 6.76k | global_hyperlinks_count--; |
122 | | |
123 | 6.76k | RB_REMOVE(hyperlinks_by_inner_tree, &hl->by_inner, hlu); |
124 | 6.76k | RB_REMOVE(hyperlinks_by_uri_tree, &hl->by_uri, hlu); |
125 | | |
126 | 6.76k | free((void *)hlu->internal_id); |
127 | 6.76k | free((void *)hlu->external_id); |
128 | 6.76k | free((void *)hlu->uri); |
129 | 6.76k | free(hlu); |
130 | 6.76k | } |
131 | | |
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) |
136 | 6.93k | { |
137 | 6.93k | struct hyperlinks_uri find, *hlu; |
138 | 6.93k | char *uri, *internal_id, *external_id; |
139 | | |
140 | | /* |
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). |
144 | | */ |
145 | 6.93k | if (internal_id_in == NULL) |
146 | 4.42k | internal_id_in = ""; |
147 | | |
148 | 6.93k | utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE); |
149 | 6.93k | if (strlen(uri) > MAX_HYPERLINK_URI) { |
150 | 0 | free(uri); |
151 | 0 | return (0); |
152 | 0 | } |
153 | | |
154 | 6.93k | utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE); |
155 | 6.93k | if (*internal_id != '\0') { |
156 | 2.50k | find.uri = uri; |
157 | 2.50k | find.internal_id = internal_id; |
158 | | |
159 | 2.50k | hlu = RB_FIND(hyperlinks_by_uri_tree, &hl->by_uri, &find); |
160 | 2.50k | if (hlu != NULL) { |
161 | 151 | free (uri); |
162 | 151 | free (internal_id); |
163 | 151 | return (hlu->inner); |
164 | 151 | } |
165 | 2.50k | } |
166 | 6.78k | xasprintf(&external_id, "tmux%llX", hyperlinks_next_external_id++); |
167 | | |
168 | 6.78k | hlu = xcalloc(1, sizeof *hlu); |
169 | 6.78k | hlu->inner = hl->next_inner++; |
170 | 6.78k | hlu->internal_id = internal_id; |
171 | 6.78k | hlu->external_id = external_id; |
172 | 6.78k | hlu->uri = uri; |
173 | 6.78k | hlu->tree = hl; |
174 | 6.78k | RB_INSERT(hyperlinks_by_uri_tree, &hl->by_uri, hlu); |
175 | 6.78k | RB_INSERT(hyperlinks_by_inner_tree, &hl->by_inner, hlu); |
176 | | |
177 | 6.78k | TAILQ_INSERT_TAIL(&global_hyperlinks, hlu, list_entry); |
178 | 6.78k | if (++global_hyperlinks_count == MAX_HYPERLINKS) |
179 | 0 | hyperlinks_remove(TAILQ_FIRST(&global_hyperlinks)); |
180 | | |
181 | 6.78k | return (hlu->inner); |
182 | 6.93k | } |
183 | | |
184 | | /* Get hyperlink by inner number. */ |
185 | | int |
186 | | hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out, |
187 | | const char **internal_id_out, const char **external_id_out) |
188 | 0 | { |
189 | 0 | struct hyperlinks_uri find, *hlu; |
190 | |
|
191 | 0 | find.inner = inner; |
192 | |
|
193 | 0 | hlu = RB_FIND(hyperlinks_by_inner_tree, &hl->by_inner, &find); |
194 | 0 | if (hlu == NULL) |
195 | 0 | return (0); |
196 | 0 | if (internal_id_out != NULL) |
197 | 0 | *internal_id_out = hlu->internal_id; |
198 | 0 | if (external_id_out != NULL) |
199 | 0 | *external_id_out = hlu->external_id; |
200 | 0 | *uri_out = hlu->uri; |
201 | 0 | return (1); |
202 | 0 | } |
203 | | |
204 | | /* Initialize hyperlink set. */ |
205 | | struct hyperlinks * |
206 | | hyperlinks_init(void) |
207 | 23.2k | { |
208 | 23.2k | struct hyperlinks *hl; |
209 | | |
210 | 23.2k | hl = xcalloc(1, sizeof *hl); |
211 | 23.2k | hl->next_inner = 1; |
212 | 23.2k | RB_INIT(&hl->by_uri); |
213 | 23.2k | RB_INIT(&hl->by_inner); |
214 | 23.2k | hl->references = 1; |
215 | 23.2k | return (hl); |
216 | 23.2k | } |
217 | | |
218 | | /* Copy hyperlink set. */ |
219 | | struct hyperlinks * |
220 | | hyperlinks_copy(struct hyperlinks *hl) |
221 | 0 | { |
222 | 0 | hl->references++; |
223 | 0 | return (hl); |
224 | 0 | } |
225 | | |
226 | | /* Free all hyperlinks but not the set itself. */ |
227 | | void |
228 | | hyperlinks_reset(struct hyperlinks *hl) |
229 | 23.2k | { |
230 | 23.2k | struct hyperlinks_uri *hlu, *hlu1; |
231 | | |
232 | 23.2k | RB_FOREACH_SAFE(hlu, hyperlinks_by_inner_tree, &hl->by_inner, hlu1) |
233 | 6.76k | hyperlinks_remove(hlu); |
234 | 23.2k | } |
235 | | |
236 | | /* Free hyperlink set. */ |
237 | | void |
238 | | hyperlinks_free(struct hyperlinks *hl) |
239 | 23.2k | { |
240 | 23.2k | if (--hl->references == 0) { |
241 | 23.2k | hyperlinks_reset(hl); |
242 | 23.2k | free(hl); |
243 | 23.2k | } |
244 | 23.2k | } |