[BACK]Return to hyperlinks.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / tmux

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: }