mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-08 19:31:14 -03:00
Prefer terminal (client) OS for selecting native key bindings
When running fish inside SSH and local and remote OS differ, fish uses key bindings for the remote OS, which is weird. Fix that by asking the terminal for the OS name. This should be available on foot and kitty soon, see https://codeberg.org/dnkl/foot/pulls/2217#issuecomment-8249741 Ref: #11107
This commit is contained in:
@@ -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 <status-terminal-os>`.
|
||||
- New :ref:`feature flag <featureflags>` ``omit-term-workarounds`` can be turned on to prevent fish from trying to work around incompatible terminals.
|
||||
|
||||
For distributors and developers
|
||||
|
||||
@@ -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 <read>`,
|
||||
so before the first ``fish_prompt`` or ``fish_read`` :ref:`event <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 <term-compat-xtgettcap>`.
|
||||
Like :ref:`status terminal <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*
|
||||
|
||||
@@ -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 <term-compat-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 <term-compat-indn>`
|
||||
|
||||
The response's second parameter is ignored.
|
||||
* ``query-os-name`` (for :ref:`status terminal-os <status-terminal-os>`)
|
||||
|
||||
Terminals running on Unix should respond with the hex encoding of ``uname=$(uname)`` as second parameter.
|
||||
|
||||
.. _term-compat-dcs-gnu-screen:
|
||||
|
||||
|
||||
3
po/de.po
3
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 ""
|
||||
|
||||
|
||||
3
po/en.po
3
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 ""
|
||||
|
||||
|
||||
3
po/fr.po
3
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 ""
|
||||
|
||||
|
||||
3
po/pl.po
3
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 ""
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
3
po/sv.po
3
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 ""
|
||||
|
||||
|
||||
@@ -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 "打印当前运行的脚本的路径 (不含文件名)"
|
||||
|
||||
|
||||
@@ -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 "印出目前執行的命令稿路徑(不包括檔名)"
|
||||
|
||||
|
||||
@@ -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"'
|
||||
|
||||
|
||||
12
share/functions/__fish_per_os_bind.fish
Normal file
12
share/functions/__fish_per_os_bind.fish
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
3
share/functions/fish_in_macos_terminal.fish
Normal file
3
share/functions/fish_in_macos_terminal.fish
Normal file
@@ -0,0 +1,3 @@
|
||||
function fish_in_macos_terminal
|
||||
test "$(status terminal-os || echo uname="$(__fish_uname)")" = uname=Darwin
|
||||
end
|
||||
@@ -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())]
|
||||
}
|
||||
|
||||
@@ -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<u8>) -> Option<KeyEvent> {
|
||||
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<u8>) -> Option<KeyEvent> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -47,6 +47,9 @@ pub fn maybe_set_scroll_content_up_capability() {
|
||||
});
|
||||
}
|
||||
|
||||
pub static TERMINAL_OS_NAME: OnceCell<Option<WString>> = OnceCell::new();
|
||||
pub(crate) const XTGETTCAP_QUERY_OS_NAME: &str = "query-os-name";
|
||||
|
||||
pub static XTVERSION: OnceCell<WString> = 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};
|
||||
|
||||
Reference in New Issue
Block a user