Annotation of src/usr.bin/tmux/hyperlinks.c, Revision 1.1
1.1 ! nicm 1: /* $OpenBSD$ */
! 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: #include <vis.h>
! 25:
! 26: #include "tmux.h"
! 27:
! 28: /*
! 29: * OSC 8 hyperlinks, described at:
! 30: *
! 31: * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
! 32: *
! 33: * Each hyperlink and ID combination is assigned a number ("inner" in this
! 34: * file) which is stored in an extended grid cell and maps into a tree here.
! 35: *
! 36: * Each URI has one inner number and one external ID (which tmux uses to send
! 37: * the hyperlink to the terminal) and one internal ID (which is received from
! 38: * the sending application inside tmux).
! 39: *
! 40: * Anonymous hyperlinks are each unique and are not reused even if they have
! 41: * the same URI (terminals will not want to tie them together).
! 42: */
! 43:
! 44: #define MAX_HYPERLINKS 5000
! 45:
! 46: static uint64_t 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: };
! 73:
! 74: static int
! 75: hyperlinks_by_uri_cmp(struct hyperlinks_uri *left, struct hyperlinks_uri *right)
! 76: {
! 77: int r;
! 78:
! 79: if (*left->internal_id == '\0' || *right->internal_id == '\0') {
! 80: /*
! 81: * If both URIs are anonymous, use the inner for comparison so
! 82: * that they do not match even if the URI is the same - each
! 83: * anonymous URI should be unique.
! 84: */
! 85: if (*left->internal_id != '\0')
! 86: return (-1);
! 87: if (*right->internal_id != '\0')
! 88: return (1);
! 89: return (left->inner - right->inner);
! 90: }
! 91:
! 92: r = strcmp(left->internal_id, right->internal_id);
! 93: if (r != 0)
! 94: return (r);
! 95: return (strcmp(left->uri, right->uri));
! 96: }
! 97: RB_PROTOTYPE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry,
! 98: hyperlinks_by_uri_cmp);
! 99: RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry,
! 100: hyperlinks_by_uri_cmp);
! 101:
! 102: static int
! 103: hyperlinks_by_inner_cmp(struct hyperlinks_uri *left,
! 104: struct hyperlinks_uri *right)
! 105: {
! 106: return (left->inner - right->inner);
! 107: }
! 108: RB_PROTOTYPE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry,
! 109: hyperlinks_by_inner_cmp);
! 110: RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry,
! 111: hyperlinks_by_inner_cmp);
! 112:
! 113: /* Remove a hyperlink. */
! 114: static void
! 115: hyperlinks_remove(struct hyperlinks_uri *hlu)
! 116: {
! 117: struct hyperlinks *hl = hlu->tree;
! 118:
! 119: TAILQ_REMOVE(&global_hyperlinks, hlu, list_entry);
! 120: global_hyperlinks_count--;
! 121:
! 122: RB_REMOVE(hyperlinks_by_inner_tree, &hl->by_inner, hlu);
! 123: RB_REMOVE(hyperlinks_by_uri_tree, &hl->by_uri, hlu);
! 124:
! 125: free((void *)hlu->internal_id);
! 126: free((void *)hlu->external_id);
! 127: free((void *)hlu->uri);
! 128: free(hlu);
! 129: }
! 130:
! 131: /* Store a new hyperlink or return if it already exists. */
! 132: u_int
! 133: hyperlinks_put(struct hyperlinks *hl, const char *uri_in,
! 134: const char *internal_id_in)
! 135: {
! 136: struct hyperlinks_uri find, *hlu;
! 137: char *uri, *internal_id, *external_id;
! 138:
! 139: /*
! 140: * Anonymous URI are stored with an empty internal ID and the tree
! 141: * comparator will make sure they never match each other (so each
! 142: * anonymous URI is unique).
! 143: */
! 144: if (internal_id_in == NULL)
! 145: internal_id_in = "";
! 146:
! 147: utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE);
! 148: utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE);
! 149:
! 150: if (*internal_id_in != '\0') {
! 151: find.uri = uri;
! 152: find.internal_id = internal_id;
! 153:
! 154: hlu = RB_FIND(hyperlinks_by_uri_tree, &hl->by_uri, &find);
! 155: if (hlu != NULL) {
! 156: free (uri);
! 157: free (internal_id);
! 158: return (hlu->inner);
! 159: }
! 160: }
! 161: xasprintf(&external_id, "tmux%llX", hyperlinks_next_external_id++);
! 162:
! 163: hlu = xcalloc(1, sizeof *hlu);
! 164: hlu->inner = hl->next_inner++;
! 165: hlu->internal_id = internal_id;
! 166: hlu->external_id = external_id;
! 167: hlu->uri = uri;
! 168: hlu->tree = hl;
! 169: RB_INSERT(hyperlinks_by_uri_tree, &hl->by_uri, hlu);
! 170: RB_INSERT(hyperlinks_by_inner_tree, &hl->by_inner, hlu);
! 171:
! 172: TAILQ_INSERT_TAIL(&global_hyperlinks, hlu, list_entry);
! 173: if (++global_hyperlinks_count == MAX_HYPERLINKS)
! 174: hyperlinks_remove(TAILQ_FIRST(&global_hyperlinks));
! 175:
! 176: return (hlu->inner);
! 177: }
! 178:
! 179: /* Get hyperlink by inner number. */
! 180: int
! 181: hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out,
! 182: const char **external_id_out)
! 183: {
! 184: struct hyperlinks_uri find, *hlu;
! 185:
! 186: find.inner = inner;
! 187:
! 188: hlu = RB_FIND(hyperlinks_by_inner_tree, &hl->by_inner, &find);
! 189: if (hlu == NULL)
! 190: return (0);
! 191: *external_id_out = hlu->external_id;
! 192: *uri_out = hlu->uri;
! 193: return (1);
! 194: }
! 195:
! 196: /* Initialize hyperlink set. */
! 197: struct hyperlinks *
! 198: hyperlinks_init(void)
! 199: {
! 200: struct hyperlinks *hl;
! 201:
! 202: hl = xcalloc(1, sizeof *hl);
! 203: hl->next_inner = 1;
! 204: RB_INIT(&hl->by_uri);
! 205: RB_INIT(&hl->by_inner);
! 206: return (hl);
! 207: }
! 208:
! 209: /* Free all hyperlinks but not the set itself. */
! 210: void
! 211: hyperlinks_reset(struct hyperlinks *hl)
! 212: {
! 213: struct hyperlinks_uri *hlu, *hlu1;
! 214:
! 215: RB_FOREACH_SAFE(hlu, hyperlinks_by_inner_tree, &hl->by_inner, hlu1)
! 216: hyperlinks_remove(hlu);
! 217: }
! 218:
! 219: /* Free hyperlink set. */
! 220: void
! 221: hyperlinks_free(struct hyperlinks *hl)
! 222: {
! 223: hyperlinks_reset(hl);
! 224: free(hl);
! 225: }