xref: /openbsd-src/usr.bin/tmux/hyperlinks.c (revision ca1080d01edca48cd5c51d6f8b7aad0a96d08ab2)
1*ca1080d0Snicm /* $OpenBSD: hyperlinks.c,v 1.4 2024/08/27 07:49:07 nicm Exp $ */
22df6775cSnicm 
32df6775cSnicm /*
42df6775cSnicm  * Copyright (c) 2021 Will <author@will.party>
52df6775cSnicm  * Copyright (c) 2022 Jeff Chiang <pobomp@gmail.com>
62df6775cSnicm  *
72df6775cSnicm  * Permission to use, copy, modify, and distribute this software for any
82df6775cSnicm  * purpose with or without fee is hereby granted, provided that the above
92df6775cSnicm  * copyright notice and this permission notice appear in all copies.
102df6775cSnicm  *
112df6775cSnicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
122df6775cSnicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
132df6775cSnicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
142df6775cSnicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
152df6775cSnicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
162df6775cSnicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
172df6775cSnicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
182df6775cSnicm  */
192df6775cSnicm 
202df6775cSnicm #include <sys/types.h>
212df6775cSnicm 
222df6775cSnicm #include <stdlib.h>
232df6775cSnicm #include <string.h>
242df6775cSnicm #include <vis.h>
252df6775cSnicm 
262df6775cSnicm #include "tmux.h"
272df6775cSnicm 
282df6775cSnicm /*
292df6775cSnicm  * OSC 8 hyperlinks, described at:
302df6775cSnicm  *
312df6775cSnicm  *     https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
322df6775cSnicm  *
332df6775cSnicm  * Each hyperlink and ID combination is assigned a number ("inner" in this
342df6775cSnicm  * file) which is stored in an extended grid cell and maps into a tree here.
352df6775cSnicm  *
362df6775cSnicm  * Each URI has one inner number and one external ID (which tmux uses to send
372df6775cSnicm  * the hyperlink to the terminal) and one internal ID (which is received from
382df6775cSnicm  * the sending application inside tmux).
392df6775cSnicm  *
402df6775cSnicm  * Anonymous hyperlinks are each unique and are not reused even if they have
412df6775cSnicm  * the same URI (terminals will not want to tie them together).
422df6775cSnicm  */
432df6775cSnicm 
442df6775cSnicm #define MAX_HYPERLINKS 5000
452df6775cSnicm 
46e3eeb0f8Snicm static long long hyperlinks_next_external_id = 1;
472df6775cSnicm static u_int global_hyperlinks_count;
482df6775cSnicm 
492df6775cSnicm struct hyperlinks_uri {
502df6775cSnicm 	struct hyperlinks	*tree;
512df6775cSnicm 
522df6775cSnicm 	u_int			 inner;
532df6775cSnicm 	const char		*internal_id;
542df6775cSnicm 	const char		*external_id;
552df6775cSnicm 	const char		*uri;
562df6775cSnicm 
572df6775cSnicm 	TAILQ_ENTRY(hyperlinks_uri) list_entry;
582df6775cSnicm 	RB_ENTRY(hyperlinks_uri)    by_inner_entry;
592df6775cSnicm 	RB_ENTRY(hyperlinks_uri)    by_uri_entry; /* by internal ID and URI */
602df6775cSnicm };
612df6775cSnicm RB_HEAD(hyperlinks_by_uri_tree, hyperlinks_uri);
622df6775cSnicm RB_HEAD(hyperlinks_by_inner_tree, hyperlinks_uri);
632df6775cSnicm 
642df6775cSnicm TAILQ_HEAD(hyperlinks_list, hyperlinks_uri);
652df6775cSnicm static struct hyperlinks_list global_hyperlinks =
662df6775cSnicm     TAILQ_HEAD_INITIALIZER(global_hyperlinks);
672df6775cSnicm 
682df6775cSnicm struct hyperlinks {
692df6775cSnicm 	u_int				next_inner;
702df6775cSnicm 	struct hyperlinks_by_inner_tree	by_inner;
712df6775cSnicm 	struct hyperlinks_by_uri_tree	by_uri;
72*ca1080d0Snicm 	u_int				references;
732df6775cSnicm };
742df6775cSnicm 
752df6775cSnicm static int
762df6775cSnicm hyperlinks_by_uri_cmp(struct hyperlinks_uri *left, struct hyperlinks_uri *right)
772df6775cSnicm {
782df6775cSnicm 	int	r;
792df6775cSnicm 
802df6775cSnicm 	if (*left->internal_id == '\0' || *right->internal_id == '\0') {
812df6775cSnicm 		/*
822df6775cSnicm 		 * If both URIs are anonymous, use the inner for comparison so
832df6775cSnicm 		 * that they do not match even if the URI is the same - each
842df6775cSnicm 		 * anonymous URI should be unique.
852df6775cSnicm 		 */
862df6775cSnicm 		if (*left->internal_id != '\0')
872df6775cSnicm 			return (-1);
882df6775cSnicm 		if (*right->internal_id != '\0')
892df6775cSnicm 			return (1);
902df6775cSnicm 		return (left->inner - right->inner);
912df6775cSnicm 	}
922df6775cSnicm 
932df6775cSnicm 	r = strcmp(left->internal_id, right->internal_id);
942df6775cSnicm 	if (r != 0)
952df6775cSnicm 		return (r);
962df6775cSnicm 	return (strcmp(left->uri, right->uri));
972df6775cSnicm }
982df6775cSnicm RB_PROTOTYPE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry,
992df6775cSnicm     hyperlinks_by_uri_cmp);
1002df6775cSnicm RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry,
1012df6775cSnicm     hyperlinks_by_uri_cmp);
1022df6775cSnicm 
1032df6775cSnicm static int
1042df6775cSnicm hyperlinks_by_inner_cmp(struct hyperlinks_uri *left,
1052df6775cSnicm     struct hyperlinks_uri *right)
1062df6775cSnicm {
1072df6775cSnicm 	return (left->inner - right->inner);
1082df6775cSnicm }
1092df6775cSnicm RB_PROTOTYPE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry,
1102df6775cSnicm     hyperlinks_by_inner_cmp);
1112df6775cSnicm RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry,
1122df6775cSnicm     hyperlinks_by_inner_cmp);
1132df6775cSnicm 
1142df6775cSnicm /* Remove a hyperlink. */
1152df6775cSnicm static void
1162df6775cSnicm hyperlinks_remove(struct hyperlinks_uri *hlu)
1172df6775cSnicm {
1182df6775cSnicm 	struct hyperlinks	*hl = hlu->tree;
1192df6775cSnicm 
1202df6775cSnicm 	TAILQ_REMOVE(&global_hyperlinks, hlu, list_entry);
1212df6775cSnicm 	global_hyperlinks_count--;
1222df6775cSnicm 
1232df6775cSnicm 	RB_REMOVE(hyperlinks_by_inner_tree, &hl->by_inner, hlu);
1242df6775cSnicm 	RB_REMOVE(hyperlinks_by_uri_tree, &hl->by_uri, hlu);
1252df6775cSnicm 
1262df6775cSnicm 	free((void *)hlu->internal_id);
1272df6775cSnicm 	free((void *)hlu->external_id);
1282df6775cSnicm 	free((void *)hlu->uri);
1292df6775cSnicm 	free(hlu);
1302df6775cSnicm }
1312df6775cSnicm 
1322df6775cSnicm /* Store a new hyperlink or return if it already exists. */
1332df6775cSnicm u_int
1342df6775cSnicm hyperlinks_put(struct hyperlinks *hl, const char *uri_in,
1352df6775cSnicm     const char *internal_id_in)
1362df6775cSnicm {
1372df6775cSnicm 	struct hyperlinks_uri	 find, *hlu;
1382df6775cSnicm 	char			*uri, *internal_id, *external_id;
1392df6775cSnicm 
1402df6775cSnicm 	/*
1412df6775cSnicm 	 * Anonymous URI are stored with an empty internal ID and the tree
1422df6775cSnicm 	 * comparator will make sure they never match each other (so each
1432df6775cSnicm 	 * anonymous URI is unique).
1442df6775cSnicm 	 */
1452df6775cSnicm 	if (internal_id_in == NULL)
1462df6775cSnicm 		internal_id_in = "";
1472df6775cSnicm 
1482df6775cSnicm 	utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE);
1492df6775cSnicm 	utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE);
1502df6775cSnicm 
1512df6775cSnicm 	if (*internal_id_in != '\0') {
1522df6775cSnicm 		find.uri = uri;
1532df6775cSnicm 		find.internal_id = internal_id;
1542df6775cSnicm 
1552df6775cSnicm 		hlu = RB_FIND(hyperlinks_by_uri_tree, &hl->by_uri, &find);
1562df6775cSnicm 		if (hlu != NULL) {
1572df6775cSnicm 			free (uri);
1582df6775cSnicm 			free (internal_id);
1592df6775cSnicm 			return (hlu->inner);
1602df6775cSnicm 		}
1612df6775cSnicm 	}
1622df6775cSnicm 	xasprintf(&external_id, "tmux%llX", hyperlinks_next_external_id++);
1632df6775cSnicm 
1642df6775cSnicm 	hlu = xcalloc(1, sizeof *hlu);
1652df6775cSnicm 	hlu->inner = hl->next_inner++;
1662df6775cSnicm 	hlu->internal_id = internal_id;
1672df6775cSnicm 	hlu->external_id = external_id;
1682df6775cSnicm 	hlu->uri = uri;
1692df6775cSnicm 	hlu->tree = hl;
1702df6775cSnicm 	RB_INSERT(hyperlinks_by_uri_tree, &hl->by_uri, hlu);
1712df6775cSnicm 	RB_INSERT(hyperlinks_by_inner_tree, &hl->by_inner, hlu);
1722df6775cSnicm 
1732df6775cSnicm 	TAILQ_INSERT_TAIL(&global_hyperlinks, hlu, list_entry);
1742df6775cSnicm 	if (++global_hyperlinks_count == MAX_HYPERLINKS)
1752df6775cSnicm 		hyperlinks_remove(TAILQ_FIRST(&global_hyperlinks));
1762df6775cSnicm 
1772df6775cSnicm 	return (hlu->inner);
1782df6775cSnicm }
1792df6775cSnicm 
1802df6775cSnicm /* Get hyperlink by inner number. */
1812df6775cSnicm int
1822df6775cSnicm hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out,
183e5878dccSnicm     const char **internal_id_out, const char **external_id_out)
1842df6775cSnicm {
1852df6775cSnicm 	struct hyperlinks_uri	find, *hlu;
1862df6775cSnicm 
1872df6775cSnicm 	find.inner = inner;
1882df6775cSnicm 
1892df6775cSnicm 	hlu = RB_FIND(hyperlinks_by_inner_tree, &hl->by_inner, &find);
1902df6775cSnicm 	if (hlu == NULL)
1912df6775cSnicm 		return (0);
192e5878dccSnicm 	if (internal_id_out != NULL)
193e5878dccSnicm 		*internal_id_out = hlu->internal_id;
194e5878dccSnicm 	if (external_id_out != NULL)
1952df6775cSnicm 		*external_id_out = hlu->external_id;
1962df6775cSnicm 	*uri_out = hlu->uri;
1972df6775cSnicm 	return (1);
1982df6775cSnicm }
1992df6775cSnicm 
2002df6775cSnicm /* Initialize hyperlink set. */
2012df6775cSnicm struct hyperlinks *
2022df6775cSnicm hyperlinks_init(void)
2032df6775cSnicm {
2042df6775cSnicm 	struct hyperlinks	*hl;
2052df6775cSnicm 
2062df6775cSnicm 	hl = xcalloc(1, sizeof *hl);
2072df6775cSnicm 	hl->next_inner = 1;
2082df6775cSnicm 	RB_INIT(&hl->by_uri);
2092df6775cSnicm 	RB_INIT(&hl->by_inner);
210*ca1080d0Snicm 	hl->references = 1;
211*ca1080d0Snicm 	return (hl);
212*ca1080d0Snicm }
213*ca1080d0Snicm 
214*ca1080d0Snicm /* Copy hyperlink set. */
215*ca1080d0Snicm struct hyperlinks *
216*ca1080d0Snicm hyperlinks_copy(struct hyperlinks *hl)
217*ca1080d0Snicm {
218*ca1080d0Snicm 	hl->references++;
2192df6775cSnicm 	return (hl);
2202df6775cSnicm }
2212df6775cSnicm 
2222df6775cSnicm /* Free all hyperlinks but not the set itself. */
2232df6775cSnicm void
2242df6775cSnicm hyperlinks_reset(struct hyperlinks *hl)
2252df6775cSnicm {
2262df6775cSnicm 	struct hyperlinks_uri	*hlu, *hlu1;
2272df6775cSnicm 
2282df6775cSnicm 	RB_FOREACH_SAFE(hlu, hyperlinks_by_inner_tree, &hl->by_inner, hlu1)
2292df6775cSnicm 		hyperlinks_remove(hlu);
2302df6775cSnicm }
2312df6775cSnicm 
2322df6775cSnicm /* Free hyperlink set. */
2332df6775cSnicm void
2342df6775cSnicm hyperlinks_free(struct hyperlinks *hl)
2352df6775cSnicm {
236*ca1080d0Snicm 	if (--hl->references == 0) {
2372df6775cSnicm 		hyperlinks_reset(hl);
2382df6775cSnicm 		free(hl);
2392df6775cSnicm 	}
240*ca1080d0Snicm }
241