Annotation of src/usr.bin/tmux/hyperlinks.c, Revision 1.2
1.2 ! nicm 1: /* $OpenBSD: hyperlinks.c,v 1.1 2022/06/30 09:55:53 nicm Exp $ */
1.1 nicm 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,
1.2 ! nicm 182: const char **internal_id_out, const char **external_id_out)
1.1 nicm 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);
1.2 ! nicm 191: if (internal_id_out != NULL)
! 192: *internal_id_out = hlu->internal_id;
! 193: if (external_id_out != NULL)
! 194: *external_id_out = hlu->external_id;
1.1 nicm 195: *uri_out = hlu->uri;
196: return (1);
197: }
198:
199: /* Initialize hyperlink set. */
200: struct hyperlinks *
201: hyperlinks_init(void)
202: {
203: struct hyperlinks *hl;
204:
205: hl = xcalloc(1, sizeof *hl);
206: hl->next_inner = 1;
207: RB_INIT(&hl->by_uri);
208: RB_INIT(&hl->by_inner);
209: return (hl);
210: }
211:
212: /* Free all hyperlinks but not the set itself. */
213: void
214: hyperlinks_reset(struct hyperlinks *hl)
215: {
216: struct hyperlinks_uri *hlu, *hlu1;
217:
218: RB_FOREACH_SAFE(hlu, hyperlinks_by_inner_tree, &hl->by_inner, hlu1)
219: hyperlinks_remove(hlu);
220: }
221:
222: /* Free hyperlink set. */
223: void
224: hyperlinks_free(struct hyperlinks *hl)
225: {
226: hyperlinks_reset(hl);
227: free(hl);
228: }