Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
target/
mutants.out*/
target-thin/
target-usage/
target-clap/
30 changes: 29 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion brush-builtins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ brush-core = { version = "^0.5.0", path = "../brush-core" }
brush-parser = { version = "^0.4.0", path = "../brush-parser" }
cfg-if = "1.0.4"
chrono = "0.4.44"
bpaf = { version = "0.9.27", features = ["derive"] }

fancy-regex = "0.19.0"
itertools = "0.14.0"
strum = "0.28.0"
Expand Down
41 changes: 30 additions & 11 deletions brush-builtins/src/alias.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
use bpaf::Bpaf;
use std::io::Write;

use brush_core::{ExecutionResult, builtins};
use brush_core::{
ExecutionResult,
argmodel::{ArgSpec, PositionalSpec},
builtins,
};

/// Manage aliases within the shell.
#[derive(Bpaf)]
pub(crate) struct AliasCommand {
/// Print all defined aliases in a reusable format.
#[bpaf(short('p'))]
print: bool,

/// List of aliases to display or update.
#[bpaf(positional("name[=value]"))]
aliases: Vec<String>,
}

impl builtins::Command for AliasCommand {
const ID_PRINT: &str = "print";
const ID_ALIASES: &str = "aliases";

impl builtins::SpecCommand for AliasCommand {
type Error = brush_core::Error;

fn parser() -> impl bpaf::Parser<Self> {
alias_command()
fn spec() -> &'static builtins::argmodel::CommandSpec {
static SPEC: builtins::argmodel::CommandSpec = builtins::argmodel::CommandSpec {
args: &[ArgSpec::flag(
ID_PRINT,
&['p'],
&[],
"Print all defined aliases in a reusable format.",
)],
positionals: &[PositionalSpec::many(ID_ALIASES, "name[=value]")],
};

&SPEC
}

fn from_matches(
values: &mut builtins::argmodel::ParsedValues,
) -> Result<Self, builtins::BuiltinArgParseError> {
Ok(Self {
print: values.flag(ID_PRINT),
aliases: values.positional_values(ID_ALIASES).to_vec(),
})
}

fn about() -> &'static str {
Expand Down
28 changes: 19 additions & 9 deletions brush-builtins/src/bg.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
use bpaf::Bpaf;

use std::io::Write;

use brush_core::{ExecutionResult, builtins};
use brush_core::{ExecutionResult, argmodel::PositionalSpec, builtins};

/// Moves a job to run in the background.
#[derive(Bpaf)]
pub(crate) struct BgCommand {
/// List of job specs to move to background.
#[bpaf(positional("JOB_SPECS"))]
job_specs: Vec<String>,
}

impl builtins::Command for BgCommand {
const ID_JOB_SPECS: &str = "job_specs";

impl builtins::SpecCommand for BgCommand {
type Error = brush_core::Error;

fn parser() -> impl bpaf::Parser<Self> {
bg_command()
fn spec() -> &'static builtins::argmodel::CommandSpec {
static SPEC: builtins::argmodel::CommandSpec = builtins::argmodel::CommandSpec {
args: &[],
positionals: &[PositionalSpec::many(ID_JOB_SPECS, "JOB_SPECS")],
};

&SPEC
}

fn from_matches(
values: &mut builtins::argmodel::ParsedValues,
) -> Result<Self, builtins::BuiltinArgParseError> {
Ok(Self {
job_specs: values.positional_values(ID_JOB_SPECS).to_vec(),
})
}

fn about() -> &'static str {
Expand Down
202 changes: 135 additions & 67 deletions brush-builtins/src/bind.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use bpaf::Parser;
use itertools::Itertools as _;
use std::{collections::HashMap, io::Write, str::FromStr, sync::Arc};
use strum::IntoEnumIterator;
use tokio::sync::Mutex;

use brush_core::{
ExecutionExitCode, ExecutionResult, builtins,
ExecutionExitCode, ExecutionResult,
argmodel::{ArgSpec, CommandSpec, PositionalSpec},
builtins,
interfaces::{self, InputFunction, KeyAction, KeySequence},
sys, trace_categories,
};
Expand Down Expand Up @@ -49,6 +50,22 @@ impl BindKeyMap {
}
}

const ID_KEYMAP: &str = "keymap";
const ID_LIST_FUNCS: &str = "list_funcs";
const ID_LIST_FUNCS_AND_BINDINGS: &str = "list_funcs_and_bindings";
const ID_LIST_FUNCS_AND_BINDINGS_REUSABLE: &str = "list_funcs_and_bindings_reusable";
const ID_LIST_KEY_SEQS_MACROS: &str = "list_key_seqs_that_invoke_macros";
const ID_LIST_KEY_SEQS_MACROS_REUSABLE: &str = "list_key_seqs_that_invoke_macros_reusable";
const ID_LIST_VARS: &str = "list_vars";
const ID_LIST_VARS_REUSABLE: &str = "list_vars_reusable";
const ID_QUERY_FUNC_BINDINGS: &str = "query_func_bindings";
const ID_REMOVE_FUNC_BINDINGS: &str = "remove_func_bindings";
const ID_REMOVE_KEY_SEQ_BINDING: &str = "remove_key_seq_binding";
const ID_BINDINGS_FILE: &str = "bindings_file";
const ID_KEY_SEQ_BINDINGS: &str = "key_seq_bindings";
const ID_LIST_KEY_SEQ_BINDINGS: &str = "list_key_seq_bindings";
const ID_KEY_SEQUENCE: &str = "key_sequence";

/// Inspect and modify key bindings and other input configuration.
pub(crate) struct BindCommand {
keymap: Option<BindKeyMap>,
Expand All @@ -68,74 +85,125 @@ pub(crate) struct BindCommand {
key_sequence: Option<String>,
}

impl builtins::Command for BindCommand {
impl builtins::SpecCommand for BindCommand {
type Error = BindError;

fn parser() -> impl bpaf::Parser<Self> {
let keymap = bpaf::short('m')
.help("Name of key map to use.")
.argument::<BindKeyMap>("KEYMAP")
.optional();
let list_funcs = bpaf::short('l').help("List functions.").switch();
let list_funcs_and_bindings = bpaf::short('P')
.help("List functions and bindings.")
.switch();
let list_funcs_and_bindings_reusable = bpaf::short('p')
.help("List functions and bindings in a format suitable for use as input.")
.switch();
let list_key_seqs_that_invoke_macros = bpaf::short('S')
.help("List key sequences that invoke macros.")
.switch();
let list_key_seqs_that_invoke_macros_reusable = bpaf::short('s')
.help("List key sequences that invoke macros in a format suitable for use as input.")
.switch();
let list_vars = bpaf::short('V').help("List variables.").switch();
let list_vars_reusable = bpaf::short('v')
.help("List variables in a format suitable for use as input.")
.switch();
let query_func_bindings = bpaf::short('q')
.help("Find the keys bound to the given named function.")
.argument::<String>("FUNC_NAME")
.optional();
let remove_func_bindings = bpaf::short('u')
.help("Remove all bindings for the given named function.")
.argument::<String>("FUNC_NAME")
.optional();
let remove_key_seq_binding = bpaf::short('r')
.help("Remove the binding for the given key sequence.")
.argument::<String>("KEY_SEQ")
.optional();
let bindings_file = bpaf::short('f')
.help("Import bindings from the given file.")
.argument::<String>("PATH")
.optional();
let key_seq_bindings = bpaf::short('x')
.help("Bind key sequence to command.")
.argument::<String>("BINDING")
.many();
let list_key_seq_bindings = bpaf::short('X')
.help("List key sequence bindings.")
.switch();
let key_sequence = bpaf::positional::<String>("KEY_SEQUENCE")
.help("Key sequence binding to readline function or command.")
.optional();

bpaf::construct!(BindCommand {
fn spec() -> &'static CommandSpec {
static SPEC: CommandSpec = CommandSpec {
args: &[
ArgSpec::value(ID_KEYMAP, &['m'], &[], "KEYMAP", "Name of key map to use."),
ArgSpec::flag(ID_LIST_FUNCS, &['l'], &[], "List functions."),
ArgSpec::flag(
ID_LIST_FUNCS_AND_BINDINGS,
&['P'],
&[],
"List functions and bindings.",
),
ArgSpec::flag(
ID_LIST_FUNCS_AND_BINDINGS_REUSABLE,
&['p'],
&[],
"List functions and bindings in a format suitable for use as input.",
),
ArgSpec::flag(
ID_LIST_KEY_SEQS_MACROS,
&['S'],
&[],
"List key sequences that invoke macros.",
),
ArgSpec::flag(
ID_LIST_KEY_SEQS_MACROS_REUSABLE,
&['s'],
&[],
"List key sequences that invoke macros in a format suitable for use as input.",
),
ArgSpec::flag(ID_LIST_VARS, &['V'], &[], "List variables."),
ArgSpec::flag(
ID_LIST_VARS_REUSABLE,
&['v'],
&[],
"List variables in a format suitable for use as input.",
),
ArgSpec::value(
ID_QUERY_FUNC_BINDINGS,
&['q'],
&[],
"FUNC_NAME",
"Find the keys bound to the given named function.",
),
ArgSpec::value(
ID_REMOVE_FUNC_BINDINGS,
&['u'],
&[],
"FUNC_NAME",
"Remove all bindings for the given named function.",
),
ArgSpec::value(
ID_REMOVE_KEY_SEQ_BINDING,
&['r'],
&[],
"KEY_SEQ",
"Remove the binding for the given key sequence.",
),
ArgSpec::value(
ID_BINDINGS_FILE,
&['f'],
&[],
"PATH",
"Import bindings from the given file.",
),
ArgSpec::value(
ID_KEY_SEQ_BINDINGS,
&['x'],
&[],
"BINDING",
"Bind key sequence to command.",
),
ArgSpec::flag(
ID_LIST_KEY_SEQ_BINDINGS,
&['X'],
&[],
"List key sequence bindings.",
),
],
positionals: &[PositionalSpec::one(ID_KEY_SEQUENCE, "KEY_SEQUENCE")],
};
&SPEC
}

fn from_matches(
values: &mut builtins::argmodel::ParsedValues,
) -> Result<Self, builtins::BuiltinArgParseError> {
let keymap =
match values.value(ID_KEYMAP) {
Some(s) => Some(BindKeyMap::from_str(s).map_err(|message| {
builtins::BuiltinArgParseError {
message,
help_request: false,
}
})?),
None => None,
};

Ok(Self {
keymap,
list_funcs,
list_funcs_and_bindings,
list_funcs_and_bindings_reusable,
list_key_seqs_that_invoke_macros,
list_key_seqs_that_invoke_macros_reusable,
list_vars,
list_vars_reusable,
query_func_bindings,
remove_func_bindings,
remove_key_seq_binding,
bindings_file,
key_seq_bindings,
list_key_seq_bindings,
key_sequence,
list_funcs: values.flag(ID_LIST_FUNCS),
list_funcs_and_bindings: values.flag(ID_LIST_FUNCS_AND_BINDINGS),
list_funcs_and_bindings_reusable: values.flag(ID_LIST_FUNCS_AND_BINDINGS_REUSABLE),
list_key_seqs_that_invoke_macros: values.flag(ID_LIST_KEY_SEQS_MACROS),
list_key_seqs_that_invoke_macros_reusable: values
.flag(ID_LIST_KEY_SEQS_MACROS_REUSABLE),
list_vars: values.flag(ID_LIST_VARS),
list_vars_reusable: values.flag(ID_LIST_VARS_REUSABLE),
query_func_bindings: values.value(ID_QUERY_FUNC_BINDINGS).map(str::to_owned),
remove_func_bindings: values.value(ID_REMOVE_FUNC_BINDINGS).map(str::to_owned),
remove_key_seq_binding: values.value(ID_REMOVE_KEY_SEQ_BINDING).map(str::to_owned),
bindings_file: values.value(ID_BINDINGS_FILE).map(str::to_owned),
key_seq_bindings: values.values(ID_KEY_SEQ_BINDINGS).to_vec(),
list_key_seq_bindings: values.flag(ID_LIST_KEY_SEQ_BINDINGS),
key_sequence: values
.value_of_positional(ID_KEY_SEQUENCE)
.map(str::to_owned),
})
}

Expand Down
Loading