diff --git a/Cargo.lock b/Cargo.lock index 28c80273d..7fb8da5fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,6 +161,7 @@ dependencies = [ "fish-build-helper", "fish-build-man-pages", "fish-common", + "fish-fallback", "fish-gettext", "fish-gettext-extraction", "fish-gettext-mo-file-parser", @@ -205,6 +206,25 @@ dependencies = [ [[package]] name = "fish-common" version = "0.0.0" +dependencies = [ + "libc", + "nix", + "once_cell", +] + +[[package]] +name = "fish-fallback" +version = "0.0.0" +dependencies = [ + "fish-build-helper", + "fish-common", + "fish-wchar", + "fish-widecharwidth", + "libc", + "once_cell", + "rsconf", + "widestring", +] [[package]] name = "fish-gettext" diff --git a/Cargo.toml b/Cargo.toml index f87bfa170..4916b466b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ errno = "0.3.0" fish-build-helper = { path = "crates/build-helper" } fish-build-man-pages = { path = "crates/build-man-pages" } fish-common = { path = "crates/common" } +fish-fallback = { path = "crates/fallback" } fish-gettext = { path = "crates/gettext" } fish-gettext-extraction = { path = "crates/gettext-extraction" } fish-gettext-maps = { path = "crates/gettext-maps" } @@ -93,6 +94,7 @@ errno.workspace = true fish-build-helper.workspace = true fish-build-man-pages = { workspace = true, optional = true } fish-common.workspace = true +fish-fallback.workspace = true fish-gettext = { workspace = true, optional = true } fish-gettext-extraction = { workspace = true, optional = true } fish-printf.workspace = true diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index cd49d4c49..2b2f29df0 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -6,5 +6,10 @@ version = "0.0.0" repository.workspace = true license.workspace = true +[dependencies] +libc.workspace = true +nix.workspace = true +once_cell.workspace = true + [lints] workspace = true diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index ebcc63853..fa51727fb 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,3 +1,8 @@ +use libc::STDIN_FILENO; +use once_cell::sync::OnceCell; +use std::env; +use std::os::unix::ffi::OsStrExt; + // 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 @@ -26,3 +31,30 @@ pub fn subslice_position(a: &[T], b: &[T]) -> Option { } a.windows(b.len()).position(|aw| aw == b) } + +/// This function attempts to distinguish between a console session (at the actual login vty) and a +/// session within a terminal emulator inside a desktop environment or over SSH. Unfortunately +/// there are few values of $TERM that we can interpret as being exclusively console sessions, and +/// most common operating systems do not use them. The value is cached for the duration of the fish +/// session. We err on the side of assuming it's not a console session. This approach isn't +/// bullet-proof and that's OK. +pub fn is_console_session() -> bool { + static IS_CONSOLE_SESSION: OnceCell = OnceCell::new(); + // TODO(terminal-workaround) + *IS_CONSOLE_SESSION.get_or_init(|| { + nix::unistd::ttyname(unsafe { std::os::fd::BorrowedFd::borrow_raw(STDIN_FILENO) }) + .is_ok_and(|buf| { + // Check if the tty matches /dev/(console|dcons|tty[uv\d]) + let is_console_tty = match buf.as_os_str().as_bytes() { + b"/dev/console" => true, + b"/dev/dcons" => true, + bytes => bytes.strip_prefix(b"/dev/tty").is_some_and(|rest| { + matches!(rest.first(), Some(b'u' | b'v' | b'0'..=b'9')) + }), + }; + + // and that $TERM is simple, e.g. `xterm` or `vt100`, not `xterm-something` or `sun-color`. + is_console_tty && env::var_os("TERM").is_none_or(|t| !t.as_bytes().contains(&b'-')) + }) + }) +} diff --git a/crates/fallback/Cargo.toml b/crates/fallback/Cargo.toml new file mode 100644 index 000000000..dce8fbe1a --- /dev/null +++ b/crates/fallback/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "fish-fallback" +edition.workspace = true +rust-version.workspace = true +version = "0.0.0" +repository.workspace = true +license.workspace = true + +[dependencies] +fish-common.workspace = true +fish-wchar.workspace = true +fish-widecharwidth.workspace = true +libc.workspace = true +once_cell.workspace = true +widestring.workspace = true + +[build-dependencies] +fish-build-helper.workspace = true +rsconf.workspace = true + +[lints] +workspace = true diff --git a/crates/fallback/build.rs b/crates/fallback/build.rs new file mode 100644 index 000000000..c91649543 --- /dev/null +++ b/crates/fallback/build.rs @@ -0,0 +1,5 @@ +use fish_build_helper::target_os_is_cygwin; + +fn main() { + rsconf::declare_cfg("cygwin", target_os_is_cygwin()) +} diff --git a/src/fallback.rs b/crates/fallback/src/lib.rs similarity index 98% rename from src/fallback.rs rename to crates/fallback/src/lib.rs index 17b021968..3dbcc3323 100644 --- a/src/fallback.rs +++ b/crates/fallback/src/lib.rs @@ -3,7 +3,7 @@ //! //! Many of these functions are more or less broken and incomplete. -use crate::wchar::prelude::*; +use fish_wchar::prelude::*; use fish_widecharwidth::{WcLookupTable, WcWidth}; use once_cell::sync::Lazy; use std::cmp; @@ -46,7 +46,7 @@ pub fn fish_wcwidth(c: char) -> isize { // 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 crate::common::is_console_session() { + if fish_common::is_console_session() { return wcwidth(c); } @@ -165,7 +165,7 @@ pub fn new(mut chars: std::iter::Map, Canonicalize>) -> Self { #[cfg(test)] mod tests { use super::wcscasecmp; - use crate::wchar::prelude::*; + use fish_wchar::prelude::*; use std::cmp::Ordering; #[test] diff --git a/src/builtins/string/pad.rs b/src/builtins/string/pad.rs index d2f6c2fd1..66b746df2 100644 --- a/src/builtins/string/pad.rs +++ b/src/builtins/string/pad.rs @@ -1,5 +1,5 @@ use super::*; -use crate::fallback::fish_wcwidth; +use fish_fallback::fish_wcwidth; pub struct Pad { char_to_pad: char, diff --git a/src/builtins/ulimit.rs b/src/builtins/ulimit.rs index ef7c776bf..22fbda1e2 100644 --- a/src/builtins/ulimit.rs +++ b/src/builtins/ulimit.rs @@ -5,8 +5,8 @@ use nix::sys::resource::Resource as ResourceEnum; use once_cell::sync::Lazy; -use crate::fallback::{fish_wcswidth, wcscasecmp}; use crate::wutil::perror; +use fish_fallback::{fish_wcswidth, wcscasecmp}; use super::prelude::*; diff --git a/src/common.rs b/src/common.rs index 0d25100d7..dceb9f552 100644 --- a/src/common.rs +++ b/src/common.rs @@ -4,7 +4,6 @@ BRACE_BEGIN, BRACE_END, BRACE_SEP, BRACE_SPACE, HOME_DIRECTORY, INTERNAL_SEPARATOR, PROCESS_EXPAND_SELF, PROCESS_EXPAND_SELF_STR, VARIABLE_EXPAND, VARIABLE_EXPAND_SINGLE, }; -use crate::fallback::fish_wcwidth; use crate::future_feature_flags::{FeatureFlag, feature_test}; use crate::global_safety::AtomicRef; use crate::global_safety::RelaxedAtomicBool; @@ -17,7 +16,8 @@ use crate::wildcard::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE}; use crate::wutil::fish_iswalnum; use bitflags::bitflags; -use fish_common::{ENCODE_DIRECT_END, char_offset, subslice_position}; +use fish_common::{ENCODE_DIRECT_END, char_offset, is_console_session, subslice_position}; +use fish_fallback::fish_wcwidth; use fish_wchar::{decode_byte_from_char, encode_byte_to_char}; use libc::{SIG_IGN, SIGTTOU, STDIN_FILENO}; use once_cell::sync::OnceCell; @@ -1796,33 +1796,6 @@ impl ScopeGuarding for ScopeGuard {} pub const fn assert_send() {} pub const fn assert_sync() {} -/// This function attempts to distinguish between a console session (at the actual login vty) and a -/// session within a terminal emulator inside a desktop environment or over SSH. Unfortunately -/// there are few values of $TERM that we can interpret as being exclusively console sessions, and -/// most common operating systems do not use them. The value is cached for the duration of the fish -/// session. We err on the side of assuming it's not a console session. This approach isn't -/// bullet-proof and that's OK. -pub fn is_console_session() -> bool { - static IS_CONSOLE_SESSION: OnceCell = OnceCell::new(); - // TODO(terminal-workaround) - *IS_CONSOLE_SESSION.get_or_init(|| { - nix::unistd::ttyname(unsafe { std::os::fd::BorrowedFd::borrow_raw(STDIN_FILENO) }) - .is_ok_and(|buf| { - // Check if the tty matches /dev/(console|dcons|tty[uv\d]) - let is_console_tty = match buf.as_os_str().as_bytes() { - b"/dev/console" => true, - b"/dev/dcons" => true, - bytes => bytes.strip_prefix(b"/dev/tty").is_some_and(|rest| { - matches!(rest.first(), Some(b'u' | b'v' | b'0'..=b'9')) - }), - }; - - // and that $TERM is simple, e.g. `xterm` or `vt100`, not `xterm-something` or `sun-color`. - is_console_tty && env::var_os("TERM").is_none_or(|t| !t.as_bytes().contains(&b'-')) - }) - }) -} - /// Asserts that a slice is alphabetically sorted by a &[wstr] `name` field. /// /// Mainly useful for static asserts/const eval. diff --git a/src/env_dispatch.rs b/src/env_dispatch.rs index 2c6e26a57..e5d0739ed 100644 --- a/src/env_dispatch.rs +++ b/src/env_dispatch.rs @@ -155,9 +155,9 @@ fn handle_timezone(var_name: &wstr, vars: &EnvStack) { } } -/// Update the value of [`FISH_EMOJI_WIDTH`](crate::fallback::FISH_EMOJI_WIDTH). +/// Update the value of [`FISH_EMOJI_WIDTH`](fish_fallback::FISH_EMOJI_WIDTH). pub fn guess_emoji_width(vars: &EnvStack) { - use crate::fallback::FISH_EMOJI_WIDTH; + use fish_fallback::FISH_EMOJI_WIDTH; if let Some(width_str) = vars.get(L!("fish_emoji_width")) { // The only valid values are 1 or 2; we default to 1 if it was an invalid int. @@ -199,7 +199,7 @@ pub fn guess_emoji_width(vars: &EnvStack) { // Default to whatever the system's wcwidth gives for U+1F603, but only if it's at least // 1 and at most 2. #[cfg(not(cygwin))] - let width = crate::fallback::wcwidth('😃').clamp(1, 2); + let width = fish_fallback::wcwidth('😃').clamp(1, 2); #[cfg(cygwin)] let width = 2_isize; FISH_EMOJI_WIDTH.store(width, Ordering::Relaxed); @@ -238,7 +238,7 @@ fn handle_change_ambiguous_width(vars: &EnvStack) { .unwrap_or(1) // Clamp in case of negative values. .max(0) as isize; - crate::fallback::FISH_AMBIGUOUS_WIDTH.store(new_width, Ordering::Relaxed); + fish_fallback::FISH_AMBIGUOUS_WIDTH.store(new_width, Ordering::Relaxed); } fn handle_term_size_change(vars: &EnvStack) { diff --git a/src/key.rs b/src/key.rs index 1e4e3224c..d46546534 100644 --- a/src/key.rs +++ b/src/key.rs @@ -2,13 +2,13 @@ use crate::{ common::{EscapeFlags, EscapeStringStyle, escape_string}, - fallback::fish_wcwidth, flog::FloggableDebug, future_feature_flags::{FeatureFlag, test as feature_test}, reader::safe_get_terminal_mode_on_startup, wchar::prelude::*, wutil::fish_wcstoul, }; +use fish_fallback::fish_wcwidth; use fish_wchar::decode_byte_from_char; pub(crate) const Backspace: char = '\u{F500}'; // below ENCODE_DIRECT_BASE diff --git a/src/lib.rs b/src/lib.rs index 95579d29d..c52033354 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,6 @@ pub mod event; pub mod exec; pub mod expand; -pub mod fallback; pub mod fd_monitor; pub mod fd_readable_set; pub mod fds; diff --git a/src/parse_constants.rs b/src/parse_constants.rs index 3d5346b6c..9d0ae079f 100644 --- a/src/parse_constants.rs +++ b/src/parse_constants.rs @@ -1,8 +1,8 @@ //! Constants used in the programmatic representation of fish code. -use crate::fallback::{fish_wcswidth, fish_wcwidth}; use crate::wchar::prelude::*; use bitflags::bitflags; +use fish_fallback::{fish_wcswidth, fish_wcwidth}; pub type SourceOffset = u32; diff --git a/src/reader/reader.rs b/src/reader/reader.rs index 8a3dd9aae..84e4c0fe4 100644 --- a/src/reader/reader.rs +++ b/src/reader/reader.rs @@ -71,7 +71,6 @@ use crate::exec::exec_subshell; use crate::expand::expand_one; use crate::expand::{ExpandFlags, ExpandResultCode, expand_string, expand_tilde}; -use crate::fallback::fish_wcwidth; use crate::fd_readable_set::poll_fd_readable; use crate::fds::{AutoCloseFd, make_fd_blocking, wopen_cloexec}; use crate::flog::{flog, flogf}; @@ -158,6 +157,7 @@ use crate::wildcard::wildcard_has; use crate::wutil::{fstat, perror, write_to_fd, wstat}; use crate::{abbrs, event, function}; +use fish_fallback::fish_wcwidth; /// A description of where fish is in the process of exiting. #[repr(u8)] diff --git a/src/screen.rs b/src/screen.rs index 570164502..dfcb725f4 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -26,7 +26,6 @@ has_working_tty_timestamps, shell_modes, wcs2bytes, write_loop, }; use crate::env::Environment; -use crate::fallback::fish_wcwidth; use crate::flog::{flog, flogf}; use crate::global_safety::RelaxedAtomicBool; use crate::highlight::{HighlightColorResolver, HighlightRole, HighlightSpec}; @@ -39,6 +38,7 @@ use crate::wchar::prelude::*; use crate::wcstringutil::{fish_wcwidth_visible, string_prefixes_string}; use crate::wutil::fstat; +use fish_fallback::fish_wcwidth; #[derive(Copy, Clone, Default)] pub enum CharOffset { diff --git a/src/wcstringutil.rs b/src/wcstringutil.rs index d4077d693..2cb2cb209 100644 --- a/src/wcstringutil.rs +++ b/src/wcstringutil.rs @@ -1,8 +1,8 @@ //! Helper functions for working with wcstring. use crate::common::{get_ellipsis_char, get_ellipsis_str}; -use crate::fallback::{fish_wcwidth, wcscasecmp, wcscasecmp_fuzzy}; use crate::wchar::prelude::*; +use fish_fallback::{fish_wcwidth, wcscasecmp, wcscasecmp_fuzzy}; use fish_wchar::decode_byte_from_char; /// Return the number of newlines in a string. diff --git a/src/wildcard.rs b/src/wildcard.rs index f08605c23..e207661ac 100644 --- a/src/wildcard.rs +++ b/src/wildcard.rs @@ -13,7 +13,6 @@ }; use crate::complete::{CompleteFlags, Completion, CompletionReceiver, PROG_COMPLETE_SEP}; use crate::expand::ExpandFlags; -use crate::fallback::wcscasecmp; use crate::future_feature_flags::FeatureFlag; use crate::future_feature_flags::feature_test; use crate::wchar::prelude::*; @@ -23,6 +22,7 @@ }; use crate::wutil::dir_iter::DirEntryType; use crate::wutil::{dir_iter::DirEntry, lwstat, waccess}; +use fish_fallback::wcscasecmp; localizable_consts!( COMPLETE_EXEC_DESC "command" diff --git a/src/wutil/mod.rs b/src/wutil/mod.rs index 58e40e18e..96da10650 100644 --- a/src/wutil/mod.rs +++ b/src/wutil/mod.rs @@ -11,8 +11,8 @@ use crate::common::{ bytes2wcstring, fish_reserved_codepoint, wcs2bytes, wcs2osstring, wcs2zstring, }; +use crate::flog; use crate::wcstringutil::{join_strings, wcs2bytes_callback}; -use crate::{fallback, flog}; use errno::errno; use fish_wchar::{L, WExt, WString, wstr}; pub use gettext::{ @@ -448,7 +448,7 @@ pub fn fish_iswalnum(c: char) -> bool { } pub fn fish_wcswidth(s: &wstr) -> isize { - fallback::fish_wcswidth(s) + fish_fallback::fish_wcswidth(s) } /// Given that `cursor` is a pointer into `base`, return the offset in characters.