2023-10-08 21:22:27 +00:00
|
|
|
use std::sync::atomic::AtomicI32;
|
|
|
|
|
|
|
|
use crate::common::{escape_string, EscapeFlags, EscapeStringStyle};
|
|
|
|
use crate::complete::{CompleteFlags, CompletionList};
|
2023-05-16 19:46:16 +00:00
|
|
|
use crate::env::Environment;
|
2023-10-08 21:22:27 +00:00
|
|
|
use crate::expand::{expand_string, ExpandFlags, ExpandResultCode};
|
|
|
|
use crate::ffi;
|
|
|
|
use crate::global_safety::RelaxedAtomicBool;
|
|
|
|
use crate::operation_context::OperationContext;
|
|
|
|
use crate::parser::Parser;
|
|
|
|
use crate::signal::signal_check_cancel;
|
|
|
|
use crate::wchar::prelude::*;
|
|
|
|
use crate::wchar_ffi::{WCharFromFFI, WCharToFFI};
|
|
|
|
use crate::wcstringutil::count_preceding_backslashes;
|
|
|
|
use crate::wildcard::wildcard_has;
|
|
|
|
use cxx::{CxxWString, UniquePtr};
|
|
|
|
use libc::SIGINT;
|
|
|
|
use std::os::fd::RawFd;
|
|
|
|
use std::sync::atomic::Ordering;
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct ReaderConfig {
|
|
|
|
/// Left prompt command, typically fish_prompt.
|
|
|
|
pub left_prompt_cmd: WString,
|
|
|
|
|
|
|
|
/// Right prompt command, typically fish_right_prompt.
|
|
|
|
pub right_prompt_cmd: WString,
|
|
|
|
|
|
|
|
/// Name of the event to trigger once we're set up.
|
|
|
|
pub event: &'static wstr,
|
|
|
|
|
|
|
|
/// Whether tab completion is OK.
|
|
|
|
pub complete_ok: bool,
|
|
|
|
|
|
|
|
/// Whether to perform syntax highlighting.
|
|
|
|
pub highlight_ok: bool,
|
|
|
|
|
|
|
|
/// Whether to perform syntax checking before returning.
|
|
|
|
pub syntax_check_ok: bool,
|
|
|
|
|
|
|
|
/// Whether to allow autosuggestions.
|
|
|
|
pub autosuggest_ok: bool,
|
|
|
|
|
|
|
|
/// Whether to expand abbreviations.
|
|
|
|
pub expand_abbrev_ok: bool,
|
|
|
|
|
|
|
|
/// Whether to exit on interrupt (^C).
|
|
|
|
pub exit_on_interrupt: bool,
|
|
|
|
|
|
|
|
/// If set, do not show what is typed.
|
|
|
|
pub in_silent_mode: bool,
|
|
|
|
|
|
|
|
/// The fd for stdin, default to actual stdin.
|
|
|
|
pub inputfd: RawFd,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reader_push(parser: &Parser, history_name: &wstr, conf: ReaderConfig) {
|
|
|
|
ffi::reader_push_ffi(
|
|
|
|
parser as *const Parser as *const autocxx::c_void,
|
|
|
|
&history_name.to_ffi(),
|
|
|
|
&conf as *const ReaderConfig as *const autocxx::c_void,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reader_readline(nchars: i32) -> Option<WString> {
|
|
|
|
let mut line = L!("").to_ffi();
|
|
|
|
if ffi::reader_readline_ffi(line.pin_mut(), autocxx::c_int(nchars)) {
|
|
|
|
Some(line.from_ffi())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reader_pop() {
|
|
|
|
ffi::reader_pop()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reader_write_title(cmd: &wstr, parser: &Parser, reset_cursor_position: bool /*=true*/) {
|
|
|
|
ffi::reader_write_title_ffi(
|
|
|
|
&cmd.to_ffi(),
|
|
|
|
parser as *const Parser as *const autocxx::c_void,
|
|
|
|
reset_cursor_position,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This variable is set to a signal by the signal handler when ^C is pressed.
|
|
|
|
static INTERRUPTED: AtomicI32 = AtomicI32::new(0);
|
|
|
|
|
|
|
|
/// The readers interrupt signal handler. Cancels all currently running blocks.
|
|
|
|
/// This is called from a signal handler!
|
|
|
|
pub fn reader_handle_sigint() {
|
|
|
|
INTERRUPTED.store(SIGINT, Ordering::Relaxed);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Clear the interrupted flag unconditionally without handling anything. The flag could have been
|
|
|
|
/// set e.g. when an interrupt arrived just as we were ending an earlier \c reader_readline
|
|
|
|
/// invocation but before the \c is_interactive_read flag was cleared.
|
|
|
|
pub fn reader_reset_interrupted() {
|
|
|
|
INTERRUPTED.store(0, Ordering::Relaxed);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return the value of the interrupted flag, which is set by the sigint handler, and clear it if it
|
|
|
|
/// was set. In practice this will return 0 or SIGINT.
|
|
|
|
fn reader_test_and_clear_interrupted() -> i32 {
|
|
|
|
let res = INTERRUPTED.load(Ordering::Relaxed);
|
|
|
|
if res != 0 {
|
|
|
|
INTERRUPTED.store(0, Ordering::Relaxed);
|
|
|
|
};
|
|
|
|
res
|
|
|
|
}
|
|
|
|
|
|
|
|
/// If set, SIGHUP has been received. This latches to true.
|
|
|
|
/// This is set from a signal handler.
|
|
|
|
static SIGHUP_RECEIVED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
|
|
|
|
|
|
|
/// Mark that we encountered SIGHUP and must (soon) exit. This is invoked from a signal handler.
|
|
|
|
pub fn reader_sighup() {
|
|
|
|
// Beware, we may be in a signal handler.
|
|
|
|
SIGHUP_RECEIVED.store(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn reader_received_sighup() -> bool {
|
|
|
|
SIGHUP_RECEIVED.load()
|
|
|
|
}
|
2023-05-16 19:46:16 +00:00
|
|
|
|
|
|
|
#[repr(u8)]
|
|
|
|
pub enum CursorSelectionMode {
|
|
|
|
Exclusive = 0,
|
|
|
|
Inclusive = 1,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_autosuggestion_enabled(vars: &dyn Environment) -> bool {
|
|
|
|
vars.get(L!("fish_autosuggestion_enabled"))
|
|
|
|
.map(|v| v.as_string())
|
|
|
|
.map(|v| v != L!("0"))
|
|
|
|
.unwrap_or(true)
|
|
|
|
}
|
2023-10-08 21:22:27 +00:00
|
|
|
|
|
|
|
pub fn reader_schedule_prompt_repaint() {
|
|
|
|
crate::ffi::reader_schedule_prompt_repaint()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// \return whether fish is currently unwinding the stack in preparation to exit.
|
|
|
|
pub fn fish_is_unwinding_for_exit() -> bool {
|
|
|
|
crate::ffi::fish_is_unwinding_for_exit()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reader_run_count() -> u64 {
|
|
|
|
crate::ffi::reader_run_count()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// When tab-completing with a wildcard, we expand the wildcard up to this many results.
|
|
|
|
/// If expansion would exceed this many results, beep and do nothing.
|
|
|
|
const TAB_COMPLETE_WILDCARD_MAX_EXPANSION: usize = 256;
|
|
|
|
|
|
|
|
/// Given that the user is tab-completing a token \p wc whose cursor is at \p pos in the token,
|
|
|
|
/// try expanding it as a wildcard, populating \p result with the expanded string.
|
|
|
|
fn try_expand_wildcard(
|
|
|
|
parser: &Parser,
|
|
|
|
wc: WString,
|
|
|
|
position: usize,
|
|
|
|
result: &mut WString,
|
|
|
|
) -> ExpandResultCode {
|
|
|
|
// Hacky from #8593: only expand if there are wildcards in the "current path component."
|
|
|
|
// Find the "current path component" by looking for an unescaped slash before and after
|
|
|
|
// our position.
|
|
|
|
// This is quite naive; for example it mishandles brackets.
|
|
|
|
let is_path_sep =
|
|
|
|
|offset| wc.char_at(offset) == '/' && count_preceding_backslashes(&wc, offset) % 2 == 0;
|
|
|
|
|
|
|
|
let mut comp_start = position;
|
|
|
|
while comp_start > 0 && !is_path_sep(comp_start - 1) {
|
|
|
|
comp_start -= 1;
|
|
|
|
}
|
|
|
|
let mut comp_end = position;
|
|
|
|
while comp_end < wc.len() && !is_path_sep(comp_end) {
|
|
|
|
comp_end += 1;
|
|
|
|
}
|
|
|
|
if !wildcard_has(&wc[comp_start..comp_end]) {
|
|
|
|
return ExpandResultCode::wildcard_no_match;
|
|
|
|
}
|
|
|
|
result.clear();
|
|
|
|
// Have a low limit on the number of matches, otherwise we will overwhelm the command line.
|
|
|
|
|
|
|
|
let ctx = OperationContext::background_with_cancel_checker(
|
|
|
|
&*parser.variables,
|
|
|
|
Box::new(|| signal_check_cancel() != 0),
|
|
|
|
TAB_COMPLETE_WILDCARD_MAX_EXPANSION,
|
|
|
|
);
|
|
|
|
|
|
|
|
// We do wildcards only.
|
|
|
|
|
|
|
|
let flags = ExpandFlags::SKIP_CMDSUBST
|
|
|
|
| ExpandFlags::SKIP_VARIABLES
|
|
|
|
| ExpandFlags::PRESERVE_HOME_TILDES;
|
|
|
|
let mut expanded = CompletionList::new();
|
|
|
|
let ret = expand_string(wc, &mut expanded, flags, &ctx, None);
|
|
|
|
if ret.result != ExpandResultCode::ok {
|
|
|
|
return ret.result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert all matches (escaped) and a trailing space.
|
|
|
|
let mut joined = WString::new();
|
|
|
|
for r#match in expanded {
|
|
|
|
if r#match.flags.contains(CompleteFlags::DONT_ESCAPE) {
|
|
|
|
joined.push_utfstr(&r#match.completion);
|
|
|
|
} else {
|
|
|
|
let tildeflag = if r#match.flags.contains(CompleteFlags::DONT_ESCAPE_TILDES) {
|
|
|
|
EscapeFlags::NO_TILDE
|
|
|
|
} else {
|
|
|
|
EscapeFlags::default()
|
|
|
|
};
|
|
|
|
joined.push_utfstr(&escape_string(
|
|
|
|
&r#match.completion,
|
|
|
|
EscapeStringStyle::Script(EscapeFlags::NO_QUOTED | tildeflag),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
joined.push(' ');
|
|
|
|
}
|
|
|
|
|
|
|
|
*result = joined;
|
|
|
|
ExpandResultCode::ok
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn completion_apply_to_command_line(
|
|
|
|
val_str: &wstr,
|
|
|
|
flags: CompleteFlags,
|
|
|
|
command_line: &wstr,
|
|
|
|
inout_cursor_pos: &mut usize,
|
|
|
|
append_only: bool,
|
|
|
|
) -> WString {
|
|
|
|
ffi::completion_apply_to_command_line(
|
|
|
|
&val_str.to_ffi(),
|
|
|
|
flags.bits(),
|
|
|
|
&command_line.to_ffi(),
|
|
|
|
inout_cursor_pos,
|
|
|
|
append_only,
|
|
|
|
)
|
|
|
|
.from_ffi()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cxx::bridge]
|
|
|
|
mod reader_ffi {
|
|
|
|
extern "C++" {
|
|
|
|
include!("operation_context.h");
|
|
|
|
include!("env.h");
|
|
|
|
include!("parser.h");
|
|
|
|
#[cxx_name = "EnvDyn"]
|
|
|
|
type EnvDynFFI = crate::env::EnvDynFFI;
|
|
|
|
type Parser = crate::parser::Parser;
|
|
|
|
}
|
|
|
|
extern "Rust" {
|
|
|
|
fn reader_reset_interrupted();
|
|
|
|
fn reader_handle_sigint();
|
|
|
|
fn reader_test_and_clear_interrupted() -> i32;
|
|
|
|
fn reader_sighup();
|
|
|
|
fn reader_received_sighup() -> bool;
|
|
|
|
}
|
|
|
|
extern "Rust" {
|
|
|
|
#[cxx_name = "try_expand_wildcard"]
|
|
|
|
fn try_expand_wildcard_ffi(
|
|
|
|
parser: &Parser,
|
|
|
|
wc: &CxxWString,
|
|
|
|
position: usize,
|
|
|
|
result: &mut UniquePtr<CxxWString>,
|
|
|
|
) -> u8;
|
|
|
|
}
|
|
|
|
extern "Rust" {
|
|
|
|
type ReaderConfig;
|
|
|
|
#[cxx_name = "left_prompt_cmd"]
|
|
|
|
fn left_prompt_cmd_ffi(&self) -> UniquePtr<CxxWString>;
|
|
|
|
#[cxx_name = "right_prompt_cmd"]
|
|
|
|
fn right_prompt_cmd_ffi(&self) -> UniquePtr<CxxWString>;
|
|
|
|
#[cxx_name = "event"]
|
|
|
|
fn event_ffi(&self) -> UniquePtr<CxxWString>;
|
|
|
|
#[cxx_name = "complete_ok"]
|
|
|
|
fn complete_ok_ffi(&self) -> bool;
|
|
|
|
#[cxx_name = "highlight_ok"]
|
|
|
|
fn highlight_ok_ffi(&self) -> bool;
|
|
|
|
#[cxx_name = "syntax_check_ok"]
|
|
|
|
fn syntax_check_ok_ffi(&self) -> bool;
|
|
|
|
#[cxx_name = "autosuggest_ok"]
|
|
|
|
fn autosuggest_ok_ffi(&self) -> bool;
|
|
|
|
#[cxx_name = "expand_abbrev_ok"]
|
|
|
|
fn expand_abbrev_ok_ffi(&self) -> bool;
|
|
|
|
#[cxx_name = "exit_on_interrupt"]
|
|
|
|
fn exit_on_interrupt_ffi(&self) -> bool;
|
|
|
|
#[cxx_name = "in_silent_mode"]
|
|
|
|
fn in_silent_mode_ffi(&self) -> bool;
|
|
|
|
#[cxx_name = "inputfd"]
|
|
|
|
fn inputfd_ffi(&self) -> i32;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ReaderConfig {
|
|
|
|
fn left_prompt_cmd_ffi(&self) -> UniquePtr<CxxWString> {
|
|
|
|
self.left_prompt_cmd.to_ffi()
|
|
|
|
}
|
|
|
|
fn right_prompt_cmd_ffi(&self) -> UniquePtr<CxxWString> {
|
|
|
|
self.right_prompt_cmd.to_ffi()
|
|
|
|
}
|
|
|
|
fn event_ffi(&self) -> UniquePtr<CxxWString> {
|
|
|
|
self.event.to_ffi()
|
|
|
|
}
|
|
|
|
fn complete_ok_ffi(&self) -> bool {
|
|
|
|
self.complete_ok
|
|
|
|
}
|
|
|
|
fn highlight_ok_ffi(&self) -> bool {
|
|
|
|
self.highlight_ok
|
|
|
|
}
|
|
|
|
fn syntax_check_ok_ffi(&self) -> bool {
|
|
|
|
self.syntax_check_ok
|
|
|
|
}
|
|
|
|
fn autosuggest_ok_ffi(&self) -> bool {
|
|
|
|
self.autosuggest_ok
|
|
|
|
}
|
|
|
|
fn expand_abbrev_ok_ffi(&self) -> bool {
|
|
|
|
self.expand_abbrev_ok
|
|
|
|
}
|
|
|
|
fn exit_on_interrupt_ffi(&self) -> bool {
|
|
|
|
self.exit_on_interrupt
|
|
|
|
}
|
|
|
|
fn in_silent_mode_ffi(&self) -> bool {
|
|
|
|
self.in_silent_mode
|
|
|
|
}
|
|
|
|
fn inputfd_ffi(&self) -> i32 {
|
|
|
|
self.inputfd as _
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn try_expand_wildcard_ffi(
|
|
|
|
parser: &Parser,
|
|
|
|
wc: &CxxWString,
|
|
|
|
position: usize,
|
|
|
|
result: &mut UniquePtr<CxxWString>,
|
|
|
|
) -> u8 {
|
|
|
|
let mut rust_result = WString::new();
|
|
|
|
let result_code = try_expand_wildcard(parser, wc.from_ffi(), position, &mut rust_result);
|
|
|
|
*result = rust_result.to_ffi();
|
|
|
|
unsafe { std::mem::transmute(result_code) }
|
|
|
|
}
|