diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ccf0e17..5b5f5e77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ to add support for this. The value `set-urgency` was chosen for forward-compatibility, in the hopes that this proposal eventualizes (https://codeberg.org/dnkl/foot/issues/157). +* **word-delimiters** option to `foot.ini` + (https://codeberg.org/dnkl/foot/issues/156). ### Changed diff --git a/config.c b/config.c index 3ebb0cae..c25c001a 100644 --- a/config.c +++ b/config.c @@ -570,6 +570,21 @@ parse_section_main(const char *key, const char *value, struct config *conf, conf->render_worker_count = count; } + else if (strcmp(key, "word-delimiters") == 0) { + size_t chars = mbstowcs(NULL, value, 0); + if (chars == (size_t)-1) { + LOG_AND_NOTIFY_ERR( + "%s:%d: [default]: word-delimiters: invalid string: %s", + path, lineno, value); + return false; + } + + free(conf->word_delimiters); + + conf->word_delimiters = xmalloc((chars + 1) * sizeof(wchar_t)); + mbstowcs(conf->word_delimiters, value, chars + 1); + } + else if (strcmp(key, "scrollback") == 0) { LOG_WARN("deprecated: %s:%d: [default]: scrollback: use 'scrollback.lines' instead'", path, lineno); @@ -648,7 +663,7 @@ parse_section_scrollback(const char *key, const char *value, struct config *conf } conf->scrollback.indicator.text = xcalloc(len + 1, sizeof(wchar_t)); - mbstowcs(conf->scrollback.indicator.text, value, len); + mbstowcs(conf->scrollback.indicator.text, value, len + 1); } } @@ -1872,6 +1887,7 @@ config_load(struct config *conf, const char *conf_path, .shell = get_shell(), .title = xstrdup("foot"), .app_id = xstrdup("foot"), + .word_delimiters = xwcsdup(L",│`|:\"'()[]{}<>"), .size = { .type = CONF_SIZE_PX, .px = { @@ -2024,6 +2040,7 @@ config_free(struct config conf) free(conf.shell); free(conf.title); free(conf.app_id); + free(conf.word_delimiters); free(conf.scrollback.indicator.text); tll_foreach(conf.fonts, it) config_font_destroy(&it->item); diff --git a/config.h b/config.h index 58b6f8a7..8e129329 100644 --- a/config.h +++ b/config.h @@ -53,6 +53,7 @@ struct config { char *shell; char *title; char *app_id; + wchar_t *word_delimiters; bool login_shell; struct { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 496162e8..6da1c26c 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -110,6 +110,11 @@ in this order: Default: _none_. +*word-delimiters* + String of characters that act as word delimiters when selecting + text. Note that whitespace characters are _always_ word + delimiters, regardless of this setting. Default: _,│`|:"'()[]{}<>_ + # SECTION: scrollback diff --git a/foot.ini b/foot.ini index fea2f2b8..a291f867 100644 --- a/foot.ini +++ b/foot.ini @@ -10,6 +10,7 @@ # login-shell=no # workers= # bell=none +# word-delimiters=,│`|:"'()[]{}<> [scrollback] # lines=1000 diff --git a/main.c b/main.c index 192a00f0..08c3b680 100644 --- a/main.c +++ b/main.c @@ -352,6 +352,13 @@ main(int argc, char *const *argv) LOG_INFO("arch: %s/%zu-bit, ", name.machine, sizeof(void *) * 8); } + setlocale(LC_CTYPE, ""); + LOG_INFO("locale: %s", setlocale(LC_CTYPE, NULL)); + if (!locale_is_utf8()) { + LOG_ERR("locale is not UTF-8"); + return ret; + } + struct config conf = {NULL}; if (!config_load(&conf, conf_path, &user_notifications, check_config)) { config_free(conf); @@ -365,13 +372,6 @@ main(int argc, char *const *argv) fcft_set_scaling_filter(conf.tweak.fcft_filter); - setlocale(LC_CTYPE, ""); - LOG_INFO("locale: %s", setlocale(LC_CTYPE, NULL)); - if (!locale_is_utf8()) { - LOG_ERR("locale is not UTF-8"); - return ret; - } - if (conf_term != NULL) { free(conf.term); conf.term = xstrdup(conf_term); diff --git a/misc.c b/misc.c index b8eb3296..feb5e73b 100644 --- a/misc.c +++ b/misc.c @@ -3,22 +3,13 @@ #include bool -isword(wchar_t wc, bool spaces_only) +isword(wchar_t wc, bool spaces_only, const wchar_t *delimiters) { if (spaces_only) return iswgraph(wc); - switch (wc) { - default: return iswgraph(wc); - - case L'(': case L')': - case L'[': case L']': - case L'{': case L'}': - case L'<': case L'>': - case L'│': case L'|': - case L',': - case L'`': case L'"': case L'\'': - case L':': + if (wcschr(delimiters, wc) != NULL) return false; - } + + return iswgraph(wc); } diff --git a/misc.h b/misc.h index 06dd3303..3c897336 100644 --- a/misc.h +++ b/misc.h @@ -3,4 +3,4 @@ #include #include -bool isword(wchar_t wc, bool spaces_only); +bool isword(wchar_t wc, bool spaces_only, const wchar_t *delimiters); diff --git a/search.c b/search.c index 551d21ba..0f1b4610 100644 --- a/search.c +++ b/search.c @@ -10,6 +10,7 @@ #define LOG_MODULE "search" #define LOG_ENABLE_DBG 0 #include "log.h" +#include "config.h" #include "grid.h" #include "input.h" #include "misc.h" @@ -368,7 +369,7 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) bool done = false; for (; end_col < term->cols; end_col++) { wchar_t wc = row->cells[end_col].wc; - if (wc == 0 || (!first && !isword(wc, spaces_only))) { + if (wc == 0 || (!first && !isword(wc, spaces_only, term->conf->word_delimiters))) { done = true; break; } diff --git a/selection.c b/selection.c index 6cfacc02..2a2e8034 100644 --- a/selection.c +++ b/selection.c @@ -15,6 +15,7 @@ #include "log.h" #include "async.h" +#include "config.h" #include "extract.h" #include "grid.h" #include "misc.h" @@ -725,7 +726,7 @@ selection_mark_word(struct seat *seat, struct terminal *term, int col, int row, const struct row *r = grid_row_in_view(term->grid, start.row); wchar_t c = r->cells[start.col].wc; - if (!(c == 0 || !isword(c, spaces_only))) { + if (!(c == 0 || !isword(c, spaces_only, term->conf->word_delimiters))) { while (true) { int next_col = start.col - 1; int next_row = start.row; @@ -740,7 +741,7 @@ selection_mark_word(struct seat *seat, struct terminal *term, int col, int row, const struct row *row = grid_row_in_view(term->grid, next_row); c = row->cells[next_col].wc; - if (c == 0 || !isword(c, spaces_only)) + if (c == 0 || !isword(c, spaces_only, term->conf->word_delimiters)) break; start.col = next_col; @@ -751,7 +752,7 @@ selection_mark_word(struct seat *seat, struct terminal *term, int col, int row, r = grid_row_in_view(term->grid, end.row); c = r->cells[end.col].wc; - if (!(c == 0 || !isword(c, spaces_only))) { + if (!(c == 0 || !isword(c, spaces_only, term->conf->word_delimiters))) { while (true) { int next_col = end.col + 1; int next_row = end.row; @@ -766,7 +767,7 @@ selection_mark_word(struct seat *seat, struct terminal *term, int col, int row, const struct row *row = grid_row_in_view(term->grid, next_row); c = row->cells[next_col].wc; - if (c == '\0' || !isword(c, spaces_only)) + if (c == '\0' || !isword(c, spaces_only, term->conf->word_delimiters)) break; end.col = next_col; diff --git a/xmalloc.c b/xmalloc.c index c90b97dc..a9a6685c 100644 --- a/xmalloc.c +++ b/xmalloc.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "xmalloc.h" @@ -54,6 +55,12 @@ xstrdup(const char *str) return check_alloc(strdup(str)); } +wchar_t * +xwcsdup(const wchar_t *str) +{ + return check_alloc(wcsdup(str)); +} + char * xstrndup(const char *str, size_t n) { diff --git a/xmalloc.h b/xmalloc.h index 2c12cf0a..13d69ca4 100644 --- a/xmalloc.h +++ b/xmalloc.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "macros.h" void *xmalloc(size_t size) XMALLOC; @@ -9,3 +10,4 @@ void *xrealloc(void *ptr, size_t size); char *xstrdup(const char *str) XSTRDUP; char *xstrndup(const char *str, size_t n) XSTRDUP; char *xasprintf(const char *format, ...) PRINTF(1) XMALLOC; +wchar_t *xwcsdup(const wchar_t *str) XSTRDUP;