diff --git a/CHANGELOG.rst b/CHANGELOG.rst index caa70a2c5..bec1fa9c2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Interactive improvements Improved terminal support ------------------------- - OSC 133 prompt markers now also mark the prompt end, which improves shell integration with terminals like iTerm2 (:issue:`11837`). +- Operating-system-specific key bindings are now decided based on the :ref:`terminal's host OS `. - New :ref:`feature flag ` ``omit-term-workarounds`` can be turned on to prevent fish from trying to work around incompatible terminals. For distributors and developers diff --git a/doc_src/cmds/status.rst b/doc_src/cmds/status.rst index 6fae22a9b..b68575e5f 100644 --- a/doc_src/cmds/status.rst +++ b/doc_src/cmds/status.rst @@ -138,6 +138,13 @@ The following operations (subcommands) are available: This is not available during early startup but only starting from when the first interactive prompt is shown, possibly via builtin :doc:`read `, so before the first ``fish_prompt`` or ``fish_read`` :ref:`event `. +.. _status-terminal-os: + +**terminal-os** + Prints the name of the operating system (OS) the terminal is running on, as reported via :ref:`XTGETTCAP query-os-name `. + Like :ref:`status terminal `, this only works once the first interactive prompt is shown. + Returns 1 if the OS name is not available. + .. _status-test-terminal-features: **test-terminal-feature** *FEATURE* diff --git a/doc_src/terminal-compatibility.rst b/doc_src/terminal-compatibility.rst index d23225ad8..3882949ff 100644 --- a/doc_src/terminal-compatibility.rst +++ b/doc_src/terminal-compatibility.rst @@ -261,12 +261,19 @@ Optional Commands - - Request terminfo capability (XTGETTCAP). The parameter is the capability's hex-encoded terminfo code. - To advertise a capability, the response must be of the form - ``\eP1+q Pt \e\\`` or ``\eP1+q Pt = Pt \e\\``. - In either variant the first parameter must be the hex-encoded terminfo code. - The second variant's second parameter is ignored. - Currently, fish only queries the :ref:`indn ` string capability. + The response must be of the form + ``\eP1+q Pt \e\\`` ("boolean") or ``\eP1+q Pt = Pt \e\\`` ("string"). + In either variant, the first parameter must be the same as the request parameter. + + fish queries the following string capabilities: + + * :ref:`indn ` + + The response's second parameter is ignored. + * ``query-os-name`` (for :ref:`status terminal-os `) + + Terminals running on Unix should respond with the hex encoding of ``uname=$(uname)`` as second parameter. .. _term-compat-dcs-gnu-screen: diff --git a/po/de.po b/po/de.po index 72d93cb66..95dad21b2 100644 --- a/po/de.po +++ b/po/de.po @@ -3147,6 +3147,9 @@ msgstr "" msgid "Print the name of the currently running command or function" msgstr "" +msgid "Print the operating system the terminal is running on" +msgstr "" + msgid "Print the path (without the file name) of the currently running script" msgstr "" diff --git a/po/en.po b/po/en.po index 2d01cf442..a32a4d97e 100644 --- a/po/en.po +++ b/po/en.po @@ -3145,6 +3145,9 @@ msgstr "" msgid "Print the name of the currently running command or function" msgstr "" +msgid "Print the operating system the terminal is running on" +msgstr "" + msgid "Print the path (without the file name) of the currently running script" msgstr "" diff --git a/po/fr.po b/po/fr.po index 1bd1257ce..8b953984c 100644 --- a/po/fr.po +++ b/po/fr.po @@ -3276,6 +3276,9 @@ msgstr "" msgid "Print the name of the currently running command or function" msgstr "" +msgid "Print the operating system the terminal is running on" +msgstr "" + msgid "Print the path (without the file name) of the currently running script" msgstr "" diff --git a/po/pl.po b/po/pl.po index 275ca666d..2657f965a 100644 --- a/po/pl.po +++ b/po/pl.po @@ -3141,6 +3141,9 @@ msgstr "" msgid "Print the name of the currently running command or function" msgstr "" +msgid "Print the operating system the terminal is running on" +msgstr "" + msgid "Print the path (without the file name) of the currently running script" msgstr "" diff --git a/po/pt_BR.po b/po/pt_BR.po index 1c12bf793..26f606cbe 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -3146,6 +3146,9 @@ msgstr "" msgid "Print the name of the currently running command or function" msgstr "" +msgid "Print the operating system the terminal is running on" +msgstr "" + msgid "Print the path (without the file name) of the currently running script" msgstr "" diff --git a/po/sv.po b/po/sv.po index 701839caf..3a9a7ac9f 100644 --- a/po/sv.po +++ b/po/sv.po @@ -3142,6 +3142,9 @@ msgstr "" msgid "Print the name of the currently running command or function" msgstr "" +msgid "Print the operating system the terminal is running on" +msgstr "" + msgid "Print the path (without the file name) of the currently running script" msgstr "" diff --git a/po/zh_CN.po b/po/zh_CN.po index c35d9d3df..7c46fe167 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -3174,6 +3174,9 @@ msgstr "打印当前函数的名称" msgid "Print the name of the currently running command or function" msgstr "打印当前运行的命令或函数的名称" +msgid "Print the operating system the terminal is running on" +msgstr "" + msgid "Print the path (without the file name) of the currently running script" msgstr "打印当前运行的脚本的路径 (不含文件名)" diff --git a/po/zh_TW.po b/po/zh_TW.po index da64b4b5a..529984297 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -3149,6 +3149,9 @@ msgstr "印出目前函式的名稱" msgid "Print the name of the currently running command or function" msgstr "印出目前執行的命令或函式名稱" +msgid "Print the operating system the terminal is running on" +msgstr "" + msgid "Print the path (without the file name) of the currently running script" msgstr "印出目前執行的命令稿路徑(不包括檔名)" diff --git a/share/completions/status.fish b/share/completions/status.fish index c3b5a3be6..348da9549 100644 --- a/share/completions/status.fish +++ b/share/completions/status.fish @@ -29,6 +29,7 @@ set -l __fish_status_all_commands \ print-stack-trace \ stack-trace \ terminal \ + terminal-os \ test-feature \ test-terminal-feature @@ -69,6 +70,7 @@ complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_com complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a help-sections -d "List section arguments for the 'help' command" complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a fish-path -d "Print the path to the current instance of fish" complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a terminal -d "Print name and version of the terminal fish is running in" +complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a terminal-os -d "Print the operating system the terminal is running on" complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a test-terminal-feature -d "Test if the terminal suports the given feature" complete -f -c status -n "__fish_seen_subcommand_from test-terminal-feature" -a 'scroll-content-up\t"Command for scrolling up terminal contents"' diff --git a/share/functions/__fish_per_os_bind.fish b/share/functions/__fish_per_os_bind.fish new file mode 100644 index 000000000..e8b57a35e --- /dev/null +++ b/share/functions/__fish_per_os_bind.fish @@ -0,0 +1,12 @@ +# localization: skip(private) +function __fish_per_os_bind + set -l macos $argv[-2] + set -l non_macos $argv[-1] + set -e argv[-2..-1] + for varname in macos non_macos + if contains -- $$varname (bind --function-names) + set $varname 'commandline -f '$$varname + end + end + bind $argv "if fish_in_macos_terminal; $macos; else $non_macos; end" +end diff --git a/share/functions/__fish_shared_key_bindings.fish b/share/functions/__fish_shared_key_bindings.fish index b702fc67a..8fef567f1 100644 --- a/share/functions/__fish_shared_key_bindings.fish +++ b/share/functions/__fish_shared_key_bindings.fish @@ -19,13 +19,8 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod bind --preset $argv left backward-char # Ctrl-left/right - these also work in vim. - if test (__fish_uname) = Darwin - bind --preset $argv ctrl-right forward-token - bind --preset $argv ctrl-left backward-token - else - bind --preset $argv ctrl-right forward-word - bind --preset $argv ctrl-left backward-word - end + __fish_per_os_bind --preset $argv ctrl-right forward-token forward-word + __fish_per_os_bind --preset $argv ctrl-left backward-token backward-word bind --preset $argv pageup beginning-of-history bind --preset $argv pagedown end-of-history @@ -52,23 +47,13 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod bind --preset $argv alt-b prevd-or-backward-word bind --preset $argv alt-f nextd-or-forward-word - # TODO(terminal-workaround) - set -l alt_right_aliases alt-right \e\[1\;9C # iTerm2 < 3.5.12 - set -l alt_left_aliases alt-left \e\[1\;9D # iTerm2 < 3.5.12 - if test (__fish_uname) = Darwin - for alt_right in $alt_right_aliases - bind --preset $argv $alt_right nextd-or-forward-word - end - for alt_left in $alt_left_aliases - bind --preset $argv $alt_left prevd-or-backward-word - end - else - for alt_right in $alt_right_aliases - bind --preset $argv $alt_right nextd-or-forward-token - end - for alt_left in $alt_left_aliases - bind --preset $argv $alt_left prevd-or-backward-token - end + for alt_right in alt-right \e\[1\;9C # TODO(terminal-workaround) iTerm2 < 3.5.12 + __fish_per_os_bind --preset $argv $alt_right \ + nextd-or-forward-word nextd-or-forward-token + end + for alt_left in alt-left \e\[1\;9D # TODO(terminal-workaround) iTerm2 < 3.5.12 + __fish_per_os_bind --preset $argv $alt_left \ + prevd-or-backward-word prevd-or-backward-token end bind --preset $argv alt-up history-token-search-backward diff --git a/share/functions/fish_default_key_bindings.fish b/share/functions/fish_default_key_bindings.fish index ae86a8a1c..c6806e02f 100644 --- a/share/functions/fish_default_key_bindings.fish +++ b/share/functions/fish_default_key_bindings.fish @@ -50,19 +50,11 @@ function fish_default_key_bindings -d "emacs-like key binds" bind --preset $argv alt-u upcase-word bind --preset $argv alt-c capitalize-word - if test (__fish_uname) = Darwin - bind --preset $argv alt-backspace backward-kill-word - bind --preset $argv ctrl-alt-h backward-kill-word - bind --preset $argv ctrl-backspace backward-kill-token - bind --preset $argv alt-delete kill-word - bind --preset $argv ctrl-delete kill-token - else - bind --preset $argv alt-backspace backward-kill-token - bind --preset $argv ctrl-alt-h backward-kill-token - bind --preset $argv ctrl-backspace backward-kill-word - bind --preset $argv alt-delete kill-token - bind --preset $argv ctrl-delete kill-word - end + __fish_per_os_bind --preset $argv alt-backspace backward-kill-word backward-kill-token + __fish_per_os_bind --preset $argv ctrl-alt-h backward-kill-word backward-kill-token + __fish_per_os_bind --preset $argv ctrl-backspace backward-kill-token backward-kill-word + __fish_per_os_bind --preset $argv alt-delete kill-word kill-token + __fish_per_os_bind --preset $argv ctrl-delete kill-token kill-word bind --preset $argv alt-\< beginning-of-buffer bind --preset $argv alt-\> end-of-buffer diff --git a/share/functions/fish_in_macos_terminal.fish b/share/functions/fish_in_macos_terminal.fish new file mode 100644 index 000000000..2668a0958 --- /dev/null +++ b/share/functions/fish_in_macos_terminal.fish @@ -0,0 +1,3 @@ +function fish_in_macos_terminal + test "$(status terminal-os || echo uname="$(__fish_uname)")" = uname=Darwin +end diff --git a/src/builtins/status.rs b/src/builtins/status.rs index f7d395a7e..86fbd64ef 100644 --- a/src/builtins/status.rs +++ b/src/builtins/status.rs @@ -8,7 +8,7 @@ JobControl, get_job_control_mode, get_login, is_interactive_session, set_job_control_mode, }; use crate::reader::reader_in_interactive_read; -use crate::tty_handoff::{get_scroll_content_up_capability, xtversion}; +use crate::tty_handoff::{TERMINAL_OS_NAME, get_scroll_content_up_capability, xtversion}; use crate::wutil::{Error, waccess, wbasename, wdirname, wrealpath}; use cfg_if::cfg_if; use libc::F_OK; @@ -66,6 +66,7 @@ enum StatusCmd { STATUS_LIST_FILES, STATUS_HELP_SECTIONS, STATUS_TERMINAL, + STATUS_TERMINAL_OS, STATUS_TEST_TERMINAL_FEATURE, } @@ -103,6 +104,7 @@ enum StatusCmd { (STATUS_STACK_TRACE, "print-stack-trace"), (STATUS_STACK_TRACE, "stack-trace"), (STATUS_TERMINAL, "terminal"), + (STATUS_TERMINAL_OS, "terminal-os"), (STATUS_TEST_FEATURE, "test-feature"), (STATUS_TEST_TERMINAL_FEATURE, "test-terminal-feature"), ); @@ -764,11 +766,13 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B } STATUS_TERMINAL => { let xtversion = xtversion().unwrap_or_default(); - let first_line = &xtversion[..xtversion - .chars() - .position(|c| c == '\n') - .unwrap_or(xtversion.len())]; - streams.out.appendln(first_line); + streams.out.appendln(xtversion); + } + STATUS_TERMINAL_OS => { + let Some(Some(terminal_os_name)) = TERMINAL_OS_NAME.get() else { + return Err(STATUS_CMD_ERROR); + }; + streams.out.appendln(first_line(terminal_os_name)); } STATUS_SET_JOB_CONTROL | STATUS_FEATURES @@ -784,3 +788,7 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B Ok(SUCCESS) } + +fn first_line(s: &wstr) -> &wstr { + &s[..s.chars().position(|c| c == '\n').unwrap_or(s.len())] +} diff --git a/src/input_common.rs b/src/input_common.rs index aa227f552..22264d32a 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -12,8 +12,8 @@ }; use crate::reader::reader_test_and_clear_interrupted; use crate::tty_handoff::{ - SCROLL_CONTENT_UP_TERMINFO_CODE, XTVERSION, maybe_set_kitty_keyboard_capability, - maybe_set_scroll_content_up_capability, + SCROLL_CONTENT_UP_TERMINFO_CODE, TERMINAL_OS_NAME, XTGETTCAP_QUERY_OS_NAME, XTVERSION, + maybe_set_kitty_keyboard_capability, maybe_set_scroll_content_up_capability, }; use crate::universal_notifier::default_notifier; use crate::wchar::{encode_byte_to_char, prelude::*}; @@ -1386,7 +1386,7 @@ fn parse_dcs(&mut self, buffer: &mut Vec) -> Option { let mut buffer = buffer.splitn(2, |&c| c == b'='); let key = buffer.next().unwrap(); let key = parse_hex(key)?; - if let Some(value) = buffer.next() { + let value = if let Some(value) = buffer.next() { let value = parse_hex(value)?; FLOG!( reader, @@ -1396,14 +1396,20 @@ fn parse_dcs(&mut self, buffer: &mut Vec) -> Option { bytes2wcstring(&value) ) ); + Some(value) } else { FLOG!( reader, format!("Received XTGETTCAP response: {}", bytes2wcstring(&key)) ); - } + None + }; if key == SCROLL_CONTENT_UP_TERMINFO_CODE.as_bytes() { maybe_set_scroll_content_up_capability(); + } else if key == XTGETTCAP_QUERY_OS_NAME.as_bytes() { + if let Some(value) = value { + TERMINAL_OS_NAME.get_or_init(|| Some(bytes2wcstring(&value))); + } } return None; } diff --git a/src/reader/reader.rs b/src/reader/reader.rs index 5d3a45a69..02ec7c7c8 100644 --- a/src/reader/reader.rs +++ b/src/reader/reader.rs @@ -143,6 +143,7 @@ Tokenizer, tok_command, }; use crate::tty_handoff::SCROLL_CONTENT_UP_TERMINFO_CODE; +use crate::tty_handoff::XTGETTCAP_QUERY_OS_NAME; use crate::tty_handoff::{ TtyHandoff, get_tty_protocols_active, initialize_tty_protocols, safe_deactivate_tty_protocols, }; @@ -2724,6 +2725,7 @@ fn query_capabilities_via_dcs(out: &mut impl Output, vars: &dyn Environment) { } out.write_command(DecsetAlternateScreenBuffer); // enable alternative screen buffer send_xtgettcap_query(out, SCROLL_CONTENT_UP_TERMINFO_CODE); + send_xtgettcap_query(out, XTGETTCAP_QUERY_OS_NAME); out.write_command(DecrstAlternateScreenBuffer); // disable alternative screen buffer } diff --git a/src/terminal.rs b/src/terminal.rs index 79ee105f7..3b4ae03f2 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -79,7 +79,7 @@ pub(crate) enum TerminalCommand<'a> { CursorRight, CursorMove(CardinalDirection, usize), - // Commands related to querying (used for backwards-incompatible features). + // Commands related to querying (used mainly for backwards-incompatible features). QueryPrimaryDeviceAttribute, QueryXtversion, QueryXtgettcap(&'static str), diff --git a/src/tty_handoff.rs b/src/tty_handoff.rs index 6fe68c8dd..b88087649 100644 --- a/src/tty_handoff.rs +++ b/src/tty_handoff.rs @@ -47,6 +47,9 @@ pub fn maybe_set_scroll_content_up_capability() { }); } +pub static TERMINAL_OS_NAME: OnceCell> = OnceCell::new(); +pub(crate) const XTGETTCAP_QUERY_OS_NAME: &str = "query-os-name"; + pub static XTVERSION: OnceCell = OnceCell::new(); pub fn xtversion() -> Option<&'static wstr> { @@ -242,6 +245,7 @@ pub fn initialize_tty_protocols(vars: &dyn Environment) { // Default missing query responses. KITTY_KEYBOARD_SUPPORTED.get_or_init(|| false); SCROLL_CONTENT_UP_SUPPORTED.get_or_init(|| false); + TERMINAL_OS_NAME.get_or_init(|| None); let xtversion = XTVERSION.get_or_init(WString::new); use std::sync::atomic::Ordering::{Acquire, Release};