diff --git a/CHANGELOG.md b/CHANGELOG.md index b4a1152c..f38d3f18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.8.0](#1-8-0) * [1.7.2](#1-7-2) * [1.7.1](#1-7-1) @@ -26,6 +27,40 @@ * [1.2.0](#1-2-0) +## Unreleased + +### Added + +* `--log-level=none` command-line option. +* `Tc`, `setrgbf` and `setrgbb` capabilities in `foot` and `foot-direct` + terminfo entries. This should make 24-bit RGB colors work in tmux and + neovim, without the need for config hacks or detection heuristics + (https://codeberg.org/dnkl/foot/issues/615). + + +### Changed + +* Grapheme cluster width is now limited to two cells by default. This + may cause cursor synchronization issues with many applications. You + can set `[tweak].grapheme-width-method=wcswidth` to revert to the + behavior from foot-1.8.0. + + +### Deprecated +### Removed +### Fixed + +* Grapheme cluster state being reset between codepoints. +* Regression: custom URL key bindings not working + (https://codeberg.org/dnkl/foot/issues/614). + + +### Security +### Contributors + +* [craigbarnes](https://codeberg.org/craigbarnes) + + ## 1.8.0 ### Grapheme shaping diff --git a/client.c b/client.c index 9764da11..7b5cd770 100644 --- a/client.c +++ b/client.c @@ -58,22 +58,22 @@ print_usage(const char *prog_name) printf("Usage: %s [OPTIONS...] command [ARGS...]\n", prog_name); printf("\n"); printf("Options:\n"); - printf(" -t,--term=TERM value to set the environment variable TERM to (foot)\n" - " -T,--title=TITLE initial window title (foot)\n" - " -a,--app-id=ID window application ID (foot)\n" - " -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n" - " -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n" - " -m,--maximized start in maximized mode\n" - " -F,--fullscreen start in fullscreen mode\n" - " -L,--login-shell start shell as a login shell\n" - " -D,--working-directory=DIR directory to start in (CWD)\n" - " -s,--server-socket=PATH path to the server UNIX domain socket (default=$XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock)\n" - " -H,--hold remain open after child process exits\n" - " -N,--no-wait detach the client process from the running terminal, exiting immediately\n" - " -o,--override=[section.]key=value override configuration option\n" - " -d,--log-level={info|warning|error} log level (info)\n" - " -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n" - " -v,--version show the version number and quit\n"); + printf(" -t,--term=TERM value to set the environment variable TERM to (foot)\n" + " -T,--title=TITLE initial window title (foot)\n" + " -a,--app-id=ID window application ID (foot)\n" + " -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n" + " -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n" + " -m,--maximized start in maximized mode\n" + " -F,--fullscreen start in fullscreen mode\n" + " -L,--login-shell start shell as a login shell\n" + " -D,--working-directory=DIR directory to start in (CWD)\n" + " -s,--server-socket=PATH path to the server UNIX domain socket (default=$XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock)\n" + " -H,--hold remain open after child process exits\n" + " -N,--no-wait detach the client process from the running terminal, exiting immediately\n" + " -o,--override=[section.]key=value override configuration option\n" + " -d,--log-level={info|warning|error|none} log level (info)\n" + " -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n" + " -v,--version show the version number and quit\n"); } static bool NOINLINE diff --git a/completions/bash/foot b/completions/bash/foot index f03a9f53..f9d9d0fd 100644 --- a/completions/bash/foot +++ b/completions/bash/foot @@ -68,7 +68,7 @@ _foot() which fc-list > /dev/null || return 1 COMPREPLY=( $(compgen -W "$(fc-list : family | sed 's/,/\n/g' | uniq | tr -d ' ')" -- ${cur}) ) elif [[ ${prev} == '--log-level' ]] ; then - COMPREPLY=( $(compgen -W "error warning info" -- ${cur}) ) + COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) elif [[ ${prev} == '--log-colorize' ]] ; then COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) elif [[ ${prev} =~ ^(--app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|--check-config)$ ]] ; then diff --git a/completions/bash/footclient b/completions/bash/footclient index 8e0aa017..fea45945 100644 --- a/completions/bash/footclient +++ b/completions/bash/footclient @@ -59,7 +59,7 @@ _footclient() which toe > /dev/null || return 1 COMPREPLY=( $(compgen -W "$(toe -a | awk '$1 ~ /[+]/ {next}; {print $1}')" -- ${cur}) ) elif [[ ${prev} == '--log-level' ]] ; then - COMPREPLY=( $(compgen -W "error warning info" -- ${cur}) ) + COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) elif [[ ${prev} == '--log-colorize' ]] ; then COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) elif [[ ${prev} =~ ^(--app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|)$ ]] ; then diff --git a/completions/fish/foot.fish b/completions/fish/foot.fish index dbec61e7..81c7da61 100644 --- a/completions/fish/foot.fish +++ b/completions/fish/foot.fish @@ -15,7 +15,7 @@ complete -c foot -x -s W -l window-size-chars complete -c foot -F -s s -l server -d "run as server; open terminals by running footclient" complete -c foot -s H -l hold -d "remain open after child process exits" complete -c foot -r -s p -l print-pid -d "print PID to this file or FD when up and running (server mode only)" -complete -c foot -x -s d -l log-level -a "info warning error" -d "log-level (info)" +complete -c foot -x -s d -l log-level -a "info warning error none" -d "log-level (info)" complete -c foot -x -s l -l log-colorize -a "always never auto" -d "enable or disable colorization of log output on stderr" complete -c foot -s S -l log-no-syslog -d "disable syslog logging (server mode only)" complete -c foot -s v -l version -d "show the version number and quit" diff --git a/completions/fish/footclient.fish b/completions/fish/footclient.fish index 9b8caa84..8133f663 100644 --- a/completions/fish/footclient.fish +++ b/completions/fish/footclient.fish @@ -12,7 +12,7 @@ complete -c footclient -F -s s -l server-socket complete -c footclient -s H -l hold -d "remain open after child process exits" complete -c footclient -s N -l no-wait -d "detach the client process from the running terminal, exiting immediately" complete -c footclient -x -s o -l override -d "configuration option to override, in form SECTION.KEY=VALUE" -complete -c footclient -x -s d -l log-level -a "info warning error" -d "log-level (info)" +complete -c footclient -x -s d -l log-level -a "info warning error none" -d "log-level (info)" complete -c footclient -x -s l -l log-colorize -a "always never auto" -d "enable or disable colorization of log output on stderr" complete -c footclient -s v -l version -d "show the version number and quit" complete -c footclient -s h -l help -d "show help message and quit" diff --git a/completions/zsh/_foot b/completions/zsh/_foot index b3f9b42c..0f184cc0 100644 --- a/completions/zsh/_foot +++ b/completions/zsh/_foot @@ -18,7 +18,7 @@ _arguments \ '(-s --server)'{-s,--server}'[run as server; open terminals by running footclient]:server:_files' \ '(-H --hold)'{-H,--hold}'[remain open after child process exits]' \ '(-p --print-pid)'{-p,--print-pid}'[print PID to this file or FD when up and running (server mode only)]:pidfile:_files' \ - '(-d --log-level)'{-d,--log-level}'[log level (info)]:loglevel:(info warning error)' \ + '(-d --log-level)'{-d,--log-level}'[log level (info)]:loglevel:(info warning error none)' \ '(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \ '(-S --log-no-syslog)'{-s,--log-no-syslog}'[disable syslog logging (server mode only)]' \ '(-v --version)'{-v,--version}'[show the version number and quit]' \ diff --git a/completions/zsh/_footclient b/completions/zsh/_footclient index 3889c488..81b2ea95 100644 --- a/completions/zsh/_footclient +++ b/completions/zsh/_footclient @@ -15,7 +15,7 @@ _arguments \ '(-H --hold)'{-H,--hold}'[remain open after child process exits]' \ '(-N --no-wait)'{-N,--no-wait}'[detach the client process from the running terminal, exiting immediately]' \ '(-o --override)'{-o,--override}'[configuration option to override, in form SECTION.KEY=VALUE]:()' \ - '(-d --log-level)'{-d,--log-level}'[log level (info)]:loglevel:(info warning error)' \ + '(-d --log-level)'{-d,--log-level}'[log level (info)]:loglevel:(info warning error none)' \ '(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \ '(-v --version)'{-v,--version}'[show the version number and quit]' \ '(-h --help)'{-h,--help}'[show help message and quit]' \ diff --git a/config.c b/config.c index a95f8485..e02ca3de 100644 --- a/config.c +++ b/config.c @@ -103,8 +103,10 @@ log_and_notify(struct config *conf, enum log_class log_class, case LOG_CLASS_INFO: case LOG_CLASS_DEBUG: - BUG("unsupported log class: %d", log_class); - break; + case LOG_CLASS_NONE: + default: + BUG("unsupported log class: %d", (int)log_class); + return; } va_list va1, va2; @@ -191,7 +193,7 @@ struct path_component { }; typedef tll(struct path_component) path_components_t; -static void +static void NOINLINE path_component_add(path_components_t *components, const char *comp, int fd) { xassert(comp != NULL); @@ -201,14 +203,14 @@ path_component_add(path_components_t *components, const char *comp, int fd) tll_push_back(*components, pc); } -static void +static void NOINLINE path_component_destroy(struct path_component *component) { xassert(component->fd >= 0); close(component->fd); } -static void +static void NOINLINE path_components_destroy(path_components_t *components) { tll_foreach(*components, it) { @@ -1660,6 +1662,43 @@ pipe_argv_from_string(const char *value, char ***argv, return remove_len; } +static void NOINLINE +remove_action_from_key_bindings_list(struct config_key_binding_list *bindings, + int action, char **pipe_argv) +{ + size_t remove_first_idx = 0; + size_t remove_count = 0; + + for (size_t i = 0; i < bindings->count; i++) { + struct config_key_binding *binding = &bindings->arr[i]; + + if (binding->action == action && + ((binding->pipe.argv.args == NULL && pipe_argv == NULL) || + (binding->pipe.argv.args != NULL && pipe_argv != NULL && + argv_compare(binding->pipe.argv.args, pipe_argv) == 0))) + { + if (remove_count++ == 0) + remove_first_idx = i; + + xassert(remove_first_idx + remove_count - 1 == i); + + if (binding->pipe.master_copy) + free_argv(&binding->pipe.argv); + } + } + + if (remove_count == 0) + return; + + size_t move_count = bindings->count - (remove_first_idx + remove_count); + + memmove( + &bindings->arr[remove_first_idx], + &bindings->arr[remove_first_idx + remove_count], + move_count * sizeof(bindings->arr[0])); + bindings->count -= remove_count; +} + static bool NOINLINE parse_key_binding_section( const char *section, const char *key, const char *value, @@ -1686,17 +1725,7 @@ parse_key_binding_section( /* Unset binding */ if (strcasecmp(value, "none") == 0) { - for (size_t i = 0; i < bindings->count; i++) { - struct config_key_binding *binding = &bindings->arr[i]; - - if (binding->action != action) - continue; - - if (binding->pipe.master_copy) - free_argv(&binding->pipe.argv); - binding->action = BIND_ACTION_NONE; - } - + remove_action_from_key_bindings_list(bindings, action, pipe_argv); free(pipe_argv); return true; } @@ -1713,21 +1742,7 @@ parse_key_binding_section( return false; } - /* Remove existing bindings for this action+pipe */ - for (size_t i = 0; i < bindings->count; i++) { - struct config_key_binding *binding = &bindings->arr[i]; - - if (binding->action == action && - ((binding->pipe.argv.args == NULL && pipe_argv == NULL) || - (binding->pipe.argv.args != NULL && pipe_argv != NULL && - argv_compare(binding->pipe.argv.args, pipe_argv) == 0))) - { - - if (binding->pipe.master_copy) - free_argv(&binding->pipe.argv); - binding->action = BIND_ACTION_NONE; - } - } + remove_action_from_key_bindings_list(bindings, action, pipe_argv); /* Emit key bindings */ size_t ofs = bindings->count; @@ -1765,6 +1780,90 @@ parse_key_binding_section( return false; } +UNITTEST +{ + enum test_actions { + TEST_ACTION_NONE, + TEST_ACTION_FOO, + TEST_ACTION_BAR, + TEST_ACTION_COUNT, + }; + + const char *const map[] = { + [TEST_ACTION_NONE] = NULL, + [TEST_ACTION_FOO] = "foo", + [TEST_ACTION_BAR] = "bar", + }; + + struct config conf = {0}; + struct config_key_binding_list bindings = {0}; + + /* + * ADD foo=Escape + * + * This verifies we can bind a single key combo to an action. + */ + xassert(parse_key_binding_section( + "", "foo", "Escape", ALEN(map), map, &bindings, &conf, "", 0)); + xassert(bindings.count == 1); + xassert(bindings.arr[0].action == TEST_ACTION_FOO); + xassert(bindings.arr[0].sym == XKB_KEY_Escape); + + /* + * ADD bar=Control+g Control+Shift+x + * + * This verifies we can bind multiple key combos to an action. + */ + xassert(parse_key_binding_section( + "", "bar", "Control+g Control+Shift+x", ALEN(map), map, + &bindings, &conf, "", 0)); + xassert(bindings.count == 3); + xassert(bindings.arr[0].action == TEST_ACTION_FOO); + xassert(bindings.arr[1].action == TEST_ACTION_BAR); + xassert(bindings.arr[1].sym == XKB_KEY_g); + xassert(bindings.arr[1].modifiers.ctrl); + xassert(bindings.arr[2].action == TEST_ACTION_BAR); + xassert(bindings.arr[2].sym == XKB_KEY_x); + xassert(bindings.arr[2].modifiers.ctrl && bindings.arr[2].modifiers.shift); + + /* + * REPLACE foo with foo=Mod+v Shift+q + * + * This verifies we can update a single-combo action with multiple + * key combos. + */ + xassert(parse_key_binding_section( + "", "foo", "Mod1+v Shift+q", ALEN(map), map, + &bindings, &conf, "", 0)); + xassert(bindings.count == 4); + xassert(bindings.arr[0].action == TEST_ACTION_BAR); + xassert(bindings.arr[1].action == TEST_ACTION_BAR); + xassert(bindings.arr[2].action == TEST_ACTION_FOO); + xassert(bindings.arr[2].sym == XKB_KEY_v); + xassert(bindings.arr[2].modifiers.alt); + xassert(bindings.arr[3].action == TEST_ACTION_FOO); + xassert(bindings.arr[3].sym == XKB_KEY_q); + xassert(bindings.arr[3].modifiers.shift); + + /* + * REMOVE bar + */ + xassert(parse_key_binding_section( + "", "bar", "none", ALEN(map), map, &bindings, &conf, "", 0)); + xassert(bindings.count == 2); + xassert(bindings.arr[0].action == TEST_ACTION_FOO); + xassert(bindings.arr[1].action == TEST_ACTION_FOO); + + /* + * REMOVE foo + */ + xassert(parse_key_binding_section( + "", "foo", "none", ALEN(map), map, &bindings, &conf, "", 0)); + xassert(bindings.count == 0); + + free(bindings.arr); +} + static bool parse_section_key_bindings( const char *key, const char *value, struct config *conf, @@ -2156,6 +2255,15 @@ parse_section_tweak( LOG_WARN("tweak: grapheme shaping"); } + else if (strcmp(key, "grapheme-width-method") == 0) { + if (strcmp(value, "double-width") == 0) + conf->tweak.grapheme_width_method = GRAPHEME_WIDTH_DOUBLE; + else if (strcmp(value, "wcswidth") == 0) + conf->tweak.grapheme_width_method = GRAPHEME_WIDTH_WCSWIDTH; + + LOG_WARN("%s:%d [tweak]: grapheme-width-method=%s", path, lineno, value); + } + else if (strcmp(key, "render-timer") == 0) { if (strcmp(value, "none") == 0) { conf->tweak.render_timer_osd = false; @@ -2724,6 +2832,7 @@ config_load(struct config *conf, const char *conf_path, .fcft_filter = FCFT_SCALING_FILTER_LANCZOS3, .allow_overflowing_double_width_glyphs = true, .grapheme_shaping = false, + .grapheme_width_method = GRAPHEME_WIDTH_DOUBLE, .delayed_render_lower_ns = 500000, /* 0.5ms */ .delayed_render_upper_ns = 16666666 / 2, /* half a frame period (60Hz) */ .max_shm_pool_size = 512 * 1024 * 1024, @@ -2850,6 +2959,15 @@ out: } } +#if defined(_DEBUG) + for (size_t i = 0; i < conf->bindings.key.count; i++) + xassert(conf->bindings.key.arr[i].action != BIND_ACTION_NONE); + for (size_t i = 0; i < conf->bindings.search.count; i++) + xassert(conf->bindings.search.arr[i].action != BIND_ACTION_SEARCH_NONE); + for (size_t i = 0; i < conf->bindings.url.count; i++) + xassert(conf->bindings.url.arr[i].action != BIND_ACTION_URL_NONE); +#endif + free(conf_file.path); if (conf_file.fd >= 0) close(conf_file.fd); @@ -3032,6 +3150,27 @@ config_clone(const struct config *old) return conf; } +UNITTEST +{ + struct config original; + user_notifications_t nots = tll_init(); + config_override_t overrides = tll_init(); + + bool ret = config_load(&original, "/dev/null", ¬s, &overrides, false); + xassert(ret); + + struct config *clone = config_clone(&original); + xassert(clone != NULL); + xassert(clone != &original); + + config_free(original); + config_free(*clone); + free(clone); + + tll_free(overrides); + tll_free(nots); +} + void config_free(struct config conf) { diff --git a/config.h b/config.h index ee427267..2300a0ad 100644 --- a/config.h +++ b/config.h @@ -246,6 +246,7 @@ struct config { enum fcft_scaling_filter fcft_filter; bool allow_overflowing_double_width_glyphs; bool grapheme_shaping; + enum {GRAPHEME_WIDTH_WCSWIDTH, GRAPHEME_WIDTH_DOUBLE} grapheme_width_method; bool render_timer_osd; bool render_timer_log; bool damage_whole_window; diff --git a/doc/benchmark-results-desktop.svg b/doc/benchmark-results-desktop.svg new file mode 100644 index 00000000..aac846c8 --- /dev/null +++ b/doc/benchmark-results-desktop.svg @@ -0,0 +1,550 @@ + + + +Gnuplot +Produced by GNUPLOT 5.4 patchlevel 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + 200 + + + + + 400 + + + + + 600 + + + + + 800 + + + + + 1000 + + + + + 1200 + + + + + 1400 + + + + + cursor_motion + + + + + dense_cells + + + + + light_cells + + + + + scrolling + + + + + scrolling_bottom_region + + + + + scrolling_bottom_small_region + + + + + scrolling_fullscreen + + + + + scrolling_top_region + + + + + scrolling_top_small_region + + + + + unicode + + + + + + + + + milliseconds (lower is better) + + + + + gnuplot_plot_1 + + + foot-pgo + + + + + + + + + + gnuplot_plot_2 + + + + + + + gnuplot_plot_3 + + + + + + + gnuplot_plot_4 + + + + + + + gnuplot_plot_5 + + + + + + + gnuplot_plot_6 + + + + + + + gnuplot_plot_7 + + + + + + + gnuplot_plot_8 + + + + + + + gnuplot_plot_9 + + + + + + + gnuplot_plot_10 + + + + + + + gnuplot_plot_11 + + + foot-no-pgo + + + + + + + + + + gnuplot_plot_12 + + + + + + + gnuplot_plot_13 + + + + + + + gnuplot_plot_14 + + + + + + + gnuplot_plot_15 + + + + + + + gnuplot_plot_16 + + + + + + + gnuplot_plot_17 + + + + + + + gnuplot_plot_18 + + + + + + + gnuplot_plot_19 + + + + + + + gnuplot_plot_20 + + + + + + + gnuplot_plot_21 + + + alacritty + + + + + + + + + + gnuplot_plot_22 + + + + + + + gnuplot_plot_23 + + + + + + + gnuplot_plot_24 + + + + + + + gnuplot_plot_25 + + + + + + + gnuplot_plot_26 + + + + + + + gnuplot_plot_27 + + + + + + + gnuplot_plot_28 + + + + + + + gnuplot_plot_29 + + + + + + + gnuplot_plot_30 + + + + + + + gnuplot_plot_31 + + + urxvt + + + + + + + + + + gnuplot_plot_32 + + + + gnuplot_plot_33 + + + + + + + gnuplot_plot_34 + + + + + + + gnuplot_plot_35 + + + + + + + gnuplot_plot_36 + + + + + + + gnuplot_plot_37 + + + + + + + gnuplot_plot_38 + + + + + + + gnuplot_plot_39 + + + + + + + gnuplot_plot_40 + + + + + + + gnuplot_plot_41 + + + xterm + + + + + + + + + + gnuplot_plot_42 + + + + gnuplot_plot_43 + + + + + + + gnuplot_plot_44 + + + + gnuplot_plot_45 + + + + gnuplot_plot_46 + + + + gnuplot_plot_47 + + + + + + + gnuplot_plot_48 + + + + gnuplot_plot_49 + + + + gnuplot_plot_50 + + + + + + + + + + + + + + + + diff --git a/doc/benchmark-results-laptop.svg b/doc/benchmark-results-laptop.svg new file mode 100644 index 00000000..df8a3ad7 --- /dev/null +++ b/doc/benchmark-results-laptop.svg @@ -0,0 +1,572 @@ + + + +Gnuplot +Produced by GNUPLOT 5.4 patchlevel 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + 200 + + + + + 400 + + + + + 600 + + + + + 800 + + + + + 1000 + + + + + 1200 + + + + + 1400 + + + + + 1600 + + + + + 1800 + + + + + 2000 + + + + + cursor_motion + + + + + dense_cells + + + + + light_cells + + + + + scrolling + + + + + scrolling_bottom_region + + + + + scrolling_bottom_small_region + + + + + scrolling_fullscreen + + + + + scrolling_top_region + + + + + scrolling_top_small_region + + + + + unicode + + + + + + + + + milliseconds (lower is better) + + + + + gnuplot_plot_1 + + + foot-pgo + + + + + + + + + + gnuplot_plot_2 + + + + + + + gnuplot_plot_3 + + + + + + + gnuplot_plot_4 + + + + + + + gnuplot_plot_5 + + + + + + + gnuplot_plot_6 + + + + + + + gnuplot_plot_7 + + + + + + + gnuplot_plot_8 + + + + + + + gnuplot_plot_9 + + + + + + + gnuplot_plot_10 + + + + + + + gnuplot_plot_11 + + + foot-no-pgo + + + + + + + + + + gnuplot_plot_12 + + + + + + + gnuplot_plot_13 + + + + + + + gnuplot_plot_14 + + + + + + + gnuplot_plot_15 + + + + + + + gnuplot_plot_16 + + + + + + + gnuplot_plot_17 + + + + + + + gnuplot_plot_18 + + + + + + + gnuplot_plot_19 + + + + + + + gnuplot_plot_20 + + + + + + + gnuplot_plot_21 + + + alacritty + + + + + + + + + + gnuplot_plot_22 + + + + + + + gnuplot_plot_23 + + + + + + + gnuplot_plot_24 + + + + + + + gnuplot_plot_25 + + + + + + + gnuplot_plot_26 + + + + + + + gnuplot_plot_27 + + + + + + + gnuplot_plot_28 + + + + + + + gnuplot_plot_29 + + + + + + + gnuplot_plot_30 + + + + + + + gnuplot_plot_31 + + + urxvt + + + + + + + + + + gnuplot_plot_32 + + + + + + + gnuplot_plot_33 + + + + + + + gnuplot_plot_34 + + + + + + + gnuplot_plot_35 + + + + + + + gnuplot_plot_36 + + + + + + + gnuplot_plot_37 + + + + + + + gnuplot_plot_38 + + + + + + + gnuplot_plot_39 + + + + + + + gnuplot_plot_40 + + + + gnuplot_plot_41 + + + xterm + + + + + + + + + + gnuplot_plot_42 + + + + gnuplot_plot_43 + + + + + + + gnuplot_plot_44 + + + + gnuplot_plot_45 + + + + gnuplot_plot_46 + + + + gnuplot_plot_47 + + + + + + + gnuplot_plot_48 + + + + gnuplot_plot_49 + + + + gnuplot_plot_50 + + + + + + + + + + + + + + + + diff --git a/doc/benchmark.md b/doc/benchmark.md index 244f25b9..8c1b5f9b 100644 --- a/doc/benchmark.md +++ b/doc/benchmark.md @@ -5,20 +5,10 @@ All benchmarks are done using [vtebench](https://github.com/alacritty/vtebench): ```sh -vtebench -h $(tput lines) -w $(tput cols) -b 104857600 alt-screen-random-write > ~/alt-random -vtebench -c -h $(tput lines) -w $(tput cols) -b 104857600 alt-screen-random-write > ~/alt-random-colors -vtebench -h $(tput lines) -w $(tput cols) -b 10485760 scrolling > ~/scrolling -vtebench -h $(tput lines) -w $(tput cols) -b 104857600 scrolling --fill-lines > ~/scrolling-filled-lines -vtebench -h $(tput lines) -w $(tput cols) -b 10485760 unicode-random-write > ~/unicode-random +./target/release/vtebench -b ./benchmarks --dat /tmp/ ``` -They were "executed" using [benchmark.py](../scripts/benchmark.py), -which will load each file into memory, and then print it to the -terminal. This is done **20** times for each test. Then it calculates -the _mean_ and _standard deviation_ for each test. - - -## 2021-03-20 +## 2021-06-25 ### System @@ -40,14 +30,21 @@ Scrollback: 10000 lines ### Results +| Benchmark (times in ms) | Foot (GCC+PGO) 1.8.0 | Foot 1.8.0 | Alacritty 0.8.0 | URxvt 9.26 | XTerm 368 | +|-------------------------------|---------------------:|-----------:|----------------:|-----------:|----------:| +| cursor motion | 12.93 | 15.37 | 26.47 | 23.41 | 1304.00 | +| dense cells | 39.16 | 47.19 | 87.26 | 9110.00 | 10883.00 | +| light cells | 5.34 | 6.42 | 12.76 | 16.00 | 60.00 | +| scrollling | 144.26 | 139.93 | 133.98 | 117.52 | 3772.67 | +| scrolling bottom region | 130.81 | 125.34 | 116.10 | 117.31 | 3574.67 | +| scrolling bottom small region | 142.46 | 127.52 | 127.32 | 135.18 | 3572.67 | +| scrolling fullscreen | 5.43 | 5.27 | 12.06 | 11.97 | 118.62 | +| scrolling top region | 129.05 | 120.24 | 121.65 | 341.70 | 3567.33 | +| scrolling top small region | 121.59 | 109.82 | 137.03 | 219.96 | 3558.67 | +| unicode | 12.03 | 11.95 | 13.94 | 667.67 | 4905.67 | -| Benchmark | Foot (GCC+PGO) 1.7.0.r2 | Foot 1.7.0.r2 | Alacritty 0.7.2 | URxvt 9.22 | XTerm 366 | -|------------------------|------------------------:|--------------:|-------------------:|---------------:|---------------:| -| alt-random | 0.382s ±0.003 | 0.550s ±0.007 | 0.995s ±0.010 | 1.201s ±0.006 | 12.756s ±0.045 | -| alt-random-colors | 0.380s ±0.002 | 0.543s ±0.003 | 1.017s ±0.013 | 1.399s ±0.018 | 11.591s ±0.141 | -| scrolling | 1.302s ±0.019 | 1.284s ±0.052 | 1.107s ±0.028 | 1.097s ±0.015 | 37.537s ±0.121 | -| scrolling-filled-lines | 0.646s ±0.016 | 0.610s ±0.003 | 1.290s ±0.012 | 1.325s ±0.037 | 6.817s ±0.084 | -| unicode-random | 0.167s ±0.001 | 0.276s ±0.445 | 0.097s ±0.002 [^1] | 18.032s ±0.334 | 29.731s ±3.746 | + +![Graph of benchmark results for a beefy desktop system](benchmark-results-desktop.svg) ## 2021-03-20 @@ -73,32 +70,17 @@ Scrollback=10000 lines ### Results -| Benchmark | Foot (GCC+PGO) 1.7.0.r2 | Foot (no PGO) 1.7.0.r2 | Alacritty 0.7.2 | URxvt 9.22 | St 0.8.4 | XTerm 366 | -|------------------------|------------------------:|-----------------------:|-------------------:|-----------------:|--------------:|----------------:| -| alt-random | 0.714s ±0.047 | 0.900s ±0.041 | 1.586s ±0.045 | 1.684s ±0.034 | 2.054s ±0.121 | 37.205s ±0.252 | -| alt-random-colors | 0.736s ±0.054 | 0.950s ±0.082 | 1.565s ±0.043 | 2.150s ±0.137 | 2.195s ±0.154 | 33.112s ±0.167 | -| scrolling | 1.593s ±0.070 | 1.559s ±0.055 | 1.517s ±0.079 | 1.462s ±0.052 | 3.308s ±0.133 | 134.432s ±0.436 | -| scrolling-filled-lines | 1.178s ±0.044 | 1.309s ±0.045 | 2.281s ±0.086 | 2.044s ±0.060 | 2.732s ±0.056 | 20.753s ±0.067 | -| unicode-random | 0.349s ±0.009 | 0.352s ±0.007 | 0.148s ±0.010 [^1] | 19.090s ±0.363 | crashed | 15.579s ±0.093 | +| Benchmark (times in ms) | Foot (GCC+PGO) 1.8.0 | Foot 1.8.0 | Alacritty 0.8.0 | URxvt 9.26 | XTerm 368 | +|-------------------------------|---------------------:|-----------:|----------------:|-----------:|----------:| +| cursor motion | 14.49 | 16.60 | 26.89 | 23.45 | 1303.38 | +| dense cells | 41.00 | 52.45 | 92.02 | 1486.57 | 11957.00 | +| light cells | 7.97 | 8.54 | 21.43 | 20.45 | 111.96 | +| scrollling | 158.85 | 158.90 | 148.06 | 138.98 | 10083.00 | +| scrolling bottom region | 153.83 | 151.38 | 142.13 | 151.30 | 9988.50 | +| scrolling bottom small region | 143.51 | 141.46 | 162.03 | 192.37 | 9938.00 | +| scrolling fullscreen | 11.56 | 11.75 | 22.96 | 21.49 | 295.40 | +| scrolling top region | 148.96 | 148.18 | 155.05 | 482.05 | 10036.00 | +| scrolling top small region | 144.26 | 149.76 | 159.40 | 321.69 | 9942.50 | +| unicode | 21.02 | 22.09 | 25.79 | 14959.00 | 88697.00 | -[^1]: [Alacritty and "unicode-random"](#alacritty-and-unicode-random) - - -# Alacritty and "unicode-random" - -Alacritty is actually **really** slow at rendering this (whether it is -fallback fonts in general, emojis, or something else, I don't know). - -I believe the reason it finishes the benchmark so quickly is because -it reads from the PTY in a separate thread, into a larger receive -buffer which is then consumed by the main thread. This allows the -client program to write its output much faster since it is no longer -stalling on a blocked PTY. - -This means Alacritty only needs to render a couple of frames since it -can reach the final VT state almost immediately. - -On the other hand, `cat`:ing the `unicode-random` test file in an -endless loop, or just manually scrolling up after the benchmark is -done is **slow**, which besides being felt (input lag), can be seen by -setting `debug.render_timer = true` in `alacritty.yml`. +![Graph of benchmark results for a laptop](benchmark-results-laptop.svg) diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 80203ad9..ab34a2dd 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -126,7 +126,7 @@ the foot command line This option can only be used in combination with *-s*,*--server*. -*-d*,*--log-level*={*info*,*warning*,*error*} +*-d*,*--log-level*={*info*,*warning*,*error*,*none*} Log level, used both for log output on stderr as well as syslog. Default: _info_. diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 2e408b3a..684497ca 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -997,8 +997,26 @@ any of these options. but is necessary to not break cursor synchronization with the application running in foot. + See also: *grapheme-width-method*. + Default: _no_ +*grapheme-width-method* + Selects which method to use when calculating the width + (i.e. number of columns) of a grapheme cluster. One of + *double-width* and *wcswidth*. + + *wcswidth* simply adds together the individual width of all + codepoints making up the cluster. + + *double-width* does the same, but limits the maximum number of + columns to 2. This is more correct, but is likely to break + applications since applications typically use *wcswidth*(3) + internally to calculate the width. This results in cursor + de-synchronization issues. + + Default: _double-width_ + *max-shm-pool-size-mb* This option controls the amount of virtual address space used by the pixmap memory to which the terminal screen content is diff --git a/doc/footclient.1.scd b/doc/footclient.1.scd index 8cb05316..a002a899 100644 --- a/doc/footclient.1.scd +++ b/doc/footclient.1.scd @@ -69,7 +69,7 @@ terminal has terminated. Override an option set in the configuration file. If _SECTION_ is not given, defaults to _main_. -*-d*,*--log-level*={*info*,*warning*,*error*} +*-d*,*--log-level*={*info*,*warning*,*error*,*none*} Log level, used both for log output on stderr as well as syslog. Default: _info_. diff --git a/foot.info b/foot.info index 41c23593..37932204 100644 --- a/foot.info +++ b/foot.info @@ -24,6 +24,7 @@ foot+base|foot base fragment, xenl, AX, XT, + Tc, cols#80, it#8, lines#24, @@ -255,6 +256,8 @@ foot+base|foot base fragment, rs1=\Ec, rs2=\E[!p\E[?3;4l\E[4l\E>, sc=\E7, + setrgbb=\E[48\:2\:\:%p1%d\:%p2%d\:%p3%dm, + setrgbf=\E[38\:2\:\:%p1%d\:%p2%d\:%p3%dm, sgr0=\E(B\E[m, sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p5%t;2%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, sitm=\E[3m, diff --git a/input.c b/input.c index e33fdff4..9cf18b53 100644 --- a/input.c +++ b/input.c @@ -529,8 +529,6 @@ convert_key_bindings(const struct config *conf, struct seat *seat) { for (size_t i = 0; i < conf->bindings.key.count; i++) { const struct config_key_binding *binding = &conf->bindings.key.arr[i]; - if (binding->action == BIND_ACTION_NONE) - continue; convert_key_binding(seat, binding, &seat->kbd.bindings.key); } } @@ -540,8 +538,6 @@ convert_search_bindings(const struct config *conf, struct seat *seat) { for (size_t i = 0; i < conf->bindings.search.count; i++) { const struct config_key_binding *binding = &conf->bindings.search.arr[i]; - if (binding->action == BIND_ACTION_SEARCH_NONE) - continue; convert_key_binding(seat, binding, &seat->kbd.bindings.search); } } @@ -551,10 +547,6 @@ convert_url_bindings(const struct config *conf, struct seat *seat) { for (size_t i = 0; i < conf->bindings.url.count; i++) { const struct config_key_binding *binding = &conf->bindings.url.arr[i]; -#if 0 - if (binding->action == BIND_ACTION_URL_NONE) - continue; -#endif convert_key_binding(seat, binding, &seat->kbd.bindings.url); } } diff --git a/log.c b/log.c index fba53876..9dfc842d 100644 --- a/log.c +++ b/log.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -15,15 +16,19 @@ static bool colorize = false; static bool do_syslog = true; -static enum log_class log_level = LOG_CLASS_INFO; +static enum log_class log_level = LOG_CLASS_NONE; -static const char log_level_map[][8] = { - [LOG_CLASS_ERROR] = "error", - [LOG_CLASS_WARNING] = "warning", - [LOG_CLASS_INFO] = "info", -#if defined(_DEBUG) - [LOG_CLASS_DEBUG] = "debug", -#endif +static const struct { + const char name[8]; + const char log_prefix[7]; + uint8_t color; + int syslog_equivalent; +} log_level_map[] = { + [LOG_CLASS_NONE] = {"none", "none", 5, -1}, + [LOG_CLASS_ERROR] = {"error", " err", 31, LOG_ERR}, + [LOG_CLASS_WARNING] = {"warning", "warn", 33, LOG_WARNING}, + [LOG_CLASS_INFO] = {"info", "info", 97, LOG_INFO}, + [LOG_CLASS_DEBUG] = {"debug", " dbg", 36, LOG_DEBUG}, }; void @@ -35,20 +40,14 @@ log_init(enum log_colorize _colorize, bool _do_syslog, [LOG_FACILITY_DAEMON] = LOG_DAEMON, }; - static const int level_map[] = { - [LOG_CLASS_ERROR] = LOG_ERR, - [LOG_CLASS_WARNING] = LOG_WARNING, - [LOG_CLASS_INFO] = LOG_INFO, - [LOG_CLASS_DEBUG] = LOG_DEBUG, - }; - colorize = _colorize == LOG_COLORIZE_NEVER ? false : _colorize == LOG_COLORIZE_ALWAYS ? true : isatty(STDERR_FILENO); do_syslog = _do_syslog; log_level = _log_level; - if (do_syslog) { + int slvl = log_level_map[_log_level].syslog_equivalent; + if (do_syslog && slvl != -1) { openlog(NULL, /*LOG_PID*/0, facility_map[syslog_facility]); - setlogmask(LOG_UPTO(level_map[_log_level])); + setlogmask(LOG_UPTO(slvl)); } } @@ -63,34 +62,31 @@ static void _log(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, int sys_errno, va_list va) { + xassert(log_class > LOG_CLASS_NONE); + xassert(log_class < ALEN(log_level_map)); + if (log_class > log_level) return; - const char *class = "abcd"; - int class_clr = 0; - switch (log_class) { - case LOG_CLASS_ERROR: class = " err"; class_clr = 31; break; - case LOG_CLASS_WARNING: class = "warn"; class_clr = 33; break; - case LOG_CLASS_INFO: class = "info"; class_clr = 97; break; - case LOG_CLASS_DEBUG: class = " dbg"; class_clr = 36; break; - } + const char *prefix = log_level_map[log_class].log_prefix; + unsigned int class_clr = log_level_map[log_class].color; char clr[16]; - snprintf(clr, sizeof(clr), "\033[%dm", class_clr); - fprintf(stderr, "%s%s%s: ", colorize ? clr : "", class, colorize ? "\033[0m" : ""); + xsnprintf(clr, sizeof(clr), "\033[%um", class_clr); + fprintf(stderr, "%s%s%s: ", colorize ? clr : "", prefix, colorize ? "\033[0m" : ""); if (colorize) - fprintf(stderr, "\033[2m"); + fputs("\033[2m", stderr); fprintf(stderr, "%s:%d: ", file, lineno); if (colorize) - fprintf(stderr, "\033[0m"); + fputs("\033[0m", stderr); vfprintf(stderr, fmt, va); if (sys_errno != 0) fprintf(stderr, ": %s", strerror(sys_errno)); - fprintf(stderr, "\n"); + fputc('\n', stderr); } static void @@ -98,19 +94,14 @@ _sys_log(enum log_class log_class, const char *module, const char UNUSED *file, int UNUSED lineno, const char *fmt, int sys_errno, va_list va) { + xassert(log_class > LOG_CLASS_NONE); + xassert(log_class < ALEN(log_level_map)); + if (!do_syslog) return; /* Map our log level to syslog's level */ - int level = -1; - switch (log_class) { - case LOG_CLASS_ERROR: level = LOG_ERR; break; - case LOG_CLASS_WARNING: level = LOG_WARNING; break; - case LOG_CLASS_INFO: level = LOG_INFO; break; - case LOG_CLASS_DEBUG: level = LOG_DEBUG; break; - } - - xassert(level != -1); + int level = log_level_map[log_class].syslog_equivalent; char msg[4096]; int n = vsnprintf(msg, sizeof(msg), fmt, va); @@ -185,14 +176,25 @@ log_errno_provided(enum log_class log_class, const char *module, va_end(va); } +static size_t +map_len(void) +{ + size_t len = ALEN(log_level_map); +#ifndef _DEBUG + /* Exclude "debug" entry for non-debug builds */ + len--; +#endif + return len; +} + int log_level_from_string(const char *str) { if (unlikely(str[0] == '\0')) return -1; - for (int i = 0, n = ALEN(log_level_map); i < n; i++) - if (strcmp(str, log_level_map[i]) == 0) + for (int i = 0, n = map_len(); i < n; i++) + if (strcmp(str, log_level_map[i].name) == 0) return i; return -1; @@ -205,8 +207,8 @@ log_level_string_hint(void) if (buf[0] != '\0') return buf; - for (size_t i = 0, pos = 0, n = ALEN(log_level_map); i < n; i++) { - const char *entry = log_level_map[i]; + for (size_t i = 0, pos = 0, n = map_len(); i < n; i++) { + const char *entry = log_level_map[i].name; const char *delim = (i + 1 < n) ? ", " : ""; pos += xsnprintf(buf + pos, sizeof(buf) - pos, "'%s'%s", entry, delim); } diff --git a/log.h b/log.h index 367fd60e..dc458199 100644 --- a/log.h +++ b/log.h @@ -5,7 +5,14 @@ enum log_colorize { LOG_COLORIZE_NEVER, LOG_COLORIZE_ALWAYS, LOG_COLORIZE_AUTO }; enum log_facility { LOG_FACILITY_USER, LOG_FACILITY_DAEMON }; -enum log_class { LOG_CLASS_ERROR, LOG_CLASS_WARNING, LOG_CLASS_INFO, LOG_CLASS_DEBUG }; + +enum log_class { + LOG_CLASS_NONE, + LOG_CLASS_ERROR, + LOG_CLASS_WARNING, + LOG_CLASS_INFO, + LOG_CLASS_DEBUG +}; void log_init(enum log_colorize colorize, bool do_syslog, enum log_facility syslog_facility, enum log_class log_level); diff --git a/main.c b/main.c index bbe89052..f58bdb52 100644 --- a/main.c +++ b/main.c @@ -61,27 +61,27 @@ print_usage(const char *prog_name) "Usage: %s [OPTIONS...] command [ARGS...]\n" "\n" "Options:\n" - " -c,--config=PATH load configuration from PATH ($XDG_CONFIG_HOME/foot/foot.ini)\n" - " -C,--check-config verify configuration, exit with 0 if ok, otherwise exit with 1\n" - " -o,--override=[section.]key=value override configuration option\n" - " -f,--font=FONT comma separated list of fonts in fontconfig format (monospace)\n" - " -t,--term=TERM value to set the environment variable TERM to (%s)\n" - " -T,--title=TITLE initial window title (foot)\n" - " -a,--app-id=ID window application ID (foot)\n" - " -m,--maximized start in maximized mode\n" - " -F,--fullscreen start in fullscreen mode\n" - " -L,--login-shell start shell as a login shell\n" - " -D,--working-directory=DIR directory to start in (CWD)\n" - " -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n" - " -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n" - " -s,--server[=PATH] run as a server (use 'footclient' to start terminals).\n" - " Without PATH, $XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock will be used.\n" - " -H,--hold remain open after child process exits\n" - " -p,--print-pid=FILE|FD print PID to file or FD (only applicable in server mode)\n" - " -d,--log-level={info|warning|error} log level (info)\n" - " -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n" - " -s,--log-no-syslog disable syslog logging (only applicable in server mode)\n" - " -v,--version show the version number and quit\n", + " -c,--config=PATH load configuration from PATH ($XDG_CONFIG_HOME/foot/foot.ini)\n" + " -C,--check-config verify configuration, exit with 0 if ok, otherwise exit with 1\n" + " -o,--override=[section.]key=value override configuration option\n" + " -f,--font=FONT comma separated list of fonts in fontconfig format (monospace)\n" + " -t,--term=TERM value to set the environment variable TERM to (%s)\n" + " -T,--title=TITLE initial window title (foot)\n" + " -a,--app-id=ID window application ID (foot)\n" + " -m,--maximized start in maximized mode\n" + " -F,--fullscreen start in fullscreen mode\n" + " -L,--login-shell start shell as a login shell\n" + " -D,--working-directory=DIR directory to start in (CWD)\n" + " -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n" + " -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n" + " -s,--server[=PATH] run as a server (use 'footclient' to start terminals).\n" + " Without PATH, $XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock will be used.\n" + " -H,--hold remain open after child process exits\n" + " -p,--print-pid=FILE|FD print PID to file or FD (only applicable in server mode)\n" + " -d,--log-level={info|warning|error|none} log level (info)\n" + " -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n" + " -s,--log-no-syslog disable syslog logging (only applicable in server mode)\n" + " -v,--version show the version number and quit\n", prog_name, prog_name, DEFAULT_TERM); } @@ -383,11 +383,14 @@ main(int argc, char *const *argv) log_init(log_colorize, as_server && log_syslog, as_server ? LOG_FACILITY_DAEMON : LOG_FACILITY_USER, log_level); - _Static_assert(LOG_CLASS_ERROR + 1 == FCFT_LOG_CLASS_ERROR, + _Static_assert((int)LOG_CLASS_ERROR == (int)FCFT_LOG_CLASS_ERROR, "fcft log level enum offset"); _Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS, "fcft colorize enum mismatch"); - fcft_log_init((enum fcft_log_colorize)log_colorize, as_server && log_syslog, log_level + 1); + fcft_log_init( + (enum fcft_log_colorize)log_colorize, + as_server && log_syslog, + (enum fcft_log_class)log_level); argc -= optind; argv += optind; diff --git a/render.c b/render.c index d3e03b63..499303f0 100644 --- a/render.c +++ b/render.c @@ -617,7 +617,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, */ if (term->conf->tweak.allow_overflowing_double_width_glyphs && ((glyph_count > 0 && - glyphs[0]->cols == 1 && + cell_cols == 1 && glyphs[0]->width >= term->cell_width * 15 / 10 && glyphs[0]->width < 3 * term->cell_width && col < term->cols - 1) || diff --git a/terminal.c b/terminal.c index cb836fef..6c175147 100644 --- a/terminal.c +++ b/terminal.c @@ -1425,10 +1425,10 @@ term_destroy(struct terminal *term) } mtx_unlock(&term->render.workers.lock); + urls_reset(term); + free(term->vt.osc.data); free(term->vt.osc8.uri); - grid_free(&term->normal); - grid_free(&term->alt); composed_free(term->composed); @@ -1471,9 +1471,11 @@ term_destroy(struct terminal *term) sixel_fini(term); - urls_reset(term); term_ime_reset(term); + grid_free(&term->normal); + grid_free(&term->alt); + free(term->foot_exe); free(term->cwd); diff --git a/vt.c b/vt.c index ec011cad..b9a4f4b0 100644 --- a/vt.c +++ b/vt.c @@ -639,10 +639,10 @@ action_utf8_print(struct terminal *term, wchar_t wc) #if defined(FOOT_GRAPHEME_CLUSTERING) if (grapheme_clustering) { /* Check if we're on a grapheme cluster break */ - /* Note: utf8proc fails to ZWJ */ - if (utf8proc_grapheme_break_stateful(last, wc, &term->vt.grapheme_state) && - last != 0x200d /* ZWJ */) + if (utf8proc_grapheme_break_stateful( + last, wc, &term->vt.grapheme_state)) { + term_reset_grapheme_state(term); goto out; } } @@ -682,6 +682,7 @@ action_utf8_print(struct terminal *term, wchar_t wc) { wc = precomposed; width = precomposed_width; + term_reset_grapheme_state(term); goto out; } } @@ -746,6 +747,7 @@ action_utf8_print(struct terminal *term, wchar_t wc) * character chains. Fall through here and print the * current zero-width character to the current cell */ LOG_WARN("maximum number of composed characters reached"); + term_reset_grapheme_state(term); goto out; } @@ -762,28 +764,36 @@ action_utf8_print(struct terminal *term, wchar_t wc) (wanted_count - 2) * sizeof(new_cc->chars[0])); } - int grapheme_width = composed != NULL ? composed->width : base_width; + const int grapheme_width = + composed != NULL ? composed->width : base_width; - if (wc == 0xfe0f && grapheme_width < 2) - grapheme_width = 2; - else - grapheme_width += width; - new_cc->width = grapheme_width; + switch (term->conf->tweak.grapheme_width_method) { + case GRAPHEME_WIDTH_DOUBLE: + if (unlikely(wc == 0xfe0f)) + width = 2; + new_cc->width = min(grapheme_width + width, 2); + break; + + case GRAPHEME_WIDTH_WCSWIDTH: + new_cc->width = grapheme_width + width; + break; + } term->composed_count++; composed_insert(&term->composed, new_cc); wc = CELL_COMB_CHARS_LO + key; - width = grapheme_width; + width = new_cc->width; xassert(wc >= CELL_COMB_CHARS_LO); xassert(wc <= CELL_COMB_CHARS_HI); goto out; } - } + } else + term_reset_grapheme_state(term); + out: - term_reset_grapheme_state(term); if (width > 0) term_print(term, wc, width); }