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