mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 04:43:10 +00:00
Port reader
This commit is contained in:
parent
1093c636e5
commit
55fd43d86c
52 changed files with 6522 additions and 6185 deletions
|
@ -97,10 +97,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL NetBSD)
|
|||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
endif()
|
||||
|
||||
# List of sources for builtin functions.
|
||||
set(FISH_BUILTIN_SRCS
|
||||
src/builtins/commandline.cpp
|
||||
)
|
||||
# List of other sources.
|
||||
set(FISH_SRCS
|
||||
src/ast.cpp
|
||||
|
@ -117,7 +113,6 @@ set(FISH_SRCS
|
|||
src/output.cpp
|
||||
src/parse_util.cpp
|
||||
src/path.cpp
|
||||
src/reader.cpp
|
||||
src/rustffi.cpp
|
||||
src/wcstringutil.cpp
|
||||
src/wgetopt.cpp
|
||||
|
@ -178,7 +173,7 @@ function(FISH_LINK_DEPS_AND_SIGN target)
|
|||
endfunction(FISH_LINK_DEPS_AND_SIGN)
|
||||
|
||||
# Define libfish.a.
|
||||
add_library(fishlib STATIC ${FISH_SRCS} ${FISH_BUILTIN_SRCS})
|
||||
add_library(fishlib STATIC ${FISH_SRCS})
|
||||
target_sources(fishlib PRIVATE ${FISH_HEADERS})
|
||||
target_link_libraries(fishlib
|
||||
fish-rust
|
||||
|
|
|
@ -114,7 +114,6 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux|Android")
|
|||
endif()
|
||||
endif()
|
||||
|
||||
check_cxx_symbol_exists(ctermid_r stdio.h HAVE_CTERMID_R)
|
||||
check_struct_has_member("struct dirent" d_type dirent.h HAVE_STRUCT_DIRENT_D_TYPE LANGUAGE CXX)
|
||||
check_cxx_symbol_exists(dirfd "sys/types.h;dirent.h" HAVE_DIRFD)
|
||||
check_include_file_cxx(execinfo.h HAVE_EXECINFO_H)
|
||||
|
|
|
@ -4,9 +4,6 @@
|
|||
/* Define to 1 if compiled on WSL */
|
||||
#cmakedefine WSL 1
|
||||
|
||||
/* Define to 1 if you have the `ctermid_r' function. */
|
||||
#cmakedefine HAVE_CTERMID_R 1
|
||||
|
||||
/* Define to 1 if C++11 thread_local is supported. */
|
||||
#cmakedefine HAVE_CX11_THREAD_LOCAL 1
|
||||
|
||||
|
|
|
@ -201,10 +201,10 @@ pub struct Replacer {
|
|||
pub replacement: WString,
|
||||
|
||||
/// If true, treat 'replacement' as the name of a function.
|
||||
is_function: bool,
|
||||
pub is_function: bool,
|
||||
|
||||
/// If set, the cursor should be moved to the first instance of this string in the expansion.
|
||||
set_cursor_marker: Option<WString>,
|
||||
pub set_cursor_marker: Option<WString>,
|
||||
}
|
||||
|
||||
impl From<Replacer> for abbrs_replacer_t {
|
||||
|
@ -219,23 +219,23 @@ impl From<Replacer> for abbrs_replacer_t {
|
|||
}
|
||||
}
|
||||
|
||||
struct Replacement {
|
||||
pub struct Replacement {
|
||||
/// The original range of the token in the command line.
|
||||
range: SourceRange,
|
||||
pub range: SourceRange,
|
||||
|
||||
/// The string to replace with.
|
||||
text: WString,
|
||||
pub text: WString,
|
||||
|
||||
/// The new cursor location, or none to use the default.
|
||||
/// This is relative to the original range.
|
||||
cursor: Option<usize>,
|
||||
pub cursor: Option<usize>,
|
||||
}
|
||||
|
||||
impl Replacement {
|
||||
/// Construct a replacement from a replacer.
|
||||
/// The \p range is the range of the text matched by the replacer in the command line.
|
||||
/// The text is passed in separately as it may be the output of the replacer's function.
|
||||
fn new(range: SourceRange, mut text: WString, set_cursor_marker: Option<WString>) -> Self {
|
||||
pub fn new(range: SourceRange, mut text: WString, set_cursor_marker: Option<WString>) -> Self {
|
||||
let mut cursor = None;
|
||||
if let Some(set_cursor_marker) = set_cursor_marker {
|
||||
let matched = text
|
||||
|
@ -353,6 +353,12 @@ impl AbbreviationSet {
|
|||
|
||||
/// \return the list of replacers for an input token, in priority order, using the global set.
|
||||
/// The \p position is given to describe where the token was found.
|
||||
pub fn abbrs_match(token: &wstr, position: Position) -> Vec<Replacer> {
|
||||
with_abbrs(|set| set.r#match(token, position))
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn abbrs_match_ffi(token: &CxxWString, position: abbrs_position_t) -> Vec<abbrs_replacer_t> {
|
||||
with_abbrs(|set| set.r#match(token.as_wstr(), position.into()))
|
||||
.into_iter()
|
||||
|
|
|
@ -1,5 +1,500 @@
|
|||
use super::prelude::*;
|
||||
use crate::common::{unescape_string, UnescapeFlags, UnescapeStringStyle};
|
||||
use crate::input::input_function_get_code;
|
||||
use crate::input_common::{CharEvent, ReadlineCmd};
|
||||
use crate::parse_constants::ParserTestErrorBits;
|
||||
use crate::parse_util::{
|
||||
parse_util_detect_errors, parse_util_job_extent, parse_util_lineno, parse_util_process_extent,
|
||||
parse_util_token_extent,
|
||||
};
|
||||
use crate::proc::is_interactive_session;
|
||||
use crate::reader::{
|
||||
commandline_get_state, commandline_set_buffer, reader_handle_command, reader_queue_ch,
|
||||
};
|
||||
use crate::tokenizer::TokenType;
|
||||
use crate::tokenizer::Tokenizer;
|
||||
use crate::tokenizer::TOK_ACCEPT_UNFINISHED;
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wcstringutil::join_strings;
|
||||
use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t};
|
||||
use std::ops::Range;
|
||||
|
||||
pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Option<c_int> {
|
||||
run_builtin_ffi(crate::ffi::builtin_commandline, parser, streams, args)
|
||||
/// Which part of the comandbuffer are we operating on.
|
||||
enum TextScope {
|
||||
String,
|
||||
Job,
|
||||
Process,
|
||||
Token,
|
||||
}
|
||||
|
||||
/// For text insertion, how should it be done.
|
||||
enum AppendMode {
|
||||
// replace current text
|
||||
Replace,
|
||||
// insert at cursor position
|
||||
Insert,
|
||||
// insert at end of current token/command/buffer
|
||||
Append,
|
||||
}
|
||||
|
||||
/// Replace/append/insert the selection with/at/after the specified string.
|
||||
///
|
||||
/// \param begin beginning of selection
|
||||
/// \param end end of selection
|
||||
/// \param insert the string to insert
|
||||
/// \param append_mode can be one of REPLACE_MODE, INSERT_MODE or APPEND_MODE, affects the way the
|
||||
/// test update is performed
|
||||
/// \param buff the original command line buffer
|
||||
/// \param cursor_pos the position of the cursor in the command line
|
||||
fn replace_part(
|
||||
range: Range<usize>,
|
||||
insert: &wstr,
|
||||
insert_mode: AppendMode,
|
||||
buff: &wstr,
|
||||
cursor_pos: usize,
|
||||
) {
|
||||
let mut out_pos = cursor_pos;
|
||||
|
||||
let mut out = buff[..range.start].to_owned();
|
||||
|
||||
match insert_mode {
|
||||
AppendMode::Replace => {
|
||||
out.push_utfstr(insert);
|
||||
out_pos = out.len();
|
||||
}
|
||||
AppendMode::Append => {
|
||||
out.push_utfstr(&buff[range.clone()]);
|
||||
out.push_utfstr(insert);
|
||||
}
|
||||
AppendMode::Insert => {
|
||||
let cursor = cursor_pos - range.start;
|
||||
assert!(range.start <= cursor);
|
||||
out.push_utfstr(&buff[range.start..cursor]);
|
||||
out.push_utfstr(&insert);
|
||||
out.push_utfstr(&buff[cursor..range.end]);
|
||||
out_pos += insert.len();
|
||||
}
|
||||
}
|
||||
|
||||
out.push_utfstr(&buff[range.end..]);
|
||||
commandline_set_buffer(out, Some(out_pos));
|
||||
}
|
||||
|
||||
/// Output the specified selection.
|
||||
///
|
||||
/// \param begin start of selection
|
||||
/// \param end end of selection
|
||||
/// \param cut_at_cursor whether printing should stop at the surrent cursor position
|
||||
/// \param tokenize whether the string should be tokenized, printing one string token on every line
|
||||
/// and skipping non-string tokens
|
||||
/// \param buffer the original command line buffer
|
||||
/// \param cursor_pos the position of the cursor in the command line
|
||||
fn write_part(
|
||||
range: Range<usize>,
|
||||
cut_at_cursor: bool,
|
||||
tokenize: bool,
|
||||
buffer: &wstr,
|
||||
cursor_pos: usize,
|
||||
streams: &mut IoStreams,
|
||||
) {
|
||||
let pos = cursor_pos - range.start;
|
||||
|
||||
if tokenize {
|
||||
let mut out = WString::new();
|
||||
let buff = &buffer[range];
|
||||
let mut tok = Tokenizer::new(buff, TOK_ACCEPT_UNFINISHED);
|
||||
while let Some(token) = tok.next() {
|
||||
if cut_at_cursor && token.end() >= pos {
|
||||
break;
|
||||
}
|
||||
|
||||
if token.type_ == TokenType::string {
|
||||
let tmp = tok.text_of(&token);
|
||||
let unescaped =
|
||||
unescape_string(tmp, UnescapeStringStyle::Script(UnescapeFlags::INCOMPLETE))
|
||||
.unwrap();
|
||||
out.push_utfstr(&unescaped);
|
||||
out.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
streams.out.append(out);
|
||||
} else {
|
||||
if cut_at_cursor {
|
||||
streams.out.append(&buffer[range.start..range.start + pos]);
|
||||
} else {
|
||||
streams.out.append(&buffer[range]);
|
||||
}
|
||||
streams.out.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
/// The commandline builtin. It is used for specifying a new value for the commandline.
|
||||
pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Option<c_int> {
|
||||
let rstate = commandline_get_state();
|
||||
|
||||
let mut buffer_part = None;
|
||||
let mut cut_at_cursor = false;
|
||||
let mut append_mode = None;
|
||||
|
||||
let mut function_mode = false;
|
||||
let mut selection_mode = false;
|
||||
|
||||
let mut tokenize = false;
|
||||
|
||||
let mut cursor_mode = false;
|
||||
let mut selection_start_mode = false;
|
||||
let mut selection_end_mode = false;
|
||||
let mut line_mode = false;
|
||||
let mut search_mode = false;
|
||||
let mut paging_mode = false;
|
||||
let mut paging_full_mode = false;
|
||||
let mut is_valid = false;
|
||||
|
||||
let mut range = 0..0;
|
||||
let mut override_buffer = None;
|
||||
|
||||
let ld = parser.libdata();
|
||||
|
||||
const short_options: &wstr = L!(":abijpctforhI:CBELSsP");
|
||||
let long_options: &[woption] = &[
|
||||
wopt(L!("append"), woption_argument_t::no_argument, 'a'),
|
||||
wopt(L!("insert"), woption_argument_t::no_argument, 'i'),
|
||||
wopt(L!("replace"), woption_argument_t::no_argument, 'r'),
|
||||
wopt(L!("current-buffer"), woption_argument_t::no_argument, 'b'),
|
||||
wopt(L!("current-job"), woption_argument_t::no_argument, 'j'),
|
||||
wopt(L!("current-process"), woption_argument_t::no_argument, 'p'),
|
||||
wopt(
|
||||
L!("current-selection"),
|
||||
woption_argument_t::no_argument,
|
||||
's',
|
||||
),
|
||||
wopt(L!("current-token"), woption_argument_t::no_argument, 't'),
|
||||
wopt(L!("cut-at-cursor"), woption_argument_t::no_argument, 'c'),
|
||||
wopt(L!("function"), woption_argument_t::no_argument, 'f'),
|
||||
wopt(L!("tokenize"), woption_argument_t::no_argument, 'o'),
|
||||
wopt(L!("help"), woption_argument_t::no_argument, 'h'),
|
||||
wopt(L!("input"), woption_argument_t::required_argument, 'I'),
|
||||
wopt(L!("cursor"), woption_argument_t::no_argument, 'C'),
|
||||
wopt(L!("selection-start"), woption_argument_t::no_argument, 'B'),
|
||||
wopt(L!("selection-end"), woption_argument_t::no_argument, 'E'),
|
||||
wopt(L!("line"), woption_argument_t::no_argument, 'L'),
|
||||
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!("is-valid"), woption_argument_t::no_argument, '\x01'),
|
||||
];
|
||||
|
||||
let mut w = wgetopter_t::new(short_options, long_options, args);
|
||||
let cmd = w.argv[0];
|
||||
while let Some(c) = w.wgetopt_long() {
|
||||
match c {
|
||||
'a' => append_mode = Some(AppendMode::Append),
|
||||
'b' => buffer_part = Some(TextScope::String),
|
||||
'i' => append_mode = Some(AppendMode::Insert),
|
||||
'r' => append_mode = Some(AppendMode::Replace),
|
||||
'c' => cut_at_cursor = true,
|
||||
't' => buffer_part = Some(TextScope::Token),
|
||||
'j' => buffer_part = Some(TextScope::Job),
|
||||
'p' => buffer_part = Some(TextScope::Process),
|
||||
'f' => function_mode = true,
|
||||
'o' => tokenize = true,
|
||||
'I' => {
|
||||
// A historical, undocumented feature. TODO: consider removing this.
|
||||
override_buffer = Some(w.woptarg.unwrap().to_owned());
|
||||
}
|
||||
'C' => cursor_mode = true,
|
||||
'B' => selection_start_mode = true,
|
||||
'E' => selection_end_mode = true,
|
||||
'L' => line_mode = true,
|
||||
'S' => search_mode = true,
|
||||
's' => selection_mode = true,
|
||||
'P' => paging_mode = true,
|
||||
'F' => paging_full_mode = true,
|
||||
'\x01' => is_valid = true,
|
||||
'h' => {
|
||||
builtin_print_help(parser, streams, cmd);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, w.argv[w.woptind - 1], true);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
'?' => {
|
||||
builtin_unknown_option(parser, streams, cmd, w.argv[w.woptind - 1], true);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
let positional_args = w.argv.len() - w.woptind;
|
||||
|
||||
if function_mode {
|
||||
// Check for invalid switch combinations.
|
||||
if buffer_part.is_some()
|
||||
|| cut_at_cursor
|
||||
|| append_mode.is_some()
|
||||
|| tokenize
|
||||
|| cursor_mode
|
||||
|| line_mode
|
||||
|| search_mode
|
||||
|| paging_mode
|
||||
|| selection_start_mode
|
||||
|| selection_end_mode
|
||||
{
|
||||
streams.err.append(wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if positional_args == 0 {
|
||||
builtin_missing_argument(parser, streams, cmd, L!("--function"), true);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
type rl = ReadlineCmd;
|
||||
for arg in &w.argv[w.woptind..] {
|
||||
let Some(cmd) = input_function_get_code(arg) else {
|
||||
streams
|
||||
.err
|
||||
.append(wgettext_fmt!("%ls: Unknown input function '%ls'", cmd, arg));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
};
|
||||
// Don't enqueue a repaint if we're currently in the middle of one,
|
||||
// because that's an infinite loop.
|
||||
if matches!(cmd, rl::RepaintMode | rl::ForceRepaint | rl::Repaint) {
|
||||
if ld.pods.is_repaint {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: Execute these right here and now so they can affect any insertions/changes
|
||||
// made via bindings. The correct solution is to change all `commandline`
|
||||
// insert/replace operations into readline functions with associated data, so that
|
||||
// all queued `commandline` operations - including buffer modifications - are
|
||||
// executed in order
|
||||
match cmd {
|
||||
rl::BeginUndoGroup | rl::EndUndoGroup => reader_handle_command(cmd),
|
||||
_ => {
|
||||
// Inserts the readline function at the back of the queue.
|
||||
reader_queue_ch(CharEvent::from_readline(cmd));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
if selection_mode {
|
||||
if let Some(selection) = rstate.selection {
|
||||
streams.out.append(&rstate.text[selection]);
|
||||
}
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
// Check for invalid switch combinations.
|
||||
if (selection_start_mode || selection_end_mode) && positional_args != 0 {
|
||||
streams
|
||||
.err
|
||||
.append(wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (search_mode || line_mode || cursor_mode || paging_mode) && positional_args > 1 {
|
||||
streams
|
||||
.err
|
||||
.append(wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (buffer_part.is_some() || tokenize || cut_at_cursor)
|
||||
&& (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)
|
||||
{
|
||||
streams.err.append(wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (tokenize || cut_at_cursor) && positional_args != 0 {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
cmd,
|
||||
"--cut-at-cursor and --tokenize can not be used when setting the commandline"
|
||||
));
|
||||
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;
|
||||
}
|
||||
|
||||
// Set default modes.
|
||||
let append_mode = append_mode.unwrap_or(AppendMode::Replace);
|
||||
|
||||
let buffer_part = buffer_part.unwrap_or(TextScope::String);
|
||||
|
||||
if line_mode {
|
||||
streams.out.append(sprintf!(
|
||||
"%d\n",
|
||||
parse_util_lineno(&rstate.text, rstate.cursor_pos)
|
||||
));
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
if search_mode {
|
||||
return if commandline_get_state().search_mode {
|
||||
STATUS_CMD_OK
|
||||
} else {
|
||||
STATUS_CMD_ERROR
|
||||
};
|
||||
}
|
||||
|
||||
if paging_mode {
|
||||
return if commandline_get_state().pager_mode {
|
||||
STATUS_CMD_OK
|
||||
} else {
|
||||
STATUS_CMD_ERROR
|
||||
};
|
||||
}
|
||||
|
||||
if paging_full_mode {
|
||||
let state = commandline_get_state();
|
||||
return if state.pager_mode && state.pager_fully_disclosed {
|
||||
STATUS_CMD_OK
|
||||
} else {
|
||||
STATUS_CMD_ERROR
|
||||
};
|
||||
}
|
||||
|
||||
if selection_start_mode {
|
||||
let Some(selection) = rstate.selection else {
|
||||
return STATUS_CMD_ERROR;
|
||||
};
|
||||
streams.out.append(sprintf!("%lu\n", selection.start));
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
if selection_end_mode {
|
||||
let Some(selection) = rstate.selection else {
|
||||
return STATUS_CMD_ERROR;
|
||||
};
|
||||
streams.out.append(sprintf!("%lu\n", selection.end));
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
// At this point we have (nearly) exhausted the options which always operate on the true command
|
||||
// line. Now we respect the possibility of a transient command line due to evaluating a wrapped
|
||||
// completion. Don't do this in cursor_mode: it makes no sense to move the cursor based on a
|
||||
// transient commandline.
|
||||
let current_buffer;
|
||||
let current_cursor_pos;
|
||||
let transient;
|
||||
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 {
|
||||
transient = ld.transient_commandlines.last().unwrap().clone();
|
||||
current_buffer = &transient;
|
||||
current_cursor_pos = transient.len();
|
||||
} else if rstate.initialized {
|
||||
current_buffer = &rstate.text;
|
||||
current_cursor_pos = rstate.cursor_pos;
|
||||
} else {
|
||||
// There is no command line, either because we are not interactive, or because we are
|
||||
// interactive and are still reading init files (in which case we silently ignore this).
|
||||
if !is_interactive_session() {
|
||||
streams.err.append(cmd);
|
||||
streams
|
||||
.err
|
||||
.append(L!(": Can not set commandline in non-interactive mode\n"));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
}
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
|
||||
if is_valid {
|
||||
if current_buffer.is_empty() {
|
||||
return Some(1);
|
||||
}
|
||||
let res = parse_util_detect_errors(current_buffer, None, /*accept_incomplete=*/ true);
|
||||
return match res {
|
||||
Ok(()) => STATUS_CMD_OK,
|
||||
Err(err) => {
|
||||
if err.contains(ParserTestErrorBits::INCOMPLETE) {
|
||||
Some(2)
|
||||
} else {
|
||||
STATUS_CMD_ERROR
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 cursor_mode {
|
||||
if positional_args != 0 {
|
||||
let arg = w.argv[w.woptind];
|
||||
let new_pos = match fish_wcstol(&arg[range.start..]) {
|
||||
Err(_) => {
|
||||
streams
|
||||
.err
|
||||
.append(wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, arg));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
0
|
||||
}
|
||||
Ok(num) => num,
|
||||
};
|
||||
|
||||
let new_pos = std::cmp::min(new_pos.max(0) as usize, current_buffer.len());
|
||||
commandline_set_buffer(current_buffer.to_owned(), Some(new_pos));
|
||||
} else {
|
||||
streams.out.append(sprintf!("%lu\n", current_cursor_pos));
|
||||
}
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
if positional_args == 0 {
|
||||
write_part(
|
||||
range,
|
||||
cut_at_cursor,
|
||||
tokenize,
|
||||
current_buffer,
|
||||
current_cursor_pos,
|
||||
streams,
|
||||
);
|
||||
} else if positional_args == 1 {
|
||||
replace_part(
|
||||
range,
|
||||
args[w.woptind],
|
||||
append_mode,
|
||||
current_buffer,
|
||||
current_cursor_pos,
|
||||
);
|
||||
} else {
|
||||
let sb = join_strings(&w.argv[w.woptind..], '\n');
|
||||
replace_part(range, &sb, append_mode, current_buffer, current_cursor_pos);
|
||||
}
|
||||
|
||||
STATUS_CMD_OK
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@ use crate::common::{
|
|||
unescape_string, unescape_string_in_place, ScopeGuard, UnescapeFlags, UnescapeStringStyle,
|
||||
};
|
||||
use crate::complete::{complete_add_wrapper, complete_remove_wrapper, CompletionRequestOptions};
|
||||
use crate::ffi;
|
||||
use crate::highlight::colorize;
|
||||
use crate::highlight::highlight_shell;
|
||||
use crate::nix::isatty;
|
||||
use crate::parse_constants::ParseErrorList;
|
||||
use crate::parse_util::parse_util_detect_errors_in_argument_list;
|
||||
use crate::parse_util::{parse_util_detect_errors, parse_util_token_extent};
|
||||
use crate::reader::{commandline_get_state, completion_apply_to_command_line};
|
||||
use crate::wcstringutil::string_suffixes_string;
|
||||
use crate::{
|
||||
common::str2wcstring,
|
||||
|
@ -485,13 +485,14 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
|||
let do_complete_param = match do_complete_param {
|
||||
None => {
|
||||
// No argument given, try to use the current commandline.
|
||||
if !ffi::commandline_get_state_initialized_ffi() {
|
||||
let commandline_state = commandline_get_state();
|
||||
if !commandline_state.initialized {
|
||||
// This corresponds to using 'complete -C' in non-interactive mode.
|
||||
// See #2361 .
|
||||
builtin_missing_argument(parser, streams, cmd, L!("-C"), true);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
ffi::commandline_get_state_text_ffi().from_ffi()
|
||||
commandline_state.text
|
||||
}
|
||||
Some(param) => param,
|
||||
};
|
||||
|
@ -536,14 +537,13 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
|||
// Make a fake commandline, and then apply the completion to it.
|
||||
let faux_cmdline = &do_complete_param[token.clone()];
|
||||
let mut tmp_cursor = faux_cmdline.len();
|
||||
let mut faux_cmdline_with_completion = ffi::completion_apply_to_command_line(
|
||||
&next.completion.to_ffi(),
|
||||
unsafe { std::mem::transmute(next.flags) },
|
||||
&faux_cmdline.to_ffi(),
|
||||
let mut faux_cmdline_with_completion = completion_apply_to_command_line(
|
||||
&next.completion,
|
||||
next.flags,
|
||||
faux_cmdline,
|
||||
&mut tmp_cursor,
|
||||
false,
|
||||
)
|
||||
.from_ffi();
|
||||
);
|
||||
|
||||
// completion_apply_to_command_line will append a space unless COMPLETE_NO_SPACE
|
||||
// is set. We don't want to set COMPLETE_NO_SPACE because that won't close
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//! Implementation of the history builtin.
|
||||
|
||||
use crate::ffi::{self};
|
||||
use crate::history::in_private_mode;
|
||||
use crate::history::{self, history_session_id, History};
|
||||
use crate::history::{in_private_mode, HistorySharedPtr};
|
||||
use crate::reader::commandline_get_state;
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
|
@ -243,17 +243,9 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
|||
|
||||
// Use the default history if we have none (which happens if invoked non-interactively, e.g.
|
||||
// from webconfig.py.
|
||||
let history = ffi::commandline_get_state_history_ffi();
|
||||
let history = if history.is_null() {
|
||||
History::with_name(&history_session_id(parser.vars()))
|
||||
} else {
|
||||
{
|
||||
*unsafe {
|
||||
Box::from_raw(ffi::commandline_get_state_history_ffi() as *mut HistorySharedPtr)
|
||||
}
|
||||
}
|
||||
.0
|
||||
};
|
||||
let history = commandline_get_state()
|
||||
.history
|
||||
.unwrap_or_else(|| History::with_name(&history_session_id(parser.vars())));
|
||||
|
||||
// If a history command hasn't already been specified via a flag check the first word.
|
||||
// Note that this can be simplified after we eliminate allowing subcommands as flags.
|
||||
|
|
|
@ -14,8 +14,8 @@ use crate::env::EnvMode;
|
|||
use crate::env::Environment;
|
||||
use crate::env::READ_BYTE_LIMIT;
|
||||
use crate::env::{EnvVar, EnvVarFlags};
|
||||
use crate::ffi;
|
||||
use crate::nix::isatty;
|
||||
use crate::reader::commandline_set_buffer;
|
||||
use crate::reader::ReaderConfig;
|
||||
use crate::reader::{reader_pop, reader_push, reader_readline};
|
||||
use crate::tokenizer::Tokenizer;
|
||||
|
@ -241,7 +241,7 @@ fn read_interactive(
|
|||
|
||||
// Keep in-memory history only.
|
||||
reader_push(parser, L!(""), conf);
|
||||
ffi::commandline_set_buffer_ffi(&commandline.to_ffi(), usize::MAX);
|
||||
commandline_set_buffer(commandline.to_owned(), None);
|
||||
|
||||
let mline = {
|
||||
let _interactive = scoped_push_replacer(
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use super::prelude::*;
|
||||
use crate::builtins::*;
|
||||
use crate::common::{escape, get_by_sorted_name, str2wcstring, Named};
|
||||
use crate::ffi;
|
||||
use crate::ffi::Repin;
|
||||
use crate::io::{IoChain, IoFd, OutputStream, OutputStreamFfi};
|
||||
use crate::parse_constants::UNKNOWN_BUILTIN_ERR_MSG;
|
||||
use crate::parse_util::parse_util_argument_is_help;
|
||||
use crate::parser::{Block, BlockType, LoopStatus};
|
||||
use crate::proc::{no_exec, ProcStatus};
|
||||
use crate::reader::reader_read;
|
||||
use crate::wchar::{wstr, WString, L};
|
||||
use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t};
|
||||
use cxx::CxxWString;
|
||||
|
@ -951,11 +951,7 @@ fn builtin_breakpoint(
|
|||
} else {
|
||||
unsafe { &mut *streams.io_chain }
|
||||
};
|
||||
ffi::reader_read_ffi(
|
||||
parser as *const Parser as *const autocxx::c_void,
|
||||
autocxx::c_int(STDIN_FILENO),
|
||||
&io_chain as *const _ as *const autocxx::c_void,
|
||||
);
|
||||
reader_read(parser, STDIN_FILENO, io_chain);
|
||||
parser.pop_block(bpb);
|
||||
Some(parser.get_last_status())
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::{
|
||||
common::{escape, scoped_push_replacer, FilenameRef},
|
||||
fds::{wopen_cloexec, AutoCloseFd},
|
||||
ffi::reader_read_ffi,
|
||||
io::IoChain,
|
||||
nix::isatty,
|
||||
parser::Block,
|
||||
reader::reader_read,
|
||||
};
|
||||
use libc::{c_int, O_RDONLY, S_IFMT, S_IFREG};
|
||||
|
||||
|
@ -103,16 +103,15 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> O
|
|||
parser.vars().set_argv(argv_list);
|
||||
|
||||
let empty_io_chain = IoChain::new();
|
||||
let retval = reader_read_ffi(
|
||||
parser as *const Parser as *const autocxx::c_void,
|
||||
unsafe { std::mem::transmute(fd) },
|
||||
let mut retval = reader_read(
|
||||
parser,
|
||||
fd,
|
||||
if !streams.io_chain.is_null() {
|
||||
unsafe { &*streams.io_chain }
|
||||
} else {
|
||||
&empty_io_chain
|
||||
} as *const _ as *const autocxx::c_void,
|
||||
},
|
||||
);
|
||||
let mut retval: c_int = unsafe { std::mem::transmute(retval) };
|
||||
|
||||
parser.pop_block(sb);
|
||||
|
||||
|
|
|
@ -1906,6 +1906,21 @@ where
|
|||
ScopeGuard::new((), restore_saved)
|
||||
}
|
||||
|
||||
pub fn scoped_push_replacer_ctx<Context, Replacer, T>(
|
||||
mut ctx: Context,
|
||||
replacer: Replacer,
|
||||
new_value: T,
|
||||
) -> impl ScopeGuarding<Target = Context>
|
||||
where
|
||||
Replacer: Fn(&mut Context, T) -> T,
|
||||
{
|
||||
let saved = replacer(&mut ctx, new_value);
|
||||
let restore_saved = move |ctx: &mut Context| {
|
||||
replacer(ctx, saved);
|
||||
};
|
||||
ScopeGuard::new(ctx, restore_saved)
|
||||
}
|
||||
|
||||
pub const fn assert_send<T: Send>() {}
|
||||
pub const fn assert_sync<T: Sync>() {}
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ impl Completion {
|
|||
pub fn new(
|
||||
completion: WString,
|
||||
description: WString,
|
||||
r#match: StringFuzzyMatch,
|
||||
r#match: StringFuzzyMatch, /* = exact_match */
|
||||
flags: CompleteFlags,
|
||||
) -> Self {
|
||||
let flags = resolve_auto_space(&completion, flags);
|
||||
|
|
17
fish-rust/src/env/environment.rs
vendored
17
fish-rust/src/env/environment.rs
vendored
|
@ -116,6 +116,16 @@ pub struct EnvDyn {
|
|||
inner: Box<dyn Environment + Send + Sync>,
|
||||
}
|
||||
|
||||
pub trait AsEnvironment {
|
||||
fn as_environment(&self) -> &(dyn Environment + Send + Sync);
|
||||
}
|
||||
|
||||
impl AsEnvironment for EnvDyn {
|
||||
fn as_environment(&self) -> &(dyn Environment + Send + Sync) {
|
||||
&*self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl EnvDyn {
|
||||
// Exposed for testing.
|
||||
pub fn new(inner: Box<dyn Environment + Send + Sync>) -> Self {
|
||||
|
@ -389,8 +399,15 @@ impl Environment for EnvStack {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO Remove Pin?
|
||||
pub type EnvStackRef = Pin<Arc<EnvStack>>;
|
||||
|
||||
impl AsEnvironment for EnvStackRef {
|
||||
fn as_environment(&self) -> &(dyn Environment + Send + Sync) {
|
||||
Pin::get_ref(Pin::as_ref(self))
|
||||
}
|
||||
}
|
||||
|
||||
// A variable stack that only represents globals.
|
||||
// Do not push or pop from this.
|
||||
lazy_static! {
|
||||
|
|
26
fish-rust/src/env/environment_impl.rs
vendored
26
fish-rust/src/env/environment_impl.rs
vendored
|
@ -4,14 +4,14 @@ use crate::env::{
|
|||
ELECTRIC_VARIABLES, PATH_ARRAY_SEP,
|
||||
};
|
||||
use crate::env_universal_common::EnvUniversal;
|
||||
use crate::ffi;
|
||||
use crate::flog::FLOG;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::history::{history_session_id_from_var, History};
|
||||
use crate::kill::kill_entries;
|
||||
use crate::null_terminated_array::OwningNullTerminatedArray;
|
||||
use crate::reader::{commandline_get_state, reader_status_count};
|
||||
use crate::threads::{is_forked_child, is_main_thread};
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wchar_ffi::{WCharFromFFI, WCharToFFI};
|
||||
use crate::wutil::fish_wcstol_radix;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
@ -41,11 +41,6 @@ pub fn uvars() -> MutexGuard<'static, EnvUniversal> {
|
|||
/// Whether we were launched with no_config; in this case setting a uvar instead sets a global.
|
||||
pub static UVAR_SCOPE_IS_GLOBAL: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
|
||||
/// Helper to get the history for a session ID.
|
||||
fn get_history_var_text(history_session_id: &wstr) -> Vec<WString> {
|
||||
ffi::get_history_variable_text_ffi(&history_session_id.to_ffi()).from_ffi()
|
||||
}
|
||||
|
||||
/// Apply the pathvar behavior, splitting about colons.
|
||||
pub fn colon_split<T: AsRef<wstr>>(val: &[T]) -> Vec<WString> {
|
||||
let mut split_val = Vec::new();
|
||||
|
@ -362,15 +357,12 @@ impl EnvScopedImpl {
|
|||
if (!is_main_thread()) {
|
||||
return None;
|
||||
}
|
||||
let fish_history_var = self
|
||||
.getf(L!("fish_history"), EnvMode::default())
|
||||
.map(|v| v.as_string());
|
||||
let history_session_id = fish_history_var
|
||||
.as_ref()
|
||||
.map(WString::as_utfstr)
|
||||
.unwrap_or(DFLT_FISH_HISTORY_SESSION_ID);
|
||||
let vals = get_history_var_text(history_session_id);
|
||||
return Some(EnvVar::new_from_name_vec("history"L, vals));
|
||||
let history = commandline_get_state().history.unwrap_or_else(|| {
|
||||
let fish_history_var = self.getf(L!("fish_history"), EnvMode::default());
|
||||
let session_id = history_session_id_from_var(fish_history_var);
|
||||
History::with_name(&session_id)
|
||||
});
|
||||
return Some(EnvVar::new_from_name_vec("history"L, history.get_history()));
|
||||
} else if key == "fish_killring"L {
|
||||
Some(EnvVar::new_from_name_vec("fish_killring"L, kill_entries()))
|
||||
} else if key == "pipestatus"L {
|
||||
|
@ -385,7 +377,7 @@ impl EnvScopedImpl {
|
|||
let js = &self.perproc_data.statuses;
|
||||
Some(EnvVar::new_from_name("status"L, js.status.to_wstring()))
|
||||
} else if key == "status_generation"L {
|
||||
let status_generation = ffi::reader_status_count();
|
||||
let status_generation = reader_status_count();
|
||||
Some(EnvVar::new_from_name(
|
||||
"status_generation"L,
|
||||
status_generation.to_wstring(),
|
||||
|
|
|
@ -8,10 +8,13 @@ use crate::function;
|
|||
use crate::input_common::{update_wait_on_escape_ms, update_wait_on_sequence_key_ms};
|
||||
use crate::output::ColorSupport;
|
||||
use crate::proc::is_interactive_session;
|
||||
use crate::reader::{
|
||||
reader_change_cursor_selection_mode, reader_change_history, reader_schedule_prompt_repaint,
|
||||
reader_set_autosuggestion_enabled,
|
||||
};
|
||||
use crate::screen::screen_set_midnight_commander_hack;
|
||||
use crate::screen::LAYOUT_CACHE_SHARED;
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wchar_ffi::WCharToFFI;
|
||||
use crate::wutil::fish_wcstoi;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
@ -222,7 +225,7 @@ pub fn env_dispatch_var_change(key: &wstr, vars: &EnvStack) {
|
|||
|
||||
fn handle_fish_term_change(vars: &EnvStack) {
|
||||
update_fish_color_support(vars);
|
||||
crate::ffi::reader_schedule_prompt_repaint();
|
||||
reader_schedule_prompt_repaint();
|
||||
}
|
||||
|
||||
fn handle_change_ambiguous_width(vars: &EnvStack) {
|
||||
|
@ -243,7 +246,7 @@ fn handle_term_size_change(vars: &EnvStack) {
|
|||
|
||||
fn handle_fish_history_change(vars: &EnvStack) {
|
||||
let session_id = crate::history::history_session_id(vars);
|
||||
crate::ffi::reader_change_history(&session_id.to_ffi());
|
||||
reader_change_history(&session_id);
|
||||
}
|
||||
|
||||
fn handle_fish_cursor_selection_mode_change(vars: &EnvStack) {
|
||||
|
@ -261,16 +264,11 @@ fn handle_fish_cursor_selection_mode_change(vars: &EnvStack) {
|
|||
CursorSelectionMode::Exclusive
|
||||
};
|
||||
|
||||
let mode = mode as u8;
|
||||
crate::ffi::reader_change_cursor_selection_mode(mode);
|
||||
reader_change_cursor_selection_mode(mode);
|
||||
}
|
||||
|
||||
fn handle_autosuggestion_change(vars: &EnvStack) {
|
||||
// TODO: This was a call to reader_set_autosuggestion_enabled(vars) and
|
||||
// reader::check_autosuggestion_enabled() should be private to the `reader` module.
|
||||
crate::ffi::reader_set_autosuggestion_enabled_ffi(crate::reader::check_autosuggestion_enabled(
|
||||
vars,
|
||||
));
|
||||
reader_set_autosuggestion_enabled(vars);
|
||||
}
|
||||
|
||||
fn handle_function_path_change(_: &EnvStack) {
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::env::{EnvMode, EnvStack, Environment, Statuses, READ_BYTE_LIMIT};
|
|||
use crate::env_dispatch::use_posix_spawn;
|
||||
use crate::fds::make_fd_blocking;
|
||||
use crate::fds::{make_autoclose_pipes, open_cloexec, AutoCloseFd, AutoClosePipes, PIPE_ERROR};
|
||||
use crate::ffi::{self, wcstring_list_ffi_t};
|
||||
use crate::ffi::wcstring_list_ffi_t;
|
||||
use crate::flog::FLOGF;
|
||||
use crate::fork_exec::blocked_signals_for_job;
|
||||
use crate::fork_exec::postfork::{
|
||||
|
@ -40,7 +40,7 @@ use crate::proc::{
|
|||
print_exit_warning_for_jobs, InternalProc, Job, JobGroupRef, ProcStatus, Process, ProcessType,
|
||||
TtyTransfer, INVALID_PID,
|
||||
};
|
||||
use crate::reader::reader_run_count;
|
||||
use crate::reader::{reader_run_count, restore_term_mode};
|
||||
use crate::redirection::{dup2_list_resolve_chain, Dup2List};
|
||||
use crate::threads::{iothread_perform_cant_wait, is_forked_child};
|
||||
use crate::timer::push_timer;
|
||||
|
@ -444,7 +444,7 @@ fn launch_process_nofork(vars: &EnvStack, p: &Process) -> ! {
|
|||
let actual_cmd = wcs2zstring(&p.actual_cmd);
|
||||
|
||||
// Ensure the terminal modes are what they were before we changed them.
|
||||
ffi::restore_term_mode();
|
||||
restore_term_mode();
|
||||
// Bounce to launch_process. This never returns.
|
||||
safe_launch_process(p, &actual_cmd, &argv, &*envp);
|
||||
}
|
||||
|
|
|
@ -35,28 +35,16 @@ include_cpp! {
|
|||
#include "tokenizer.h"
|
||||
#include "wutil.h"
|
||||
|
||||
#include "builtins/commandline.h"
|
||||
|
||||
safety!(unsafe_ffi)
|
||||
|
||||
generate_pod!("wcharz_t")
|
||||
generate!("wcstring_list_ffi_t")
|
||||
generate!("set_inheriteds_ffi")
|
||||
|
||||
generate!("reader_init")
|
||||
generate!("reader_run_count")
|
||||
generate!("term_copy_modes")
|
||||
generate!("set_profiling_active")
|
||||
generate!("reader_read_ffi")
|
||||
generate!("fish_is_unwinding_for_exit")
|
||||
generate!("restore_term_mode")
|
||||
generate!("read_generation_count")
|
||||
generate!("set_flog_output_file_ffi")
|
||||
generate!("flog_setlinebuf_ffi")
|
||||
generate!("activate_flog_categories_by_pattern")
|
||||
generate!("restore_term_foreground_process_group_for_exit")
|
||||
|
||||
generate!("builtin_commandline")
|
||||
|
||||
generate!("shell_modes_ffi")
|
||||
|
||||
|
@ -68,26 +56,9 @@ include_cpp! {
|
|||
|
||||
generate!("rgb_color_t")
|
||||
generate_pod!("color24_t")
|
||||
generate!("reader_status_count")
|
||||
generate!("reader_write_title_ffi")
|
||||
generate!("reader_push_ffi")
|
||||
generate!("reader_readline_ffi")
|
||||
generate!("reader_pop")
|
||||
generate!("commandline_get_state_history_ffi")
|
||||
generate!("commandline_set_buffer_ffi")
|
||||
generate!("commandline_get_state_initialized_ffi")
|
||||
generate!("commandline_get_state_text_ffi")
|
||||
generate!("completion_apply_to_command_line")
|
||||
|
||||
generate!("get_history_variable_text_ffi")
|
||||
|
||||
generate_pod!("escape_string_style_t")
|
||||
|
||||
generate!("reader_schedule_prompt_repaint")
|
||||
generate!("reader_reading_interrupted")
|
||||
generate!("reader_change_history")
|
||||
generate!("reader_change_cursor_selection_mode")
|
||||
generate!("reader_set_autosuggestion_enabled_ffi")
|
||||
}
|
||||
|
||||
/// Allow wcharz_t to be "into" wstr.
|
||||
|
|
|
@ -17,15 +17,14 @@ along with this program; if not, write to the Free Software
|
|||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
use autocxx::prelude::*;
|
||||
|
||||
use crate::{
|
||||
ast::Ast,
|
||||
builtins::shared::{
|
||||
BUILTIN_ERR_MISSING, BUILTIN_ERR_UNKNOWN, STATUS_CMD_OK, STATUS_CMD_UNKNOWN,
|
||||
},
|
||||
common::{
|
||||
escape, exit_without_destructors, get_executable_path, save_term_foreground_process_group,
|
||||
escape, exit_without_destructors, get_executable_path,
|
||||
restore_term_foreground_process_group_for_exit, save_term_foreground_process_group,
|
||||
scoped_push_replacer, str2wcstring, wcs2string, PROFILING_ACTIVE, PROGRAM_NAME,
|
||||
},
|
||||
env::Statuses,
|
||||
|
@ -50,6 +49,7 @@ use crate::{
|
|||
get_login, is_interactive_session, mark_login, mark_no_exec, proc_init,
|
||||
set_interactive_session,
|
||||
},
|
||||
reader::{reader_init, reader_read, restore_term_mode, term_copy_modes},
|
||||
signal::{signal_clear_cancel, signal_unblock_all},
|
||||
threads::{self, asan_maybe_exit},
|
||||
topic_monitor,
|
||||
|
@ -637,7 +637,7 @@ fn main() -> i32 {
|
|||
features::set_from_string(opts.features.as_utfstr());
|
||||
proc_init();
|
||||
crate::env::misc_init();
|
||||
ffi::reader_init();
|
||||
reader_init();
|
||||
|
||||
let parser = Parser::principal_parser();
|
||||
parser.set_syncs_uvars(!opts.no_config);
|
||||
|
@ -659,7 +659,7 @@ fn main() -> i32 {
|
|||
}
|
||||
|
||||
// Re-read the terminal modes after config, it might have changed them.
|
||||
ffi::term_copy_modes();
|
||||
term_copy_modes();
|
||||
|
||||
// Stomp the exit status of any initialization commands (issue #635).
|
||||
parser.set_last_statuses(Statuses::just(STATUS_CMD_OK.unwrap()));
|
||||
|
@ -712,12 +712,7 @@ fn main() -> i32 {
|
|||
// above line should always exit
|
||||
return libc::EXIT_FAILURE;
|
||||
}
|
||||
res = ffi::reader_read_ffi(
|
||||
parser as *const Parser as *const autocxx::c_void,
|
||||
c_int(libc::STDIN_FILENO),
|
||||
&IoChain::new() as *const _ as *const autocxx::c_void,
|
||||
)
|
||||
.into();
|
||||
res = reader_read(parser, libc::STDIN_FILENO, &IoChain::new());
|
||||
} else {
|
||||
// C++ had not converted at this point, we must undo
|
||||
let n = wcs2string(&args[my_optind]);
|
||||
|
@ -749,12 +744,7 @@ fn main() -> i32 {
|
|||
},
|
||||
Some(Arc::new(rel_filename.to_owned())),
|
||||
);
|
||||
res = ffi::reader_read_ffi(
|
||||
parser as *const Parser as *const autocxx::c_void,
|
||||
c_int(f.as_raw_fd()),
|
||||
&IoChain::new() as *const _ as *const autocxx::c_void,
|
||||
)
|
||||
.into();
|
||||
res = reader_read(parser, f.as_raw_fd(), &IoChain::new());
|
||||
if res != 0 {
|
||||
FLOGF!(
|
||||
warning,
|
||||
|
@ -781,9 +771,9 @@ fn main() -> i32 {
|
|||
vec![exit_status.to_wstring()],
|
||||
);
|
||||
|
||||
ffi::restore_term_mode();
|
||||
restore_term_mode();
|
||||
// this is ported, but not adopted
|
||||
ffi::restore_term_foreground_process_group_for_exit();
|
||||
restore_term_foreground_process_group_for_exit();
|
||||
|
||||
if let Some(profile_output) = opts.profile_output {
|
||||
let s = cstr_from_osstr(&profile_output);
|
||||
|
|
|
@ -134,6 +134,8 @@ pub mod categories {
|
|||
|
||||
(screen, "screen", "Screen repaints");
|
||||
|
||||
(abbrs, "abbrs", "Abbreviation expansion");
|
||||
|
||||
(refcell, "refcell", "Refcell dynamic borrowing");
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ pub trait IsOkAnd {
|
|||
type Error;
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_ok_and(self, s: impl FnOnce(Self::Type) -> bool) -> bool;
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_err_and(self, s: impl FnOnce(Self::Error) -> bool) -> bool;
|
||||
}
|
||||
impl<T, E> IsOkAnd for Result<T, E> {
|
||||
type Type = T;
|
||||
|
@ -38,6 +40,12 @@ impl<T, E> IsOkAnd for Result<T, E> {
|
|||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
fn is_err_and(self, f: impl FnOnce(E) -> bool) -> bool {
|
||||
match self {
|
||||
Ok(_) => false,
|
||||
Err(e) => f(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IsSorted {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
//! 5. The chaos_mode boolean can be set to true to do things like lower buffer sizes which can
|
||||
//! trigger race conditions. This is useful for testing.
|
||||
|
||||
use crate::{common::cstr2wcstring, wcstringutil::trim};
|
||||
use crate::{common::cstr2wcstring, env::EnvVar, wcstringutil::trim};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, HashMap, HashSet, VecDeque},
|
||||
|
@ -46,7 +46,7 @@ use crate::{
|
|||
str2wcstring, unescape_string, valid_var_name, wcs2zstring, write_loop, CancelChecker,
|
||||
UnescapeStringStyle,
|
||||
},
|
||||
env::{EnvDyn, EnvMode, EnvStack, EnvStackRefFFI, Environment},
|
||||
env::{AsEnvironment, EnvMode, EnvStack, EnvStackRefFFI, Environment},
|
||||
expand::{expand_one, ExpandFlags},
|
||||
fallback::fish_mkstemp_cloexec,
|
||||
fds::{wopen_cloexec, AutoCloseFd},
|
||||
|
@ -66,7 +66,7 @@ use crate::{
|
|||
util::find_subslice,
|
||||
wchar::prelude::*,
|
||||
wchar_ext::WExt,
|
||||
wchar_ffi::{AsWstr, WCharFromFFI, WCharToFFI},
|
||||
wchar_ffi::{WCharFromFFI, WCharToFFI},
|
||||
wcstringutil::subsequence_in_string,
|
||||
wildcard::{wildcard_match, ANY_STRING},
|
||||
wutil::{
|
||||
|
@ -129,12 +129,6 @@ mod history_ffi {
|
|||
Backward,
|
||||
}
|
||||
|
||||
pub enum HistoryPagerInvocation {
|
||||
Anew,
|
||||
Advance,
|
||||
Refresh,
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
#[cxx_name = "history_save_all"]
|
||||
fn save_all();
|
||||
|
@ -205,13 +199,6 @@ mod history_ffi {
|
|||
fn item_at_index(&self, idx: usize) -> Box<HistoryItem>;
|
||||
fn size(&self) -> usize;
|
||||
fn clone(&self) -> Box<HistorySharedPtr>;
|
||||
#[cxx_name = "add_pending_with_file_detection"]
|
||||
fn add_pending_with_file_detection_ffi(
|
||||
&self,
|
||||
s: &CxxWString,
|
||||
vars: &EnvStackRefFFI,
|
||||
persist_mode: PersistenceMode,
|
||||
);
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
|
@ -1701,7 +1688,7 @@ impl History {
|
|||
pub fn add_pending_with_file_detection(
|
||||
self: Arc<Self>,
|
||||
s: &wstr,
|
||||
vars: EnvDyn,
|
||||
vars: impl AsEnvironment + Send + Sync + 'static,
|
||||
persist_mode: PersistenceMode, /*=disk*/
|
||||
) {
|
||||
// We use empty items as sentinels to indicate the end of history.
|
||||
|
@ -1760,7 +1747,8 @@ impl History {
|
|||
drop(imp);
|
||||
iothread_perform(move || {
|
||||
// Don't hold the lock while we perform this file detection.
|
||||
let validated_paths = expand_and_detect_paths(potential_paths, &vars);
|
||||
let validated_paths =
|
||||
expand_and_detect_paths(potential_paths, vars.as_environment());
|
||||
let mut imp = self.imp();
|
||||
imp.set_valid_file_paths(validated_paths, identifier);
|
||||
imp.enable_automatic_saving();
|
||||
|
@ -2096,10 +2084,13 @@ pub fn save_all() {
|
|||
|
||||
/// Return the prefix for the files to be used for command and read history.
|
||||
pub fn history_session_id(vars: &dyn Environment) -> WString {
|
||||
let Some(var) = vars.get(L!("fish_history")) else {
|
||||
history_session_id_from_var(vars.get(L!("fish_history")))
|
||||
}
|
||||
|
||||
pub fn history_session_id_from_var(history_name_var: Option<EnvVar>) -> WString {
|
||||
let Some(var) = history_name_var else {
|
||||
return DFLT_FISH_HISTORY_SESSION_ID.to_owned();
|
||||
};
|
||||
|
||||
let session_id = var.as_string();
|
||||
if session_id.is_empty() || valid_var_name(&session_id) {
|
||||
session_id
|
||||
|
@ -2335,18 +2326,6 @@ impl HistorySharedPtr {
|
|||
fn clone(&self) -> Box<Self> {
|
||||
Box::new(Self(Arc::clone(&self.0)))
|
||||
}
|
||||
fn add_pending_with_file_detection_ffi(
|
||||
&self,
|
||||
s: &CxxWString,
|
||||
vars: &EnvStackRefFFI,
|
||||
persist_mode: PersistenceMode,
|
||||
) {
|
||||
Arc::clone(&self.0).add_pending_with_file_detection(
|
||||
s.as_wstr(),
|
||||
vars.0.snapshot(),
|
||||
persist_mode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn history_with_name(name: &CxxWString) -> Box<HistorySharedPtr> {
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::common::{is_windows_subsystem_for_linux, read_blocked};
|
|||
use crate::env::{EnvStack, Environment};
|
||||
use crate::fd_readable_set::FdReadableSet;
|
||||
use crate::flog::FLOG;
|
||||
use crate::reader::reader_current_data;
|
||||
use crate::threads::{iothread_port, iothread_service_main};
|
||||
use crate::universal_notifier::default_notifier;
|
||||
use crate::wchar::prelude::*;
|
||||
|
@ -307,7 +308,7 @@ pub trait InputEventQueuer {
|
|||
}
|
||||
|
||||
ReadbResult::IOPortNotified => {
|
||||
iothread_service_main();
|
||||
iothread_service_main(reader_current_data().unwrap());
|
||||
}
|
||||
|
||||
ReadbResult::Byte(read_byte) => {
|
||||
|
|
|
@ -93,6 +93,7 @@ mod print_help;
|
|||
mod proc;
|
||||
mod re;
|
||||
mod reader;
|
||||
mod reader_history_search;
|
||||
mod redirection;
|
||||
mod screen;
|
||||
mod signal;
|
||||
|
|
|
@ -6,6 +6,8 @@ use crate::proc::JobGroupRef;
|
|||
use once_cell::sync::Lazy;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::reader::read_generation_count;
|
||||
|
||||
/// A common helper which always returns false.
|
||||
pub fn no_cancel() -> bool {
|
||||
false
|
||||
|
@ -146,7 +148,7 @@ impl<'a> OperationContext<'a> {
|
|||
pub fn get_bg_context(env: &EnvDyn, generation_count: u32) -> OperationContext {
|
||||
let cancel_checker = move || {
|
||||
// Cancel if the generation count changed.
|
||||
generation_count != crate::ffi::read_generation_count()
|
||||
generation_count != read_generation_count()
|
||||
};
|
||||
OperationContext::background_with_cancel_checker(
|
||||
env,
|
||||
|
|
|
@ -399,10 +399,10 @@ fn job_or_process_extent(
|
|||
{
|
||||
if tok_begin >= pos {
|
||||
finished = true;
|
||||
result.start = tok_begin;
|
||||
result.end = tok_begin;
|
||||
} else {
|
||||
// Statement at cursor might start after this token.
|
||||
result.end = tok_begin + token.length();
|
||||
result.start = tok_begin + token.length();
|
||||
out_tokens.as_mut().map(|tokens| tokens.clear());
|
||||
}
|
||||
continue; // Do not add this to tokens
|
||||
|
@ -490,7 +490,7 @@ pub fn parse_util_lineno(s: &wstr, offset: usize) -> usize {
|
|||
}
|
||||
|
||||
let end = offset.min(s.len());
|
||||
s.chars().take(end).filter(|c| *c == '\n').count()
|
||||
s.chars().take(end).filter(|c| *c == '\n').count() + 1
|
||||
}
|
||||
|
||||
/// Calculate the line number of the specified cursor position.
|
||||
|
@ -570,6 +570,52 @@ pub fn parse_util_unescape_wildcards(s: &wstr) -> WString {
|
|||
result
|
||||
}
|
||||
|
||||
/// Return if the given string contains wildcard characters.
|
||||
pub fn parse_util_contains_wildcards(s: &wstr) -> bool {
|
||||
let unesc_qmark = !feature_test(FeatureFlag::qmark_noglob);
|
||||
|
||||
let mut i = 0;
|
||||
while i < s.len() {
|
||||
let c = s.as_char_slice()[i];
|
||||
if c == '*' {
|
||||
return true;
|
||||
} else if unesc_qmark && c == '?' {
|
||||
return true;
|
||||
} else if c == '\\' {
|
||||
if s.char_at(i + 1) == '*' {
|
||||
i += 1;
|
||||
} else if unesc_qmark && s.char_at(i + 1) == '?' {
|
||||
i += 1;
|
||||
} else if s.char_at(i + 1) == '\\' {
|
||||
// Not a wildcard, but ensure the next iteration doesn't see this escaped backslash.
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Escape any wildcard characters in the given string. e.g. convert
|
||||
/// "a*b" to "a\*b".
|
||||
pub fn parse_util_escape_wildcards(s: &wstr) -> WString {
|
||||
let mut result = WString::with_capacity(s.len());
|
||||
let unesc_qmark = !feature_test(FeatureFlag::qmark_noglob);
|
||||
|
||||
for c in s.chars() {
|
||||
if c == '*' {
|
||||
result.push_str("\\*");
|
||||
} else if unesc_qmark && c == '?' {
|
||||
result.push_str("\\?");
|
||||
} else if c == '\\' {
|
||||
result.push_str("\\\\");
|
||||
} else {
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Checks if the specified string is a help option.
|
||||
#[widestrs]
|
||||
pub fn parse_util_argument_is_help(s: &wstr) -> bool {
|
||||
|
|
File diff suppressed because it is too large
Load diff
272
fish-rust/src/reader_history_search.rs
Normal file
272
fish-rust/src/reader_history_search.rs
Normal file
|
@ -0,0 +1,272 @@
|
|||
//! Encapsulation of the reader's history search functionality.
|
||||
|
||||
use crate::history::{self, History, HistorySearch, SearchDirection, SearchFlags, SearchType};
|
||||
use crate::parse_constants::SourceRange;
|
||||
use crate::tokenizer::{TokenType, Tokenizer, TOK_ACCEPT_UNFINISHED};
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wcstringutil::ifind;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Make the search case-insensitive unless we have an uppercase character.
|
||||
pub fn smartcase_flags(query: &wstr) -> history::SearchFlags {
|
||||
if query == query.to_lowercase() {
|
||||
history::SearchFlags::IGNORE_CASE
|
||||
} else {
|
||||
history::SearchFlags::default()
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchMatch {
|
||||
/// The text of the match.
|
||||
pub text: WString,
|
||||
/// The offset of the current search string in this match.
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl SearchMatch {
|
||||
fn new(text: WString, offset: usize) -> Self {
|
||||
Self { text, offset }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, Default, PartialEq)]
|
||||
pub enum SearchMode {
|
||||
#[default]
|
||||
/// no search
|
||||
Inactive,
|
||||
/// searching by line
|
||||
Line,
|
||||
/// searching by prefix
|
||||
Prefix,
|
||||
/// searching by token
|
||||
Token,
|
||||
}
|
||||
|
||||
/// Encapsulation of the reader's history search functionality.
|
||||
#[derive(Default)]
|
||||
pub struct ReaderHistorySearch {
|
||||
/// The type of search performed.
|
||||
mode: SearchMode,
|
||||
|
||||
/// Our history search itself.
|
||||
search: Option<HistorySearch>,
|
||||
|
||||
/// The ordered list of matches. This may grow long.
|
||||
matches: Vec<SearchMatch>,
|
||||
|
||||
/// A set of new items to skip, corresponding to matches_ and anything added in skip().
|
||||
skips: HashSet<WString>,
|
||||
|
||||
/// Index into our matches list.
|
||||
match_index: usize,
|
||||
|
||||
/// The offset of the current token in the command line. Only non-zero for a token search.
|
||||
token_offset: usize,
|
||||
}
|
||||
|
||||
impl ReaderHistorySearch {
|
||||
pub fn active(&self) -> bool {
|
||||
self.mode != SearchMode::Inactive
|
||||
}
|
||||
pub fn by_token(&self) -> bool {
|
||||
self.mode == SearchMode::Token
|
||||
}
|
||||
pub fn by_line(&self) -> bool {
|
||||
self.mode == SearchMode::Line
|
||||
}
|
||||
pub fn by_prefix(&self) -> bool {
|
||||
self.mode == SearchMode::Prefix
|
||||
}
|
||||
|
||||
/// Move the history search in the given direction \p dir.
|
||||
pub fn move_in_direction(&mut self, dir: SearchDirection) -> bool {
|
||||
if dir == SearchDirection::Forward {
|
||||
self.move_forwards()
|
||||
} else {
|
||||
self.move_backwards()
|
||||
}
|
||||
}
|
||||
|
||||
/// Go to the beginning (earliest) of the search.
|
||||
pub fn go_to_beginning(&mut self) {
|
||||
if self.matches.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.match_index = self.matches.len() - 1;
|
||||
}
|
||||
|
||||
/// Go to the end (most recent) of the search.
|
||||
pub fn go_to_end(&mut self) {
|
||||
self.match_index = 0;
|
||||
}
|
||||
|
||||
/// \return the current search result.
|
||||
pub fn current_result(&self) -> &wstr {
|
||||
&self.matches[self.match_index].text
|
||||
}
|
||||
|
||||
/// \return the string we are searching for.
|
||||
pub fn search_string(&self) -> &wstr {
|
||||
self.search().original_term()
|
||||
}
|
||||
|
||||
/// \return the range of the original search string in the new command line.
|
||||
pub fn search_range_if_active(&self) -> Option<SourceRange> {
|
||||
if !self.active() || self.is_at_end() {
|
||||
return None;
|
||||
}
|
||||
Some(SourceRange::new(
|
||||
self.token_offset + self.matches[self.match_index].offset,
|
||||
self.search_string().len(),
|
||||
))
|
||||
}
|
||||
|
||||
/// \return whether we are at the end (most recent) of our search.
|
||||
pub fn is_at_end(&self) -> bool {
|
||||
self.match_index == 0
|
||||
}
|
||||
|
||||
// Add an item to skip.
|
||||
// \return true if it was added, false if already present.
|
||||
pub fn add_skip(&mut self, s: WString) -> bool {
|
||||
self.skips.insert(s)
|
||||
}
|
||||
|
||||
/// Reset, beginning a new line or token mode search.
|
||||
pub fn reset_to_mode(
|
||||
&mut self,
|
||||
text: WString,
|
||||
hist: Arc<History>,
|
||||
mode: SearchMode,
|
||||
token_offset: usize,
|
||||
) {
|
||||
assert!(
|
||||
mode != SearchMode::Inactive,
|
||||
"mode cannot be inactive in this setter"
|
||||
);
|
||||
self.skips = HashSet::from([text.clone()]);
|
||||
self.matches = vec![SearchMatch::new(text.clone(), 0)];
|
||||
self.match_index = 0;
|
||||
self.mode = mode;
|
||||
self.token_offset = token_offset;
|
||||
let flags = SearchFlags::NO_DEDUP | smartcase_flags(&text);
|
||||
// We can skip dedup in history_search_t because we do it ourselves in skips_.
|
||||
self.search = Some(HistorySearch::new_with(
|
||||
hist,
|
||||
text,
|
||||
if self.by_prefix() {
|
||||
SearchType::Prefix
|
||||
} else {
|
||||
SearchType::Contains
|
||||
},
|
||||
flags,
|
||||
0,
|
||||
));
|
||||
}
|
||||
|
||||
/// Reset to inactive search.
|
||||
pub fn reset(&mut self) {
|
||||
self.matches.clear();
|
||||
self.skips.clear();
|
||||
self.match_index = 0;
|
||||
self.mode = SearchMode::Inactive;
|
||||
self.token_offset = 0;
|
||||
self.search = None;
|
||||
}
|
||||
|
||||
/// Adds the given match if we haven't seen it before.
|
||||
fn add_if_new(&mut self, search_match: SearchMatch) {
|
||||
if self.add_skip(search_match.text.clone()) {
|
||||
self.matches.push(search_match);
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to append matches from the current history item.
|
||||
/// \return true if something was appended.
|
||||
fn append_matches_from_search(&mut self) -> bool {
|
||||
fn find(zelf: &ReaderHistorySearch, haystack: &wstr, needle: &wstr) -> Option<usize> {
|
||||
if zelf.search().ignores_case() {
|
||||
return ifind(haystack, needle, false);
|
||||
}
|
||||
haystack.find(needle)
|
||||
}
|
||||
let before = self.matches.len();
|
||||
let text = self.search().current_string();
|
||||
let needle = self.search_string();
|
||||
if matches!(self.mode, SearchMode::Line | SearchMode::Prefix) {
|
||||
// FIXME: Previous versions asserted out if this wasn't true.
|
||||
// This could be hit with a needle of "ö" and haystack of "echo Ö"
|
||||
// I'm not sure why - this points to a bug in ifind (probably wrong locale?)
|
||||
// However, because the user experience of having it crash is horrible,
|
||||
// and the worst thing that can otherwise happen here is that a search is unsuccessful,
|
||||
// we just check it instead.
|
||||
if let Some(offset) = find(self, text, needle) {
|
||||
self.add_if_new(SearchMatch::new(text.to_owned(), offset));
|
||||
}
|
||||
} else if self.mode == SearchMode::Token {
|
||||
let mut tok = Tokenizer::new(text, TOK_ACCEPT_UNFINISHED);
|
||||
|
||||
let mut local_tokens = vec![];
|
||||
while let Some(token) = tok.next() {
|
||||
if token.type_ != TokenType::string {
|
||||
continue;
|
||||
}
|
||||
let text = tok.text_of(&token);
|
||||
if let Some(offset) = find(self, text, needle) {
|
||||
local_tokens.push(SearchMatch::new(text.to_owned(), offset));
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure tokens are added in reverse order. See #5150
|
||||
for tok in local_tokens.into_iter().rev() {
|
||||
self.add_if_new(tok);
|
||||
}
|
||||
}
|
||||
self.matches.len() > before
|
||||
}
|
||||
|
||||
fn move_forwards(&mut self) -> bool {
|
||||
// Try to move within our previously discovered matches.
|
||||
if self.match_index > 0 {
|
||||
self.match_index -= 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn move_backwards(&mut self) -> bool {
|
||||
// Try to move backwards within our previously discovered matches.
|
||||
if self.match_index + 1 < self.matches.len() {
|
||||
self.match_index += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add more items from our search.
|
||||
while self
|
||||
.search_mut()
|
||||
.go_to_next_match(SearchDirection::Backward)
|
||||
{
|
||||
if self.append_matches_from_search() {
|
||||
self.match_index += 1;
|
||||
assert!(
|
||||
self.match_index < self.matches.len(),
|
||||
"Should have found more matches"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Here we failed to go backwards past the last history item.
|
||||
false
|
||||
}
|
||||
|
||||
fn search(&self) -> &HistorySearch {
|
||||
self.search.as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn search_mut(&mut self) -> &mut HistorySearch {
|
||||
self.search.as_mut().unwrap()
|
||||
}
|
||||
}
|
|
@ -255,7 +255,7 @@ impl Screen {
|
|||
commandline: &wstr,
|
||||
explicit_len: usize,
|
||||
colors: &[HighlightSpec],
|
||||
indent: &[usize],
|
||||
indent: &[i32],
|
||||
cursor_pos: usize,
|
||||
vars: &dyn Environment,
|
||||
pager: &mut Pager,
|
||||
|
@ -336,7 +336,7 @@ impl Screen {
|
|||
self.desired_append_char(
|
||||
effective_commandline.as_char_slice()[i],
|
||||
colors[i],
|
||||
indent[i],
|
||||
usize::try_from(indent[i]).unwrap(),
|
||||
first_line_prompt_space,
|
||||
usize::try_from(fish_wcwidth_visible(
|
||||
effective_commandline.as_char_slice()[i],
|
||||
|
@ -1960,17 +1960,13 @@ impl Screen {
|
|||
cursor_is_within_pager: bool,
|
||||
) {
|
||||
let vars = unsafe { Box::from_raw(vars as *mut EnvStackRef) };
|
||||
let mut my_indent = vec![];
|
||||
for n in indent.as_slice() {
|
||||
my_indent.push(usize::try_from(*n).unwrap());
|
||||
}
|
||||
self.write(
|
||||
left_prompt.as_wstr(),
|
||||
right_prompt.as_wstr(),
|
||||
commandline.as_wstr(),
|
||||
explicit_len,
|
||||
&colors.0,
|
||||
&my_indent,
|
||||
indent.as_slice(),
|
||||
cursor_pos,
|
||||
vars.as_ref().as_ref().get_ref(),
|
||||
pager.get_mut(),
|
||||
|
|
|
@ -280,7 +280,7 @@ pub fn handle_columns_lines_var_change(vars: &dyn Environment) {
|
|||
SHARED_CONTAINER.handle_columns_lines_var_change(vars);
|
||||
}
|
||||
|
||||
fn termsize_update(parser: &Parser) -> Termsize {
|
||||
pub fn termsize_update(parser: &Parser) -> Termsize {
|
||||
SHARED_CONTAINER.updating(parser)
|
||||
}
|
||||
|
||||
|
|
139
fish-rust/src/tests/abbrs.rs
Normal file
139
fish-rust/src/tests/abbrs.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
use crate::abbrs::{self, abbrs_get_set, abbrs_match, Abbreviation};
|
||||
use crate::complete::CompleteFlags;
|
||||
use crate::editable_line::{apply_edit, Edit};
|
||||
use crate::highlight::HighlightSpec;
|
||||
use crate::parser::Parser;
|
||||
use crate::reader::{
|
||||
combine_command_and_autosuggestion, completion_apply_to_command_line,
|
||||
reader_expand_abbreviation_at_cursor,
|
||||
};
|
||||
use crate::wchar::prelude::*;
|
||||
|
||||
crate::ffi_tests::add_test!("test_abbreviations", || {
|
||||
{
|
||||
let mut abbrs = abbrs_get_set();
|
||||
abbrs.add(Abbreviation::new(
|
||||
L!("gc").to_owned(),
|
||||
L!("gc").to_owned(),
|
||||
L!("git checkout").to_owned(),
|
||||
abbrs::Position::Command,
|
||||
false,
|
||||
));
|
||||
abbrs.add(Abbreviation::new(
|
||||
L!("foo").to_owned(),
|
||||
L!("foo").to_owned(),
|
||||
L!("bar").to_owned(),
|
||||
abbrs::Position::Command,
|
||||
false,
|
||||
));
|
||||
abbrs.add(Abbreviation::new(
|
||||
L!("gx").to_owned(),
|
||||
L!("gx").to_owned(),
|
||||
L!("git checkout").to_owned(),
|
||||
abbrs::Position::Command,
|
||||
false,
|
||||
));
|
||||
abbrs.add(Abbreviation::new(
|
||||
L!("yin").to_owned(),
|
||||
L!("yin").to_owned(),
|
||||
L!("yang").to_owned(),
|
||||
abbrs::Position::Anywhere,
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
||||
// Helper to expand an abbreviation, enforcing we have no more than one result.
|
||||
macro_rules! abbr_expand_1 {
|
||||
($token:expr, $position:expr) => {
|
||||
let result = abbrs_match(L!($token), $position);
|
||||
assert_eq!(result, vec![]);
|
||||
};
|
||||
($token:expr, $position:expr, $expected:expr) => {
|
||||
let result = abbrs_match(L!($token), $position);
|
||||
assert_eq!(
|
||||
result
|
||||
.into_iter()
|
||||
.map(|a| a.replacement)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![L!($expected).to_owned()]
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
let cmd = abbrs::Position::Command;
|
||||
abbr_expand_1!("", cmd);
|
||||
abbr_expand_1!("nothing", cmd);
|
||||
|
||||
abbr_expand_1!("gc", cmd, "git checkout");
|
||||
abbr_expand_1!("foo", cmd, "bar");
|
||||
|
||||
fn expand_abbreviation_in_command(
|
||||
cmdline: &wstr,
|
||||
cursor_pos: Option<usize>,
|
||||
) -> Option<WString> {
|
||||
let replacement = reader_expand_abbreviation_at_cursor(
|
||||
cmdline,
|
||||
cursor_pos.unwrap_or(cmdline.len()),
|
||||
Parser::principal_parser(),
|
||||
)?;
|
||||
let mut cmdline_expanded = cmdline.to_owned();
|
||||
let mut colors = vec![HighlightSpec::new(); cmdline.len()];
|
||||
apply_edit(
|
||||
&mut cmdline_expanded,
|
||||
&mut colors,
|
||||
&Edit::new(replacement.range.into(), replacement.text),
|
||||
);
|
||||
Some(cmdline_expanded)
|
||||
}
|
||||
|
||||
macro_rules! validate {
|
||||
($cmdline:expr, $cursor:expr) => {{
|
||||
let actual = expand_abbreviation_in_command(L!($cmdline), $cursor);
|
||||
assert_eq!(actual, None);
|
||||
}};
|
||||
($cmdline:expr, $cursor:expr, $expected:expr) => {{
|
||||
let actual = expand_abbreviation_in_command(L!($cmdline), $cursor);
|
||||
assert_eq!(actual, Some(L!($expected).to_owned()));
|
||||
}};
|
||||
}
|
||||
|
||||
validate!("just a command", Some(3));
|
||||
validate!("gc somebranch", Some(0), "git checkout somebranch");
|
||||
|
||||
validate!(
|
||||
"gc somebranch",
|
||||
Some("gc".chars().count()),
|
||||
"git checkout somebranch"
|
||||
);
|
||||
|
||||
// Space separation.
|
||||
validate!(
|
||||
"gx somebranch",
|
||||
Some("gc".chars().count()),
|
||||
"git checkout somebranch"
|
||||
);
|
||||
|
||||
validate!(
|
||||
"echo hi ; gc somebranch",
|
||||
Some("echo hi ; g".chars().count()),
|
||||
"echo hi ; git checkout somebranch"
|
||||
);
|
||||
|
||||
validate!(
|
||||
"echo (echo (echo (echo (gc ",
|
||||
Some("echo (echo (echo (echo (gc".chars().count()),
|
||||
"echo (echo (echo (echo (git checkout "
|
||||
);
|
||||
|
||||
// If commands should be expanded.
|
||||
validate!("if gc", None, "if git checkout");
|
||||
|
||||
// Others should not be.
|
||||
validate!("of gc", None);
|
||||
|
||||
// Others should not be.
|
||||
validate!("command gc", None);
|
||||
|
||||
// yin/yang expands everywhere.
|
||||
validate!("command yin", None, "command yang");
|
||||
});
|
|
@ -4,8 +4,11 @@ use std::sync::{
|
|||
};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::common::ScopeGuard;
|
||||
use crate::ffi_tests::add_test;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::parser::Parser;
|
||||
use crate::reader::{reader_current_data, reader_pop, reader_push, ReaderConfig, ReaderData};
|
||||
use crate::threads::{iothread_drain_all, iothread_service_main, Debounce};
|
||||
use crate::wchar::prelude::*;
|
||||
|
||||
|
@ -43,7 +46,7 @@ add_test!("test_debounce", || {
|
|||
};
|
||||
let completer = {
|
||||
let ctx = ctx.clone();
|
||||
move |idx: usize| {
|
||||
move |_ctx: &mut ReaderData, idx: usize| {
|
||||
ctx.completion_ran[idx].store(true);
|
||||
}
|
||||
};
|
||||
|
@ -55,10 +58,13 @@ add_test!("test_debounce", || {
|
|||
ctx.cv.notify_all();
|
||||
|
||||
// Wait until the last completion is done.
|
||||
reader_push(Parser::principal_parser(), L!(""), ReaderConfig::default());
|
||||
let _pop = ScopeGuard::new((), |()| reader_pop());
|
||||
let reader_data = reader_current_data().unwrap();
|
||||
while !ctx.completion_ran.last().unwrap().load() {
|
||||
iothread_service_main();
|
||||
iothread_service_main(reader_data);
|
||||
}
|
||||
unsafe { iothread_drain_all() };
|
||||
unsafe { iothread_drain_all(reader_data) };
|
||||
|
||||
// Each perform() call may displace an existing queued operation.
|
||||
// Each operation waits until all are queued.
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use crate::common::wcs2osstring;
|
||||
use crate::common::ScopeGuard;
|
||||
use crate::env::{EnvVar, EnvVarFlags, VarTable};
|
||||
use crate::env_universal_common::{CallbackDataList, EnvUniversal, UvarFormat};
|
||||
use crate::ffi_tests::add_test;
|
||||
use crate::flog::FLOG;
|
||||
use crate::parser::Parser;
|
||||
use crate::reader::{reader_current_data, reader_pop, reader_push, ReaderConfig};
|
||||
use crate::threads::{iothread_drain_all, iothread_perform};
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wutil::file_id_for_path;
|
||||
|
@ -37,11 +40,14 @@ add_test!("test_universal", || {
|
|||
let _ = std::fs::remove_dir_all("test/fish_uvars_test/");
|
||||
std::fs::create_dir_all("test/fish_uvars_test/").unwrap();
|
||||
|
||||
reader_push(Parser::principal_parser(), L!(""), ReaderConfig::default());
|
||||
let _pop = ScopeGuard::new((), |()| reader_pop());
|
||||
|
||||
let threads = 1;
|
||||
for i in 0..threads {
|
||||
iothread_perform(move || test_universal_helper(i));
|
||||
}
|
||||
unsafe { iothread_drain_all() };
|
||||
unsafe { iothread_drain_all(reader_current_data().unwrap()) };
|
||||
|
||||
let mut uvars = EnvUniversal::new();
|
||||
let mut callbacks = CallbackDataList::new();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::common::{
|
||||
cstr2wcstring, is_windows_subsystem_for_linux, str2wcstring, wcs2osstring, wcs2string,
|
||||
};
|
||||
use crate::env::EnvDyn;
|
||||
use crate::env::{EnvDyn, Environment};
|
||||
use crate::fds::{wopen_cloexec, AutoCloseFd};
|
||||
use crate::ffi_tests::add_test;
|
||||
use crate::history::{self, History, HistoryItem, HistorySearch, PathList, SearchDirection};
|
||||
|
@ -456,7 +456,7 @@ add_test!("test_history_path_detection", || {
|
|||
let mut test_vars = TestEnvironment::default();
|
||||
test_vars.vars.insert(L!("PWD").to_owned(), tmpdir.clone());
|
||||
test_vars.vars.insert(L!("HOME").to_owned(), tmpdir.clone());
|
||||
let vars = || EnvDyn::new(Box::new(test_vars.clone()));
|
||||
let vars = || EnvDyn::new(Box::new(test_vars.clone()) as Box<dyn Environment + Send + Sync>);
|
||||
|
||||
let history = History::with_name(L!("path_detection"));
|
||||
history.clear();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::wchar::prelude::*;
|
||||
|
||||
mod abbrs;
|
||||
#[cfg(test)]
|
||||
mod common;
|
||||
mod complete;
|
||||
|
@ -18,6 +19,8 @@ mod pager;
|
|||
mod parse_util;
|
||||
mod parser;
|
||||
#[cfg(test)]
|
||||
mod reader;
|
||||
#[cfg(test)]
|
||||
mod redirection;
|
||||
mod screen;
|
||||
mod string_escape;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::ast::{self, Ast, List, Node, Traversal};
|
||||
use crate::builtins::shared::{STATUS_CMD_OK, STATUS_UNMATCHED_WILDCARD};
|
||||
use crate::common::ScopeGuard;
|
||||
use crate::expand::ExpandFlags;
|
||||
use crate::io::{IoBufferfill, IoChain};
|
||||
use crate::parse_constants::{
|
||||
|
@ -7,7 +8,9 @@ use crate::parse_constants::{
|
|||
};
|
||||
use crate::parse_util::{parse_util_detect_errors, parse_util_detect_errors_in_argument};
|
||||
use crate::parser::Parser;
|
||||
use crate::reader::reader_reset_interrupted;
|
||||
use crate::reader::{
|
||||
reader_current_data, reader_pop, reader_push, reader_reset_interrupted, ReaderConfig,
|
||||
};
|
||||
use crate::signal::{signal_clear_cancel, signal_reset_handlers, signal_set_handlers};
|
||||
use crate::tests::prelude::*;
|
||||
use crate::threads::{iothread_drain_all, iothread_perform};
|
||||
|
@ -681,11 +684,14 @@ fn test_1_cancellation(src: &wstr) {
|
|||
);
|
||||
assert!(res.status.signal_exited() && res.status.signal_code() == SIGINT);
|
||||
unsafe {
|
||||
iothread_drain_all();
|
||||
iothread_drain_all(reader_current_data().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
add_test!("test_cancellation", || {
|
||||
reader_push(Parser::principal_parser(), L!(""), ReaderConfig::default());
|
||||
let _pop = ScopeGuard::new((), |()| reader_pop());
|
||||
|
||||
println!("Testing Ctrl-C cancellation. If this hangs, that's a bug!");
|
||||
|
||||
// Enable fish's signal handling here.
|
||||
|
|
167
fish-rust/src/tests/reader.rs
Normal file
167
fish-rust/src/tests/reader.rs
Normal file
|
@ -0,0 +1,167 @@
|
|||
use crate::complete::CompleteFlags;
|
||||
use crate::reader::{combine_command_and_autosuggestion, completion_apply_to_command_line};
|
||||
use crate::wchar::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_autosuggestion_combining() {
|
||||
assert_eq!(
|
||||
combine_command_and_autosuggestion(L!("alpha"), L!("alphabeta")),
|
||||
L!("alphabeta")
|
||||
);
|
||||
|
||||
// When the last token contains no capital letters, we use the case of the autosuggestion.
|
||||
assert_eq!(
|
||||
combine_command_and_autosuggestion(L!("alpha"), L!("ALPHABETA")),
|
||||
L!("ALPHABETA")
|
||||
);
|
||||
|
||||
// When the last token contains capital letters, we use its case.
|
||||
assert_eq!(
|
||||
combine_command_and_autosuggestion(L!("alPha"), L!("alphabeTa")),
|
||||
L!("alPhabeTa")
|
||||
);
|
||||
|
||||
// If autosuggestion is not longer than input, use the input's case.
|
||||
assert_eq!(
|
||||
combine_command_and_autosuggestion(L!("alpha"), L!("ALPHAA")),
|
||||
L!("ALPHAA")
|
||||
);
|
||||
assert_eq!(
|
||||
combine_command_and_autosuggestion(L!("alpha"), L!("ALPHA")),
|
||||
L!("alpha")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_insertions() {
|
||||
macro_rules! validate {
|
||||
(
|
||||
$line:expr, $completion:expr,
|
||||
$flags:expr, $append_only:expr,
|
||||
$expected:expr
|
||||
) => {
|
||||
// line is given with a caret, which we use to represent the cursor position. Find it.
|
||||
let mut line = L!($line).to_owned();
|
||||
let completion = L!($completion);
|
||||
let mut expected = L!($expected).to_owned();
|
||||
let in_cursor_pos = line.find(L!("^")).unwrap();
|
||||
line.remove(in_cursor_pos);
|
||||
|
||||
let out_cursor_pos = expected.find(L!("^")).unwrap();
|
||||
expected.remove(out_cursor_pos);
|
||||
|
||||
let mut cursor_pos = in_cursor_pos;
|
||||
let result = completion_apply_to_command_line(
|
||||
completion,
|
||||
$flags,
|
||||
&line,
|
||||
&mut cursor_pos,
|
||||
$append_only,
|
||||
);
|
||||
assert_eq!(result, expected);
|
||||
assert_eq!(cursor_pos, out_cursor_pos);
|
||||
};
|
||||
}
|
||||
|
||||
validate!("foo^", "bar", CompleteFlags::default(), false, "foobar ^");
|
||||
// An unambiguous completion of a token that is already trailed by a space character.
|
||||
// After completing, the cursor moves on to the next token, suggesting to the user that the
|
||||
// current token is finished.
|
||||
validate!(
|
||||
"foo^ baz",
|
||||
"bar",
|
||||
CompleteFlags::default(),
|
||||
false,
|
||||
"foobar ^baz"
|
||||
);
|
||||
validate!(
|
||||
"'foo^",
|
||||
"bar",
|
||||
CompleteFlags::default(),
|
||||
false,
|
||||
"'foobar' ^"
|
||||
);
|
||||
validate!(
|
||||
"'foo'^",
|
||||
"bar",
|
||||
CompleteFlags::default(),
|
||||
false,
|
||||
"'foobar' ^"
|
||||
);
|
||||
validate!(
|
||||
"'foo\\'^",
|
||||
"bar",
|
||||
CompleteFlags::default(),
|
||||
false,
|
||||
"'foo\\'bar' ^"
|
||||
);
|
||||
validate!(
|
||||
"foo\\'^",
|
||||
"bar",
|
||||
CompleteFlags::default(),
|
||||
false,
|
||||
"foo\\'bar ^"
|
||||
);
|
||||
|
||||
// Test append only.
|
||||
validate!("foo^", "bar", CompleteFlags::default(), true, "foobar ^");
|
||||
validate!(
|
||||
"foo^ baz",
|
||||
"bar",
|
||||
CompleteFlags::default(),
|
||||
true,
|
||||
"foobar ^baz"
|
||||
);
|
||||
validate!("'foo^", "bar", CompleteFlags::default(), true, "'foobar' ^");
|
||||
validate!(
|
||||
"'foo'^",
|
||||
"bar",
|
||||
CompleteFlags::default(),
|
||||
true,
|
||||
"'foo'bar ^"
|
||||
);
|
||||
validate!(
|
||||
"'foo\\'^",
|
||||
"bar",
|
||||
CompleteFlags::default(),
|
||||
true,
|
||||
"'foo\\'bar' ^"
|
||||
);
|
||||
validate!(
|
||||
"foo\\'^",
|
||||
"bar",
|
||||
CompleteFlags::default(),
|
||||
true,
|
||||
"foo\\'bar ^"
|
||||
);
|
||||
|
||||
validate!("foo^", "bar", CompleteFlags::NO_SPACE, false, "foobar^");
|
||||
validate!("'foo^", "bar", CompleteFlags::NO_SPACE, false, "'foobar^");
|
||||
validate!("'foo'^", "bar", CompleteFlags::NO_SPACE, false, "'foobar'^");
|
||||
validate!(
|
||||
"'foo\\'^",
|
||||
"bar",
|
||||
CompleteFlags::NO_SPACE,
|
||||
false,
|
||||
"'foo\\'bar^"
|
||||
);
|
||||
validate!(
|
||||
"foo\\'^",
|
||||
"bar",
|
||||
CompleteFlags::NO_SPACE,
|
||||
false,
|
||||
"foo\\'bar^"
|
||||
);
|
||||
|
||||
validate!("foo^", "bar", CompleteFlags::REPLACES_TOKEN, false, "bar ^");
|
||||
validate!(
|
||||
"'foo^",
|
||||
"bar",
|
||||
CompleteFlags::REPLACES_TOKEN,
|
||||
false,
|
||||
"bar ^"
|
||||
);
|
||||
|
||||
// See #6130
|
||||
validate!(": (:^ ''", "", CompleteFlags::default(), false, ": (: ^''");
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
//! ported directly from the cpp code so we can use rust threads instead of using pthreads.
|
||||
|
||||
use crate::flog::{FloggableDebug, FLOG};
|
||||
use crate::reader::ReaderData;
|
||||
use once_cell::race::OnceBox;
|
||||
use std::num::NonZeroU64;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
@ -73,47 +74,24 @@ mod ffi {
|
|||
|
||||
extern "Rust" {
|
||||
fn iothread_port() -> i32;
|
||||
fn iothread_service_main();
|
||||
#[cxx_name = "iothread_service_main_with_timeout"]
|
||||
fn iothread_service_main_with_timeout_ffi(timeout_usec: u64);
|
||||
#[cxx_name = "iothread_drain_all"]
|
||||
fn iothread_drain_all_ffi();
|
||||
#[cxx_name = "iothread_service_main"]
|
||||
fn iothread_service_main_ffi(ctx: *mut u8);
|
||||
#[cxx_name = "iothread_perform"]
|
||||
fn iothread_perform_ffi(callback: &SharedPtr<CppCallback>);
|
||||
#[cxx_name = "iothread_perform_cantwait"]
|
||||
fn iothread_perform_cant_wait_ffi(callback: &SharedPtr<CppCallback>);
|
||||
}
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
#[cxx_name = "debounce_t"]
|
||||
type Debounce;
|
||||
|
||||
#[cxx_name = "perform"]
|
||||
fn perform_ffi(&self, callback: &SharedPtr<CppCallback>) -> u64;
|
||||
#[cxx_name = "perform_with_completion"]
|
||||
fn perform_with_completion_ffi(
|
||||
&self,
|
||||
callback: &SharedPtr<CppCallback>,
|
||||
completion: &SharedPtr<CppCallback>,
|
||||
) -> u64;
|
||||
|
||||
#[cxx_name = "new_debounce_t"]
|
||||
fn new_debounce_ffi(timeout_ms: u64) -> Box<Debounce>;
|
||||
}
|
||||
fn iothread_service_main_ffi(ctx: *mut u8) {
|
||||
let ctx = unsafe { &mut *(ctx as *mut ReaderData) };
|
||||
iothread_service_main(ctx);
|
||||
}
|
||||
|
||||
pub use ffi::CppCallback;
|
||||
unsafe impl Send for ffi::CppCallback {}
|
||||
unsafe impl Sync for ffi::CppCallback {}
|
||||
|
||||
fn iothread_service_main_with_timeout_ffi(timeout_usec: u64) {
|
||||
iothread_service_main_with_timeout(Duration::from_micros(timeout_usec))
|
||||
}
|
||||
|
||||
fn iothread_drain_all_ffi() {
|
||||
unsafe { iothread_drain_all() }
|
||||
}
|
||||
|
||||
fn iothread_perform_ffi(callback: &cxx::SharedPtr<ffi::CppCallback>) {
|
||||
let callback = callback.clone();
|
||||
|
||||
|
@ -130,7 +108,7 @@ fn iothread_perform_cant_wait_ffi(callback: &cxx::SharedPtr<ffi::CppCallback>) {
|
|||
});
|
||||
}
|
||||
|
||||
/// A [`ThreadPool`] or [`Debounce`] work request.
|
||||
/// A [`ThreadPool`] work request.
|
||||
type WorkItem = Box<dyn FnOnce() + 'static + Send>;
|
||||
|
||||
// A helper type to allow us to (temporarily) send an object to another thread.
|
||||
|
@ -140,7 +118,7 @@ struct ForceSend<T>(T);
|
|||
unsafe impl<T> Send for ForceSend<T> {}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
type DebounceCallback = ForceSend<Box<dyn FnOnce() + 'static>>;
|
||||
type DebounceCallback = ForceSend<Box<dyn FnOnce(&mut ReaderData) + 'static>>;
|
||||
|
||||
/// The queue of [`WorkItem`]s to be executed on the main thread. This is read from in
|
||||
/// `iothread_service_main()`.
|
||||
|
@ -561,13 +539,13 @@ pub fn iothread_port() -> i32 {
|
|||
NOTIFY_SIGNALLER.read_fd()
|
||||
}
|
||||
|
||||
pub fn iothread_service_main_with_timeout(timeout: Duration) {
|
||||
pub fn iothread_service_main_with_timeout(ctx: &mut ReaderData, timeout: Duration) {
|
||||
if crate::fd_readable_set::is_fd_readable(iothread_port(), timeout.as_millis() as u64) {
|
||||
iothread_service_main();
|
||||
iothread_service_main(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iothread_service_main() {
|
||||
pub fn iothread_service_main(ctx: &mut ReaderData) {
|
||||
self::assert_is_main_thread();
|
||||
|
||||
// Note: the order here is important. We must consume events before handling requests, as
|
||||
|
@ -578,12 +556,12 @@ pub fn iothread_service_main() {
|
|||
|
||||
// Perform each completion in order.
|
||||
for callback in queue {
|
||||
(callback.0)();
|
||||
(callback.0)(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Does nasty polling via select() and marked as unsafe because it should only be used for testing.
|
||||
pub unsafe fn iothread_drain_all() {
|
||||
pub unsafe fn iothread_drain_all(ctx: &mut ReaderData) {
|
||||
while borrow_io_thread_pool()
|
||||
.shared
|
||||
.mutex
|
||||
|
@ -592,7 +570,7 @@ pub unsafe fn iothread_drain_all() {
|
|||
.total_threads
|
||||
> 0
|
||||
{
|
||||
iothread_service_main_with_timeout(Duration::from_millis(1000));
|
||||
iothread_service_main_with_timeout(ctx, Duration::from_millis(1000));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -626,10 +604,6 @@ struct DebounceData {
|
|||
start_time: Instant,
|
||||
}
|
||||
|
||||
fn new_debounce_ffi(timeout_ms: u64) -> Box<Debounce> {
|
||||
Box::new(Debounce::new(Duration::from_millis(timeout_ms)))
|
||||
}
|
||||
|
||||
impl Debounce {
|
||||
pub fn new(timeout: Duration) -> Self {
|
||||
Self {
|
||||
|
@ -673,33 +647,7 @@ impl Debounce {
|
|||
///
|
||||
/// The result is a token which is only of interest to the test suite.
|
||||
pub fn perform(&self, handler: impl FnOnce() + 'static + Send) -> NonZeroU64 {
|
||||
self.perform_with_completion(handler, |_result| ())
|
||||
}
|
||||
|
||||
fn perform_ffi(&self, callback: &cxx::SharedPtr<ffi::CppCallback>) -> u64 {
|
||||
let callback = callback.clone();
|
||||
|
||||
self.perform(move || {
|
||||
callback.invoke();
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn perform_with_completion_ffi(
|
||||
&self,
|
||||
callback: &cxx::SharedPtr<ffi::CppCallback>,
|
||||
completion: &cxx::SharedPtr<ffi::CppCallback>,
|
||||
) -> u64 {
|
||||
let callback = callback.clone();
|
||||
let completion = completion.clone();
|
||||
|
||||
self.perform_with_completion(
|
||||
move || -> crate::ffi::void_ptr { callback.invoke().into() },
|
||||
move |result| {
|
||||
completion.invoke_with_param(result.into());
|
||||
},
|
||||
)
|
||||
.into()
|
||||
self.perform_with_completion(handler, |_ctx, _result| ())
|
||||
}
|
||||
|
||||
/// Enqueue `handler` to be performed on a background thread with [`Completion`] `completion`
|
||||
|
@ -713,16 +661,16 @@ impl Debounce {
|
|||
pub fn perform_with_completion<H, R, C>(&self, handler: H, completion: C) -> NonZeroU64
|
||||
where
|
||||
H: FnOnce() -> R + 'static + Send,
|
||||
C: FnOnce(R) + 'static,
|
||||
C: FnOnce(&mut ReaderData, R) + 'static,
|
||||
R: 'static + Send,
|
||||
{
|
||||
assert_is_main_thread();
|
||||
let completion_wrapper = ForceSend(completion);
|
||||
let work_item = Box::new(move || {
|
||||
let result = handler();
|
||||
let callback: DebounceCallback = ForceSend(Box::new(move || {
|
||||
let callback: DebounceCallback = ForceSend(Box::new(move |ctx| {
|
||||
let completion = completion_wrapper;
|
||||
(completion.0)(result);
|
||||
(completion.0)(ctx, result);
|
||||
}));
|
||||
MAIN_THREAD_QUEUE.lock().unwrap().push(callback);
|
||||
NOTIFY_SIGNALLER.post();
|
||||
|
|
|
@ -300,6 +300,9 @@ impl Tok {
|
|||
pub fn set_length(&mut self, value: usize) {
|
||||
self.length = value.try_into().unwrap();
|
||||
}
|
||||
pub fn end(&self) -> usize {
|
||||
self.offset() + self.length()
|
||||
}
|
||||
pub fn set_error_offset_within_token(&mut self, value: usize) {
|
||||
self.error_offset_within_token = value.try_into().unwrap();
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ macro_rules! impl_to_wstring_unsigned {
|
|||
};
|
||||
}
|
||||
|
||||
impl_to_wstring_unsigned!(u8, u16, u32, u64, usize);
|
||||
impl_to_wstring_unsigned!(u8, u16, u32, u64, u128, usize);
|
||||
|
||||
#[test]
|
||||
fn test_to_wstring() {
|
||||
|
|
|
@ -61,7 +61,10 @@ pub fn subsequence_in_string(needle: &wstr, haystack: &wstr) -> bool {
|
|||
/// expanded to include symbolic characters (#3584).
|
||||
/// \return the offset of the first case-insensitive matching instance of `needle` within
|
||||
/// `haystack`, or `string::npos()` if no results were found.
|
||||
pub fn ifind(haystack: &wstr, needle: &wstr, fuzzy: bool) -> Option<usize> {
|
||||
pub fn ifind(haystack: &wstr, needle: &wstr, fuzzy: bool /* = false */) -> Option<usize> {
|
||||
if needle.is_empty() {
|
||||
return Some(0);
|
||||
}
|
||||
haystack
|
||||
.as_char_slice()
|
||||
.windows(needle.len())
|
||||
|
|
|
@ -1,523 +0,0 @@
|
|||
// Functions used for implementing the commandline builtin.
|
||||
#include "config.h" // IWYU pragma: keep
|
||||
|
||||
#include "commandline.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cwchar>
|
||||
#include <string>
|
||||
|
||||
#include "../builtin.h"
|
||||
#include "../common.h"
|
||||
#include "../fallback.h" // IWYU pragma: keep
|
||||
#include "../io.h"
|
||||
#include "../maybe.h"
|
||||
#include "../parse_constants.h"
|
||||
#include "../parse_util.h"
|
||||
#include "../parser.h"
|
||||
#include "../proc.h"
|
||||
#include "../reader.h"
|
||||
#include "../tokenizer.h"
|
||||
#include "../wgetopt.h"
|
||||
#include "../wutil.h" // IWYU pragma: keep
|
||||
#include "builtins/shared.rs.h"
|
||||
#include "input_ffi.rs.h"
|
||||
|
||||
/// Which part of the comandbuffer are we operating on.
|
||||
enum {
|
||||
STRING_MODE = 1, // operate on entire buffer
|
||||
JOB_MODE, // operate on job under cursor
|
||||
PROCESS_MODE, // operate on process under cursor
|
||||
TOKEN_MODE // operate on token under cursor
|
||||
};
|
||||
|
||||
/// For text insertion, how should it be done.
|
||||
enum {
|
||||
REPLACE_MODE = 1, // replace current text
|
||||
INSERT_MODE, // insert at cursor position
|
||||
APPEND_MODE // insert at end of current token/command/buffer
|
||||
};
|
||||
|
||||
/// Handle a single readline_cmd_t command out-of-band.
|
||||
void reader_handle_command(readline_cmd_t cmd);
|
||||
|
||||
/// Replace/append/insert the selection with/at/after the specified string.
|
||||
///
|
||||
/// \param begin beginning of selection
|
||||
/// \param end end of selection
|
||||
/// \param insert the string to insert
|
||||
/// \param append_mode can be one of REPLACE_MODE, INSERT_MODE or APPEND_MODE, affects the way the
|
||||
/// test update is performed
|
||||
/// \param buff the original command line buffer
|
||||
/// \param cursor_pos the position of the cursor in the command line
|
||||
static void replace_part(const wchar_t *begin, const wchar_t *end, const wchar_t *insert,
|
||||
int append_mode, const wchar_t *buff, size_t cursor_pos) {
|
||||
size_t out_pos = cursor_pos;
|
||||
|
||||
wcstring out;
|
||||
|
||||
out.append(buff, begin - buff);
|
||||
|
||||
switch (append_mode) {
|
||||
case REPLACE_MODE: {
|
||||
out.append(insert);
|
||||
out_pos = out.size();
|
||||
break;
|
||||
}
|
||||
case APPEND_MODE: {
|
||||
out.append(begin, end - begin);
|
||||
out.append(insert);
|
||||
break;
|
||||
}
|
||||
case INSERT_MODE: {
|
||||
long cursor = cursor_pos - (begin - buff);
|
||||
out.append(begin, cursor);
|
||||
out.append(insert);
|
||||
out.append(begin + cursor, end - begin - cursor);
|
||||
out_pos += std::wcslen(insert);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
DIE("unexpected append_mode");
|
||||
}
|
||||
}
|
||||
out.append(end);
|
||||
commandline_set_buffer(out, out_pos);
|
||||
}
|
||||
|
||||
/// Output the specified selection.
|
||||
///
|
||||
/// \param begin start of selection
|
||||
/// \param end end of selection
|
||||
/// \param cut_at_cursor whether printing should stop at the surrent cursor position
|
||||
/// \param tokenize whether the string should be tokenized, printing one string token on every line
|
||||
/// and skipping non-string tokens
|
||||
/// \param buffer the original command line buffer
|
||||
/// \param cursor_pos the position of the cursor in the command line
|
||||
static void write_part(const wchar_t *begin, const wchar_t *end, int cut_at_cursor, int tokenize,
|
||||
const wchar_t *buffer, size_t cursor_pos, io_streams_t &streams) {
|
||||
size_t pos = cursor_pos - (begin - buffer);
|
||||
|
||||
if (tokenize) {
|
||||
// std::fwprintf( stderr, L"Subshell: %ls, end char %lc\n", buff, *end );
|
||||
wcstring out;
|
||||
wcstring buff(begin, end - begin);
|
||||
auto tok = new_tokenizer(buff.c_str(), TOK_ACCEPT_UNFINISHED);
|
||||
while (auto token = tok->next()) {
|
||||
if ((cut_at_cursor) && (token->offset + token->length >= pos)) break;
|
||||
|
||||
if (token->type_ == token_type_t::string) {
|
||||
wcstring tmp = *tok->text_of(*token);
|
||||
auto maybe_unescaped = unescape_string(tmp.c_str(), tmp.size(), UNESCAPE_INCOMPLETE,
|
||||
STRING_STYLE_SCRIPT);
|
||||
assert(maybe_unescaped);
|
||||
out.append(*maybe_unescaped);
|
||||
out.push_back(L'\n');
|
||||
}
|
||||
}
|
||||
|
||||
streams.out()->append(out);
|
||||
} else {
|
||||
if (cut_at_cursor) {
|
||||
streams.out()->append(wcstring{begin, pos});
|
||||
} else {
|
||||
streams.out()->append(wcstring{begin, end});
|
||||
}
|
||||
streams.out()->push(L'\n');
|
||||
}
|
||||
}
|
||||
|
||||
/// The commandline builtin. It is used for specifying a new value for the commandline.
|
||||
int builtin_commandline(const void *_parser, void *_streams, void *_argv) {
|
||||
const auto &parser = *static_cast<const parser_t *>(_parser);
|
||||
auto &streams = *static_cast<io_streams_t *>(_streams);
|
||||
auto argv = static_cast<const wchar_t **>(_argv);
|
||||
const commandline_state_t rstate = commandline_get_state();
|
||||
const wchar_t *cmd = argv[0];
|
||||
int buffer_part = 0;
|
||||
bool cut_at_cursor = false;
|
||||
|
||||
int argc = builtin_count_args(argv);
|
||||
int append_mode = 0;
|
||||
|
||||
bool function_mode = false;
|
||||
bool selection_mode = false;
|
||||
|
||||
bool tokenize = false;
|
||||
|
||||
bool cursor_mode = false;
|
||||
bool selection_start_mode = false;
|
||||
bool selection_end_mode = false;
|
||||
bool line_mode = false;
|
||||
bool search_mode = false;
|
||||
bool paging_mode = false;
|
||||
bool paging_full_mode = false;
|
||||
bool is_valid = false;
|
||||
const wchar_t *begin = nullptr, *end = nullptr;
|
||||
const wchar_t *override_buffer = nullptr;
|
||||
|
||||
const auto &ld = parser.libdata();
|
||||
|
||||
static const wchar_t *const short_options = L":abijpctforhI:CBELSsP";
|
||||
static const struct woption long_options[] = {{L"append", no_argument, 'a'},
|
||||
{L"insert", no_argument, 'i'},
|
||||
{L"replace", no_argument, 'r'},
|
||||
{L"current-buffer", no_argument, 'b'},
|
||||
{L"current-job", no_argument, 'j'},
|
||||
{L"current-process", no_argument, 'p'},
|
||||
{L"current-selection", no_argument, 's'},
|
||||
{L"current-token", no_argument, 't'},
|
||||
{L"cut-at-cursor", no_argument, 'c'},
|
||||
{L"function", no_argument, 'f'},
|
||||
{L"tokenize", no_argument, 'o'},
|
||||
{L"help", no_argument, 'h'},
|
||||
{L"input", required_argument, 'I'},
|
||||
{L"cursor", no_argument, 'C'},
|
||||
{L"selection-start", no_argument, 'B'},
|
||||
{L"selection-end", no_argument, 'E'},
|
||||
{L"line", no_argument, 'L'},
|
||||
{L"search-mode", no_argument, 'S'},
|
||||
{L"paging-mode", no_argument, 'P'},
|
||||
{L"paging-full-mode", no_argument, 'F'},
|
||||
{L"is-valid", no_argument, 1},
|
||||
{}};
|
||||
|
||||
int opt;
|
||||
wgetopter_t w;
|
||||
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) {
|
||||
switch (opt) {
|
||||
case L'a': {
|
||||
append_mode = APPEND_MODE;
|
||||
break;
|
||||
}
|
||||
case L'b': {
|
||||
buffer_part = STRING_MODE;
|
||||
break;
|
||||
}
|
||||
case L'i': {
|
||||
append_mode = INSERT_MODE;
|
||||
break;
|
||||
}
|
||||
case L'r': {
|
||||
append_mode = REPLACE_MODE;
|
||||
break;
|
||||
}
|
||||
case 'c': {
|
||||
cut_at_cursor = true;
|
||||
break;
|
||||
}
|
||||
case 't': {
|
||||
buffer_part = TOKEN_MODE;
|
||||
break;
|
||||
}
|
||||
case 'j': {
|
||||
buffer_part = JOB_MODE;
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
buffer_part = PROCESS_MODE;
|
||||
break;
|
||||
}
|
||||
case 'f': {
|
||||
function_mode = true;
|
||||
break;
|
||||
}
|
||||
case 'o': {
|
||||
tokenize = true;
|
||||
break;
|
||||
}
|
||||
case 'I': {
|
||||
// A historical, undocumented feature. TODO: consider removing this.
|
||||
override_buffer = w.woptarg;
|
||||
break;
|
||||
}
|
||||
case 'C': {
|
||||
cursor_mode = true;
|
||||
break;
|
||||
}
|
||||
case 'B': {
|
||||
selection_start_mode = true;
|
||||
break;
|
||||
}
|
||||
case 'E': {
|
||||
selection_end_mode = true;
|
||||
break;
|
||||
}
|
||||
case 'L': {
|
||||
line_mode = true;
|
||||
break;
|
||||
}
|
||||
case 'S': {
|
||||
search_mode = true;
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
selection_mode = true;
|
||||
break;
|
||||
}
|
||||
case 'P': {
|
||||
paging_mode = true;
|
||||
break;
|
||||
}
|
||||
case 'F': {
|
||||
paging_full_mode = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
is_valid = true;
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
builtin_print_help(parser, streams, cmd);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
case ':': {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1], true);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
case L'?': {
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1], true);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
default: {
|
||||
DIE("unexpected retval from wgetopt_long");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (function_mode) {
|
||||
int i;
|
||||
|
||||
// Check for invalid switch combinations.
|
||||
if (buffer_part || cut_at_cursor || append_mode || tokenize || cursor_mode || line_mode ||
|
||||
search_mode || paging_mode || selection_start_mode || selection_end_mode) {
|
||||
streams.err()->append(format_string(BUILTIN_ERR_COMBO, argv[0]));
|
||||
builtin_print_error_trailer(parser, *streams.err(), cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (argc == w.woptind) {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[0], true);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
using rl = readline_cmd_t;
|
||||
for (i = w.woptind; i < argc; i++) {
|
||||
int mci = input_function_get_code(argv[i]);
|
||||
if (mci >= 0) {
|
||||
readline_cmd_t mc = static_cast<readline_cmd_t>(mci);
|
||||
// Don't enqueue a repaint if we're currently in the middle of one,
|
||||
// because that's an infinite loop.
|
||||
if (mc == rl::RepaintMode || mc == rl::ForceRepaint || mc == rl::Repaint) {
|
||||
if (ld.is_repaint()) continue;
|
||||
}
|
||||
|
||||
// HACK: Execute these right here and now so they can affect any insertions/changes
|
||||
// made via bindings. The correct solution is to change all `commandline`
|
||||
// insert/replace operations into readline functions with associated data, so that
|
||||
// all queued `commandline` operations - including buffer modifications - are
|
||||
// executed in order
|
||||
if (mc == rl::BeginUndoGroup || mc == rl::EndUndoGroup) {
|
||||
reader_handle_command(mc);
|
||||
} else {
|
||||
// Inserts the readline function at the back of the queue.
|
||||
reader_queue_ch(char_event_from_readline(mc));
|
||||
}
|
||||
} else {
|
||||
streams.err()->append(
|
||||
format_string(_(L"%ls: Unknown input function '%ls'"), cmd, argv[i]));
|
||||
builtin_print_error_trailer(parser, *streams.err(), cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
}
|
||||
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
if (selection_mode) {
|
||||
if (rstate.selection) {
|
||||
streams.out()->append(
|
||||
{rstate.text.c_str() + rstate.selection->start, rstate.selection->length});
|
||||
}
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
// Check for invalid switch combinations.
|
||||
if ((selection_start_mode || selection_end_mode) && (argc - w.woptind)) {
|
||||
streams.err()->append(format_string(BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]));
|
||||
builtin_print_error_trailer(parser, *streams.err(), cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if ((search_mode || line_mode || cursor_mode || paging_mode) && (argc - w.woptind > 1)) {
|
||||
streams.err()->append(format_string(BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]));
|
||||
builtin_print_error_trailer(parser, *streams.err(), cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if ((buffer_part || tokenize || cut_at_cursor) &&
|
||||
(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 && cursor_mode)) {
|
||||
streams.err()->append(format_string(BUILTIN_ERR_COMBO, argv[0]));
|
||||
builtin_print_error_trailer(parser, *streams.err(), cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if ((tokenize || cut_at_cursor) && (argc - w.woptind)) {
|
||||
streams.err()->append(format_string(
|
||||
BUILTIN_ERR_COMBO2, cmd,
|
||||
L"--cut-at-cursor and --tokenize can not be used when setting the commandline"));
|
||||
builtin_print_error_trailer(parser, *streams.err(), cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (append_mode && !(argc - w.woptind)) {
|
||||
// No tokens in insert mode just means we do nothing.
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
|
||||
// Set default modes.
|
||||
if (!append_mode) {
|
||||
append_mode = REPLACE_MODE;
|
||||
}
|
||||
|
||||
if (!buffer_part) {
|
||||
buffer_part = STRING_MODE;
|
||||
}
|
||||
|
||||
if (line_mode) {
|
||||
streams.out()->append(
|
||||
format_string(L"%d\n", parse_util_lineno(rstate.text, rstate.cursor_pos)));
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
if (search_mode) {
|
||||
return commandline_get_state().search_mode ? 0 : 1;
|
||||
}
|
||||
|
||||
if (paging_mode) {
|
||||
return commandline_get_state().pager_mode ? 0 : 1;
|
||||
}
|
||||
|
||||
if (paging_full_mode) {
|
||||
auto state = commandline_get_state();
|
||||
return (state.pager_mode && state.pager_fully_disclosed) ? 0 : 1;
|
||||
}
|
||||
|
||||
if (selection_start_mode) {
|
||||
if (!rstate.selection) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
source_offset_t start = rstate.selection->start;
|
||||
streams.out()->append(format_string(L"%lu\n", static_cast<unsigned long>(start)));
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
if (selection_end_mode) {
|
||||
if (!rstate.selection) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
source_offset_t end = rstate.selection->end();
|
||||
streams.out()->append(format_string(L"%lu\n", static_cast<unsigned long>(end)));
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
// At this point we have (nearly) exhausted the options which always operate on the true command
|
||||
// line. Now we respect the possibility of a transient command line due to evaluating a wrapped
|
||||
// completion. Don't do this in cursor_mode: it makes no sense to move the cursor based on a
|
||||
// transient commandline.
|
||||
const wchar_t *current_buffer = nullptr;
|
||||
size_t current_cursor_pos{0};
|
||||
wcstring transient;
|
||||
if (override_buffer) {
|
||||
current_buffer = override_buffer;
|
||||
current_cursor_pos = std::wcslen(current_buffer);
|
||||
} else if (!ld.transient_commandlines_empty() && !cursor_mode) {
|
||||
transient = *ld.transient_commandlines_back();
|
||||
current_buffer = transient.c_str();
|
||||
current_cursor_pos = transient.size();
|
||||
} else if (rstate.initialized) {
|
||||
current_buffer = rstate.text.c_str();
|
||||
current_cursor_pos = rstate.cursor_pos;
|
||||
} else {
|
||||
// There is no command line, either because we are not interactive, or because we are
|
||||
// interactive and are still reading init files (in which case we silently ignore this).
|
||||
if (!is_interactive_session()) {
|
||||
streams.err()->append(cmd);
|
||||
streams.err()->append(L": Can not set commandline in non-interactive mode\n");
|
||||
builtin_print_error_trailer(parser, *streams.err(), cmd);
|
||||
}
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
|
||||
if (is_valid) {
|
||||
if (!*current_buffer) return 1;
|
||||
parser_test_error_bits_t res = parse_util_detect_errors(
|
||||
current_buffer, nullptr, true /* accept incomplete so we can tell the difference */);
|
||||
if (res & PARSER_TEST_INCOMPLETE) {
|
||||
return 2;
|
||||
}
|
||||
return res & PARSER_TEST_ERROR ? STATUS_CMD_ERROR : STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
switch (buffer_part) {
|
||||
case STRING_MODE: {
|
||||
begin = current_buffer;
|
||||
end = begin + std::wcslen(begin);
|
||||
break;
|
||||
}
|
||||
case PROCESS_MODE: {
|
||||
parse_util_process_extent(current_buffer, current_cursor_pos, &begin, &end, nullptr);
|
||||
break;
|
||||
}
|
||||
case JOB_MODE: {
|
||||
parse_util_job_extent(current_buffer, current_cursor_pos, &begin, &end);
|
||||
break;
|
||||
}
|
||||
case TOKEN_MODE: {
|
||||
parse_util_token_extent(current_buffer, current_cursor_pos, &begin, &end, nullptr,
|
||||
nullptr);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
DIE("unexpected buffer_part");
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor_mode) {
|
||||
if (argc - w.woptind) {
|
||||
long new_pos = fish_wcstol(argv[w.woptind]) + (begin - current_buffer);
|
||||
if (errno) {
|
||||
streams.err()->append(format_string(BUILTIN_ERR_NOT_NUMBER, cmd, argv[w.woptind]));
|
||||
builtin_print_error_trailer(parser, *streams.err(), cmd);
|
||||
}
|
||||
|
||||
new_pos =
|
||||
std::max(0L, std::min(new_pos, static_cast<long>(std::wcslen(current_buffer))));
|
||||
commandline_set_buffer(current_buffer, static_cast<size_t>(new_pos));
|
||||
} else {
|
||||
size_t pos = current_cursor_pos - (begin - current_buffer);
|
||||
streams.out()->append(format_string(L"%lu\n", static_cast<unsigned long>(pos)));
|
||||
}
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
int arg_count = argc - w.woptind;
|
||||
if (arg_count == 0) {
|
||||
write_part(begin, end, cut_at_cursor, tokenize, current_buffer, current_cursor_pos,
|
||||
streams);
|
||||
} else if (arg_count == 1) {
|
||||
replace_part(begin, end, argv[w.woptind], append_mode, current_buffer, current_cursor_pos);
|
||||
} else {
|
||||
wcstring sb = argv[w.woptind];
|
||||
for (int i = w.woptind + 1; i < argc; i++) {
|
||||
sb.push_back(L'\n');
|
||||
sb.append(argv[i]);
|
||||
}
|
||||
replace_part(begin, end, sb.c_str(), append_mode, current_buffer, current_cursor_pos);
|
||||
}
|
||||
|
||||
return STATUS_CMD_OK;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// Prototypes for functions for executing builtin_commandline functions.
|
||||
#ifndef FISH_BUILTIN_COMMANDLINE_H
|
||||
#define FISH_BUILTIN_COMMANDLINE_H
|
||||
|
||||
#include "../maybe.h"
|
||||
|
||||
struct Parser;
|
||||
using parser_t = Parser;
|
||||
struct IoStreams;
|
||||
using io_streams_t = IoStreams;
|
||||
|
||||
int builtin_commandline(const void *parser, void *streams, void *argv);
|
||||
#endif
|
|
@ -757,22 +757,6 @@ void exit_without_destructors(int code) { _exit(code); }
|
|||
|
||||
extern "C" void debug_thread_error();
|
||||
|
||||
void save_term_foreground_process_group() { initial_fg_process_group = tcgetpgrp(STDIN_FILENO); }
|
||||
|
||||
void restore_term_foreground_process_group_for_exit() {
|
||||
// We wish to restore the tty to the initial owner. There's two ways this can go wrong:
|
||||
// 1. We may steal the tty from someone else (#7060).
|
||||
// 2. The call to tcsetpgrp may deliver SIGSTOP to us, and we will not exit.
|
||||
// Hanging on exit seems worse, so ensure that SIGTTOU is ignored so we do not get SIGSTOP.
|
||||
// Note initial_fg_process_group == 0 is possible with Linux pid namespaces.
|
||||
// This is called during shutdown and from a signal handler. We don't bother to complain on
|
||||
// failure because doing so is unlikely to be noticed.
|
||||
if (initial_fg_process_group > 0 && initial_fg_process_group != getpgrp()) {
|
||||
(void)signal(SIGTTOU, SIG_IGN);
|
||||
(void)tcsetpgrp(STDIN_FILENO, initial_fg_process_group);
|
||||
}
|
||||
}
|
||||
|
||||
/// Test if the specified character is in a range that fish uses internally to store special tokens.
|
||||
///
|
||||
/// NOTE: This is used when tokenizing the input. It is also used when reading input, before
|
||||
|
|
|
@ -485,10 +485,6 @@ wcstring escape_string(const wcstring &in, escape_flags_t flags = 0,
|
|||
using timepoint_t = double;
|
||||
timepoint_t timef();
|
||||
|
||||
/// Save the value of tcgetpgrp so we can restore it on exit.
|
||||
void save_term_foreground_process_group();
|
||||
void restore_term_foreground_process_group_for_exit();
|
||||
|
||||
/// Determines if we are running under Microsoft's Windows Subsystem for Linux to work around
|
||||
/// some known limitations and/or bugs.
|
||||
/// See https://github.com/Microsoft/WSL/issues/423 and Microsoft/WSL#2997
|
||||
|
|
27
src/env.cpp
27
src/env.cpp
|
@ -222,31 +222,4 @@ void unsetenv_lock(const char *name) {
|
|||
}
|
||||
}
|
||||
|
||||
wcstring_list_ffi_t get_history_variable_text_ffi(const wcstring &fish_history_val) {
|
||||
wcstring_list_ffi_t out{};
|
||||
maybe_t<rust::Box<HistorySharedPtr>> history = commandline_get_state().history;
|
||||
if (!history) {
|
||||
// Effective duplication of history_session_id().
|
||||
wcstring session_id{};
|
||||
if (fish_history_val.empty()) {
|
||||
// No session.
|
||||
session_id.clear();
|
||||
} else if (!valid_var_name(fish_history_val)) {
|
||||
session_id = L"fish";
|
||||
FLOGF(error,
|
||||
_(L"History session ID '%ls' is not a valid variable name. "
|
||||
L"Falling back to `%ls`."),
|
||||
fish_history_val.c_str(), session_id.c_str());
|
||||
} else {
|
||||
// Valid session.
|
||||
session_id = fish_history_val;
|
||||
}
|
||||
history = history_with_name(session_id);
|
||||
}
|
||||
if (history) {
|
||||
out = *(*history)->get_history();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const EnvStackRef &env_stack_t::get_impl_ffi() const { return *impl_; }
|
||||
|
|
|
@ -321,10 +321,6 @@ void setenv_lock(const char *name, const char *value, int overwrite);
|
|||
void unsetenv_lock(const char *name);
|
||||
}
|
||||
|
||||
/// Populate the values in the "$history" variable.
|
||||
/// fish_history_val is the value of the "$fish_history" variable, or "fish" if not set.
|
||||
wcstring_list_ffi_t get_history_variable_text_ffi(const wcstring &fish_history_val);
|
||||
|
||||
void set_inheriteds_ffi();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "builtin.h"
|
||||
#include "builtins/commandline.h"
|
||||
#include "event.h"
|
||||
#include "fds.h"
|
||||
#include "highlight.h"
|
||||
|
@ -16,20 +15,11 @@ void mark_as_used(const parser_t& parser, env_stack_t& env_stack) {
|
|||
event_fire_generic(parser, {});
|
||||
event_fire_generic(parser, {}, {});
|
||||
expand_tilde(s, env_stack);
|
||||
get_history_variable_text_ffi({});
|
||||
highlight_spec_t{};
|
||||
reader_change_cursor_selection_mode(cursor_selection_mode_t::exclusive);
|
||||
reader_change_history({});
|
||||
reader_read_ffi({}, {}, {});
|
||||
reader_schedule_prompt_repaint();
|
||||
reader_set_autosuggestion_enabled_ffi({});
|
||||
reader_status_count();
|
||||
restore_term_mode();
|
||||
rgb_color_t{};
|
||||
setenv_lock({}, {}, {});
|
||||
set_inheriteds_ffi();
|
||||
term_copy_modes();
|
||||
unsetenv_lock({});
|
||||
|
||||
builtin_commandline({}, {}, {});
|
||||
rgb_color_t::white();
|
||||
rgb_color_t{};
|
||||
}
|
||||
|
|
|
@ -230,14 +230,14 @@ static void process_input(bool continuous_mode, bool verbose) {
|
|||
std::vector<wchar_t> bind_chars;
|
||||
|
||||
std::fwprintf(stderr, L"Press a key:\n");
|
||||
while (!check_exit_loop_maybe_warning(nullptr)) {
|
||||
maybe_t<rust::Box<char_event_t>> evt{};
|
||||
while (!check_exit_loop_maybe_warning()) {
|
||||
maybe_t<rust::Box<CharEvent>> evt{};
|
||||
if (reader_test_and_clear_interrupted()) {
|
||||
evt = char_event_from_char(shell_modes.c_cc[VINTR]);
|
||||
} else {
|
||||
char_event_t *evt_raw = queue->readch_timed_esc();
|
||||
CharEvent *evt_raw = queue->readch_timed_esc();
|
||||
if (evt_raw) {
|
||||
evt = rust::Box<char_event_t>::from_raw(evt_raw);
|
||||
evt = rust::Box<CharEvent>::from_raw(evt_raw);
|
||||
}
|
||||
}
|
||||
if (!evt || !(*evt)->is_char()) {
|
||||
|
|
|
@ -614,114 +614,6 @@ static void test_lru() {
|
|||
do_test(cache.size() == 0);
|
||||
}
|
||||
|
||||
// todo!("port this")
|
||||
static void test_abbreviations() {
|
||||
say(L"Testing abbreviations");
|
||||
{
|
||||
auto abbrs = abbrs_get_set();
|
||||
abbrs->add(L"gc", L"gc", L"git checkout", abbrs_position_t::command, false);
|
||||
abbrs->add(L"foo", L"foo", L"bar", abbrs_position_t::command, false);
|
||||
abbrs->add(L"gx", L"gx", L"git checkout", abbrs_position_t::command, false);
|
||||
abbrs->add(L"yin", L"yin", L"yang", abbrs_position_t::anywhere, false);
|
||||
}
|
||||
|
||||
// Helper to expand an abbreviation, enforcing we have no more than one result.
|
||||
auto abbr_expand_1 = [](const wcstring &token, abbrs_position_t pos) -> maybe_t<wcstring> {
|
||||
auto result = abbrs_match(token, pos);
|
||||
if (result.size() > 1) {
|
||||
err(L"abbreviation expansion for %ls returned more than 1 result", token.c_str());
|
||||
}
|
||||
if (result.empty()) {
|
||||
return none();
|
||||
}
|
||||
return *result.front().replacement;
|
||||
};
|
||||
|
||||
auto cmd = abbrs_position_t::command;
|
||||
if (abbr_expand_1(L"", cmd)) err(L"Unexpected success with empty abbreviation");
|
||||
if (abbr_expand_1(L"nothing", cmd)) err(L"Unexpected success with missing abbreviation");
|
||||
|
||||
auto mresult = abbr_expand_1(L"gc", cmd);
|
||||
if (!mresult) err(L"Unexpected failure with gc abbreviation");
|
||||
if (*mresult != L"git checkout") err(L"Wrong abbreviation result for gc");
|
||||
|
||||
mresult = abbr_expand_1(L"foo", cmd);
|
||||
if (!mresult) err(L"Unexpected failure with foo abbreviation");
|
||||
if (*mresult != L"bar") err(L"Wrong abbreviation result for foo");
|
||||
|
||||
maybe_t<wcstring> result;
|
||||
auto expand_abbreviation_in_command = [](const wcstring &cmdline,
|
||||
maybe_t<size_t> cursor_pos = {}) -> maybe_t<wcstring> {
|
||||
if (auto replacement = reader_expand_abbreviation_at_cursor(
|
||||
cmdline, cursor_pos.value_or(cmdline.size()), parser_principal_parser()->deref())) {
|
||||
wcstring cmdline_expanded = cmdline;
|
||||
std::vector<highlight_spec_t> colors{cmdline_expanded.size()};
|
||||
auto ffi_colors = new_highlight_spec_list();
|
||||
for (auto &c : colors) {
|
||||
ffi_colors->push(c);
|
||||
}
|
||||
cmdline_expanded = *apply_edit(
|
||||
cmdline_expanded, *ffi_colors,
|
||||
new_edit(replacement->range.start, replacement->range.end(), *replacement->text));
|
||||
colors.clear();
|
||||
for (size_t i = 0; i < ffi_colors->size(); i++) {
|
||||
colors.push_back(ffi_colors->at(i));
|
||||
}
|
||||
return cmdline_expanded;
|
||||
}
|
||||
return none_t();
|
||||
};
|
||||
result = expand_abbreviation_in_command(L"just a command", 3);
|
||||
if (result) err(L"Command wrongly expanded on line %ld", (long)__LINE__);
|
||||
result = expand_abbreviation_in_command(L"gc somebranch", 0);
|
||||
if (!result) err(L"Command not expanded on line %ld", (long)__LINE__);
|
||||
|
||||
result = expand_abbreviation_in_command(L"gc somebranch", const_strlen(L"gc"));
|
||||
if (!result) err(L"gc not expanded");
|
||||
if (result != L"git checkout somebranch")
|
||||
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
|
||||
|
||||
// Space separation.
|
||||
result = expand_abbreviation_in_command(L"gx somebranch", const_strlen(L"gc"));
|
||||
if (!result) err(L"gx not expanded");
|
||||
if (result != L"git checkout somebranch")
|
||||
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
|
||||
|
||||
result =
|
||||
expand_abbreviation_in_command(L"echo hi ; gc somebranch", const_strlen(L"echo hi ; g"));
|
||||
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
|
||||
if (result != L"echo hi ; git checkout somebranch")
|
||||
err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
|
||||
|
||||
result = expand_abbreviation_in_command(L"echo (echo (echo (echo (gc ",
|
||||
const_strlen(L"echo (echo (echo (echo (gc"));
|
||||
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
|
||||
if (result != L"echo (echo (echo (echo (git checkout ")
|
||||
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
|
||||
|
||||
// If commands should be expanded.
|
||||
result = expand_abbreviation_in_command(L"if gc");
|
||||
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
|
||||
if (result != L"if git checkout")
|
||||
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
|
||||
|
||||
// Others should not be.
|
||||
result = expand_abbreviation_in_command(L"of gc");
|
||||
if (result) err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
|
||||
|
||||
// Others should not be.
|
||||
result = expand_abbreviation_in_command(L"command gc");
|
||||
if (result) err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
|
||||
|
||||
// yin/yang expands everywhere.
|
||||
result = expand_abbreviation_in_command(L"command yin");
|
||||
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
|
||||
if (result != L"command yang") {
|
||||
err(L"command yin incorrectly expanded on line %ld to '%ls'", (long)__LINE__,
|
||||
result->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// todo!("already ported, delete this")
|
||||
/// Testing colors.
|
||||
static void test_colors() {
|
||||
|
@ -740,79 +632,6 @@ static void test_colors() {
|
|||
}
|
||||
|
||||
// todo!("port this")
|
||||
static void test_1_completion(wcstring line, const wcstring &completion, complete_flags_t flags,
|
||||
bool append_only, wcstring expected, long source_line) {
|
||||
// str is given with a caret, which we use to represent the cursor position. Find it.
|
||||
const size_t in_cursor_pos = line.find(L'^');
|
||||
do_test(in_cursor_pos != wcstring::npos);
|
||||
line.erase(in_cursor_pos, 1);
|
||||
|
||||
const size_t out_cursor_pos = expected.find(L'^');
|
||||
do_test(out_cursor_pos != wcstring::npos);
|
||||
expected.erase(out_cursor_pos, 1);
|
||||
|
||||
size_t cursor_pos = in_cursor_pos;
|
||||
wcstring result =
|
||||
completion_apply_to_command_line(completion, flags, line, &cursor_pos, append_only);
|
||||
if (result != expected) {
|
||||
std::fwprintf(stderr, L"line %ld: %ls + %ls -> [%ls], expected [%ls]\n", source_line,
|
||||
line.c_str(), completion.c_str(), result.c_str(), expected.c_str());
|
||||
}
|
||||
do_test(result == expected);
|
||||
do_test(cursor_pos == out_cursor_pos);
|
||||
}
|
||||
|
||||
// todo!("port this")
|
||||
static void test_completion_insertions() {
|
||||
#define TEST_1_COMPLETION(a, b, c, d, e) test_1_completion(a, b, c, d, e, __LINE__)
|
||||
say(L"Testing completion insertions");
|
||||
TEST_1_COMPLETION(L"foo^", L"bar", 0, false, L"foobar ^");
|
||||
// An unambiguous completion of a token that is already trailed by a space character.
|
||||
// After completing, the cursor moves on to the next token, suggesting to the user that the
|
||||
// current token is finished.
|
||||
TEST_1_COMPLETION(L"foo^ baz", L"bar", 0, false, L"foobar ^baz");
|
||||
TEST_1_COMPLETION(L"'foo^", L"bar", 0, false, L"'foobar' ^");
|
||||
TEST_1_COMPLETION(L"'foo'^", L"bar", 0, false, L"'foobar' ^");
|
||||
TEST_1_COMPLETION(L"'foo\\'^", L"bar", 0, false, L"'foo\\'bar' ^");
|
||||
TEST_1_COMPLETION(L"foo\\'^", L"bar", 0, false, L"foo\\'bar ^");
|
||||
|
||||
// Test append only.
|
||||
TEST_1_COMPLETION(L"foo^", L"bar", 0, true, L"foobar ^");
|
||||
TEST_1_COMPLETION(L"foo^ baz", L"bar", 0, true, L"foobar ^baz");
|
||||
TEST_1_COMPLETION(L"'foo^", L"bar", 0, true, L"'foobar' ^");
|
||||
TEST_1_COMPLETION(L"'foo'^", L"bar", 0, true, L"'foo'bar ^");
|
||||
TEST_1_COMPLETION(L"'foo\\'^", L"bar", 0, true, L"'foo\\'bar' ^");
|
||||
TEST_1_COMPLETION(L"foo\\'^", L"bar", 0, true, L"foo\\'bar ^");
|
||||
|
||||
TEST_1_COMPLETION(L"foo^", L"bar", COMPLETE_NO_SPACE, false, L"foobar^");
|
||||
TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_NO_SPACE, false, L"'foobar^");
|
||||
TEST_1_COMPLETION(L"'foo'^", L"bar", COMPLETE_NO_SPACE, false, L"'foobar'^");
|
||||
TEST_1_COMPLETION(L"'foo\\'^", L"bar", COMPLETE_NO_SPACE, false, L"'foo\\'bar^");
|
||||
TEST_1_COMPLETION(L"foo\\'^", L"bar", COMPLETE_NO_SPACE, false, L"foo\\'bar^");
|
||||
|
||||
TEST_1_COMPLETION(L"foo^", L"bar", COMPLETE_REPLACES_TOKEN, false, L"bar ^");
|
||||
TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_REPLACES_TOKEN, false, L"bar ^");
|
||||
|
||||
// See #6130
|
||||
TEST_1_COMPLETION(L": (:^ ''", L"", 0, false, L": (: ^''");
|
||||
}
|
||||
|
||||
// todo!("port this")
|
||||
static void test_autosuggestion_combining() {
|
||||
say(L"Testing autosuggestion combining");
|
||||
do_test(combine_command_and_autosuggestion(L"alpha", L"alphabeta") == L"alphabeta");
|
||||
|
||||
// When the last token contains no capital letters, we use the case of the autosuggestion.
|
||||
do_test(combine_command_and_autosuggestion(L"alpha", L"ALPHABETA") == L"ALPHABETA");
|
||||
|
||||
// When the last token contains capital letters, we use its case.
|
||||
do_test(combine_command_and_autosuggestion(L"alPha", L"alphabeTa") == L"alPhabeTa");
|
||||
|
||||
// If autosuggestion is not longer than input, use the input's case.
|
||||
do_test(combine_command_and_autosuggestion(L"alpha", L"ALPHAA") == L"ALPHAA");
|
||||
do_test(combine_command_and_autosuggestion(L"alpha", L"ALPHA") == L"alpha");
|
||||
}
|
||||
|
||||
/// Helper for test_timezone_env_vars().
|
||||
long return_timezone_hour(time_t tstamp, const wchar_t *timezone) {
|
||||
env_stack_t vars{parser_principal_parser()->deref().vars_boxed()};
|
||||
|
@ -1013,15 +832,12 @@ static const test_t s_tests[]{
|
|||
{TEST_GROUP("str_to_num"), test_str_to_num},
|
||||
{TEST_GROUP("enum"), test_enum_set},
|
||||
{TEST_GROUP("enum"), test_enum_array},
|
||||
{TEST_GROUP("autosuggestion"), test_autosuggestion_combining},
|
||||
{TEST_GROUP("test_abbreviations"), test_abbreviations},
|
||||
{TEST_GROUP("convert"), test_convert},
|
||||
{TEST_GROUP("convert"), test_convert_private_use},
|
||||
{TEST_GROUP("convert_ascii"), test_convert_ascii},
|
||||
{TEST_GROUP("iothread"), test_iothread},
|
||||
{TEST_GROUP("lru"), test_lru},
|
||||
{TEST_GROUP("colors"), test_colors},
|
||||
{TEST_GROUP("completion_insertions"), test_completion_insertions},
|
||||
{TEST_GROUP("maybe"), test_maybe},
|
||||
{TEST_GROUP("normalize"), test_normalize_path},
|
||||
{TEST_GROUP("dirname"), test_dirname_basename},
|
||||
|
|
|
@ -27,53 +27,6 @@ inline void iothread_perform_cantwait(std::function<void()> &&func) {
|
|||
iothread_perform_cantwait(callback);
|
||||
}
|
||||
|
||||
inline uint64_t debounce_perform(const debounce_t &debouncer, const std::function<void()> &func) {
|
||||
std::shared_ptr<callback_t> callback = std::make_shared<callback_t>([=](const void *) {
|
||||
func();
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
return debouncer.perform(callback);
|
||||
}
|
||||
|
||||
template <typename R>
|
||||
inline void debounce_perform_with_completion(const debounce_t &debouncer, std::function<R()> &&func,
|
||||
std::function<void(R)> &&completion) {
|
||||
std::shared_ptr<callback_t> callback2 = std::make_shared<callback_t>([=](const void *r) {
|
||||
assert(r != nullptr && "callback1 result was null!");
|
||||
const R *result = (const R *)r;
|
||||
completion(*result);
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
std::shared_ptr<callback_t> callback1 = std::make_shared<callback_t>([=](const void *) {
|
||||
const R *result = new R(func());
|
||||
callback2->cleanups.push_back([result]() { delete result; });
|
||||
return (void *)result;
|
||||
});
|
||||
|
||||
debouncer.perform_with_completion(callback1, callback2);
|
||||
}
|
||||
|
||||
template <typename R>
|
||||
inline void debounce_perform_with_completion(const debounce_t &debouncer, std::function<R()> &&func,
|
||||
std::function<void(const R &)> &&completion) {
|
||||
std::shared_ptr<callback_t> callback2 = std::make_shared<callback_t>([=](const void *r) {
|
||||
assert(r != nullptr && "callback1 result was null!");
|
||||
const R *result = (const R *)r;
|
||||
completion(*result);
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
std::shared_ptr<callback_t> callback1 = std::make_shared<callback_t>([=](const void *) {
|
||||
const R *result = new R(func());
|
||||
callback2->cleanups.push_back([result]() { delete result; });
|
||||
return (void *)result;
|
||||
});
|
||||
|
||||
debouncer.perform_with_completion(callback1, callback2);
|
||||
}
|
||||
|
||||
inline bool make_detached_pthread(const std::function<void()> &func) {
|
||||
std::shared_ptr<callback_t> callback = std::make_shared<callback_t>([=](const void *) {
|
||||
func();
|
||||
|
|
4754
src/reader.cpp
4754
src/reader.cpp
File diff suppressed because it is too large
Load diff
178
src/reader.h
178
src/reader.h
|
@ -1,6 +1,3 @@
|
|||
// Prototypes for functions for reading data from stdin and passing to the parser. If stdin is a
|
||||
// keyboard, it supplies a killring, history, syntax highlighting, tab-completion and various other
|
||||
// features.
|
||||
#ifndef FISH_READER_H
|
||||
#define FISH_READER_H
|
||||
|
||||
|
@ -28,179 +25,4 @@
|
|||
|
||||
#include "editable_line.h"
|
||||
|
||||
int reader_read_ffi(const void *parser, int fd, const void *io_chain);
|
||||
/// Read commands from \c fd until encountering EOF.
|
||||
/// The fd is not closed.
|
||||
int reader_read(const parser_t &parser, int fd, const io_chain_t &io);
|
||||
|
||||
/// Initialize the reader.
|
||||
void reader_init();
|
||||
void term_copy_modes();
|
||||
|
||||
/// Restore the term mode at startup.
|
||||
void restore_term_mode();
|
||||
|
||||
/// Change the history file for the current command reading context.
|
||||
void reader_change_history(const wcstring &name);
|
||||
|
||||
/// Strategy for determining how the selection behaves.
|
||||
enum class cursor_selection_mode_t : uint8_t {
|
||||
/// The character at/after the cursor is excluded.
|
||||
/// This is most useful with a line cursor shape.
|
||||
exclusive,
|
||||
/// The character at/after the cursor is included.
|
||||
/// This is most useful with a block or underscore cursor shape.
|
||||
inclusive,
|
||||
};
|
||||
|
||||
#if INCLUDE_RUST_HEADERS
|
||||
void reader_change_cursor_selection_mode(cursor_selection_mode_t selection_mode);
|
||||
#else
|
||||
void reader_change_cursor_selection_mode(uint8_t selection_mode);
|
||||
#endif
|
||||
|
||||
struct EnvDyn;
|
||||
/// Enable or disable autosuggestions based on the associated variable.
|
||||
void reader_set_autosuggestion_enabled(const env_stack_t &vars);
|
||||
void reader_set_autosuggestion_enabled_ffi(bool enabled);
|
||||
|
||||
/// Write the title to the titlebar. This function is called just before a new application starts
|
||||
/// executing and just after it finishes.
|
||||
///
|
||||
/// \param cmd Command line string passed to \c fish_title if is defined.
|
||||
/// \param parser The parser to use for autoloading fish_title.
|
||||
/// \param reset_cursor_position If set, issue a \r so the line driver knows where we are
|
||||
void reader_write_title(const wcstring &cmd, const parser_t &parser,
|
||||
bool reset_cursor_position = true);
|
||||
|
||||
void reader_write_title_ffi(const wcstring &cmd, const void *parser, bool reset_cursor_position);
|
||||
|
||||
/// Tell the reader that it needs to re-exec the prompt and repaint.
|
||||
/// This may be called in response to e.g. a color variable change.
|
||||
void reader_schedule_prompt_repaint();
|
||||
|
||||
/// Enqueue an event to the back of the reader's input queue.
|
||||
struct CharEvent;
|
||||
using char_event_t = CharEvent;
|
||||
void reader_queue_ch(rust::Box<char_event_t> ch);
|
||||
|
||||
/// Return the value of the interrupted flag, which is set by the sigint handler, and clear it if it
|
||||
/// was set. If the current reader is interruptible, call \c reader_exit().
|
||||
int reader_reading_interrupted();
|
||||
|
||||
/// Read one line of input. Before calling this function, reader_push() must have been called in
|
||||
/// order to set up a valid reader environment. If nchars > 0, return after reading that many
|
||||
/// characters even if a full line has not yet been read. Note: the returned value may be longer
|
||||
/// than nchars if a single keypress resulted in multiple characters being inserted into the
|
||||
/// commandline.
|
||||
maybe_t<wcstring> reader_readline(int nchars);
|
||||
|
||||
bool reader_readline_ffi(wcstring &line, int nchars);
|
||||
|
||||
/// Configuration that we provide to a reader.
|
||||
struct reader_config_t {
|
||||
/// Left prompt command, typically fish_prompt.
|
||||
wcstring left_prompt_cmd{};
|
||||
|
||||
/// Right prompt command, typically fish_right_prompt.
|
||||
wcstring right_prompt_cmd{};
|
||||
|
||||
/// Name of the event to trigger once we're set up.
|
||||
wcstring event{};
|
||||
|
||||
/// Whether tab completion is OK.
|
||||
bool complete_ok{false};
|
||||
|
||||
/// Whether to perform syntax highlighting.
|
||||
bool highlight_ok{false};
|
||||
|
||||
/// Whether to perform syntax checking before returning.
|
||||
bool syntax_check_ok{false};
|
||||
|
||||
/// Whether to allow autosuggestions.
|
||||
bool autosuggest_ok{false};
|
||||
|
||||
/// Whether to expand abbreviations.
|
||||
bool expand_abbrev_ok{false};
|
||||
|
||||
/// Whether to exit on interrupt (^C).
|
||||
bool exit_on_interrupt{false};
|
||||
|
||||
/// If set, do not show what is typed.
|
||||
bool in_silent_mode{false};
|
||||
|
||||
/// The fd for stdin, default to actual stdin.
|
||||
int in{0};
|
||||
};
|
||||
|
||||
class reader_data_t;
|
||||
bool check_exit_loop_maybe_warning(reader_data_t *data);
|
||||
|
||||
/// Push a new reader environment controlled by \p conf, using the given history name.
|
||||
/// If \p history_name is empty, then save history in-memory only; do not write it to disk.
|
||||
void reader_push(const parser_t &parser, const wcstring &history_name, reader_config_t &&conf);
|
||||
|
||||
void reader_push_ffi(const void *parser, const wcstring &history_name, const void *conf);
|
||||
|
||||
/// Return to previous reader environment.
|
||||
void reader_pop();
|
||||
|
||||
/// \return whether fish is currently unwinding the stack in preparation to exit.
|
||||
bool fish_is_unwinding_for_exit();
|
||||
|
||||
/// Given a command line and an autosuggestion, return the string that gets shown to the user.
|
||||
/// Exposed for testing purposes only.
|
||||
wcstring combine_command_and_autosuggestion(const wcstring &cmdline,
|
||||
const wcstring &autosuggestion);
|
||||
|
||||
/// Expand at most one abbreviation at the given cursor position, updating the position if the
|
||||
/// abbreviation wants to move the cursor. Use the parser to run any abbreviations which want
|
||||
/// function calls. \return none if no abbreviations were expanded, otherwise the resulting
|
||||
/// replacement.
|
||||
struct abbrs_replacement_t;
|
||||
maybe_t<abbrs_replacement_t> reader_expand_abbreviation_at_cursor(const wcstring &cmdline,
|
||||
size_t cursor_pos,
|
||||
const parser_t &parser);
|
||||
|
||||
/// Apply a completion string. Exposed for testing only.
|
||||
wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flags_t flags,
|
||||
const wcstring &command_line, size_t *inout_cursor_pos,
|
||||
bool append_only);
|
||||
|
||||
/// Snapshotted state from the reader.
|
||||
struct commandline_state_t {
|
||||
wcstring text; // command line text, or empty if not interactive
|
||||
size_t cursor_pos{0}; // position of the cursor, may be as large as text.size()
|
||||
maybe_t<source_range_t> selection{}; // visual selection, or none if none
|
||||
maybe_t<rust::Box<HistorySharedPtr>>
|
||||
history{}; // current reader history, or null if not interactive
|
||||
bool pager_mode{false}; // pager is visible
|
||||
bool pager_fully_disclosed{false}; // pager already shows everything if possible
|
||||
bool search_mode{false}; // pager is visible and search is active
|
||||
bool initialized{false}; // if false, the reader has not yet been entered
|
||||
};
|
||||
|
||||
/// Get the command line state. This may be fetched on a background thread.
|
||||
commandline_state_t commandline_get_state();
|
||||
|
||||
HistorySharedPtr *commandline_get_state_history_ffi();
|
||||
bool commandline_get_state_initialized_ffi();
|
||||
wcstring commandline_get_state_text_ffi();
|
||||
|
||||
/// Set the command line text and position. This may be called on a background thread; the reader
|
||||
/// will pick it up when it is done executing.
|
||||
void commandline_set_buffer(wcstring text, size_t cursor_pos = -1);
|
||||
void commandline_set_buffer_ffi(const wcstring &text, size_t cursor_pos);
|
||||
|
||||
/// Return the current interactive reads loop count. Useful for determining how many commands have
|
||||
/// been executed between invocations of code.
|
||||
uint64_t reader_run_count();
|
||||
|
||||
/// Returns the current "generation" of interactive status. Useful for determining whether the
|
||||
/// previous command produced a status.
|
||||
uint64_t reader_status_count();
|
||||
|
||||
// For FFI
|
||||
uint32_t read_generation_count();
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue