use std::{
collections::HashMap,
fmt::Display,
ops::Range,
sync::{Arc, Mutex},
};
pub use self::{global::*, parameters::*};
use crate::{
data::Pass,
form::FormId,
text::{Text, txt},
utils::catch_panic,
};
mod default;
mod parameters;
mod global {
use std::{
any::TypeId,
ops::Range,
sync::{Arc, Mutex},
};
use super::{CmdResult, Commands};
use crate::data::BulkDataWriter;
#[doc(inline)]
use crate::{cmd::CmdFn, context, data::Pass, form::FormId, session::DuatEvent, text::Text};
static COMMANDS: BulkDataWriter<Commands> = BulkDataWriter::new();
pub struct CmdBuilder {
command: Option<super::Command>,
param_n: usize,
}
impl CmdBuilder {
pub fn doc(mut self, short: Text, long: Option<Text>) -> Self {
let command = self.command.as_mut().unwrap();
command.doc.short = Some(Arc::new(short));
command.doc.long = long.map(Arc::new);
self
}
#[track_caller]
pub fn doc_param(mut self, short: Text, long: Option<Text>, name: Option<Text>) -> Self {
assert!(
!self.command.as_ref().unwrap().doc.params.is_empty(),
"Command has no parameters to be documented"
);
assert!(
self.param_n < self.command.as_ref().unwrap().doc.params.len(),
"Too many docs for the number of Parameters",
);
let param = {
let params = &mut self.command.as_mut().unwrap().doc.params;
&mut Arc::get_mut(params).unwrap()[self.param_n]
};
param.short = Some(short);
param.long = long;
if let Some(name) = name {
param.arg_name = name;
}
self.param_n += 1;
self
}
}
impl Drop for CmdBuilder {
fn drop(&mut self) {
let command = self.command.take().unwrap();
COMMANDS.mutate(move |cmds| cmds.add(command));
}
}
#[derive(Debug, Clone)]
pub struct ParamDoc {
pub arg_name: Text,
pub short: Option<Text>,
pub long: Option<Text>,
}
pub fn add<Cmd: CmdFn<impl std::any::Any>>(caller: &str, mut cmd: Cmd) -> CmdBuilder {
CmdBuilder {
command: Some(super::Command::new(
caller.to_string(),
Arc::new(Mutex::new(move |pa: &mut Pass, args: super::Args| {
cmd.call(pa, args)
})),
Cmd::check_args,
Cmd::param_arg_names(),
)),
param_n: 0,
}
}
pub fn quit() {
queue("quit");
}
pub fn edit(pa: &mut Pass, buffer: impl std::fmt::Display) -> CmdResult {
call(pa, format!("edit {buffer}"))
}
pub fn buffer(pa: &mut Pass, buffer: impl std::fmt::Display) -> CmdResult {
call(pa, format!("buffer {buffer}"))
}
pub fn next_buffer(pa: &mut Pass) -> CmdResult {
call(pa, "next-buffer")
}
pub fn prev_buffer(pa: &mut Pass) -> CmdResult {
call(pa, "prev-buffer")
}
pub fn next_global_buffer(pa: &mut Pass) -> CmdResult {
call(pa, "next-buffer --global")
}
pub fn prev_global_buffer(pa: &mut Pass) -> CmdResult {
call(pa, "prev-buffer --global")
}
pub fn alias(alias: impl ToString, command: impl ToString) {
let alias = alias.to_string();
let command = command.to_string();
COMMANDS.mutate(move |cmds| context::logs().push_cmd_result(cmds.alias(alias, command)))
}
pub fn call(pa: &mut Pass, call: impl std::fmt::Display) -> CmdResult {
COMMANDS.write(pa).get_cmd(call).and_then(|cmd| cmd(pa))
}
#[allow(unused_must_use)]
pub fn call_notify(pa: &mut Pass, call: impl std::fmt::Display) -> CmdResult {
let result = COMMANDS
.write(pa)
.get_cmd(call.to_string())
.and_then(|cmd| cmd(pa));
context::logs().push_cmd_result(result.clone());
result
}
pub fn queue(call: impl std::fmt::Display) {
let call = call.to_string();
crate::context::sender().send(DuatEvent::QueuedFunction(Box::new(move |pa| {
let _ = COMMANDS.write(pa).get_cmd(call).and_then(|cmd| cmd(pa));
})));
}
pub fn queue_notify(call: impl std::fmt::Display) {
let call = call.to_string();
crate::context::sender().send(DuatEvent::QueuedFunction(Box::new(move |pa| {
context::logs()
.push_cmd_result(COMMANDS.write(pa).get_cmd(call).and_then(|cmd| cmd(pa)));
})))
}
pub fn queue_and(call: impl std::fmt::Display, map: impl FnOnce(CmdResult) + Send + 'static) {
let call = call.to_string();
crate::context::sender().send(DuatEvent::QueuedFunction(Box::new(move |pa| {
map(COMMANDS.write(pa).get_cmd(call).and_then(|cmd| cmd(pa)));
})))
}
pub fn queue_notify_and(
call: impl std::fmt::Display,
map: impl FnOnce(CmdResult) + Send + 'static,
) {
let call = call.to_string();
crate::context::sender().send(DuatEvent::QueuedFunction(Box::new(move |pa| {
let result = COMMANDS.write(pa).get_cmd(call).and_then(|cmd| cmd(pa));
context::logs().push_cmd_result(result.clone());
map(result)
})));
}
pub fn check_args(
pa: &mut Pass,
call: &str,
) -> Option<(
Vec<(Range<usize>, Option<FormId>)>,
Option<(Range<usize>, Text)>,
)> {
COMMANDS.write(pa).check_args(call).map(|ca| ca(pa))
}
pub fn last_parsed_parameters(pa: &mut Pass, call: &str) -> Option<Vec<TypeId>> {
Some(COMMANDS.write(pa).args_after_check(call)?(pa).last_parsed())
}
#[derive(Debug, Clone)]
pub struct CmdDoc {
pub caller: Arc<str>,
pub short: Option<Arc<Text>>,
pub long: Option<Arc<Text>>,
pub params: Arc<[ParamDoc]>,
}
#[derive(Clone)]
pub struct AliasDescription {
pub caller: Arc<str>,
pub replacement: Arc<str>,
pub cmd: CmdDoc,
}
#[derive(Clone)]
pub enum Description {
Command(CmdDoc),
Alias(AliasDescription),
}
impl Description {
pub fn caller(&self) -> &str {
match self {
Description::Command(cmd_description) => &cmd_description.caller,
Description::Alias(alias_description) => &alias_description.caller,
}
}
}
pub fn cmd_list(pa: &mut Pass) -> Vec<Description> {
let commands = COMMANDS.write(pa);
commands
.list
.iter()
.map(|cmd| Description::Command(cmd.doc.clone()))
.chain(commands.aliases.iter().map(|(caller, (cmd, replacement))| {
Description::Alias(AliasDescription {
caller: caller.clone(),
replacement: replacement.clone(),
cmd: cmd.doc.clone(),
})
}))
.collect()
}
}
struct Commands {
list: Vec<Command>,
aliases: HashMap<Arc<str>, (Command, Arc<str>)>,
}
impl Commands {
fn alias(&mut self, alias: String, call: String) -> CmdResult {
if alias.split_whitespace().count() != 1 {
return Err(txt!("Alias [a]{alias}[] is not a single word"));
}
let caller = call
.split_whitespace()
.next()
.ok_or(txt!("The command is empty"))?
.to_string();
let mut cmds = self.list.iter();
if let Some(command) = cmds.find(|cmd| cmd.doc.caller.as_ref() == caller) {
let entry = (command.clone(), Arc::from(call));
self.aliases.insert(Arc::from(alias), entry);
Ok(None)
} else {
Err(txt!("The caller [a]{caller}[] was not found"))
}
}
fn get_cmd(
&self,
call: impl Display,
) -> Result<impl for<'a> FnOnce(&'a mut Pass) -> CmdResult + 'static, Text> {
let call = call.to_string();
let mut args = call.split_whitespace();
let caller = args.next().ok_or(txt!("The command is empty"))?.to_string();
let (command, call) = {
if let Some(command) = self.aliases.get(caller.as_str()) {
let (alias, aliased_call) = command;
let mut aliased_call = aliased_call.to_string() + " ";
aliased_call.push_str(call.strip_prefix(&caller).unwrap());
(alias.clone(), aliased_call)
} else {
let command = self
.list
.iter()
.find(|cmd| cmd.doc.caller.as_ref() == caller)
.ok_or(txt!("[a]{caller}[]: No such command"))?;
(command.clone(), call.clone())
}
};
let silent = call.len() > call.trim_start().len();
let cmd = command.cmd.clone();
Ok(move |pa: &mut Pass| {
let args = Args::new(&call);
match catch_panic(move || cmd.lock().unwrap()(pa, args)) {
Some(result) => result
.map(|ok| ok.filter(|_| !silent))
.map_err(|err| txt!("[a]{caller}[]: {err}")),
None => Err(txt!("[a]{caller}[]: Command panicked")),
}
})
}
fn add(&mut self, command: Command) {
self.list.retain(|cmd| cmd.doc.caller != command.doc.caller);
self.list.push(command);
}
fn check_args<'a>(&self, call: &'a str) -> Option<impl FnOnce(&Pass) -> CheckedArgs + 'a> {
let mut args = call.split_whitespace();
let caller = args.next()?.to_string();
let check_args = if let Some((command, _)) = self.aliases.get(caller.as_str()) {
command.check_args
} else {
let command = self
.list
.iter()
.find(|cmd| cmd.doc.caller.as_ref() == caller)?;
command.check_args
};
Some(move |pa: &Pass| check_args(pa, &mut Args::new(call)))
}
fn args_after_check<'a>(&self, call: &'a str) -> Option<impl FnOnce(&Pass) -> Args<'a> + 'a> {
let mut args = call.split_whitespace();
let caller = args.next()?.to_string();
let check_args = if let Some((command, _)) = self.aliases.get(caller.as_str()) {
command.check_args
} else {
let command = self
.list
.iter()
.find(|cmd| cmd.doc.caller.as_ref() == caller)?;
command.check_args
};
Some(move |pa: &Pass| {
let mut args = Args::new(call);
check_args(pa, &mut args);
args
})
}
}
impl Default for Commands {
fn default() -> Self {
default::add_defalt_commands();
Self {
list: Default::default(),
aliases: Default::default(),
}
}
}
pub type CmdResult = Result<Option<Text>, Text>;
#[derive(Clone)]
struct Command {
cmd: InnerCmdFn,
check_args: CheckerFn,
doc: CmdDoc,
}
impl Command {
fn new(
caller: String,
cmd: InnerCmdFn,
check_args: CheckerFn,
param_arg_names: Vec<Text>,
) -> Self {
if caller.split_whitespace().count() != 1 {
panic!("Command caller \"{caller}\" contains more than one word");
}
Self {
cmd,
check_args,
doc: CmdDoc {
caller: caller.into(),
short: None,
long: None,
params: param_arg_names
.into_iter()
.map(|arg_name| ParamDoc { arg_name, short: None, long: None })
.collect(),
},
}
}
}
pub trait Caller<'a>: Sized {
fn into_callers(self) -> impl Iterator<Item = &'a str>;
}
impl<'a> Caller<'a> for &'a str {
fn into_callers(self) -> impl Iterator<Item = &'a str> {
[self].into_iter()
}
}
impl<'a> Caller<'a> for &'a [&'a str] {
fn into_callers(self) -> impl Iterator<Item = &'a str> {
self.iter().cloned()
}
}
impl<'a, const N: usize> Caller<'a> for [&'a str; N] {
fn into_callers(self) -> impl Iterator<Item = &'a str> {
self.into_iter()
}
}
type InnerCmdFn = Arc<Mutex<dyn FnMut(&mut Pass, Args) -> CmdResult + Send + 'static>>;
type CheckerFn = fn(&Pass, &mut Args) -> CheckedArgs;
type CheckedArgs = (
Vec<(Range<usize>, Option<FormId>)>,
Option<(Range<usize>, Text)>,
);
trait CmdFn<Arguments>: Send + 'static {
fn call(&mut self, pa: &mut Pass, args: Args) -> CmdResult;
fn check_args(
pa: &Pass,
args: &mut Args,
) -> (
Vec<(Range<usize>, Option<FormId>)>,
Option<(Range<usize>, Text)>,
);
fn param_arg_names() -> Vec<Text>;
}
impl<F: FnMut(&mut Pass) -> CmdResult + Send + 'static> CmdFn<()> for F {
fn call(&mut self, pa: &mut Pass, _: Args) -> CmdResult {
self(pa)
}
fn check_args(
_: &Pass,
args: &mut Args,
) -> (
Vec<(Range<usize>, Option<FormId>)>,
Option<(Range<usize>, Text)>,
) {
if let Some(start) = args.next_start() {
let err = txt!("Too many arguments");
return (Vec::new(), Some((start..args.param_range().end, err)));
}
(Vec::new(), None)
}
fn param_arg_names() -> Vec<Text> {
Vec::new()
}
}
macro_rules! implCmdFn {
($($param:ident),+) => {
impl<$($param),+, F> CmdFn<($($param,)+)> for F
where
$($param: Parameter,)+
F: FnMut(&mut Pass, $($param),+) -> CmdResult + Send + 'static
{
#[allow(non_snake_case)]
fn call(&mut self, pa: &mut Pass, mut args: Args) -> CmdResult {
$(
let ($param, _) = $param::from_args(pa, &mut args)?;
)+
self(pa, $($param),+)
}
fn check_args(
pa: &Pass,
args: &mut Args,
) -> (
Vec<(Range<usize>, Option<FormId>)>,
Option<(Range<usize>, Text)>,
) {
let mut ok_ranges = Vec::new();
$(
let start = args.next_start();
args.use_completions_for::<$param>();
let result = args.next_as_with_form::<$param>(pa);
match result {
Ok((_, form)) => {
if let Some(start) = start.filter(|s| args.param_range().end > *s) {
ok_ranges.push((start..args.param_range().end, form));
}
}
Err(err) => match start.filter(|s| args.param_range().end > *s) {
Some(_) => return (ok_ranges, Some((args.param_range(), err))),
None => return (ok_ranges, None)
}
}
)+
if let Some(start) = args.next_start() {
let err = txt!("Too many arguments");
return (ok_ranges, Some((start..args.param_range().end, err)));
}
(ok_ranges, None)
}
fn param_arg_names() -> Vec<Text> {
vec![$($param::arg_name()),+]
}
}
}
}
implCmdFn!(P0);
implCmdFn!(P0, P1);
implCmdFn!(P0, P1, P2);
implCmdFn!(P0, P1, P2, P3);
implCmdFn!(P0, P1, P2, P3, P4);
implCmdFn!(P0, P1, P2, P3, P4, P5);
implCmdFn!(P0, P1, P2, P3, P4, P5, P6);
implCmdFn!(P0, P1, P2, P3, P4, P5, P6, P7);
implCmdFn!(P0, P1, P2, P3, P4, P5, P6, P7, P8);
implCmdFn!(P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
implCmdFn!(P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10);
implCmdFn!(P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11);