fish-shell/fish-rust/src/reader.rs

341 lines
10 KiB
Rust
Raw Normal View History

use std::sync::atomic::AtomicI32;
use crate::common::{escape_string, EscapeFlags, EscapeStringStyle};
use crate::complete::{CompleteFlags, CompletionList};
use crate::env::Environment;
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()
}
#[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)
}
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) }
}