Stop using wcwidth entirely

wcwidth isn't a great idea - it returns "-1" for anything it doesn't
know and non-printables, which can easily break text.

It is also unclear that it would be accurate to the system console,
and that's a minority use-case over using ssh to access older systems.

Additionally, it means we use one less function from libc and
simplifies the code.

Closes #12562
This commit is contained in:
Fabian Boehm
2026-03-15 14:07:37 +01:00
committed by Johannes Altmanninger
parent 8561008513
commit 146384abc6
2 changed files with 2 additions and 40 deletions

View File

@@ -29,32 +29,9 @@
static WC_LOOKUP_TABLE: LazyLock<WcLookupTable> = LazyLock::new(WcLookupTable::new);
/// A safe wrapper around the system `wcwidth()` function
#[cfg(not(cygwin))]
pub fn wcwidth(c: char) -> isize {
unsafe extern "C" {
pub unsafe fn wcwidth(c: libc::wchar_t) -> libc::c_int;
}
const {
assert!(size_of::<libc::wchar_t>() >= size_of::<char>());
}
let width = unsafe { wcwidth(c as libc::wchar_t) };
isize::try_from(width).unwrap()
}
// Big hack to use our versions of wcswidth where we know them to be broken, which is
// EVERYWHERE (https://github.com/fish-shell/fish-shell/issues/2199)
pub fn fish_wcwidth(c: char) -> isize {
// The system version of wcwidth should accurately reflect the ability to represent characters
// in the console session, but knows nothing about the capabilities of other terminal emulators
// or ttys. Use it from the start only if we are logged in to the physical console.
#[cfg(not(cygwin))]
if fish_common::is_console_session() {
return wcwidth(c);
}
// Check for VS16 which selects emoji presentation. This "promotes" a character like U+2764
// (width 1) to an emoji (probably width 2). So treat it as width 1 so the sums work. See #2652.
// VS15 selects text presentation.
@@ -75,18 +52,7 @@ pub fn fish_wcwidth(c: char) -> isize {
let width = WC_LOOKUP_TABLE.classify(c);
match width {
WcWidth::NonCharacter | WcWidth::NonPrint | WcWidth::Combining | WcWidth::Unassigned => {
#[cfg(not(cygwin))]
{
// Fall back to system wcwidth in this case.
wcwidth(c)
}
#[cfg(cygwin)]
{
// No system wcwidth for UTF-32 on cygwin.
0
}
}
WcWidth::NonCharacter | WcWidth::NonPrint | WcWidth::Combining | WcWidth::Unassigned => 0,
WcWidth::Ambiguous | WcWidth::PrivateUse => {
// TR11: "All private-use characters are by default classified as Ambiguous".
FISH_AMBIGUOUS_WIDTH.load(Ordering::Relaxed)
@@ -97,8 +63,7 @@ pub fn fish_wcwidth(c: char) -> isize {
}
}
/// fish's internal versions of wcwidth and wcswidth, which can use an internal implementation if
/// the system one is busted.
/// fish's internal versions of wcwidth and wcswidth
pub fn fish_wcswidth(s: &wstr) -> isize {
// ascii fast path; empty iterator returns true for .all()
if s.chars().all(|c| c.is_ascii() && !c.is_ascii_control()) {

View File

@@ -35,9 +35,6 @@ pub unsafe fn set_libc_locales(log_ok: bool) -> bool {
}
ok &= locale_string.is_some();
};
// For wcwidth(3p)
set("LC_CTYPE", libc::LC_CTYPE, from_environment);
set("LC_CTYPE", libc::LC_CTYPE, c"C.UTF-8");
// For strerror(3p) and strsignal(3p)
set("LC_MESSAGES", libc::LC_MESSAGES, from_environment);
// For builtin printf