config+url: add support for user-defined regex patterns

Users can now define their own regex patterns, and use them via key
bindings:

    [regex:foo]
    regex=foo(bar)?
    launch=path-to-script-or-application {match}

    [key-bindings]
    regex-launch=[foo] Control+Shift+q
    regex-copy=[foo] Control+Mod1+Shift+q

That is, add a section called 'regex:', followed by an
identifier. Define a regex and a launcher command line.

Add a key-binding, regex-launch and/or regex-copy (similar to
show-urls-launch and show-urls-copy), and connect them to the regex
with the "[regex-name]" syntax (similar to how the pipe-* bindings
work).
This commit is contained in:
Daniel Eklöf 2025-02-03 08:55:47 +01:00
parent f718cb3fb0
commit 051cd6ecfc
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
8 changed files with 310 additions and 48 deletions

263
config.c
View file

@ -140,6 +140,8 @@ static const char *const binding_action_map[] = {
[BIND_ACTION_PROMPT_NEXT] = "prompt-next",
[BIND_ACTION_UNICODE_INPUT] = "unicode-input",
[BIND_ACTION_QUIT] = "quit",
[BIND_ACTION_REGEX_LAUNCH] = "regex-launch",
[BIND_ACTION_REGEX_COPY] = "regex-copy",
/* Mouse-specific actions */
[BIND_ACTION_SCROLLBACK_UP_MOUSE] = "scrollback-up-mouse",
@ -207,6 +209,7 @@ static_assert(ALEN(url_binding_action_map) == BIND_ACTION_URL_COUNT,
struct context {
struct config *conf;
const char *section;
const char *section_suffix;
const char *key;
const char *value;
@ -257,8 +260,9 @@ log_contextual(struct context *ctx, enum log_class log_class,
char *formatted_msg = xvasprintf(fmt, va);
va_end(va);
bool print_dot = ctx->key != NULL;
bool print_colon = ctx->value != NULL;
const bool print_dot = ctx->key != NULL;
const bool print_colon = ctx->value != NULL;
const bool print_section_suffix = ctx->section_suffix != NULL;
if (!print_dot)
ctx->key = "";
@ -266,10 +270,15 @@ log_contextual(struct context *ctx, enum log_class log_class,
if (!print_colon)
ctx->value = "";
if (!print_section_suffix)
ctx->section_suffix = "";
log_and_notify(
ctx->conf, log_class, file, lineno, "%s:%d: [%s]%s%s%s%s: %s",
ctx->path, ctx->lineno, ctx->section, print_dot ? "." : "",
ctx->key, print_colon ? ": " : "", ctx->value, formatted_msg);
ctx->conf, log_class, file, lineno, "%s:%d: [%s%s%s]%s%s%s%s: %s",
ctx->path, ctx->lineno, ctx->section,
print_section_suffix ? ":" : "", ctx->section_suffix,
print_dot ? "." : "", ctx->key, print_colon ? ": " : "",
ctx->value, formatted_msg);
free(formatted_msg);
}
@ -1261,6 +1270,72 @@ parse_section_url(struct context *ctx)
}
}
static bool
parse_section_regex(struct context *ctx)
{
struct config *conf = ctx->conf;
const char *key = ctx->key;
const char *regex_name =
ctx->section_suffix != NULL ? ctx->section_suffix : "";
struct custom_regex *regex = NULL;
tll_foreach(conf->custom_regexes, it) {
if (streq(it->item.name, regex_name)) {
regex = &it->item;
break;
}
}
if (streq(key, "regex")) {
const char *regex_string = ctx->value;
regex_t preg;
int r = regcomp(&preg, regex_string, REG_EXTENDED);
if (r != 0) {
char err_buf[128];
regerror(r, &preg, err_buf, sizeof(err_buf));
LOG_CONTEXTUAL_ERR("invalid regex: %s", err_buf);
return false;
}
if (regex == NULL) {
tll_push_back(conf->custom_regexes,
((struct custom_regex){.name = xstrdup(regex_name)}));
regex = &tll_back(conf->custom_regexes);
}
regfree(&regex->preg);
free(regex->regex);
regex->regex = xstrdup(regex_string);
regex->preg = preg;
return true;
}
else if (streq(key, "launch")) {
struct config_spawn_template launch;
if (!value_to_spawn_template(ctx, &launch))
return false;
if (regex == NULL) {
tll_push_back(conf->custom_regexes,
((struct custom_regex){.name = xstrdup(regex_name)}));
regex = &tll_back(conf->custom_regexes);
}
spawn_template_free(&regex->launch);
regex->launch = launch;
return true;
}
else {
LOG_CONTEXTUAL_ERR("not a valid option: %s", key);
return false;
}
}
static bool
parse_section_colors(struct context *ctx)
{
@ -1602,6 +1677,7 @@ free_binding_aux(struct binding_aux *aux)
case BINDING_AUX_NONE: break;
case BINDING_AUX_PIPE: free_argv(&aux->pipe); break;
case BINDING_AUX_TEXT: free(aux->text.data); break;
case BINDING_AUX_REGEX: free(aux->regex_name); break;
}
}
@ -1691,7 +1767,10 @@ binding_aux_equal(const struct binding_aux *a,
case BINDING_AUX_TEXT:
return a->text.len == b->text.len &&
memcmp(a->text.data, b->text.data, a->text.len) == 0;
memcmp(a->text.data, b->text.data, a->text.len) == 0;
case BINDING_AUX_REGEX:
return streq(a->regex_name, b->regex_name);
}
BUG("invalid AUX type: %d", a->type);
@ -1965,19 +2044,23 @@ modifiers_disjoint(const config_modifier_list_t *mods1,
}
static char * NOINLINE
modifiers_to_str(const config_modifier_list_t *mods)
modifiers_to_str(const config_modifier_list_t *mods, bool strip_last_plus)
{
size_t len = tll_length(*mods); /* '+' , and NULL terminator */
size_t len = tll_length(*mods); /* '+' separator */
tll_foreach(*mods, it)
len += strlen(it->item);
char *ret = xmalloc(len);
char *ret = xmalloc(len + 1);
size_t idx = 0;
tll_foreach(*mods, it) {
idx += snprintf(&ret[idx], len - idx, "%s", it->item);
ret[idx++] = '+';
}
ret[--idx] = '\0';
if (strip_last_plus)
idx--;
ret[idx] = '\0';
return ret;
}
@ -2036,21 +2119,40 @@ pipe_argv_from_value(struct context *ctx, struct argv *argv)
return remove_len;
}
static ssize_t NOINLINE
regex_name_from_value(struct context *ctx, char **regex_name)
{
*regex_name = NULL;
if (ctx->value[0] != '[')
return 0;
const char *regex_end = strrchr(ctx->value, ']');
if (regex_end == NULL) {
LOG_CONTEXTUAL_ERR("unclosed '['");
return -1;
}
size_t regex_len = regex_end - ctx->value - 1;
*regex_name = xstrndup(&ctx->value[1], regex_len);
ssize_t remove_len = regex_end + 1 - ctx->value;
ctx->value = regex_end + 1;
while (isspace(*ctx->value)) {
ctx->value++;
remove_len++;
}
return remove_len;
}
static bool NOINLINE
parse_key_binding_section(struct context *ctx,
int action_count,
const char *const action_map[static action_count],
struct config_key_binding_list *bindings)
{
struct binding_aux aux;
ssize_t pipe_remove_len = pipe_argv_from_value(ctx, &aux.pipe);
if (pipe_remove_len < 0)
return false;
aux.type = pipe_remove_len == 0 ? BINDING_AUX_NONE : BINDING_AUX_PIPE;
aux.master_copy = true;
for (int action = 0; action < action_count; action++) {
if (action_map[action] == NULL)
continue;
@ -2058,6 +2160,33 @@ parse_key_binding_section(struct context *ctx,
if (!streq(ctx->key, action_map[action]))
continue;
struct binding_aux aux = {.type = BINDING_AUX_NONE, .master_copy = true};
/* TODO: this is ugly... */
if (action_map == binding_action_map &&
action >= BIND_ACTION_PIPE_SCROLLBACK &&
action <= BIND_ACTION_PIPE_COMMAND_OUTPUT)
{
ssize_t pipe_remove_len = pipe_argv_from_value(ctx, &aux.pipe);
if (pipe_remove_len <= 0)
return false;
aux.type = BINDING_AUX_PIPE;
aux.master_copy = true;
} else if (action_map == binding_action_map &&
action >= BIND_ACTION_REGEX_LAUNCH &&
action <= BIND_ACTION_REGEX_COPY)
{
char *regex_name = NULL;
ssize_t regex_remove_len = regex_name_from_value(ctx, &regex_name);
if (regex_remove_len <= 0)
return false;
aux.type = BINDING_AUX_REGEX;
aux.master_copy = true;
aux.regex_name = regex_name;
}
if (!value_to_key_combos(ctx, action, &aux, bindings, KEY_BINDING)) {
free_binding_aux(&aux);
return false;
@ -2067,7 +2196,6 @@ parse_key_binding_section(struct context *ctx,
}
LOG_CONTEXTUAL_ERR("not a valid action: %s", ctx->key);
free_binding_aux(&aux);
return false;
}
@ -2265,7 +2393,7 @@ resolve_key_binding_collisions(struct config *conf, const char *section_name,
}
if (collision_type != COLLISION_NONE) {
char *modifier_names = modifiers_to_str(mods1);
char *modifier_names = modifiers_to_str(mods1, false);
char sym_name[64];
switch (type){
@ -2307,7 +2435,7 @@ resolve_key_binding_collisions(struct config *conf, const char *section_name,
case COLLISION_OVERRIDE: {
char *override_names = modifiers_to_str(
&conf->mouse.selection_override_modifiers);
&conf->mouse.selection_override_modifiers, true);
if (override_names[0] != '\0')
override_names[strlen(override_names) - 1] = '\0';
@ -2646,7 +2774,7 @@ parse_section_touch(struct context *ctx) {
}
static bool
parse_key_value(char *kv, const char **section, const char **key, const char **value)
parse_key_value(char *kv, char **section, const char **key, const char **value)
{
bool section_is_needed = section != NULL;
@ -2715,6 +2843,7 @@ enum section {
SECTION_DESKTOP_NOTIFICATIONS,
SECTION_SCROLLBACK,
SECTION_URL,
SECTION_REGEX,
SECTION_COLORS,
SECTION_CURSOR,
SECTION_MOUSE,
@ -2736,6 +2865,7 @@ typedef bool (*parser_fun_t)(struct context *ctx);
static const struct {
parser_fun_t fun;
const char *name;
bool allow_colon_suffix;
} section_info[] = {
[SECTION_MAIN] = {&parse_section_main, "main"},
[SECTION_SECURITY] = {&parse_section_security, "security"},
@ -2743,6 +2873,7 @@ static const struct {
[SECTION_DESKTOP_NOTIFICATIONS] = {&parse_section_desktop_notifications, "desktop-notifications"},
[SECTION_SCROLLBACK] = {&parse_section_scrollback, "scrollback"},
[SECTION_URL] = {&parse_section_url, "url"},
[SECTION_REGEX] = {&parse_section_regex, "regex", true},
[SECTION_COLORS] = {&parse_section_colors, "colors"},
[SECTION_CURSOR] = {&parse_section_cursor, "cursor"},
[SECTION_MOUSE] = {&parse_section_mouse, "mouse"},
@ -2760,11 +2891,29 @@ static const struct {
static_assert(ALEN(section_info) == SECTION_COUNT, "section info array size mismatch");
static enum section
str_to_section(const char *str)
str_to_section(char *str, char **suffix)
{
*suffix = NULL;
for (enum section section = SECTION_MAIN; section < SECTION_COUNT; ++section) {
if (streq(str, section_info[section].name))
const char *name = section_info[section].name;
if (streq(str, name))
return section;
else if (section_info[section].allow_colon_suffix) {
const size_t str_len = strlen(str);
const size_t name_len = strlen(name);
/* At least "section:" chars? */
if (str_len > name_len + 1) {
if (strncmp(str, name, name_len) == 0 && str[name_len] == ':') {
str[name_len] = '\0';
*suffix = &str[name_len + 1];
return section;
}
}
}
}
return SECTION_COUNT;
}
@ -2788,10 +2937,12 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar
}
char *section_name = xstrdup("main");
char *section_suffix = NULL;
struct context context = {
.conf = conf,
.section = section_name,
.section_suffix = section_suffix,
.path = path,
.lineno = 0,
.errors_are_fatal = errors_are_fatal,
@ -2872,7 +3023,8 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar
error_or_continue();
}
section = str_to_section(key_value);
char *maybe_section_suffix;
section = str_to_section(key_value, &maybe_section_suffix);
if (section == SECTION_COUNT) {
context.section = key_value;
LOG_CONTEXTUAL_ERR("invalid section name: %s", key_value);
@ -2881,8 +3033,11 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar
}
free(section_name);
free(section_suffix);
section_name = xstrdup(key_value);
section_suffix = maybe_section_suffix != NULL ? xstrdup(maybe_section_suffix) : NULL;
context.section = section_name;
context.section_suffix = section_suffix;
/* Process next line */
continue;
@ -2922,6 +3077,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar
done:
free(section_name);
free(section_suffix);
free(_line);
return ret;
}
@ -3016,7 +3172,6 @@ add_default_search_bindings(struct config *conf)
{BIND_ACTION_SEARCH_DELETE_NEXT_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_Delete}}},
{BIND_ACTION_SEARCH_DELETE_NEXT_WORD, m(XKB_MOD_NAME_ALT), {{XKB_KEY_d}}},
{BIND_ACTION_SEARCH_EXTEND_CHAR, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Right}}},
{BIND_ACTION_SEARCH_EXTEND_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_w}}},
{BIND_ACTION_SEARCH_EXTEND_WORD, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_Right}}},
{BIND_ACTION_SEARCH_EXTEND_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_w}}},
{BIND_ACTION_SEARCH_EXTEND_WORD_WS, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_w}}},
@ -3146,6 +3301,7 @@ config_load(struct config *conf, const char *conf_path,
.label_letters = xc32dup(U"sadfjklewcmpgh"),
.osc8_underline = OSC8_UNDERLINE_URL_MODE,
},
.custom_regexes = tll_init(),
.can_shape_grapheme = fcft_caps & FCFT_CAPABILITY_GRAPHEME_SHAPING,
.scrollback = {
.lines = 1000,
@ -3385,6 +3541,8 @@ bool
config_override_apply(struct config *conf, config_override_t *overrides,
bool errors_are_fatal)
{
char *section_name = NULL;
struct context context = {
.conf = conf,
.path = "override",
@ -3396,8 +3554,7 @@ config_override_apply(struct config *conf, config_override_t *overrides,
tll_foreach(*overrides, it) {
context.lineno++;
if (!parse_key_value(
it->item, &context.section, &context.key, &context.value))
if (!parse_key_value(it->item, &section_name, &context.key, &context.value))
{
LOG_CONTEXTUAL_ERR("syntax error: key/value pair has no %s",
context.key == NULL ? "key" : "value");
@ -3406,20 +3563,28 @@ config_override_apply(struct config *conf, config_override_t *overrides,
continue;
}
if (context.section[0] == '\0') {
if (section_name[0] == '\0') {
LOG_CONTEXTUAL_ERR("empty section name");
if (errors_are_fatal)
return false;
continue;
}
enum section section = str_to_section(context.section);
LOG_ERR("section-name=%s", section_name);
char *maybe_section_suffix = NULL;
enum section section = str_to_section(section_name, &maybe_section_suffix);
context.section = section_name;
context.section_suffix = maybe_section_suffix;
if (section == SECTION_COUNT) {
LOG_CONTEXTUAL_ERR("invalid section name: %s", context.section);
LOG_CONTEXTUAL_ERR("invalid section name: %s", section_name);
if (errors_are_fatal)
return false;
continue;
}
parser_fun_t section_parser = section_info[section].fun;
xassert(section_parser != NULL);
@ -3455,6 +3620,7 @@ key_binding_list_clone(struct config_key_binding_list *dst,
struct argv *last_master_argv = NULL;
uint8_t *last_master_text_data = NULL;
size_t last_master_text_len = 0;
char *last_master_regex_name = NULL;
dst->count = src->count;
dst->arr = xmalloc(src->count * sizeof(dst->arr[0]));
@ -3502,6 +3668,16 @@ key_binding_list_clone(struct config_key_binding_list *dst,
}
last_master_argv = NULL;
break;
case BINDING_AUX_REGEX:
if (old->aux.master_copy) {
new->aux.regex_name = xstrdup(old->aux.regex_name);
last_master_regex_name = new->aux.regex_name;
} else {
xassert(last_master_regex_name != NULL);
new->aux.regex_name = last_master_regex_name;
}
break;
}
}
}
@ -3536,6 +3712,20 @@ config_clone(const struct config *old)
conf->url.regex = xstrdup(old->url.regex);
regcomp(&conf->url.preg, conf->url.regex, REG_EXTENDED);
memset(&conf->custom_regexes, 0, sizeof(conf->custom_regexes));
tll_foreach(old->custom_regexes, it) {
const struct custom_regex *old_regex = &it->item;
tll_push_back(conf->custom_regexes,
((struct custom_regex){.name = xstrdup(old_regex->name),
.regex = xstrdup(old_regex->regex)}));
struct custom_regex *new_regex = &tll_back(conf->custom_regexes);
regcomp(&new_regex->preg, new_regex->regex, REG_EXTENDED);
spawn_template_clone(&new_regex->launch, &old_regex->launch);
}
key_binding_list_clone(&conf->bindings.key, &old->bindings.key);
key_binding_list_clone(&conf->bindings.search, &old->bindings.search);
key_binding_list_clone(&conf->bindings.url, &old->bindings.url);
@ -3618,6 +3808,15 @@ config_free(struct config *conf)
regfree(&conf->url.preg);
free(conf->url.regex);
tll_foreach(conf->custom_regexes, it) {
struct custom_regex *regex = &it->item;
free(regex->name);
free(regex->regex);
regfree(&regex->preg);
spawn_template_free(&regex->launch);
tll_remove(conf->custom_regexes, it);
}
free_key_binding_list(&conf->bindings.key);
free_key_binding_list(&conf->bindings.search);
free_key_binding_list(&conf->bindings.url);

View file

@ -61,6 +61,7 @@ enum binding_aux_type {
BINDING_AUX_NONE,
BINDING_AUX_PIPE,
BINDING_AUX_TEXT,
BINDING_AUX_REGEX,
};
struct binding_aux {
@ -74,6 +75,8 @@ struct binding_aux {
uint8_t *data;
size_t len;
} text;
char *regex_name;
};
};
@ -121,6 +124,13 @@ struct env_var {
};
typedef tll(struct env_var) env_var_list_t;
struct custom_regex {
char *name;
char *regex;
regex_t preg;
struct config_spawn_template launch;
};
struct config {
char *term;
char *shell;
@ -225,6 +235,8 @@ struct config {
regex_t preg;
} url;
tll(struct custom_regex) custom_regexes;
struct {
uint32_t fg;
uint32_t bg;

View file

@ -71,7 +71,9 @@
# osc8-underline=url-mode
# regex=([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’])
# [regex:your-fancy-name]
# regex=<a POSIX-Extended Regular Expression>
# launch=<path to script or application> {match}
[cursor]
# style=block

40
input.c
View file

@ -349,9 +349,9 @@ execute_binding(struct seat *seat, struct terminal *term,
action == BIND_ACTION_SHOW_URLS_LAUNCH ? URL_ACTION_LAUNCH :
URL_ACTION_PERSISTENT;
urls_collect(term, url_action, &term->urls);
urls_collect(term, url_action, &term->conf->url.preg, true, &term->urls);
urls_assign_key_combos(term->conf, &term->urls);
urls_render(term);
urls_render(term, &term->conf->url.launch);
return true;
}
@ -448,6 +448,42 @@ execute_binding(struct seat *seat, struct terminal *term,
term_shutdown(term);
return true;
case BIND_ACTION_REGEX_LAUNCH:
case BIND_ACTION_REGEX_COPY:
if (binding->aux->type != BINDING_AUX_REGEX)
return true;
tll_foreach(term->conf->custom_regexes, it) {
const struct custom_regex *regex = &it->item;
if (streq(regex->name, binding->aux->regex_name)) {
xassert(!urls_mode_is_active(term));
enum url_action url_action = action == BIND_ACTION_REGEX_LAUNCH
? URL_ACTION_LAUNCH : URL_ACTION_COPY;
if (regex->regex == NULL) {
LOG_ERR("regex:%s has no regex defined", regex->name);
return true;
}
if (url_action == URL_ACTION_LAUNCH && regex->launch.argv.args == NULL) {
LOG_ERR("regex:%s has no launch command defined", regex->name);
return true;
}
urls_collect(term, url_action, &regex->preg, false, &term->urls);
urls_assign_key_combos(term->conf, &term->urls);
urls_render(term, &regex->launch);
return true;
}
}
LOG_ERR(
"no regex section named '%s' defined in the configuration",
binding->aux->regex_name);
return true;
case BIND_ACTION_SELECT_BEGIN:
selection_start(
term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE, false);

View file

@ -41,6 +41,8 @@ enum bind_action_normal {
BIND_ACTION_PROMPT_NEXT,
BIND_ACTION_UNICODE_INPUT,
BIND_ACTION_QUIT,
BIND_ACTION_REGEX_LAUNCH,
BIND_ACTION_REGEX_COPY,
/* Mouse specific actions - i.e. they require a mouse coordinate */
BIND_ACTION_SCROLLBACK_UP_MOUSE,
@ -54,7 +56,7 @@ enum bind_action_normal {
BIND_ACTION_SELECT_QUOTE,
BIND_ACTION_SELECT_ROW,
BIND_ACTION_KEY_COUNT = BIND_ACTION_QUIT + 1,
BIND_ACTION_KEY_COUNT = BIND_ACTION_REGEX_COPY + 1,
BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1,
};

View file

@ -789,6 +789,7 @@ struct terminal {
bool urls_show_uri_on_jump_label;
struct grid *url_grid_snapshot;
bool ime_reenable_after_url_mode;
const struct config_spawn_template *url_launch;
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
bool ime_enabled;

View file

@ -67,12 +67,13 @@ spawn_url_launcher_with_token(struct terminal *term,
return false;
}
xassert(term->url_launch != NULL);
bool ret = false;
if (spawn_expand_template(
&term->conf->url.launch, 1,
(const char *[]){"url"},
(const char *[]){url},
term->url_launch, 2,
(const char *[]){"url", "match"},
(const char *[]){url, url},
&argc, &argv))
{
ret = spawn(
@ -84,6 +85,8 @@ spawn_url_launcher_with_token(struct terminal *term,
free(argv);
}
term->url_launch = NULL;
close(dev_null);
return ret;
}
@ -107,6 +110,8 @@ static bool
spawn_url_launcher(struct seat *seat, struct terminal *term, const char *url,
uint32_t serial)
{
xassert(term->url_launch != NULL);
struct spawn_activation_context *ctx = xmalloc(sizeof(*ctx));
*ctx = (struct spawn_activation_context){
.term = term,
@ -300,7 +305,8 @@ struct vline {
};
static void
regex_detected(const struct terminal *term, enum url_action action, url_list_t *urls)
regex_detected(const struct terminal *term, enum url_action action,
const regex_t *preg, url_list_t *urls)
{
/*
* Use regcomp()+regexec() to find patterns.
@ -381,8 +387,6 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t *
}
}
const regex_t *preg = &term->conf->url.preg;
for (size_t i = 0; i < ALEN(vlines); i++) {
const struct vline *v = &vlines[i];
if (v->utf8 == NULL)
@ -523,11 +527,13 @@ remove_overlapping(url_list_t *urls, int cols)
}
void
urls_collect(const struct terminal *term, enum url_action action, url_list_t *urls)
urls_collect(const struct terminal *term, enum url_action action,
const regex_t *preg, bool osc8, url_list_t *urls)
{
xassert(tll_length(term->urls) == 0);
osc8_uris(term, action, urls);
regex_detected(term, action, urls);
if (osc8)
osc8_uris(term, action, urls);
regex_detected(term, action, preg, urls);
remove_overlapping(urls, term->grid->num_cols);
}
@ -710,7 +716,7 @@ tag_cells_for_url(struct terminal *term, const struct url *url, bool value)
}
void
urls_render(struct terminal *term)
urls_render(struct terminal *term, const struct config_spawn_template *launch)
{
struct wl_window *win = term->window;
@ -745,6 +751,9 @@ urls_render(struct terminal *term)
/* Snapshot the current grid */
term->url_grid_snapshot = grid_snapshot(term->grid);
/* Remember which launcher to use */
term->url_launch = launch;
xassert(tll_length(win->urls) == 0);
tll_foreach(win->term->urls, it) {
struct wl_url url = {.url = &it->item};

View file

@ -14,10 +14,11 @@ static inline bool urls_mode_is_active(const struct terminal *term)
}
void urls_collect(
const struct terminal *term, enum url_action action, url_list_t *urls);
const struct terminal *term, enum url_action action, const regex_t *preg,
bool osc8, url_list_t *urls);
void urls_assign_key_combos(const struct config *conf, url_list_t *urls);
void urls_render(struct terminal *term);
void urls_render(struct terminal *term, const struct config_spawn_template *launch);
void urls_reset(struct terminal *term);
void urls_input(struct seat *seat, struct terminal *term,