Query terminal only just before reading from it

Commit 5e317497ef (Query terminal before reading config, 2025-05-17)
disabled the kitty keyboard protocol in "fish -c read".  This seems
surprising, and it's not actually necessary that we query before
reading config; we only need query results before we read from
the TTY for the first time (which is about the time we call
__fish_config_interactive). Let's do that, reverting parts of
5e317497ef.
This commit is contained in:
Johannes Altmanninger
2025-09-25 19:22:37 +02:00
parent 7713e90aeb
commit 7e3fac561d
8 changed files with 80 additions and 81 deletions

View File

@@ -1696,6 +1696,8 @@ Fish also provides additional information through the values of certain environm
.. envvar:: fish_terminal
the name and version of the terminal fish is running inside (for example as reported via :ref:`XTVERSION <term-compat-xtversion>`).
This is initialized just before the first interactive prompt is shown (possibly via builtin :doc:`read <cmds/read>`),
that is, on the first ``fish_prompt`` or ``fish_read`` :ref:`event <event>`.
.. envvar:: history

View File

@@ -39,13 +39,11 @@
environment::{env_init, EnvStack, Environment},
EnvMode, Statuses,
},
env_dispatch::guess_emoji_width,
eprintf,
event::{self, Event},
flog::{self, activate_flog_categories_by_pattern, set_flog_file_fd, FLOG, FLOGF},
fprintf, function, future_feature_flags as features,
history::{self, start_private_mode},
input_common::InputEventQueuer,
io::IoChain,
nix::{getpid, getrusage, isatty, RUsage},
panic::panic_handler,
@@ -59,11 +57,10 @@
get_login, is_interactive_session, mark_login, mark_no_exec, proc_init,
set_interactive_session, Pid,
},
reader::{reader_init, reader_read, term_copy_modes, terminal_init},
reader::{reader_init, reader_read, term_copy_modes},
signal::{signal_clear_cancel, signal_unblock_all},
threads::{self},
topic_monitor,
tty_handoff::xtversion,
wchar::prelude::*,
wutil::waccess,
};
@@ -525,44 +522,6 @@ fn throwing_main() -> i32 {
let parser = &Parser::new(env, CancelBehavior::Clear);
parser.set_syncs_uvars(!opts.no_config);
#[derive(Eq, PartialEq)]
enum CommandSource {
Arguments,
Stdin,
File,
}
let command_source = if !opts.batch_cmds.is_empty() {
CommandSource::Arguments
} else if my_optind == args.len() {
CommandSource::Stdin
} else {
CommandSource::File
};
if command_source == CommandSource::Stdin && isatty(STDIN_FILENO) {
// Implicitly interactive mode.
if opts.no_exec {
FLOG!(
error,
"no-execute mode enabled and no script given. Exiting"
);
// above line should always exit
return libc::EXIT_FAILURE;
}
let mut input_queue = terminal_init();
let input_data = input_queue.get_input_data_mut();
parser
.pending_input
.borrow_mut()
.extend(std::mem::take(&mut input_data.queue));
parser.vars().set_one(
L!("fish_terminal"),
EnvMode::GLOBAL,
xtversion().unwrap().to_owned(),
);
guess_emoji_width(parser.vars());
}
if !opts.no_exec && !opts.no_config {
read_init(parser, config_paths.as_ref().unwrap());
}
@@ -604,7 +563,7 @@ enum CommandSource {
// Clear signals in case we were interrupted (#9024).
signal_clear_cancel();
if command_source == CommandSource::Arguments {
if !opts.batch_cmds.is_empty() {
// Run the commands specified as arguments, if any.
if get_login() {
// Do something nasty to support OpenSUSE assuming we're bash. This may modify cmds.
@@ -621,8 +580,17 @@ enum CommandSource {
);
res = run_command_list(parser, &opts.batch_cmds);
parser.libdata_mut().exit_current_script = false;
} else if command_source == CommandSource::Stdin {
res = reader_read(parser, STDIN_FILENO, &IoChain::new());
} else if my_optind == args.len() {
// Implicitly interactive mode.
if opts.no_exec && isatty(libc::STDIN_FILENO) {
FLOG!(
error,
"no-execute mode enabled and no script given. Exiting"
);
// above line should always exit
return libc::EXIT_FAILURE;
}
res = reader_read(parser, libc::STDIN_FILENO, &IoChain::new());
} else {
let n = wcs2string(&args[my_optind]);
let path = OsStr::from_bytes(&n);

View File

@@ -313,7 +313,12 @@ fn throwing_main() -> i32 {
return 1;
}
let input_queue = terminal_init();
let input_queue = {
let vars = EnvStack::new();
env_stack_set_from_env!(vars, "STY");
env_stack_set_from_env!(vars, "TERM");
terminal_init(&vars, STDIN_FILENO)
};
setup_and_process_keys(&mut streams, continuous_mode, verbose, input_queue)
.builtin_status_code()

View File

@@ -2079,3 +2079,17 @@ fn to_cstring(self) -> CString {
CString::new(self).unwrap()
}
}
#[macro_export]
macro_rules! env_stack_set_from_env {
($vars:ident, $var_name:literal) => {{
use std::os::unix::ffi::OsStrExt;
if let Some(var) = std::env::var_os($var_name) {
$vars.set_one(
L!($var_name),
$crate::env::EnvMode::GLOBAL,
$crate::common::str2wcstring(var.as_bytes()),
);
}
}};
}

View File

@@ -369,6 +369,7 @@ pub fn env_dispatch_init(vars: &EnvStack) {
fn run_inits(vars: &EnvStack) {
init_locale(vars);
init_terminal(vars);
guess_emoji_width(vars);
update_wait_on_escape_ms(vars);
update_wait_on_sequence_key_ms(vars);
handle_read_limit_change(vars);

View File

@@ -38,7 +38,6 @@
use std::ops::Range;
use std::os::fd::BorrowedFd;
use std::os::fd::{AsRawFd, RawFd};
use std::os::unix::ffi::OsStrExt;
use std::pin::Pin;
use std::rc::Rc;
#[cfg(target_has_atomic = "64")]
@@ -66,6 +65,7 @@
use crate::editable_line::{line_at_cursor, range_of_line_at_cursor, Edit, EditableLine};
use crate::env::EnvStack;
use crate::env::{EnvMode, Environment, Statuses};
use crate::env_dispatch::guess_emoji_width;
use crate::exec::exec_subshell;
use crate::expand::expand_one;
use crate::expand::{expand_string, expand_tilde, ExpandFlags, ExpandResultCode};
@@ -148,6 +148,7 @@
TOK_SHOW_COMMENTS,
};
use crate::tty_handoff::get_scroll_content_up_capability;
use crate::tty_handoff::xtversion;
use crate::tty_handoff::SCROLL_CONTENT_UP_TERMINFO_CODE;
use crate::tty_handoff::{
get_tty_protocols_active, initialize_tty_metadata, safe_deactivate_tty_protocols, TtyHandoff,
@@ -269,26 +270,26 @@ fn querying_allowed(in_fd: RawFd) -> bool {
&& isatty(STDOUT_FILENO)
}
pub fn terminal_init() -> InputEventQueue {
pub fn terminal_init(vars: &dyn Environment, inputfd: RawFd) -> InputEventQueue {
reader_interactive_init();
let mut input_queue = InputEventQueue::new(STDIN_FILENO);
let mut input_queue = InputEventQueue::new(inputfd);
let _init_tty_metadata = ScopeGuard::new((), |()| {
initialize_tty_metadata();
});
if !querying_allowed(STDIN_FILENO) {
if !querying_allowed(inputfd) {
return input_queue;
}
set_shell_modes(STDIN_FILENO, "initial query");
set_shell_modes(inputfd, "initial query");
{
let mut out = BufferedOutputter::new(Outputter::stdoutput());
// Query for kitty keyboard protocol support.
out.write_command(QueryKittyKeyboardProgressiveEnhancements);
out.write_command(QueryXtversion);
query_capabilities_via_dcs(out.by_ref());
query_capabilities_via_dcs(out.by_ref(), vars);
out.write_command(QueryPrimaryDeviceAttribute);
}
input_queue.blocking_query().replace(TerminalQuery::Initial);
@@ -363,13 +364,20 @@ pub fn current_data() -> Option<&'static mut ReaderData> {
/// If `history_name` is empty, then save history in-memory only; do not write it to disk.
pub fn reader_push<'a>(parser: &'a Parser, history_name: &wstr, conf: ReaderConfig) -> Reader<'a> {
assert_is_main_thread();
let hist = History::with_name(history_name);
hist.resolve_pending();
let data = ReaderData::new(hist, conf, reader_data_stack().is_empty());
reader_data_stack().push(data);
let data = current_data().unwrap();
data.command_line_changed(EditableLineTag::Commandline, AutosuggestionUpdate::Remove);
if !parser.interactive_initialized.swap(true) {
let mut input_queue = terminal_init(parser.vars(), conf.inputfd);
let input_data = input_queue.get_input_data_mut();
parser
.pending_input
.borrow_mut()
.extend(std::mem::take(&mut input_data.queue));
parser.vars().set_one(
L!("fish_terminal"),
EnvMode::GLOBAL,
xtversion().unwrap().to_owned(),
);
guess_emoji_width(parser.vars());
// Provide value for `status current-command`
parser.libdata_mut().status_vars.command = L!("fish").to_owned();
// Also provide a value for the deprecated fish 2.0 $_ variable
@@ -377,7 +385,15 @@ pub fn reader_push<'a>(parser: &'a Parser, history_name: &wstr, conf: ReaderConf
.vars()
.set_one(L!("_"), EnvMode::GLOBAL, L!("fish").to_owned());
}
Reader { data, parser }
let hist = History::with_name(history_name);
hist.resolve_pending();
let data = ReaderData::new(hist, conf, reader_data_stack().is_empty());
reader_data_stack().push(data);
let data = current_data().unwrap();
data.command_line_changed(EditableLineTag::Commandline, AutosuggestionUpdate::Remove);
let mut reader = Reader { data, parser };
reader.insert_front(parser.pending_input.take());
reader
}
/// Return to previous reader environment.
@@ -2303,7 +2319,6 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
// Start out as initially dirty.
self.force_exec_prompt_and_repaint = true;
self.insert_front(self.parser.pending_input.take());
while !self.rls().finished && !check_exit_loop_maybe_warning(Some(self)) {
// Enable tty protocols while we read input.
tty.enable_tty_protocols();
@@ -2664,13 +2679,12 @@ fn send_xtgettcap_query(out: &mut impl Output, cap: &'static str) {
#[allow(renamed_and_removed_lints)]
#[allow(clippy::blocks_in_if_conditions)] // for old clippy
fn query_capabilities_via_dcs(out: &mut impl Output) {
fn query_capabilities_via_dcs(out: &mut impl Output, vars: &dyn Environment) {
if {
use std::env::var_os;
var_os("STY").is_some()
|| var_os("TERM").is_some_and(|term| {
let screens: [&[u8]; 2] = [b"screen", b"screen-256color"];
screens.contains(&term.as_bytes())
vars.get_unless_empty(L!("STY")).is_some()
|| vars.get_unless_empty(L!("TERM")).is_some_and(|term| {
let term = &term.as_list()[0];
term == "screen" || term == "screen-256color"
})
} {
return;

View File

@@ -167,22 +167,11 @@ pub fn initialize_gettext() {}
/// available. Without this, early error messages cannot be localized.
#[cfg(feature = "localize-messages")]
pub fn initialize_gettext() {
use crate::common::str2wcstring;
use crate::env::EnvMode;
use std::os::unix::ffi::OsStrExt;
let locale_vars = EnvStack::new();
macro_rules! from_env {
($var_name:literal) => {
if let Some(var) = std::env::var_os($var_name) {
locale_vars.set_one(L!($var_name), EnvMode::GLOBAL, str2wcstring(var.as_bytes()));
}
};
}
from_env!("LANGUAGE");
from_env!("LC_ALL");
from_env!("LC_MESSAGES");
from_env!("LANG");
env_stack_set_from_env!(locale_vars, "LANGUAGE");
env_stack_set_from_env!(locale_vars, "LC_ALL");
env_stack_set_from_env!(locale_vars, "LC_MESSAGES");
env_stack_set_from_env!(locale_vars, "LANG");
gettext_impl::update_locale_from_env(&locale_vars);
}

View File

@@ -27,3 +27,9 @@ sendline("true ($fish_test_helper abandon_tty)")
expect_prompt()
sendline("echo even cooler")
expect_prompt("even cooler")
sendline("TERM=not-dumb $fish -c read")
sp.send_primary_device_attribute()
sendline("something")
sp.expect_re(r"\x1b\[\?u")
expect_prompt()