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