diff --git a/CMakeLists.txt b/CMakeLists.txt index be0ea5193..3fbda66a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,7 +100,7 @@ endif() # List of sources for builtin functions. set(FISH_BUILTIN_SRCS src/builtin.cpp src/builtins/argparse.cpp - src/builtins/bg.cpp src/builtins/bind.cpp src/builtins/block.cpp + src/builtins/bg.cpp src/builtins/bind.cpp src/builtins/builtin.cpp src/builtins/cd.cpp src/builtins/command.cpp src/builtins/commandline.cpp src/builtins/complete.cpp src/builtins/disown.cpp diff --git a/fish-rust/src/builtins/block.rs b/fish-rust/src/builtins/block.rs new file mode 100644 index 000000000..589847b03 --- /dev/null +++ b/fish-rust/src/builtins/block.rs @@ -0,0 +1,158 @@ +// Implementation of the block builtin. +use super::shared::{ + builtin_missing_argument, builtin_print_help, io_streams_t, STATUS_CMD_ERROR, STATUS_CMD_OK, + STATUS_INVALID_ARGS, +}; +use crate::{ + builtins::shared::builtin_unknown_option, + ffi::{parser_t, Repin}, + wchar::{wstr, L}, + wgetopt::{wgetopter_t, wopt, woption, woption_argument_t}, + wutil::wgettext_fmt, +}; +use libc::c_int; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Scope { + Unset, + Global, + Local, +} + +impl Default for Scope { + fn default() -> Self { + Self::Unset + } +} + +#[derive(Debug, Clone, Copy, Default)] +struct Options { + scope: Scope, + erase: bool, + print_help: bool, +} + +fn parse_options( + args: &mut [&wstr], + parser: &mut parser_t, + streams: &mut io_streams_t, +) -> Result<(Options, usize), Option> { + let cmd = args[0]; + + const SHORT_OPTS: &wstr = L!(":eghl"); + const LONG_OPTS: &[woption] = &[ + wopt(L!("erase"), woption_argument_t::no_argument, 'e'), + wopt(L!("local"), woption_argument_t::no_argument, 'l'), + wopt(L!("global"), woption_argument_t::no_argument, 'g'), + wopt(L!("help"), woption_argument_t::no_argument, 'h'), + ]; + + let mut opts = Options::default(); + + let mut w = wgetopter_t::new(SHORT_OPTS, LONG_OPTS, args); + while let Some(c) = w.wgetopt_long() { + match c { + 'h' => { + opts.print_help = true; + } + 'g' => { + opts.scope = Scope::Global; + } + 'l' => { + opts.scope = Scope::Local; + } + 'e' => { + opts.erase = true; + } + ':' => { + builtin_missing_argument(parser, streams, cmd, args[w.woptind - 1], false); + return Err(STATUS_INVALID_ARGS); + } + '?' => { + builtin_unknown_option(parser, streams, cmd, args[w.woptind - 1], false); + return Err(STATUS_INVALID_ARGS); + } + _ => { + panic!("unexpected retval from wgetopt_long"); + } + } + } + + Ok((opts, w.woptind)) +} + +/// The block builtin, used for temporarily blocking events. +pub fn block( + parser: &mut parser_t, + streams: &mut io_streams_t, + args: &mut [&wstr], +) -> Option { + let cmd = args[0]; + + let opts = match parse_options(args, parser, streams) { + Ok((opts, _)) => opts, + Err(err @ Some(_)) if err != STATUS_CMD_OK => return err, + Err(err) => panic!("Illogical exit code from parse_options(): {err:?}"), + }; + + if opts.print_help { + builtin_print_help(parser, streams, cmd); + return STATUS_CMD_OK; + } + + if opts.erase { + if opts.scope != Scope::Unset { + streams.err.append(wgettext_fmt!( + "%ls: Can not specify scope when removing block\n", + cmd + )); + return STATUS_INVALID_ARGS; + } + + if parser.ffi_global_event_blocks() == 0 { + streams + .err + .append(wgettext_fmt!("%ls: No blocks defined\n", cmd)); + return STATUS_CMD_ERROR; + } + parser.pin().ffi_decr_global_event_blocks(); + return STATUS_CMD_OK; + } + + let mut block_idx = 0; + let mut block = unsafe { parser.pin().block_at_index1(block_idx).as_mut() }; + + match opts.scope { + Scope::Local => { + // If this is the outermost block, then we're global + if block_idx + 1 >= parser.ffi_blocks_size() { + block = None; + } + } + Scope::Global => { + block = None; + } + Scope::Unset => { + loop { + block = if let Some(block) = block.as_mut() { + if !block.is_function_call() { + break; + } + // Set it in function scope + block_idx += 1; + unsafe { parser.pin().block_at_index1(block_idx).as_mut() } + } else { + break; + } + } + } + } + + if let Some(block) = block.as_mut() { + block.pin().ffi_incr_event_blocks(); + } else { + parser.pin().ffi_incr_global_event_blocks(); + } + + return STATUS_CMD_OK; +} diff --git a/fish-rust/src/builtins/mod.rs b/fish-rust/src/builtins/mod.rs index 42fc971fb..eda5ab03e 100644 --- a/fish-rust/src/builtins/mod.rs +++ b/fish-rust/src/builtins/mod.rs @@ -1,6 +1,7 @@ pub mod shared; pub mod abbr; +pub mod block; pub mod contains; pub mod echo; pub mod emit; diff --git a/fish-rust/src/builtins/shared.rs b/fish-rust/src/builtins/shared.rs index 2ba08469a..7ae629a75 100644 --- a/fish-rust/src/builtins/shared.rs +++ b/fish-rust/src/builtins/shared.rs @@ -119,6 +119,7 @@ pub fn run_builtin( ) -> Option { match builtin { RustBuiltin::Abbr => super::abbr::abbr(parser, streams, args), + RustBuiltin::Block => super::block::block(parser, streams, args), RustBuiltin::Contains => super::contains::contains(parser, streams, args), RustBuiltin::Echo => super::echo::echo(parser, streams, args), RustBuiltin::Emit => super::emit::emit(parser, streams, args), diff --git a/fish-rust/src/ffi.rs b/fish-rust/src/ffi.rs index b2174810f..a544b1946 100644 --- a/fish-rust/src/ffi.rs +++ b/fish-rust/src/ffi.rs @@ -53,6 +53,7 @@ include_cpp! { generate!("wildcard_match") generate!("wgettext_ptr") + generate!("block_t") generate!("parser_t") generate!("job_t") generate!("process_t") @@ -163,6 +164,7 @@ pub trait Repin { } // Implement Repin for our types. +impl Repin for block_t {} impl Repin for env_stack_t {} impl Repin for io_streams_t {} impl Repin for job_t {} diff --git a/src/builtin.cpp b/src/builtin.cpp index ea05cfc0c..2d7aab1c7 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -32,7 +32,6 @@ #include "builtins/argparse.h" #include "builtins/bg.h" #include "builtins/bind.h" -#include "builtins/block.h" #include "builtins/builtin.h" #include "builtins/cd.h" #include "builtins/command.h" @@ -364,7 +363,7 @@ static constexpr builtin_data_t builtin_datas[] = { {L"begin", &builtin_generic, N_(L"Create a block of code")}, {L"bg", &builtin_bg, N_(L"Send job to background")}, {L"bind", &builtin_bind, N_(L"Handle fish key bindings")}, - {L"block", &builtin_block, N_(L"Temporarily block delivery of events")}, + {L"block", &implemented_in_rust, N_(L"Temporarily block delivery of events")}, {L"break", &builtin_break_continue, N_(L"Stop the innermost loop")}, {L"breakpoint", &builtin_breakpoint, N_(L"Halt execution and start debug prompt")}, {L"builtin", &builtin_builtin, N_(L"Run a builtin specifically")}, @@ -525,6 +524,9 @@ static maybe_t try_get_rust_builtin(const wcstring &cmd) { if (cmd == L"abbr") { return RustBuiltin::Abbr; } + if (cmd == L"block") { + return RustBuiltin::Block; + } if (cmd == L"contains") { return RustBuiltin::Contains; } diff --git a/src/builtin.h b/src/builtin.h index cf4727dea..cb71578a8 100644 --- a/src/builtin.h +++ b/src/builtin.h @@ -110,6 +110,7 @@ int parse_help_only_cmd_opts(help_only_cmd_opts_t &opts, int *optind, int argc, /// An enum of the builtins implemented in Rust. enum RustBuiltin : int32_t { Abbr, + Block, Contains, Echo, Emit, diff --git a/src/builtins/block.cpp b/src/builtins/block.cpp deleted file mode 100644 index 426ada1dd..000000000 --- a/src/builtins/block.cpp +++ /dev/null @@ -1,135 +0,0 @@ -// Implementation of the block builtin. -#include "config.h" // IWYU pragma: keep - -#include "block.h" - -#include -#include - -#include "../builtin.h" -#include "../common.h" -#include "../fallback.h" // IWYU pragma: keep -#include "../io.h" -#include "../maybe.h" -#include "../parser.h" -#include "../wgetopt.h" -#include "../wutil.h" // IWYU pragma: keep - -enum { UNSET, GLOBAL, LOCAL }; -struct block_cmd_opts_t { - int scope = UNSET; - bool erase = false; - bool print_help = false; -}; - -static int parse_cmd_opts(block_cmd_opts_t &opts, int *optind, //!OCLINT(high ncss method) - int argc, const wchar_t **argv, parser_t &parser, io_streams_t &streams) { - const wchar_t *cmd = argv[0]; - static const wchar_t *const short_options = L":eghl"; - static const struct woption long_options[] = {{L"erase", no_argument, 'e'}, - {L"local", no_argument, 'l'}, - {L"global", no_argument, 'g'}, - {L"help", no_argument, 'h'}, - {}}; - - int opt; - wgetopter_t w; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) { - switch (opt) { - case 'h': { - opts.print_help = true; - break; - } - case 'g': { - opts.scope = GLOBAL; - break; - } - case 'l': { - opts.scope = LOCAL; - break; - } - case 'e': { - opts.erase = true; - break; - } - case ':': { - builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - case '?': { - builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - } - } - } - - *optind = w.woptind; - return STATUS_CMD_OK; -} - -/// The block builtin, used for temporarily blocking events. -maybe_t builtin_block(parser_t &parser, io_streams_t &streams, const wchar_t **argv) { - const wchar_t *cmd = argv[0]; - int argc = builtin_count_args(argv); - block_cmd_opts_t opts; - - int optind; - int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams); - if (retval != STATUS_CMD_OK) return retval; - - if (opts.print_help) { - builtin_print_help(parser, streams, cmd); - return STATUS_CMD_OK; - } - - if (opts.erase) { - if (opts.scope != UNSET) { - streams.err.append_format(_(L"%ls: Can not specify scope when removing block\n"), cmd); - return STATUS_INVALID_ARGS; - } - - if (!parser.global_event_blocks) { - streams.err.append_format(_(L"%ls: No blocks defined\n"), cmd); - return STATUS_CMD_ERROR; - } - --parser.global_event_blocks; - return STATUS_CMD_OK; - } - - size_t block_idx = 0; - block_t *block = parser.block_at_index(block_idx); - - switch (opts.scope) { - case LOCAL: { - // If this is the outermost block, then we're global - if (block_idx + 1 >= parser.blocks().size()) { - block = nullptr; - } - break; - } - case GLOBAL: { - block = nullptr; - break; - } - case UNSET: { - while (block && !block->is_function_call()) { - // Set it in function scope - block = parser.block_at_index(++block_idx); - } - break; - } - default: { - DIE("unexpected scope"); - } - } - if (block) { - ++block->event_blocks; - } else { - ++parser.global_event_blocks; - } - - return STATUS_CMD_OK; -} diff --git a/src/builtins/block.h b/src/builtins/block.h deleted file mode 100644 index 5a6897258..000000000 --- a/src/builtins/block.h +++ /dev/null @@ -1,11 +0,0 @@ -// Prototypes for executing builtin_block function. -#ifndef FISH_BUILTIN_BLOCK_H -#define FISH_BUILTIN_BLOCK_H - -#include "../maybe.h" - -class parser_t; -struct io_streams_t; - -maybe_t builtin_block(parser_t &parser, io_streams_t &streams, const wchar_t **argv); -#endif diff --git a/src/parser.cpp b/src/parser.cpp index 2c64be027..382f28173 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -680,6 +680,12 @@ bool parser_t::ffi_has_funtion_block() const { return false; } +uint64_t parser_t::ffi_global_event_blocks() const { return global_event_blocks; } +void parser_t::ffi_incr_global_event_blocks() { ++global_event_blocks; } +void parser_t::ffi_decr_global_event_blocks() { --global_event_blocks; } + +size_t parser_t::ffi_blocks_size() const { return block_list.size(); } + block_t::block_t(block_type_t t) : block_type(t) {} wcstring block_t::description() const { @@ -748,6 +754,10 @@ wcstring block_t::description() const { return result; } +bool block_t::is_function_call() const { + return type() == block_type_t::function_call || type() == block_type_t::function_call_no_shadow; +} + // Various block constructors. block_t block_t::if_block() { return block_t(block_type_t::if_block); } @@ -782,3 +792,7 @@ block_t block_t::scope_block(block_type_t type) { } block_t block_t::breakpoint_block() { return block_t(block_type_t::breakpoint); } block_t block_t::variable_assignment_block() { return block_t(block_type_t::variable_assignment); } + +void block_t::ffi_incr_event_blocks() { + ++event_blocks; +} diff --git a/src/parser.h b/src/parser.h index ee19b6b88..496d04ee7 100644 --- a/src/parser.h +++ b/src/parser.h @@ -97,10 +97,7 @@ class block_t { block_type_t type() const { return this->block_type; } /// \return if we are a function call (with or without shadowing). - bool is_function_call() const { - return type() == block_type_t::function_call || - type() == block_type_t::function_call_no_shadow; - } + bool is_function_call() const; /// Entry points for creating blocks. static block_t if_block(); @@ -113,6 +110,9 @@ class block_t { static block_t scope_block(block_type_t type); static block_t breakpoint_block(); static block_t variable_assignment_block(); + + /// autocxx junk. + void ffi_incr_event_blocks(); }; struct profile_item_t { @@ -484,6 +484,14 @@ class parser_t : public std::enable_shared_from_this { /// autocxx junk. bool ffi_has_funtion_block() const; + /// autocxx junk. + uint64_t ffi_global_event_blocks() const; + void ffi_incr_global_event_blocks(); + void ffi_decr_global_event_blocks(); + + /// autocxx junk. + size_t ffi_blocks_size() const; + ~parser_t(); };