mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-23 16:51:16 -03:00
As described in https://github.com/fish-shell/fish-shell/pull/9990#discussion_r1382494440, prior to77aeb6a2a8(Port execution, 2023-10-08), "Parser" was passed by mutable reference ("parser_t&"), even though operation context was passed as "const operation_context_t &". This worked because C++ doesn't propagate const to pointers by default (see https://en.cppreference.com/cpp/experimental/propagate_const). class operation_context_t { std::shared_ptr<parser_t> parser; ... }; So "*ctx->parser" was a "parser_t&", not "const parser_t&". Rust has stricter const propagation rules which means that const operation context can't simply hand out a non-const reference to parser. To be able to port code without changing its structure,77aeb6a2a8passed "Parser" by shared reference, using interior mutability (RefCell) to modify parser fields. This is a bit ugly (c.f. https://doc.rust-lang.org/std/cell/index.html "interior mutability is something of a last resort") and means that some borrowing conflicts are not found at compile time but runtime. Pass both parser and operation context by exclusive reference, and remove the interior mutability wrappers from parser's fields. Since "libdata" is no longer inside a "RefCell", add a "ScopedRefCell" around "transient_commandline". The downside is that "ScopeGuard" use can become more intrusive when we pass "Parser" or "OperationContext" as context (especially when we use "zelf" since we can't shadow "self"), see *2930466d53(Introduce ScopedCell and ScopedRefCell, 2025-03-15) *29ae571afa(Make scoped_push nicer, 2024-12-28) Avoid this in some cases, specifically when using "ScopedCell" or "ScopedRefCell". Since "&mut Parser" prevents the "ScopeCell"'s "ScopeGuard" from holding a shared reference, use an "Rc" to capture a dynamically-checked reference to the Cell. We could also use raw pointers instead. Change "Completer::apply_var_assignments" to return a block ID, to avoid the need to return a "zelf" "ScopeGuard". In future, we could probably untangle completer and get away with returning a "ScopeGuard" called "ctx". Closes #12694
166 lines
5.3 KiB
Rust
166 lines
5.3 KiB
Rust
use crate::common::CancelChecker;
|
|
use crate::env::EnvDyn;
|
|
use crate::env::{EnvStack, Environment};
|
|
use crate::parser::Parser;
|
|
use crate::proc::JobGroupRef;
|
|
|
|
use crate::reader::read_generation_count;
|
|
use crate::signal::signal_check_cancel;
|
|
|
|
/// A common helper which always returns false.
|
|
pub fn no_cancel() -> bool {
|
|
false
|
|
}
|
|
|
|
// Default limits for expansion.
|
|
/// The default maximum number of items from expansion.
|
|
pub const EXPANSION_LIMIT_DEFAULT: usize = 512 * 1024;
|
|
/// A smaller limit for background operations like syntax highlighting.
|
|
pub const EXPANSION_LIMIT_BACKGROUND: usize = 512;
|
|
|
|
#[allow(clippy::enum_variant_names)]
|
|
enum Vars<'a> {
|
|
// The parser, if this is a foreground operation. If this is a background operation, this may be
|
|
// nullptr.
|
|
Parser(&'a mut Parser),
|
|
// A set of variables.
|
|
Vars(&'a dyn Environment),
|
|
|
|
TestOnly(&'a mut Parser, &'a dyn Environment),
|
|
}
|
|
|
|
/// A operation_context_t is a simple property bag which wraps up data needed for highlighting,
|
|
/// expansion, completion, and more.
|
|
pub struct OperationContext<'a> {
|
|
vars: Vars<'a>,
|
|
|
|
// The limit in the number of expansions which should be produced.
|
|
pub expansion_limit: usize,
|
|
|
|
/// The job group of the parental job.
|
|
/// This is used only when expanding command substitutions. If this is set, any jobs created
|
|
/// by the command substitutions should use this tree.
|
|
pub job_group: Option<JobGroupRef>,
|
|
|
|
// A function which may be used to poll for cancellation.
|
|
pub cancel_checker: CancelChecker,
|
|
}
|
|
|
|
impl<'a> OperationContext<'a> {
|
|
pub fn vars(&self) -> &dyn Environment {
|
|
match &self.vars {
|
|
Vars::Parser(parser) => &parser.variables,
|
|
Vars::Vars(vars) => *vars,
|
|
Vars::TestOnly(_, vars) => *vars,
|
|
}
|
|
}
|
|
|
|
// Return an "empty" context which contains no variables, no parser, and never cancels.
|
|
pub fn empty() -> OperationContext<'static> {
|
|
use std::sync::LazyLock;
|
|
static NULL_ENV: LazyLock<EnvStack> = LazyLock::new(EnvStack::new);
|
|
OperationContext::background(&*NULL_ENV, EXPANSION_LIMIT_DEFAULT)
|
|
}
|
|
|
|
// Return an operation context that contains only global variables, no parser, and never
|
|
// cancels.
|
|
pub fn globals() -> OperationContext<'static> {
|
|
OperationContext::background(EnvStack::globals(), EXPANSION_LIMIT_DEFAULT)
|
|
}
|
|
|
|
/// Construct from a full set of properties.
|
|
pub fn foreground(
|
|
parser: &'a mut Parser,
|
|
cancel_checker: CancelChecker,
|
|
expansion_limit: usize,
|
|
) -> OperationContext<'a> {
|
|
OperationContext {
|
|
vars: Vars::Parser(parser),
|
|
expansion_limit,
|
|
job_group: None,
|
|
cancel_checker,
|
|
}
|
|
}
|
|
|
|
pub fn test_only_foreground(
|
|
parser: &'a mut Parser,
|
|
vars: &'a dyn Environment,
|
|
cancel_checker: CancelChecker,
|
|
) -> OperationContext<'a> {
|
|
OperationContext {
|
|
vars: Vars::TestOnly(parser, vars),
|
|
expansion_limit: EXPANSION_LIMIT_DEFAULT,
|
|
job_group: None,
|
|
cancel_checker,
|
|
}
|
|
}
|
|
|
|
/// Construct from vars alone.
|
|
pub fn background(vars: &'a dyn Environment, expansion_limit: usize) -> OperationContext<'a> {
|
|
OperationContext {
|
|
vars: Vars::Vars(vars),
|
|
expansion_limit,
|
|
job_group: None,
|
|
cancel_checker: Box::new(no_cancel),
|
|
}
|
|
}
|
|
|
|
pub fn background_with_cancel_checker(
|
|
vars: &'a dyn Environment,
|
|
cancel_checker: CancelChecker,
|
|
expansion_limit: usize,
|
|
) -> OperationContext<'a> {
|
|
OperationContext {
|
|
vars: Vars::Vars(vars),
|
|
expansion_limit,
|
|
job_group: None,
|
|
cancel_checker,
|
|
}
|
|
}
|
|
|
|
pub fn background_interruptible(env: &dyn Environment) -> OperationContext<'_> {
|
|
OperationContext::background_with_cancel_checker(
|
|
env,
|
|
Box::new(|| signal_check_cancel() != 0),
|
|
EXPANSION_LIMIT_BACKGROUND,
|
|
)
|
|
}
|
|
|
|
pub fn has_parser(&self) -> bool {
|
|
matches!(self.vars, Vars::Parser(_) | Vars::TestOnly(_, _))
|
|
}
|
|
pub fn maybe_parser(&mut self) -> Option<&mut Parser> {
|
|
match &mut self.vars {
|
|
Vars::Parser(parser) => Some(parser),
|
|
Vars::Vars(_) => None,
|
|
Vars::TestOnly(parser, _) => Some(parser),
|
|
}
|
|
}
|
|
pub fn parser(&mut self) -> &mut Parser {
|
|
match &mut self.vars {
|
|
Vars::Parser(parser) => parser,
|
|
Vars::Vars(_) => panic!(),
|
|
Vars::TestOnly(parser, _) => parser,
|
|
}
|
|
}
|
|
// Invoke the cancel checker. Return if we should cancel.
|
|
pub fn check_cancel(&self) -> bool {
|
|
(self.cancel_checker)()
|
|
}
|
|
}
|
|
|
|
/// Return an operation context for a background operation..
|
|
/// Crucially the operation context itself does not contain a parser.
|
|
/// It is the caller's responsibility to ensure the environment lives as long as the result.
|
|
pub fn get_bg_context(env: &EnvDyn, generation_count: u32) -> OperationContext<'_> {
|
|
let cancel_checker = move || {
|
|
// Cancel if the generation count changed.
|
|
generation_count != read_generation_count()
|
|
};
|
|
OperationContext::background_with_cancel_checker(
|
|
env,
|
|
Box::new(cancel_checker),
|
|
EXPANSION_LIMIT_BACKGROUND,
|
|
)
|
|
}
|