cleanup: remove obsolete ellipsis complexity

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
This commit is contained in:
Daniel Rainer
2026-02-28 22:03:28 +01:00
committed by Johannes Altmanninger
parent 121b8fffa6
commit 1df7a8ba29
20 changed files with 71 additions and 127 deletions

1
Cargo.lock generated
View File

@@ -410,7 +410,6 @@ name = "fish-wcstringutil"
version = "0.0.0"
dependencies = [
"fish-build-helper",
"fish-common",
"fish-fallback",
"fish-widestring",
"rsconf",

View File

@@ -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);

View File

@@ -7,7 +7,6 @@ repository.workspace = true
license.workspace = true
[dependencies]
fish-common.workspace = true
fish-fallback.workspace = true
fish-widestring.workspace = true

View File

@@ -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<EllipsisType>) -> 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
}

View File

@@ -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

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 dune 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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -769,11 +769,6 @@ msgstr "%s值未完全轉換無法轉換「%s」"
msgid "%s: variable '%s' is read-only"
msgstr "%s變數「%s」是唯讀的"
# 第一個 %s 是刪節號。此字串出現在按下 <Tab> 的清單太長的情況。
#, 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 是刪節號。此字串出現在按下 <Tab> 的清單太長的情況。
#, fuzzy, c-format
msgid "…and %u more rows"
msgstr "…還有 %u 列"
msgid "fish-section-tier1-from-script-explicitly-added"
msgstr ""

View File

@@ -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;

View File

@@ -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,

View File

@@ -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<PagerComp> {
#[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
);

View File

@@ -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 {

View File

@@ -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<usize>, mut comp: Vec<Comple
prefix = full;
} else {
// Collapse parent directories and append end of string
prefix.push(get_ellipsis_char());
prefix.push(ELLIPSIS_CHAR);
let truncated = &full[full.len() - PREFIX_MAX_LEN..];
let (i, last_component) = truncated.split('/').enumerate().last().unwrap();

View File

@@ -7,9 +7,7 @@
//! The current implementation is less smart than ncurses allows and can not for example move blocks
//! of text around to handle text insertion.
use crate::common::{
get_ellipsis_char, get_omitted_newline_str, has_working_tty_timestamps, shell_modes, write_loop,
};
use crate::common::{get_omitted_newline_str, has_working_tty_timestamps, shell_modes, write_loop};
use crate::editable_line::line_at_cursor;
use crate::env::Environment;
use crate::flog::{flog, flogf};
@@ -28,6 +26,7 @@
use crate::wutil::fstat;
use fish_fallback::fish_wcwidth;
use fish_wcstringutil::{fish_wcwidth_visible, string_prefixes_string, wcs2bytes};
use fish_widestring::ELLIPSIS_CHAR;
use libc::{STDERR_FILENO, STDOUT_FILENO};
use nix::sys::termios;
use std::cell::RefCell;
@@ -338,7 +337,6 @@ struct ScrolledCursor {
// Compute a layout.
let layout = compute_layout(
get_ellipsis_char(),
screen_width,
screen_height,
self.viewport_y,
@@ -1744,8 +1742,7 @@ fn truncate_run(
// Bravely prepend ellipsis char and skip it.
// Ellipsis is always width 1.
let ellipsis = get_ellipsis_char();
run.insert(0, ellipsis);
run.insert(0, ELLIPSIS_CHAR);
curr_width += 1;
// Start removing characters after ellipsis.
@@ -1845,7 +1842,6 @@ struct ScreenLayout {
#[allow(clippy::too_many_arguments)]
fn compute_layout(
ellipsis_char: char,
screen_width: usize,
screen_height: usize,
screen_viewport_y: Option<usize>,
@@ -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),