mirror of
https://github.com/tmux/tmux.git
synced 2024-11-10 21:58:50 +00:00
240 lines
6.4 KiB
C
240 lines
6.4 KiB
C
/* $OpenBSD$ */
|
|
|
|
/*
|
|
* Copyright (c) 2021 Will <author@will.party>
|
|
* Copyright (c) 2022 Jeff Chiang <pobomp@gmail.com>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "tmux.h"
|
|
|
|
/*
|
|
* OSC 8 hyperlinks, described at:
|
|
*
|
|
* https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
|
*
|
|
* Each hyperlink and ID combination is assigned a number ("inner" in this
|
|
* file) which is stored in an extended grid cell and maps into a tree here.
|
|
*
|
|
* Each URI has one inner number and one external ID (which tmux uses to send
|
|
* the hyperlink to the terminal) and one internal ID (which is received from
|
|
* the sending application inside tmux).
|
|
*
|
|
* Anonymous hyperlinks are each unique and are not reused even if they have
|
|
* the same URI (terminals will not want to tie them together).
|
|
*/
|
|
|
|
#define MAX_HYPERLINKS 5000
|
|
|
|
static long long hyperlinks_next_external_id = 1;
|
|
static u_int global_hyperlinks_count;
|
|
|
|
struct hyperlinks_uri {
|
|
struct hyperlinks *tree;
|
|
|
|
u_int inner;
|
|
const char *internal_id;
|
|
const char *external_id;
|
|
const char *uri;
|
|
|
|
TAILQ_ENTRY(hyperlinks_uri) list_entry;
|
|
RB_ENTRY(hyperlinks_uri) by_inner_entry;
|
|
RB_ENTRY(hyperlinks_uri) by_uri_entry; /* by internal ID and URI */
|
|
};
|
|
RB_HEAD(hyperlinks_by_uri_tree, hyperlinks_uri);
|
|
RB_HEAD(hyperlinks_by_inner_tree, hyperlinks_uri);
|
|
|
|
TAILQ_HEAD(hyperlinks_list, hyperlinks_uri);
|
|
static struct hyperlinks_list global_hyperlinks =
|
|
TAILQ_HEAD_INITIALIZER(global_hyperlinks);
|
|
|
|
struct hyperlinks {
|
|
u_int next_inner;
|
|
struct hyperlinks_by_inner_tree by_inner;
|
|
struct hyperlinks_by_uri_tree by_uri;
|
|
u_int references;
|
|
};
|
|
|
|
static int
|
|
hyperlinks_by_uri_cmp(struct hyperlinks_uri *left, struct hyperlinks_uri *right)
|
|
{
|
|
int r;
|
|
|
|
if (*left->internal_id == '\0' || *right->internal_id == '\0') {
|
|
/*
|
|
* If both URIs are anonymous, use the inner for comparison so
|
|
* that they do not match even if the URI is the same - each
|
|
* anonymous URI should be unique.
|
|
*/
|
|
if (*left->internal_id != '\0')
|
|
return (-1);
|
|
if (*right->internal_id != '\0')
|
|
return (1);
|
|
return (left->inner - right->inner);
|
|
}
|
|
|
|
r = strcmp(left->internal_id, right->internal_id);
|
|
if (r != 0)
|
|
return (r);
|
|
return (strcmp(left->uri, right->uri));
|
|
}
|
|
RB_PROTOTYPE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry,
|
|
hyperlinks_by_uri_cmp);
|
|
RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry,
|
|
hyperlinks_by_uri_cmp);
|
|
|
|
static int
|
|
hyperlinks_by_inner_cmp(struct hyperlinks_uri *left,
|
|
struct hyperlinks_uri *right)
|
|
{
|
|
return (left->inner - right->inner);
|
|
}
|
|
RB_PROTOTYPE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry,
|
|
hyperlinks_by_inner_cmp);
|
|
RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry,
|
|
hyperlinks_by_inner_cmp);
|
|
|
|
/* Remove a hyperlink. */
|
|
static void
|
|
hyperlinks_remove(struct hyperlinks_uri *hlu)
|
|
{
|
|
struct hyperlinks *hl = hlu->tree;
|
|
|
|
TAILQ_REMOVE(&global_hyperlinks, hlu, list_entry);
|
|
global_hyperlinks_count--;
|
|
|
|
RB_REMOVE(hyperlinks_by_inner_tree, &hl->by_inner, hlu);
|
|
RB_REMOVE(hyperlinks_by_uri_tree, &hl->by_uri, hlu);
|
|
|
|
free((void *)hlu->internal_id);
|
|
free((void *)hlu->external_id);
|
|
free((void *)hlu->uri);
|
|
free(hlu);
|
|
}
|
|
|
|
/* Store a new hyperlink or return if it already exists. */
|
|
u_int
|
|
hyperlinks_put(struct hyperlinks *hl, const char *uri_in,
|
|
const char *internal_id_in)
|
|
{
|
|
struct hyperlinks_uri find, *hlu;
|
|
char *uri, *internal_id, *external_id;
|
|
|
|
/*
|
|
* Anonymous URI are stored with an empty internal ID and the tree
|
|
* comparator will make sure they never match each other (so each
|
|
* anonymous URI is unique).
|
|
*/
|
|
if (internal_id_in == NULL)
|
|
internal_id_in = "";
|
|
|
|
utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE);
|
|
utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE);
|
|
|
|
if (*internal_id_in != '\0') {
|
|
find.uri = uri;
|
|
find.internal_id = internal_id;
|
|
|
|
hlu = RB_FIND(hyperlinks_by_uri_tree, &hl->by_uri, &find);
|
|
if (hlu != NULL) {
|
|
free (uri);
|
|
free (internal_id);
|
|
return (hlu->inner);
|
|
}
|
|
}
|
|
xasprintf(&external_id, "tmux%llX", hyperlinks_next_external_id++);
|
|
|
|
hlu = xcalloc(1, sizeof *hlu);
|
|
hlu->inner = hl->next_inner++;
|
|
hlu->internal_id = internal_id;
|
|
hlu->external_id = external_id;
|
|
hlu->uri = uri;
|
|
hlu->tree = hl;
|
|
RB_INSERT(hyperlinks_by_uri_tree, &hl->by_uri, hlu);
|
|
RB_INSERT(hyperlinks_by_inner_tree, &hl->by_inner, hlu);
|
|
|
|
TAILQ_INSERT_TAIL(&global_hyperlinks, hlu, list_entry);
|
|
if (++global_hyperlinks_count == MAX_HYPERLINKS)
|
|
hyperlinks_remove(TAILQ_FIRST(&global_hyperlinks));
|
|
|
|
return (hlu->inner);
|
|
}
|
|
|
|
/* Get hyperlink by inner number. */
|
|
int
|
|
hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out,
|
|
const char **internal_id_out, const char **external_id_out)
|
|
{
|
|
struct hyperlinks_uri find, *hlu;
|
|
|
|
find.inner = inner;
|
|
|
|
hlu = RB_FIND(hyperlinks_by_inner_tree, &hl->by_inner, &find);
|
|
if (hlu == NULL)
|
|
return (0);
|
|
if (internal_id_out != NULL)
|
|
*internal_id_out = hlu->internal_id;
|
|
if (external_id_out != NULL)
|
|
*external_id_out = hlu->external_id;
|
|
*uri_out = hlu->uri;
|
|
return (1);
|
|
}
|
|
|
|
/* Initialize hyperlink set. */
|
|
struct hyperlinks *
|
|
hyperlinks_init(void)
|
|
{
|
|
struct hyperlinks *hl;
|
|
|
|
hl = xcalloc(1, sizeof *hl);
|
|
hl->next_inner = 1;
|
|
RB_INIT(&hl->by_uri);
|
|
RB_INIT(&hl->by_inner);
|
|
hl->references = 1;
|
|
return (hl);
|
|
}
|
|
|
|
/* Copy hyperlink set. */
|
|
struct hyperlinks *
|
|
hyperlinks_copy(struct hyperlinks *hl)
|
|
{
|
|
hl->references++;
|
|
return (hl);
|
|
}
|
|
|
|
/* Free all hyperlinks but not the set itself. */
|
|
void
|
|
hyperlinks_reset(struct hyperlinks *hl)
|
|
{
|
|
struct hyperlinks_uri *hlu, *hlu1;
|
|
|
|
RB_FOREACH_SAFE(hlu, hyperlinks_by_inner_tree, &hl->by_inner, hlu1)
|
|
hyperlinks_remove(hlu);
|
|
}
|
|
|
|
/* Free hyperlink set. */
|
|
void
|
|
hyperlinks_free(struct hyperlinks *hl)
|
|
{
|
|
if (--hl->references == 0) {
|
|
hyperlinks_reset(hl);
|
|
free(hl);
|
|
}
|
|
}
|