pub mod attr;
pub mod ctx;
pub mod def;
pub mod opt;
#[cfg(test)]
mod attr_tests;
#[cfg(test)]
mod opt_tests;
use crate::buf::unicode::char_is_whitespace;
use crate::chan::ExCommandReq;
use crate::js::JsFuture;
use crate::js::JsRuntime;
use crate::js::TaskId;
use crate::js::command::ctx::CommandContext;
use crate::js::command::ctx::CommandContextBuilder;
use crate::js::converter::*;
use crate::js::execute_module;
use crate::prelude::*;
use compact_str::CompactString;
use compact_str::ToCompactString;
use def::CommandDefinitionRc;
use itertools::Itertools;
const JS_COMMAND_NAME: &str = "js";
#[derive(Debug, Clone)]
pub struct CommandFuture {
pub task_id: TaskId,
pub name: CompactString,
pub context: CommandContext,
pub is_builtin_js: bool,
pub definition: Option<CommandDefinitionRc>,
}
impl JsFuture for CommandFuture {
fn run(&mut self, scope: &mut v8::PinScope) {
trace!(
"|CommandFuture| command name:{:?}({:?})",
self.name, self.task_id
);
if self.is_builtin_js {
let filename = format!("<command-js:{}>", self.task_id);
debug_assert_eq!(self.context.args.len(), 1);
execute_module(scope, &filename, Some(self.context.args[0].trim()));
} else {
let def = self.definition.clone().unwrap();
let undefined = v8::undefined(scope).into();
let callback = v8::Local::new(scope, (*def.callback).clone());
let args: Vec<v8::Local<v8::Value>> =
vec![self.context.to_v8(scope).into()];
v8::tc_scope!(let tc_scope, scope);
callback.call(tc_scope, undefined, &args);
if tc_scope.has_caught() {
let exception = tc_scope.exception().unwrap();
let exception = v8::Global::new(tc_scope, exception);
let state_rc = JsRuntime::state(tc_scope);
state_rc
.borrow_mut()
.exceptions
.capture_exception(exception);
}
}
}
}
#[derive(Debug, Default)]
pub struct CommandManager {
commands: BTreeMap<CompactString, CommandDefinitionRc>,
aliases: FoldMap<CompactString, CompactString>,
}
arc_mutex_ptr!(CommandManager);
pub type CommandManagerKeys<'a> =
std::collections::btree_map::Keys<'a, CompactString, CommandDefinitionRc>;
pub type CommandManagerValues<'a> =
std::collections::btree_map::Values<'a, CompactString, CommandDefinitionRc>;
pub type CommandManagerIter<'a> =
std::collections::btree_map::Iter<'a, CompactString, CommandDefinitionRc>;
impl CommandManager {
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
pub fn len(&self) -> usize {
self.commands.len()
}
pub fn remove(&mut self, name: &str) -> Option<CommandDefinitionRc> {
self.commands.remove(name)
}
pub fn insert(
&mut self,
name: CompactString,
definition: CommandDefinitionRc,
) -> TheResult<Option<CommandDefinitionRc>> {
let alias = definition.options.alias.clone();
if !definition.options.force {
if self.commands.contains_key(&name) {
return Err(TheErr::CommandAlreadyExist(name));
}
if let Some(ref alias) = alias
&& self.aliases.contains_key(alias.as_str())
{
return Err(TheErr::CommandAlreadyExist(alias.clone()));
}
}
if let Some(alias) = alias {
self.aliases.insert(alias, name.clone());
}
let maybe_old = self.commands.insert(name, definition);
Ok(maybe_old)
}
pub fn get(&self, name: &str) -> Option<CommandDefinitionRc> {
self.commands.get(name).cloned()
}
pub fn contains_key(&self, name: &str) -> bool {
self.commands.contains_key(name)
}
pub fn keys(&self) -> CommandManagerKeys<'_> {
self.commands.keys()
}
pub fn values(&self) -> CommandManagerValues<'_> {
self.commands.values()
}
pub fn iter(&self) -> CommandManagerIter<'_> {
self.commands.iter()
}
pub fn first_key_value(
&self,
) -> Option<(&CompactString, &CommandDefinitionRc)> {
self.commands.first_key_value()
}
pub fn last_key_value(
&self,
) -> Option<(&CompactString, &CommandDefinitionRc)> {
self.commands.last_key_value()
}
}
impl CommandManager {
pub fn parse(&self, req: &ExCommandReq) -> Option<CommandFuture> {
debug_assert_eq!(req.payload.trim(), req.payload);
let mut context = CommandContextBuilder::default();
context.current_buffer_id(req.current_buf_id);
context.current_window_id(req.current_win_id);
let (mut name, body) = match req.payload.find(char_is_whitespace) {
Some(pos) => {
let name = req.payload.get(0..pos).unwrap().trim().to_compact_string();
let body = req.payload.get(pos..).unwrap().to_compact_string();
(name, body)
}
None => {
let name = req.payload.trim().to_compact_string();
let body = "".to_compact_string();
(name, body)
}
};
if name.ends_with("!") {
let _last = name.pop();
debug_assert_eq!(_last, Some('!'));
context.bang(true);
}
let is_builtin_js = name == JS_COMMAND_NAME;
let task_id = TaskId::next();
if is_builtin_js {
debug_assert!(!self.commands.contains_key(&name));
let args = vec![body];
context.args(args);
let context = context.build().unwrap();
Some(CommandFuture {
task_id,
name,
context,
is_builtin_js,
definition: None,
})
} else if self.commands.contains_key(&name)
|| self.aliases.contains_key(&name)
{
let name = self.aliases.get(&name).unwrap_or(&name).clone();
debug_assert!(self.commands.contains_key(&name));
let args = body
.split(char_is_whitespace)
.map(|a| a.to_compact_string())
.collect_vec();
context.args(args);
let context = context.build().unwrap();
let definition = Some(self.commands.get(&name).unwrap().clone());
Some(CommandFuture {
task_id,
name,
context,
is_builtin_js,
definition,
})
} else {
None
}
}
}