mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-06 00:41:15 -03:00
Port reader
This commit is contained in:
@@ -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 @@ fn from(value: Replacer) -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ pub fn list(&self) -> &[Abbreviation] {
|
||||
|
||||
/// \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 @@
|
||||
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::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 @@ pub fn scoped_push_replacer<Replacer, T>(
|
||||
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 @@ fn get_pwd_slash(&self) -> WString {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 @@
|
||||
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 @@ fn try_get_computed(&self, key: &wstr) -> Option<EnvVar> {
|
||||
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 @@ fn try_get_computed(&self, key: &wstr) -> Option<EnvVar> {
|
||||
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::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_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 @@
|
||||
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 "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 @@
|
||||
|
||||
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 @@
|
||||
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 @@
|
||||
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 fn all_categories() -> Vec<&'static category_t> {
|
||||
|
||||
(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 @@ fn is_ok_and(self, f: impl FnOnce(T) -> bool) -> bool {
|
||||
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 @@
|
||||
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 @@
|
||||
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 @@ pub enum SearchDirection {
|
||||
Backward,
|
||||
}
|
||||
|
||||
pub enum HistoryPagerInvocation {
|
||||
Anew,
|
||||
Advance,
|
||||
Refresh,
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
#[cxx_name = "history_save_all"]
|
||||
fn save_all();
|
||||
@@ -205,13 +199,6 @@ fn rust_history_item_new(
|
||||
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 @@ pub fn remove_ephemeral_items(&self) {
|
||||
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 @@ pub fn add_pending_with_file_detection(
|
||||
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 @@ fn size(&self) -> usize {
|
||||
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::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 @@ fn readch(&mut self) -> CharEvent {
|
||||
}
|
||||
|
||||
ReadbResult::IOPortNotified => {
|
||||
iothread_service_main();
|
||||
iothread_service_main(reader_current_data().unwrap());
|
||||
}
|
||||
|
||||
ReadbResult::Byte(read_byte) => {
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
mod proc;
|
||||
mod re;
|
||||
mod reader;
|
||||
mod reader_history_search;
|
||||
mod redirection;
|
||||
mod screen;
|
||||
mod signal;
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
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 @@ pub fn check_cancel(&self) -> bool {
|
||||
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 @@ pub fn write(
|
||||
commandline: &wstr,
|
||||
explicit_len: usize,
|
||||
colors: &[HighlightSpec],
|
||||
indent: &[usize],
|
||||
indent: &[i32],
|
||||
cursor_pos: usize,
|
||||
vars: &dyn Environment,
|
||||
pager: &mut Pager,
|
||||
@@ -336,7 +336,7 @@ pub fn write(
|
||||
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 @@ fn write_ffi(
|
||||
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::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 @@ struct Context {
|
||||
};
|
||||
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 @@ struct Context {
|
||||
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 @@ fn test_universal_helper(x: usize) {
|
||||
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 @@ fn test_history_races_pound_on_history(item_count: usize, idx: usize) {
|
||||
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 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_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 @@ fn iothread_perform_cant_wait_ffi(callback: &cxx::SharedPtr<ffi::CppCallback>) {
|
||||
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 @@ fn run_next(&self, token: NonZeroU64) -> bool {
|
||||
///
|
||||
/// 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 @@ fn perform_with_completion_ffi(
|
||||
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 @@ pub fn length(&self) -> usize {
|
||||
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 @@ fn to_wstring(&self) -> WString {
|
||||
};
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
Reference in New Issue
Block a user