From 1df7a8ba29b08aa363d4f664e920f0f4b373dabe Mon Sep 17 00:00:00 2001 From: Daniel Rainer Date: Sat, 28 Feb 2026 22:03:28 +0100 Subject: [PATCH] cleanup: remove obsolete ellipsis complexity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, we chose the ellipsis character/string based on the locale. We now assume a UTF-8 locale, and accordingly always use the Unicode HORIZONTAL ELLIPSIS U+2026 `…`. When this was changed, some of the logic for handling different ellipsis values was left behind. It no longer serves a purpose, so remove it. The functions returning constants are replaced by constants. Since the ellipsis as a `wstr` is only used in a single file, make it a local const there and define it via the `ELLIPSIS_CHAR` const. Put the `ELLIPSIS_CHAR` definition into `fish-widestring`, removing the dependency of `fish-wcstringutil` on `fish-common`, helping future extraction efforts. One localized message contains an ellipsis, which was inserted via a placeholder, preventing translators from localizing it. Since the ellipsis is a constant, put it directly into the localized string. Closes #12493 --- Cargo.lock | 1 - crates/common/src/lib.rs | 11 ----------- crates/wcstringutil/Cargo.toml | 1 - crates/wcstringutil/src/lib.rs | 28 ++++------------------------ crates/widestring/src/lib.rs | 3 +++ localization/po/de.po | 8 ++++---- localization/po/en.po | 8 ++++---- localization/po/es.po | 8 ++++---- localization/po/fr.po | 8 ++++---- localization/po/pl.po | 8 ++++---- localization/po/pt_BR.po | 8 ++++---- localization/po/sv.po | 8 ++++---- localization/po/zh_CN.po | 8 ++++---- localization/po/zh_TW.po | 10 +++++----- src/builtins/set.rs | 15 ++++----------- src/builtins/string/shorten.rs | 8 +++++--- src/pager.rs | 26 +++++--------------------- src/parse_util.rs | 4 ++-- src/reader/reader.rs | 10 +++++----- src/screen.rs | 17 ++++++----------- 20 files changed, 71 insertions(+), 127 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1100312c6..b98013e19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -410,7 +410,6 @@ name = "fish-wcstringutil" version = "0.0.0" dependencies = [ "fish-build-helper", - "fish-common", "fish-fallback", "fish-widestring", "rsconf", diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index f45022cc7..084783db6 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -159,17 +159,6 @@ pub fn exit_without_destructors(code: libc::c_int) -> ! { unsafe { libc::_exit(code) }; } -/// The character to use where the text has been truncated. -pub fn get_ellipsis_char() -> char { - '\u{2026}' // ('…') -} - -/// The character or string to use where text has been truncated (ellipsis if possible, otherwise -/// ...) -pub fn get_ellipsis_str() -> &'static wstr { - L!("\u{2026}") -} - // Only pub for `src/common.rs` pub static OBFUSCATION_READ_CHAR: AtomicU32 = AtomicU32::new(0); diff --git a/crates/wcstringutil/Cargo.toml b/crates/wcstringutil/Cargo.toml index a30db6d70..27793635f 100644 --- a/crates/wcstringutil/Cargo.toml +++ b/crates/wcstringutil/Cargo.toml @@ -7,7 +7,6 @@ repository.workspace = true license.workspace = true [dependencies] -fish-common.workspace = true fish-fallback.workspace = true fish-widestring.workspace = true diff --git a/crates/wcstringutil/src/lib.rs b/crates/wcstringutil/src/lib.rs index 10cbf4d73..3a726b8ba 100644 --- a/crates/wcstringutil/src/lib.rs +++ b/crates/wcstringutil/src/lib.rs @@ -5,9 +5,8 @@ os::unix::ffi::OsStringExt as _, }; -use fish_common::{get_ellipsis_char, get_ellipsis_str}; use fish_fallback::{fish_wcwidth, lowercase, lowercase_rev, wcscasecmp, wcscasecmp_fuzzy}; -use fish_widestring::{decode_byte_from_char, prelude::*}; +use fish_widestring::{ELLIPSIS_CHAR, decode_byte_from_char, prelude::*}; /// Return the number of newlines in a string. pub fn count_newlines(s: &wstr) -> usize { @@ -593,32 +592,13 @@ pub fn split_about<'haystack>( output } -#[derive(Eq, PartialEq)] -pub enum EllipsisType { - None, - // Prefer niceness over minimalness - Prettiest, - // Make every character count ($ instead of ...) - Shortest, -} - -pub fn truncate(input: &wstr, max_len: usize, etype: Option) -> WString { - let etype = etype.unwrap_or(EllipsisType::Prettiest); +// TODO: This should work on render width rather than the number of codepoints. +pub fn truncate(input: &wstr, max_len: usize) -> WString { if input.len() <= max_len { return input.to_owned(); } - - if etype == EllipsisType::None { - return input[..max_len].to_owned(); - } - if etype == EllipsisType::Prettiest { - let ellipsis_str = get_ellipsis_str(); - let mut output = input[..max_len - ellipsis_str.len()].to_owned(); - output += ellipsis_str; - return output; - } let mut output = input[..max_len - 1].to_owned(); - output.push(get_ellipsis_char()); + output.push(ELLIPSIS_CHAR); output } diff --git a/crates/widestring/src/lib.rs b/crates/widestring/src/lib.rs index 374dc4836..1efc34b5d 100644 --- a/crates/widestring/src/lib.rs +++ b/crates/widestring/src/lib.rs @@ -13,6 +13,9 @@ pub mod prelude { pub use crate::{IntoCharIter, L, ToWString, WExt, WString, wstr}; } +/// The character to use where the text has been truncated. +pub const ELLIPSIS_CHAR: char = '\u{2026}'; // ('…') + // These are in the Unicode private-use range. We really shouldn't use this // range but have little choice in the matter given how our lexer/parser works. // We can't use non-characters for these two ranges because there are only 66 of diff --git a/localization/po/de.po b/localization/po/de.po index 9a90a73a0..b85cc21aa 100644 --- a/localization/po/de.po +++ b/localization/po/de.po @@ -775,10 +775,6 @@ msgstr "%s: Wert nicht vollständig konvertiert (kann '%s' nicht konvertieren)" msgid "%s: variable '%s' is read-only" msgstr "" -#, c-format -msgid "%sand %u more rows" -msgstr "%sund %u weitere Zeilen" - msgid "'break' while not inside of loop" msgstr "" @@ -1874,6 +1870,10 @@ msgstr "mit Definition" msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr." msgstr "|& ist ungültig. In fish, nutze &| um stdout und stderr gleichzeitig zu pipen" +#, c-format +msgid "…and %u more rows" +msgstr "…und %u weitere Zeilen" + msgid "fish-section-tier1-from-script-explicitly-added" msgstr "" diff --git a/localization/po/en.po b/localization/po/en.po index 89300ee76..ae910e311 100644 --- a/localization/po/en.po +++ b/localization/po/en.po @@ -775,10 +775,6 @@ msgstr "" msgid "%s: variable '%s' is read-only" msgstr "" -#, c-format -msgid "%sand %u more rows" -msgstr "%sand %u more rows" - msgid "'break' while not inside of loop" msgstr "" @@ -1874,6 +1870,10 @@ msgstr "" msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr." msgstr "" +#, c-format +msgid "…and %u more rows" +msgstr "…and %u more rows" + msgid "fish-section-tier1-from-script-explicitly-added" msgstr "" diff --git a/localization/po/es.po b/localization/po/es.po index 0e136c5e4..d3ca6c720 100644 --- a/localization/po/es.po +++ b/localization/po/es.po @@ -775,10 +775,6 @@ msgstr "%s: el valor no se convirtió completamente (no es posible convertir '%s msgid "%s: variable '%s' is read-only" msgstr "%s: la variable '%s' es de solo lectura" -#, c-format -msgid "%sand %u more rows" -msgstr "%s y %u filas más" - msgid "'break' while not inside of loop" msgstr "'break' usado fuera de un bucle" @@ -1877,6 +1873,10 @@ msgstr "con definición" msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr." msgstr "|& no es válido. En fish, usa &| para redirigir stdout y stderr." +#, c-format +msgid "…and %u more rows" +msgstr "…y %u filas más" + msgid "fish-section-tier1-from-script-explicitly-added" msgstr "" diff --git a/localization/po/fr.po b/localization/po/fr.po index 4defa9c85..fc3e1f6ec 100644 --- a/localization/po/fr.po +++ b/localization/po/fr.po @@ -904,10 +904,6 @@ msgstr "" msgid "%s: variable '%s' is read-only" msgstr "" -#, c-format -msgid "%sand %u more rows" -msgstr "%set %u lignes de plus" - msgid "'break' while not inside of loop" msgstr "« break » hors d’une boucle" @@ -2003,6 +1999,10 @@ msgstr "avec la définition" msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr." msgstr "" +#, c-format +msgid "…and %u more rows" +msgstr "…et %u lignes de plus" + msgid "fish-section-tier1-from-script-explicitly-added" msgstr "" diff --git a/localization/po/pl.po b/localization/po/pl.po index 316ea9dc6..ee2fd3003 100644 --- a/localization/po/pl.po +++ b/localization/po/pl.po @@ -771,10 +771,6 @@ msgstr "" msgid "%s: variable '%s' is read-only" msgstr "" -#, c-format -msgid "%sand %u more rows" -msgstr "%sand %u więcej rzędów" - msgid "'break' while not inside of loop" msgstr "'break' użyte poza pętlą" @@ -1870,6 +1866,10 @@ msgstr "" msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr." msgstr "" +#, fuzzy, c-format +msgid "…and %u more rows" +msgstr "…and %u więcej rzędów" + msgid "fish-section-tier1-from-script-explicitly-added" msgstr "" diff --git a/localization/po/pt_BR.po b/localization/po/pt_BR.po index f7f7f04d6..b56a9f563 100644 --- a/localization/po/pt_BR.po +++ b/localization/po/pt_BR.po @@ -776,10 +776,6 @@ msgstr "" msgid "%s: variable '%s' is read-only" msgstr "" -#, c-format -msgid "%sand %u more rows" -msgstr "%se mais %u linhas" - msgid "'break' while not inside of loop" msgstr "'break' enquanto fora de um laço" @@ -1875,6 +1871,10 @@ msgstr "com definição" msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr." msgstr "" +#, fuzzy, c-format +msgid "…and %u more rows" +msgstr "…e mais %u linhas" + msgid "fish-section-tier1-from-script-explicitly-added" msgstr "" diff --git a/localization/po/sv.po b/localization/po/sv.po index 45c6da8ab..8ed50c9fc 100644 --- a/localization/po/sv.po +++ b/localization/po/sv.po @@ -772,10 +772,6 @@ msgstr "" msgid "%s: variable '%s' is read-only" msgstr "" -#, c-format -msgid "%sand %u more rows" -msgstr "%soch %u rader till" - msgid "'break' while not inside of loop" msgstr "" @@ -1871,6 +1867,10 @@ msgstr "" msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr." msgstr "" +#, fuzzy, c-format +msgid "…and %u more rows" +msgstr "…och %u rader till" + msgid "fish-section-tier1-from-script-explicitly-added" msgstr "" diff --git a/localization/po/zh_CN.po b/localization/po/zh_CN.po index af166579c..863d464c5 100644 --- a/localization/po/zh_CN.po +++ b/localization/po/zh_CN.po @@ -796,10 +796,6 @@ msgstr "%s: 数值未完全转换 (无法转换 '%s')" msgid "%s: variable '%s' is read-only" msgstr "%s: 变量 '%s' 只读" -#, c-format -msgid "%sand %u more rows" -msgstr "%s还有 %u 行" - msgid "'break' while not inside of loop" msgstr "'break' 不在循环内" @@ -1898,6 +1894,10 @@ msgstr ",定义为" msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr." msgstr "|& 无效。在 fish 中,用 &| 来同时管道链接 stdout 和 stderr。" +#, fuzzy, c-format +msgid "…and %u more rows" +msgstr "…还有 %u 行" + msgid "fish-section-tier1-from-script-explicitly-added" msgstr "" diff --git a/localization/po/zh_TW.po b/localization/po/zh_TW.po index 17c3a833c..85da8bd0a 100644 --- a/localization/po/zh_TW.po +++ b/localization/po/zh_TW.po @@ -769,11 +769,6 @@ msgstr "%s:值未完全轉換(無法轉換「%s」)" msgid "%s: variable '%s' is read-only" msgstr "%s:變數「%s」是唯讀的" -# 第一個 %s 是刪節號。此字串出現在按下 的清單太長的情況。 -#, c-format -msgid "%sand %u more rows" -msgstr "%s還有 %u 列" - msgid "'break' while not inside of loop" msgstr "「break」不在迴圈裡面" @@ -1873,6 +1868,11 @@ msgstr ",定義為" msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr." msgstr "|& 無效。在 fish 中請使用 &| 來同時管道傳輸 stdout 和 stderr。" +# 第一個 %s 是刪節號。此字串出現在按下 的清單太長的情況。 +#, fuzzy, c-format +msgid "…and %u more rows" +msgstr "…還有 %u 列" + msgid "fish-section-tier1-from-script-explicitly-added" msgstr "" diff --git a/src/builtins/set.rs b/src/builtins/set.rs index f6344fab7..d7bd8e39d 100644 --- a/src/builtins/set.rs +++ b/src/builtins/set.rs @@ -3,8 +3,6 @@ use crate::common::EscapeStringStyle; use crate::common::escape; use crate::common::escape_string; -use crate::common::get_ellipsis_char; -use crate::common::get_ellipsis_str; use crate::common::valid_var_name; use crate::env::EnvStackSetResult; use crate::env::EnvVarFlags; @@ -22,6 +20,7 @@ wutil::wcstoi::wcstoi_partial, }; use fish_common::help_section; +use fish_widestring::ELLIPSIS_CHAR; #[derive(Debug, Clone)] struct Options { @@ -582,7 +581,7 @@ fn list(opts: &Options, parser: &Parser, streams: &mut IoStreams) -> BuiltinResu out.push_utfstr(&val); if shorten { - out.push(get_ellipsis_char()); + out.push(ELLIPSIS_CHAR); } } } @@ -680,14 +679,8 @@ fn show_scope(var_name: &wstr, scope: EnvMode, streams: &mut IoStreams, vars: &d for i in 0..vals.len() { if vals.len() > 100 { if i == 50 { - // try to print a mid-line ellipsis because we are eliding lines not words - streams - .out - .appendln(if u32::from(get_ellipsis_char()) > 256 { - L!("\u{22EF}") - } else { - get_ellipsis_str() - }); + // print a mid-line ellipsis because we are eliding lines not words + streams.out.appendln(L!("\u{22EF}")); // ⋯ } if i >= 50 && i < vals.len() - 50 { continue; diff --git a/src/builtins/string/shorten.rs b/src/builtins/string/shorten.rs index 142fa7153..1b7dc1de1 100644 --- a/src/builtins/string/shorten.rs +++ b/src/builtins/string/shorten.rs @@ -1,5 +1,5 @@ use super::*; -use crate::common::get_ellipsis_str; +use fish_widestring::{ELLIPSIS_CHAR, decoded_width}; pub struct Shorten<'args> { ellipsis: &'args wstr, @@ -9,12 +9,14 @@ pub struct Shorten<'args> { quiet: bool, shorten_from: Direction, } +/// The character to use where the text has been truncated, in a [`wstr`]. +const ELLIPSIS_WSTR: &wstr = wstr::from_char_slice(&[ELLIPSIS_CHAR]); impl Default for Shorten<'_> { fn default() -> Self { Self { - ellipsis: get_ellipsis_str(), - ellipsis_width: width_without_escapes(get_ellipsis_str(), 0), + ellipsis: ELLIPSIS_WSTR, + ellipsis_width: decoded_width(ELLIPSIS_WSTR), max: None, no_newline: false, quiet: false, diff --git a/src/pager.rs b/src/pager.rs index ba4c4574d..e7dadaca6 100644 --- a/src/pager.rs +++ b/src/pager.rs @@ -4,9 +4,7 @@ use std::collections::HashMap; use std::collections::hash_map::Entry; -use crate::common::{ - EscapeFlags, EscapeStringStyle, escape_string, get_ellipsis_char, get_ellipsis_str, -}; +use crate::common::{EscapeFlags, EscapeStringStyle, escape_string}; use crate::complete::{CompleteFlags, Completion}; use crate::editable_line::EditableLine; use crate::highlight::{HighlightRole, HighlightSpec, highlight_shell}; @@ -15,7 +13,7 @@ use crate::screen::{CharOffset, Line, ScreenData, wcswidth_rendered, wcwidth_rendered}; use crate::termsize::Termsize; use fish_wcstringutil::string_fuzzy_match_string; -use fish_widestring::decoded_width; +use fish_widestring::{ELLIPSIS_CHAR, decoded_width}; /// Represents rendering from the pager. #[derive(Default)] @@ -271,11 +269,7 @@ fn completion_try_print( let mut progress_text = WString::new(); assert_ne!(rendering.remaining_to_disclose, 1); if rendering.remaining_to_disclose > 1 { - progress_text = wgettext_fmt!( - "%sand %u more rows", - get_ellipsis_str(), - rendering.remaining_to_disclose - ); + progress_text = wgettext_fmt!("…and %u more rows", rendering.remaining_to_disclose); } else if start_row > 0 || stop_row < row_count { // We have a scrollable interface. The +1 here is because we are zero indexed, but want // to present things as 1-indexed. We do not add 1 to stop_row or row_count because @@ -1140,7 +1134,7 @@ fn print_max_impl( break; } - let ellipsis = get_ellipsis_char(); + let ellipsis = ELLIPSIS_CHAR; if (width_c == remaining) && (has_more || i + 1 < s.len()) { line.append(ellipsis, color(i), offset_in_cmdline); let ellipsis_width = wcwidth_rendered(ellipsis); @@ -1287,7 +1281,6 @@ fn process_completions_into_infos(lst: &[Completion]) -> Vec { #[cfg(test)] mod tests { use super::{Pager, SelectionMotion}; - use crate::common::get_ellipsis_char; use crate::complete::{CompleteFlags, Completion}; use crate::prelude::*; use crate::termsize::Termsize; @@ -1404,21 +1397,12 @@ fn test_pager_layout() { let line = sd.line(0); WString::from(Vec::from_iter((0..line.len()).map(|i| line.char_at(i)))) }; - let compute_expected = |expected: &wstr| { - let ellipsis_char = get_ellipsis_char(); - if ellipsis_char != '\u{2026}' { - // hack: handle the case where ellipsis is not L'\x2026' - expected.replace(L!("\u{2026}"), wstr::from_char_slice(&[ellipsis_char])) - } else { - expected.to_owned() - } - }; macro_rules! validate { ($pager:expr, $width:expr, $expected:expr) => { assert_eq!( rendered_line($pager, $width), - compute_expected($expected), + $expected.to_owned(), "width {}", $width ); diff --git a/src/parse_util.rs b/src/parse_util.rs index 05a24fb9d..69d112ffc 100644 --- a/src/parse_util.rs +++ b/src/parse_util.rs @@ -1890,7 +1890,7 @@ pub fn expand_variable_error( global_after_dollar_pos, 1, ERROR_BRACKETED_VARIABLE_QUOTED1, - truncate(var_name, VAR_ERR_LEN, None) + truncate(var_name, VAR_ERR_LEN) ); } else { append_syntax_error!( @@ -1898,7 +1898,7 @@ pub fn expand_variable_error( global_after_dollar_pos, 1, ERROR_BRACKETED_VARIABLE1, - truncate(var_name, VAR_ERR_LEN, None), + truncate(var_name, VAR_ERR_LEN), ); } } else { diff --git a/src/reader/reader.rs b/src/reader/reader.rs index 6c37634b5..5bafd2896 100644 --- a/src/reader/reader.rs +++ b/src/reader/reader.rs @@ -28,7 +28,7 @@ use crate::common::ScopeGuarding; use crate::common::{ EscapeFlags, EscapeStringStyle, ScopeGuard, bytes2wcstring, escape, escape_string, - exit_without_destructors, get_ellipsis_char, get_obfuscation_read_char, get_program_name, + exit_without_destructors, get_obfuscation_read_char, get_program_name, restore_term_foreground_process_group_for_exit, shell_modes, write_loop, }; use crate::complete::{ @@ -121,11 +121,11 @@ use fish_fallback::fish_wcwidth; use fish_fallback::lowercase; use fish_wcstringutil::{ - CaseSensitivity, StringFuzzyMatch, count_preceding_backslashes, join_strings, - string_prefixes_string, string_prefixes_string_case_insensitive, + CaseSensitivity, IsPrefix, StringFuzzyMatch, count_preceding_backslashes, is_prefix, + join_strings, string_prefixes_string, string_prefixes_string_case_insensitive, string_prefixes_string_maybe_case_insensitive, }; -use fish_wcstringutil::{IsPrefix, is_prefix}; +use fish_widestring::ELLIPSIS_CHAR; use libc::{ _POSIX_VDISABLE, EIO, EISDIR, ENOTTY, EPERM, ESRCH, O_NONBLOCK, O_RDONLY, SIGINT, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO, VMIN, VQUIT, VSUSP, VTIME, c_char, @@ -7109,7 +7109,7 @@ fn handle_completions(&mut self, token_range: Range, mut comp: Vec, @@ -2040,7 +2036,7 @@ fn consumed_lines_or_truncated_suggestion( indent.drain(suggestion_end..suggestion_end + truncated_chars); if truncated_vertically && displayed_len > 0 { let suggestion_last = suggestion_end - 1; - autosuggestion.push(ellipsis_char); + autosuggestion.push(ELLIPSIS_CHAR); colors.insert(suggestion_end, colors[suggestion_last]); indent.insert(suggestion_end, indent[suggestion_last]); } @@ -2075,7 +2071,6 @@ pub fn wcswidth_rendered(s: &wstr) -> isize { #[cfg(test)] mod tests { - use crate::common::get_ellipsis_char; use crate::highlight::HighlightSpec; use crate::parse_util::compute_indents; use crate::prelude::*; @@ -2084,6 +2079,7 @@ mod tests { }; use crate::tests::prelude::*; use fish_wcstringutil::join_strings; + use fish_widestring::ELLIPSIS_CHAR; #[test] #[serial] @@ -2199,7 +2195,7 @@ fn test_prompt_truncation() { let mut cache = LayoutCache::new(); let mut trunc = WString::new(); - let ellipsis = || WString::from_chars([get_ellipsis_char()]); + let ellipsis = || WString::from_chars([ELLIPSIS_CHAR]); // No truncation. let layout = cache.calc_prompt_layout(L!("abcd"), Some(&mut trunc), usize::MAX); @@ -2343,7 +2339,6 @@ macro_rules! validate { let mut indent = compute_indents(&full_commandline); assert_eq!( compute_layout( - '…', $screen_width, /*screen_height=*/ 24, /*screen_viewport_y=*/ Some(0),