builtin commandline: apply commandline+cursor to first top-level reader

Historically, `fish -C "commandline echo"` was silently ignored.  Make it do
the expected thing.  This won't affect subsequent readers because we only do
it for top-level ones, and reader_pop() will clear the commandline state again.

This improves consistency with the parent commit.  We probably don't want to
support arbitrary readline commands before the first reader is initialized,
but setting the initial commandline seems useful: first, it would have helped
me in the past for debugging fish.  Second, it would allow one to rewrite
an application launcher:

	 foot --app-id my-foot-launcher -e fish -C '
	 	set fish_history launcher
	 	bind escape exit
	 	bind ctrl-\[ exit
	-	function fish_should_add_to_history
	-		false
	-	end
	-	for enter in enter ctrl-j
	-		bind $enter '\''
	-			history append -- "$(commandline)"
	-			commandline "setsid $(commandline) </dev/null >/dev/null 2>&1 & disown && exit"
	-			commandline -f execute
	-		'\''
	-	end
	+	commandline "setsid  </dev/null >/dev/null 2>&1 & disown && exit"
	+	commandline --cursor $(string length "setsid ")
	 '

which is probably not desirable today because it will disable autosuggestions.
Though that could be fixed eventually by making autosuggestions smarter.

If we find a generally-useful use case, we should mention this in the changelog.

Ref: https://github.com/fish-shell/fish-shell/pull/11570#discussion_r2144544053
This commit is contained in:
Johannes Altmanninger
2025-07-03 13:13:21 +02:00
parent 32c36aa5f8
commit b5bb50d742
3 changed files with 29 additions and 8 deletions

View File

@@ -39,7 +39,7 @@ struct Options {
prompt: Option<WString>,
prompt_str: Option<WString>,
right_prompt: WString,
commandline: WString,
commandline: Option<WString>,
// If a delimiter was given. Used to distinguish between the default
// empty string and a given empty delimiter.
delimiter: Option<WString>,
@@ -100,7 +100,7 @@ fn parse_cmd_opts(
opts.array = true;
}
'c' => {
opts.commandline = w.woptarg.unwrap().to_owned();
opts.commandline = Some(w.woptarg.unwrap().to_owned());
}
'd' => {
opts.delimiter = Some(w.woptarg.unwrap().to_owned());
@@ -207,7 +207,7 @@ fn read_interactive(
silent: bool,
prompt: &wstr,
right_prompt: &wstr,
commandline: &wstr,
commandline: &Option<WString>,
inputfd: RawFd,
) -> BuiltinResult {
let mut exit_res = Ok(SUCCESS);
@@ -238,7 +238,9 @@ fn read_interactive(
s.readonly_commandline = false;
})
});
commandline_set_buffer(parser, Some(commandline.to_owned()), None);
if let Some(commandline) = commandline {
commandline_set_buffer(parser, Some(commandline.clone()), None);
}
let mline = {
let _interactive = parser.push_scope(|s| s.is_interactive = true);

View File

@@ -317,11 +317,12 @@ pub fn reader_push<'a>(parser: &'a Parser, history_name: &wstr, conf: ReaderConf
assert_is_main_thread();
let hist = History::with_name(history_name);
hist.resolve_pending();
let data = ReaderData::new(hist, conf);
let is_top_level = reader_data_stack().is_empty();
let data = ReaderData::new(hist, conf, is_top_level);
reader_data_stack().push(data);
let data = current_data().unwrap();
data.command_line_changed(EditableLineTag::Commandline, AutosuggestionUpdate::Remove);
if reader_data_stack().len() == 1 {
if is_top_level {
reader_interactive_init(parser);
}
Reader { data, parser }
@@ -1201,12 +1202,18 @@ fn reader_received_sighup() -> bool {
}
impl ReaderData {
fn new(history: Arc<History>, conf: ReaderConfig) -> Pin<Box<Self>> {
fn new(history: Arc<History>, conf: ReaderConfig, is_top_level: bool) -> Pin<Box<Self>> {
let input_data = InputData::new(conf.inputfd);
let mut command_line = EditableLine::default();
if is_top_level {
let state = commandline_state_snapshot();
command_line.push_edit(Edit::new(0..0, state.text.clone()), false);
command_line.set_position(state.cursor_pos);
}
Pin::new(Box::new(Self {
canary: Rc::new(()),
conf,
command_line: Default::default(),
command_line,
command_line_transient_edit: None,
rendered_layout: Default::default(),
autosuggestion: Default::default(),

View File

@@ -102,6 +102,18 @@ send(control("k"))
sendline('echo "process extent is [$tmp]"')
expect_str("process extent is [echo process # comment]")
sendline(
"""$fish -C 'commandline "sq 2; exit"; commandline --cursor 1; commandline -i e'"""
)
expect_str("seq 2")
send("\r")
expect_str("1\r\n2\r\n")
sendline("""$fish -C 'commandline 123; read'""")
expect_str("read> 123")
sendline("456; exit")
expect_str("123456")
# DISABLED because it keeps failing under ASAN
# sendline(r"bind ctrl-b 'set tmp (commandline --current-process | count)'")
# sendline(r'commandline "echo line1 \\" "# comment" "line2"')