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