fish_clipboard_paste: don't bypass pager search field.

To do so add an ad-hoc "commandline --search-field" to operate on pager
search field.

This is primarily motivated because a following commit reuses the
fish_clipboard_paste logic for bracketed paste. This avoids a regression.
This commit is contained in:
Johannes Altmanninger 2024-03-30 16:10:12 +01:00
parent d0cdb142de
commit 22717339b4
5 changed files with 95 additions and 19 deletions

View file

@ -70,6 +70,9 @@ The following options change what part of the commandline is printed or updated:
**-t** or **--current-token**
Selects the current token
**--search-field**
Use the pager search field instead of the command line. Returns false is the search field is not shown.
The following options change the way ``commandline`` prints the current commandline buffer:
**-c** or **--cut-at-cursor**

View file

@ -22,6 +22,7 @@ complete -c commandline -s L -l line -d "Print the line that the cursor is on"
complete -c commandline -s S -l search-mode -d "Return true if performing a history search"
complete -c commandline -s P -l paging-mode -d "Return true if showing pager content"
complete -c commandline -l paging-full-mode -d "Return true if pager is showing all content"
complete -c commandline -l search-field -d "Operate on the pager search field"
complete -c commandline -l is-valid -d "Return true if the command line is syntactically valid and complete"
complete -c commandline -n '__fish_contains_opt -s f function' -a '(bind --function-names)' -d 'Function name' -x

View file

@ -26,6 +26,11 @@ function fish_clipboard_paste
# Also split on \r, otherwise it looks confusing
set data (string split \r -- $data | string split \n)
if commandline --search-field >/dev/null
commandline --search-field -i -- $data
return
end
# If the current token has an unmatched single-quote,
# escape all single-quotes (and backslashes) in the paste,
# in order to turn it into a single literal token.

View file

