use crate::{
editor::{
Action::*,
Actions::{self, *},
Editor, ViewPort,
},
system::System,
};
use std::path::Path;
#[cfg(feature = "fuzz")]
pub fn parse_command_fuzz(input: &str) {
_ = parse_command(input, 0, Path::new("/home/fuzz"));
}
fn parse_command(input: &str, active_buffer_id: usize, cwd: &Path) -> Result<Actions, String> {
if let Some(actions) = try_parse_single_char_command(input) {
return Ok(actions);
}
let input = input.trim_end();
let (command, args) = match input.split_once(' ') {
Some((command, args)) => (command, args),
None => (input, ""),
};
match command {
"b" | "buffer" => match args.parse::<usize>() {
Ok(id) => Ok(Single(FocusBuffer { id })),
Err(_) => Err(format!("'{args}' is not a valid buffer id")),
},
"bn" | "next-buffer" => Ok(Single(NextBuffer)),
"bp" | "prev-buffer" => Ok(Single(PreviousBuffer)),
"next-column" => Ok(Single(NextColumn)),
"next-window" => Ok(Single(NextWindowInColumn)),
"prev-column" => Ok(Single(PreviousColumn)),
"prev-window" => Ok(Single(PreviousWindowInColumn)),
"balance-all" => Ok(Single(BalanceAll)),
"balance-column" => Ok(Single(BalanceActiveColumn)),
"balance-columns" => Ok(Single(BalanceColumns)),
"balance-windows" => Ok(Single(BalanceWindows)),
"cd" | "change-directory" => {
if args.is_empty() {
Ok(Single(ChangeDirectory { path: None }))
} else {
Ok(Single(ChangeDirectory {
path: Some(args.to_string()),
}))
}
}
"mark-clean" => {
let bufid = if args.is_empty() {
active_buffer_id
} else {
match args.parse::<usize>() {
Ok(bufid) => bufid,
Err(_) => return Err(format!("'{args}' is not a valid buffer id")),
}
};
Ok(Single(MarkClean { bufid }))
}
"db" | "delete-buffer" => Ok(Single(DeleteBuffer { force: false })),
"db!" | "delete-buffer!" => Ok(Single(DeleteBuffer { force: true })),
"dc" | "delete-column" => Ok(Single(DeleteColumn { force: false })),
"dc!" | "delete-column!" => Ok(Single(DeleteColumn { force: true })),
"dw" | "delete-window" => Ok(Single(DeleteWindow { force: false })),
"dw!" | "delete-window!" => Ok(Single(DeleteWindow { force: true })),
"echo" => Ok(Single(SetStatusMessage {
message: args.to_string(),
})),
"expand-dot" => Ok(Single(ExpandDot)),
"E" | "Edit" => {
if args.is_empty() {
Err("No Edit script provided".to_string())
} else {
Ok(Single(EditCommand {
cmd: args.to_string(),
}))
}
}
"execute" => Ok(Single(ExecuteDot)),
"help" => Ok(Single(ShowHelp)),
"kill" => Ok(Single(KillRunningChild)),
"load" => Ok(Single(LoadDot { new_window: false })),
"plumb" => Ok(Single(Plumb {
txt: args.to_string(),
new_window: false,
})),
"lsp-completion" => Ok(Single(LspCompletion)),
"lsp-find-references" => Ok(Single(LspReferences)),
"lsp-format" => Ok(Single(LspFormat)),
"lsp-goto-declaration" => Ok(Single(LspGotoDeclaration)),
"lsp-goto-definition" => Ok(Single(LspGotoDefinition)),
"lsp-goto-type-definition" => Ok(Single(LspGotoTypeDefinition)),
"lsp-hover" => Ok(Single(LspHover)),
"lsp-rename" => Ok(Single(LspRenamePrepare)),
"lsp-show-capabilities" => Ok(Single(LspShowCapabilities)),
"lsp-show-diagnostics" => Ok(Single(LspShowDiagnostics)),
"lsp-start" => Ok(Single(LspStart)),
"lsp-stop" => Ok(Single(LspStop)),
"o" | "open" => {
if args.is_empty() {
Err("No filename provided".to_string())
} else {
Ok(Single(OpenFile {
path: args.to_string(),
}))
}
}
"O" | "open-in-new-window" => {
if args.is_empty() {
Err("No filename provided".to_string())
} else {
Ok(Single(OpenFileInNewWindow {
path: args.to_string(),
}))
}
}
"new-column" => Ok(Single(NewColumn)),
"new-window" => Ok(Single(NewWindow)),
"pwd" => Ok(Single(SetStatusMessage {
message: cwd.display().to_string(),
})),
"q" | "quit" | "Exit" => Ok(Single(Exit { force: false })),
"q!" | "quit!" | "Exit!" => Ok(Single(Exit { force: true })),
"reload-config" => Ok(Single(ReloadConfig)),
"reload-buffer" | "Get" => {
if args.is_empty() {
Ok(Single(ReloadActiveBuffer))
} else {
match args.parse::<usize>() {
Ok(id) => Ok(Single(ReloadBuffer { id })),
Err(_) => Err(format!("'{args}' is not a valid buffer id")),
}
}
}
"rename-buffer" => Ok(Single(RenameActiveBuffer {
name: args.to_string(),
})),
"resize-column" => match args.parse::<i16>() {
Ok(delta) => Ok(Single(ResizeActiveColumn { delta })),
Err(_) => Err(format!("'{args}' is not a valid delta")),
},
"resize-window" => match args.parse::<i16>() {
Ok(delta) => Ok(Single(ResizeActiveWindow { delta })),
Err(_) => Err(format!("'{args}' is not a valid delta")),
},
"clear-scratch" => Ok(Single(ClearScratch)),
"toggle-scratch" => Ok(Single(ToggleScratch)),
"ts-show-tree" => Ok(Single(TsShowTree)),
"view-logs" => Ok(Single(ViewLogs)),
"w" | "write" => {
if args.is_empty() {
Ok(Single(SaveBuffer { force: false }))
} else {
Ok(Single(SaveBufferAs {
path: args.to_string(),
force: false,
}))
}
}
"w!" | "write!" => {
if args.is_empty() {
Ok(Single(SaveBuffer { force: true }))
} else {
Ok(Single(SaveBufferAs {
path: args.to_string(),
force: true,
}))
}
}
"wa" | "write-all" => Ok(Single(SaveBufferAll { force: false })),
"wa!" | "write-all!" => Ok(Single(SaveBufferAll { force: true })),
"wq" | "write-quit" => Ok(Multi(vec![
SaveBuffer { force: false },
Exit { force: false },
])),
"wq!" | "write-quit!" => Ok(Multi(vec![
SaveBuffer { force: true },
Exit { force: true },
])),
"viewport-bottom" => Ok(Single(SetViewPort(ViewPort::Bottom))),
"viewport-top" => Ok(Single(SetViewPort(ViewPort::Top))),
"viewport-center" => Ok(Single(SetViewPort(ViewPort::Center))),
"" => Err(String::new()),
_ => Err(String::new()),
}
}
impl<S> Editor<S>
where
S: System,
{
pub(super) fn parse_command(&mut self, input: &str) -> Option<Actions> {
match parse_command(input, self.active_buffer_id(), &self.cwd) {
Ok(actions) => Some(actions),
Err(msg) if msg.is_empty() => None,
Err(msg) => {
self.set_status_message(&msg);
None
}
}
}
}
fn try_parse_single_char_command(input: &str) -> Option<Actions> {
match input.chars().next() {
Some('!') => Some(Single(ShellRun {
cmd: input[1..].to_string(),
})),
Some('|') => Some(Single(ShellPipe {
cmd: input[1..].to_string(),
})),
Some('<') => Some(Single(ShellReplace {
cmd: input[1..].to_string(),
})),
Some('>') => Some(Single(ShellSend {
cmd: input[1..].to_string(),
})),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::editor::built_in_commands::built_in_commands;
use std::path::PathBuf;
#[test]
fn known_commands_parse() {
for (cmds, _) in built_in_commands().into_iter() {
for raw_cmd in cmds.into_iter() {
let cmd = format!("{raw_cmd} 1");
if let Err(msg) = parse_command(&cmd, 0, &PathBuf::new()) {
panic!("{cmd:?} failed to parse: {msg:?}");
}
}
}
for ch in "!<>|".chars() {
let cmd = format!("{ch}some-shell-command");
if let Err(msg) = parse_command(&cmd, 0, &PathBuf::new()) {
panic!("{cmd:?} failed to parse: {msg:?}");
}
}
}
}