@ -11,7 +11,9 @@ use crate::parse_util::{
parse_util_token_extent,
};
use crate::proc::is_interactive_session;
use crate::reader::{commandline_get_state, commandline_set_buffer, reader_queue_ch};
use crate::reader::{
commandline_get_state, commandline_set_buffer, commandline_set_search_field, reader_queue_ch,
};
use crate::tokenizer::TOK_ACCEPT_UNFINISHED;
use crate::tokenizer::{TokenType, Tokenizer};
use crate::wchar::prelude::*;
@ -57,6 +59,7 @@ fn replace_part(
insert_mode: AppendMode,
buff: &wstr,
cursor_pos: usize,
search_field_mode: bool,
) {
let mut out_pos = cursor_pos;
let mut out = buff[..range.start].to_owned();
@ -81,7 +84,11 @@ fn replace_part(
}
out.push_utfstr(&buff[range.end..]);
commandline_set_buffer(out, Some(out_pos));
if search_field_mode {
commandline_set_search_field(out, Some(out_pos));
} else {
commandline_set_buffer(out, Some(out_pos));
}
}
/// Output the specified selection.
@ -191,6 +198,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let mut search_mode = false;
let mut paging_mode = false;
let mut paging_full_mode = false;
let mut search_field_mode = false;
let mut is_valid = false;
let mut range = 0..0;
@ -224,6 +232,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
wopt(L!("search-mode"), woption_argument_t::no_argument, 'S'),
wopt(L!("paging-mode"), woption_argument_t::no_argument, 'P'),
wopt(L!("paging-full-mode"), woption_argument_t::no_argument, 'F'),
wopt(L!("search-field"), woption_argument_t::no_argument, '\x03'),
wopt(L!("is-valid"), woption_argument_t::no_argument, '\x01'),
];
@ -269,6 +278,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
's' => selection_mode = true,
'P' => paging_mode = true,
'F' => paging_full_mode = true,
'\x03' => search_field_mode = true,
'\x01' => is_valid = true,
'h' => {
builtin_print_help(parser, streams, cmd);
@ -361,10 +371,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
return STATUS_INVALID_ARGS;
}
if (buffer_part.is_some() || token_mode.is_some() || cut_at_cursor)
if (buffer_part.is_some() || token_mode.is_some() || cut_at_cursor || search_field_mode)
&& (cursor_mode || line_mode || search_mode || paging_mode || paging_full_mode)
// Special case - we allow to get/set cursor position relative to the process/job/token.
&& (buffer_part.is_none() || !cursor_mode)
&& ((buffer_part.is_none() && !search_field_mode) || !cursor_mode)
{
streams.err.append(wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
@ -381,6 +391,12 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
return STATUS_INVALID_ARGS;
}
if search_field_mode && buffer_part.is_some() {
streams.err.append(wgettext_fmt!(BUILTIN_ERR_COMBO, cmd,));
builtin_print_error_trailer(parser, streams.err, cmd);
return STATUS_INVALID_ARGS;
}
if append_mode.is_some() && positional_args == 0 {
// No tokens in insert mode just means we do nothing.
return STATUS_CMD_ERROR;
@ -447,7 +463,15 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let current_buffer;
let current_cursor_pos;
let transient;
if let Some(override_buffer) = &override_buffer {
if search_field_mode {
let Some((search_field_text, cursor_pos)) = commandline_get_state().search_field else {
return STATUS_CMD_ERROR;
};
transient = search_field_text;
current_buffer = &transient;
current_cursor_pos = cursor_pos;
} else if let Some(override_buffer) = &override_buffer {
current_buffer = override_buffer;
current_cursor_pos = current_buffer.len();
} else if !ld.transient_commandlines.is_empty() && !cursor_mode {
@ -487,18 +511,22 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
};
}
match buffer_part {
TextScope::String => {
range = 0..current_buffer.len();
}
TextScope::Job => {
range = parse_util_job_extent(current_buffer, current_cursor_pos, None);
}
TextScope::Process => {
range = parse_util_process_extent(current_buffer, current_cursor_pos, None);
}
TextScope::Token => {
parse_util_token_extent(current_buffer, current_cursor_pos, &mut range, None);
if search_field_mode {
range = 0..current_buffer.len();
} else {
match buffer_part {
TextScope::String => {
range = 0..current_buffer.len();
}
TextScope::Job => {
range = parse_util_job_extent(current_buffer, current_cursor_pos, None);
}
TextScope::Process => {
range = parse_util_process_extent(current_buffer, current_cursor_pos, None);
}
TextScope::Token => {
parse_util_token_extent(current_buffer, current_cursor_pos, &mut range, None);
}
}
}
@ -546,10 +574,18 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
append_mode,
current_buffer,
current_cursor_pos,
search_field_mode,
);
} else {
let sb = join_strings(&w.argv[w.woptind..], '\n');
replace_part(range, &sb, append_mode, current_buffer, current_cursor_pos);
replace_part(
range,
&sb,
append_mode,
current_buffer,
current_cursor_pos,
search_field_mode,
);
}
STATUS_CMD_OK

View file

@ -21,6 +21,7 @@ use nix::fcntl::OFlag;
use nix::sys::stat::Mode;
use once_cell::sync::Lazy;
use std::cell::UnsafeCell;
use std::cmp;
use std::io::BufReader;
use std::num::NonZeroUsize;
use std::ops::Range;
@ -305,6 +306,8 @@ pub struct CommandlineState {
pub pager_mode: bool,
/// pager already shows everything if possible
pub pager_fully_disclosed: bool,
/// The search field, if shown.
pub search_field: Option<(WString, usize)>,
/// pager is visible and search is active
pub search_mode: bool,
/// if false, the reader has not yet been entered
@ -320,6 +323,7 @@ impl CommandlineState {
history: None,
pager_mode: false,
pager_fully_disclosed: false,
search_field: None,
search_mode: false,
initialized: false,
}
@ -937,10 +941,17 @@ pub fn commandline_get_state() -> CommandlineState {
/// will pick it up when it is done executing.
pub fn commandline_set_buffer(text: WString, cursor_pos: Option<usize>) {
let mut state = commandline_state_snapshot();
state.cursor_pos = std::cmp::min(cursor_pos.unwrap_or(usize::MAX), text.len());
state.cursor_pos = cmp::min(cursor_pos.unwrap_or(usize::MAX), text.len());
state.text = text;
}
pub fn commandline_set_search_field(text: WString, cursor_pos: Option<usize>) {
let mut state = commandline_state_snapshot();
assert!(state.search_field.is_some());
let new_pos = cmp::min(cursor_pos.unwrap_or(usize::MAX), text.len());
state.search_field = Some((text, new_pos));
}
/// Return the current interactive reads loop count. Useful for determining how many commands have
/// been executed between invocations of code.
pub fn reader_run_count() -> u64 {
@ -1163,6 +1174,12 @@ impl ReaderData {
snapshot.selection = self.get_selection();
snapshot.pager_mode = !self.pager.is_empty();
snapshot.pager_fully_disclosed = self.current_page_rendering.remaining_to_disclose == 0;
snapshot.search_field = self.pager.search_field_shown.then(|| {
(
self.pager.search_field_line.text().to_owned(),
self.pager.search_field_line.position(),
)
});
snapshot.search_mode = self.history_search.active();
snapshot.initialized = true;
}
@ -1179,6 +1196,20 @@ impl ReaderData {
self.clear_pager();
self.set_buffer_maintaining_pager(&state.text, state.cursor_pos, false);
self.reset_loop_state = true;
} else if let Some((new_search_field, new_cursor_pos)) = state.search_field {
if !self.pager.search_field_shown {
return; // Not yet supported.
}
if new_search_field == self.pager.search_field_line.text()
&& new_cursor_pos == self.pager.search_field_line.position()
{
return;
}
self.push_edit(
EditableLineTag::SearchField,
Edit::new(0..self.pager.search_field_line.len(), new_search_field),
);
self.pager.search_field_line.set_position(new_cursor_pos);
}
}