use std::sync::LazyLock;
use hjkl_ex::{ArgKind, ExEffect, HostCmd};
use super::App;
pub(crate) struct TabNextCmd;
impl HostCmd<App> for TabNextCmd {
fn name(&self) -> &'static str {
"tabnext"
}
fn aliases(&self) -> &'static [&'static str] {
&["tabn"]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.do_tabnext();
Some(ExEffect::Ok)
}
}
pub(crate) struct SplitCmd;
impl HostCmd<App> for SplitCmd {
fn name(&self) -> &'static str {
"split"
}
fn aliases(&self) -> &'static [&'static str] {
&["sp"]
}
fn min_prefix(&self) -> usize {
2
}
fn arg_kind(&self) -> ArgKind {
ArgKind::Path
}
fn run(&self, app: &mut App, args: &str) -> Option<ExEffect> {
app.do_split(args.trim());
Some(ExEffect::Ok)
}
}
pub(crate) struct VsplitCmd;
impl HostCmd<App> for VsplitCmd {
fn name(&self) -> &'static str {
"vsplit"
}
fn aliases(&self) -> &'static [&'static str] {
&["vsp"]
}
fn min_prefix(&self) -> usize {
2
}
fn arg_kind(&self) -> ArgKind {
ArgKind::Path
}
fn run(&self, app: &mut App, args: &str) -> Option<ExEffect> {
app.do_vsplit(args.trim());
Some(ExEffect::Ok)
}
}
pub(crate) struct CloseCmd;
impl HostCmd<App> for CloseCmd {
fn name(&self) -> &'static str {
"close"
}
fn aliases(&self) -> &'static [&'static str] {
&["clo"]
}
fn min_prefix(&self) -> usize {
3
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.close_focused_window();
Some(ExEffect::Ok)
}
}
pub(crate) struct TabnewCmd;
impl HostCmd<App> for TabnewCmd {
fn name(&self) -> &'static str {
"tabnew"
}
fn aliases(&self) -> &'static [&'static str] {
&["tabedit", "tabe"]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::Path
}
fn run(&self, app: &mut App, args: &str) -> Option<ExEffect> {
app.do_tabnew(args.trim());
Some(ExEffect::Ok)
}
}
pub(crate) struct TabprevCmd;
impl HostCmd<App> for TabprevCmd {
fn name(&self) -> &'static str {
"tabprev"
}
fn aliases(&self) -> &'static [&'static str] {
&["tabp", "tabN"]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.do_tabprev();
Some(ExEffect::Ok)
}
}
pub(crate) struct TabcloseCmd;
impl HostCmd<App> for TabcloseCmd {
fn name(&self) -> &'static str {
"tabclose"
}
fn aliases(&self) -> &'static [&'static str] {
&["tabc"]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.do_tabclose();
Some(ExEffect::Ok)
}
}
pub(crate) struct TabmoveCmd;
impl HostCmd<App> for TabmoveCmd {
fn name(&self) -> &'static str {
"tabmove"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::Raw
}
fn run(&self, app: &mut App, args: &str) -> Option<ExEffect> {
app.do_tabmove(args.trim());
Some(ExEffect::Ok)
}
}
pub(crate) struct OnlyCmd;
impl HostCmd<App> for OnlyCmd {
fn name(&self) -> &'static str {
"only"
}
fn aliases(&self) -> &'static [&'static str] {
&["on"]
}
fn min_prefix(&self) -> usize {
2
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.only_focused_window();
Some(ExEffect::Ok)
}
}
pub(crate) struct BnextCmd;
impl HostCmd<App> for BnextCmd {
fn name(&self) -> &'static str {
"bnext"
}
fn aliases(&self) -> &'static [&'static str] {
&["bn"]
}
fn min_prefix(&self) -> usize {
2
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.buffer_next();
Some(ExEffect::Ok)
}
}
pub(crate) struct BprevCmd;
impl HostCmd<App> for BprevCmd {
fn name(&self) -> &'static str {
"bprevious"
}
fn aliases(&self) -> &'static [&'static str] {
&["bp", "bNext"]
}
fn min_prefix(&self) -> usize {
2
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.buffer_prev();
Some(ExEffect::Ok)
}
}
pub(crate) struct BfirstCmd;
impl HostCmd<App> for BfirstCmd {
fn name(&self) -> &'static str {
"bfirst"
}
fn aliases(&self) -> &'static [&'static str] {
&["bf"]
}
fn min_prefix(&self) -> usize {
2
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.switch_to(0);
Some(ExEffect::Ok)
}
}
pub(crate) struct BlastCmd;
impl HostCmd<App> for BlastCmd {
fn name(&self) -> &'static str {
"blast"
}
fn aliases(&self) -> &'static [&'static str] {
&["bl"]
}
fn min_prefix(&self) -> usize {
2
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
let last = app.slots.len().saturating_sub(1);
app.switch_to(last);
Some(ExEffect::Ok)
}
}
pub(crate) struct BuffersCmd;
impl HostCmd<App> for BuffersCmd {
fn name(&self) -> &'static str {
"buffers"
}
fn aliases(&self) -> &'static [&'static str] {
&["ls", "files"]
}
fn min_prefix(&self) -> usize {
2
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
let info = app.list_buffers();
Some(ExEffect::Info(info))
}
}
pub(crate) struct ClipboardCmd;
impl HostCmd<App> for ClipboardCmd {
fn name(&self) -> &'static str {
"clipboard"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
let info = app.clipboard_status();
Some(ExEffect::Info(info))
}
}
pub(crate) struct PerfCmd;
impl HostCmd<App> for PerfCmd {
fn name(&self) -> &'static str {
"perf"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.perf_overlay = !app.perf_overlay;
app.recompute_hits = 0;
app.recompute_throttled = 0;
app.recompute_runs = 0;
app.status_message = Some(if app.perf_overlay {
"perf overlay: on (counters reset)".into()
} else {
"perf overlay: off".into()
});
Some(ExEffect::Ok)
}
}
pub(crate) struct PickerCmd;
impl HostCmd<App> for PickerCmd {
fn name(&self) -> &'static str {
"picker"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
6
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.open_picker();
Some(ExEffect::Ok)
}
}
pub(crate) struct RgCmd;
impl HostCmd<App> for RgCmd {
fn name(&self) -> &'static str {
"rg"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
2
}
fn arg_kind(&self) -> ArgKind {
ArgKind::Path
}
fn run(&self, app: &mut App, args: &str) -> Option<ExEffect> {
let pattern = if args.is_empty() { None } else { Some(args) };
app.open_grep_picker(pattern);
Some(ExEffect::Ok)
}
}
pub(crate) struct BCmd;
impl HostCmd<App> for BCmd {
fn name(&self) -> &'static str {
"b"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
1
}
fn arg_kind(&self) -> ArgKind {
ArgKind::Buffer
}
fn run(&self, app: &mut App, args: &str) -> Option<ExEffect> {
let arg = args.trim();
if arg.is_empty() {
return Some(ExEffect::Error("E94: No matching buffer".into()));
}
if arg.chars().all(|c| c.is_ascii_digit()) {
let n: usize = arg.parse().unwrap_or(0);
if n == 0 || n > app.slots.len() {
return Some(ExEffect::Error(format!("E86: Buffer {n} does not exist")));
}
app.switch_to(n - 1);
} else {
let arg_lower = arg.to_lowercase();
let matches: Vec<usize> = app
.slots
.iter()
.enumerate()
.filter(|(_, s)| {
s.filename
.as_ref()
.and_then(|p| p.file_name())
.and_then(|n| n.to_str())
.map(|n| n.to_lowercase().contains(&arg_lower))
.unwrap_or(false)
})
.map(|(i, _)| i)
.collect();
match matches.len() {
0 => {
return Some(ExEffect::Error(format!(
"E94: No matching buffer for {arg}"
)));
}
1 => {
app.switch_to(matches[0]);
}
_ => {
return Some(ExEffect::Error(format!(
"E93: More than one match for {arg}"
)));
}
}
}
Some(ExEffect::Ok)
}
}
pub(crate) struct BpickerCmd;
impl HostCmd<App> for BpickerCmd {
fn name(&self) -> &'static str {
"bpicker"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
6
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.open_buffer_picker();
Some(ExEffect::Ok)
}
}
pub(crate) struct ChecktimeCmd;
impl HostCmd<App> for ChecktimeCmd {
fn name(&self) -> &'static str {
"checktime"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
5
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.checktime_all();
Some(ExEffect::Ok)
}
}
pub(crate) struct VnewCmd;
impl HostCmd<App> for VnewCmd {
fn name(&self) -> &'static str {
"vnew"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.do_vnew();
Some(ExEffect::Ok)
}
}
pub(crate) struct NewCmd;
impl HostCmd<App> for NewCmd {
fn name(&self) -> &'static str {
"new"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
3
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.do_new();
Some(ExEffect::Ok)
}
}
pub(crate) struct TabfirstCmd;
impl HostCmd<App> for TabfirstCmd {
fn name(&self) -> &'static str {
"tabfirst"
}
fn aliases(&self) -> &'static [&'static str] {
&["tabrewind", "tabr"]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.do_tabfirst();
Some(ExEffect::Ok)
}
}
pub(crate) struct TablastCmd;
impl HostCmd<App> for TablastCmd {
fn name(&self) -> &'static str {
"tablast"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.do_tablast();
Some(ExEffect::Ok)
}
}
fn parse_resize_arg(arg: &str) -> Option<i32> {
let arg = arg.trim();
if arg.is_empty() {
return None;
}
if let Some(rest) = arg.strip_prefix('+') {
rest.trim().parse::<i32>().ok()
} else if let Some(rest) = arg.strip_prefix('-') {
rest.trim().parse::<i32>().ok().map(|n| -n)
} else {
arg.parse::<i32>().ok()
}
}
pub(crate) struct TabonlyCmd;
impl HostCmd<App> for TabonlyCmd {
fn name(&self) -> &'static str {
"tabonly"
}
fn aliases(&self) -> &'static [&'static str] {
&["tabo"]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.do_tabonly();
Some(ExEffect::Ok)
}
}
pub(crate) struct TabsCmd;
impl HostCmd<App> for TabsCmd {
fn name(&self) -> &'static str {
"tabs"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.do_tabs();
Some(ExEffect::Ok)
}
}
pub(crate) struct ResizeCmd;
impl HostCmd<App> for ResizeCmd {
fn name(&self) -> &'static str {
"resize"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
6
}
fn arg_kind(&self) -> ArgKind {
ArgKind::Raw
}
fn run(&self, app: &mut App, args: &str) -> Option<ExEffect> {
if let Some(delta) = parse_resize_arg(args) {
app.resize_height(delta);
Some(ExEffect::Ok)
} else {
Some(ExEffect::Error("E: invalid resize argument".into()))
}
}
}
pub(crate) struct VerticalResizeCmd;
impl HostCmd<App> for VerticalResizeCmd {
fn name(&self) -> &'static str {
"vertical"
}
fn aliases(&self) -> &'static [&'static str] {
&["vert"]
}
fn min_prefix(&self) -> usize {
4
}
fn arg_kind(&self) -> ArgKind {
ArgKind::Raw
}
fn run(&self, app: &mut App, args: &str) -> Option<ExEffect> {
let rest = args
.strip_prefix("resize")
.or_else(|| args.strip_prefix("res"))?;
if let Some(delta) = parse_resize_arg(rest) {
app.resize_width(delta);
Some(ExEffect::Ok)
} else {
Some(ExEffect::Error("E: invalid resize argument".into()))
}
}
}
pub(crate) struct RenameCmd;
impl HostCmd<App> for RenameCmd {
fn name(&self) -> &'static str {
"Rename"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
6
}
fn arg_kind(&self) -> ArgKind {
ArgKind::Raw
}
fn run(&self, app: &mut App, args: &str) -> Option<ExEffect> {
let new_name = args.trim().to_string();
if new_name.is_empty() {
Some(ExEffect::Error("E: usage: :Rename <newname>".into()))
} else {
app.lsp_rename(new_name);
Some(ExEffect::Ok)
}
}
}
pub(crate) struct LspFormatCmd;
impl HostCmd<App> for LspFormatCmd {
fn name(&self) -> &'static str {
"LspFormat"
}
fn aliases(&self) -> &'static [&'static str] {
&["Format"]
}
fn min_prefix(&self) -> usize {
9
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.lsp_format();
Some(ExEffect::Ok)
}
}
pub(crate) struct LspCodeActionCmd;
impl HostCmd<App> for LspCodeActionCmd {
fn name(&self) -> &'static str {
"LspCodeAction"
}
fn aliases(&self) -> &'static [&'static str] {
&["CodeAction"]
}
fn min_prefix(&self) -> usize {
13
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.lsp_code_actions();
Some(ExEffect::Ok)
}
}
pub(crate) struct LopenCmd;
impl HostCmd<App> for LopenCmd {
fn name(&self) -> &'static str {
"lopen"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
5
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.open_diag_picker();
Some(ExEffect::Ok)
}
}
pub(crate) struct LnextCmd;
impl HostCmd<App> for LnextCmd {
fn name(&self) -> &'static str {
"lnext"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
5
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.lnext_severity(None);
Some(ExEffect::Ok)
}
}
pub(crate) struct LprevCmd;
impl HostCmd<App> for LprevCmd {
fn name(&self) -> &'static str {
"lprev"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
5
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.lprev_severity(None);
Some(ExEffect::Ok)
}
}
pub(crate) struct LfirstCmd;
impl HostCmd<App> for LfirstCmd {
fn name(&self) -> &'static str {
"lfirst"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
6
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.ldiag_first();
Some(ExEffect::Ok)
}
}
pub(crate) struct LlastCmd;
impl HostCmd<App> for LlastCmd {
fn name(&self) -> &'static str {
"llast"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
5
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.ldiag_last();
Some(ExEffect::Ok)
}
}
pub(crate) struct LspInfoCmd;
impl HostCmd<App> for LspInfoCmd {
fn name(&self) -> &'static str {
"LspInfo"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
7
}
fn arg_kind(&self) -> ArgKind {
ArgKind::None
}
fn run(&self, app: &mut App, _args: &str) -> Option<ExEffect> {
app.show_lsp_info();
Some(ExEffect::Ok)
}
}
pub(crate) struct AnvilCmd;
impl HostCmd<App> for AnvilCmd {
fn name(&self) -> &'static str {
"Anvil"
}
fn aliases(&self) -> &'static [&'static str] {
&[]
}
fn min_prefix(&self) -> usize {
5
}
fn arg_kind(&self) -> ArgKind {
ArgKind::Raw
}
fn run(&self, app: &mut App, args: &str) -> Option<ExEffect> {
let parts: Vec<&str> = args.split_whitespace().collect();
match parts.as_slice() {
[] => {
app.open_anvil_picker();
Some(ExEffect::Ok)
}
["install", name] => {
app.anvil_install(name);
Some(ExEffect::Ok)
}
["uninstall", name] => {
app.anvil_uninstall(name);
Some(ExEffect::Ok)
}
["update"] => {
app.anvil_update_all();
Some(ExEffect::Ok)
}
["update", name] => {
app.anvil_update(name);
Some(ExEffect::Ok)
}
_ => Some(ExEffect::Error(
"usage: :Anvil [install|uninstall|update] [name]".into(),
)),
}
}
}
fn build_registry() -> hjkl_ex::HostRegistry<App> {
let mut reg = hjkl_ex::HostRegistry::new();
reg.add(Box::new(TabNextCmd));
reg.add(Box::new(SplitCmd));
reg.add(Box::new(VsplitCmd));
reg.add(Box::new(CloseCmd));
reg.add(Box::new(TabnewCmd));
reg.add(Box::new(TabprevCmd));
reg.add(Box::new(TabcloseCmd));
reg.add(Box::new(TabmoveCmd));
reg.add(Box::new(OnlyCmd));
reg.add(Box::new(BnextCmd));
reg.add(Box::new(BprevCmd));
reg.add(Box::new(BfirstCmd));
reg.add(Box::new(BlastCmd));
reg.add(Box::new(BuffersCmd));
reg.add(Box::new(ClipboardCmd));
reg.add(Box::new(PerfCmd));
reg.add(Box::new(PickerCmd));
reg.add(Box::new(RgCmd));
reg.add(Box::new(BCmd));
reg.add(Box::new(BpickerCmd));
reg.add(Box::new(ChecktimeCmd));
reg.add(Box::new(VnewCmd));
reg.add(Box::new(NewCmd));
reg.add(Box::new(TabfirstCmd));
reg.add(Box::new(TablastCmd));
reg.add(Box::new(TabonlyCmd));
reg.add(Box::new(TabsCmd));
reg.add(Box::new(ResizeCmd));
reg.add(Box::new(VerticalResizeCmd));
reg.add(Box::new(RenameCmd));
reg.add(Box::new(LspFormatCmd));
reg.add(Box::new(LspCodeActionCmd));
reg.add(Box::new(LopenCmd));
reg.add(Box::new(LnextCmd));
reg.add(Box::new(LprevCmd));
reg.add(Box::new(LfirstCmd));
reg.add(Box::new(LlastCmd));
reg.add(Box::new(LspInfoCmd));
reg.add(Box::new(AnvilCmd));
reg
}
static HOST_REGISTRY: LazyLock<hjkl_ex::HostRegistry<App>> = LazyLock::new(build_registry);
pub(crate) fn host_registry() -> &'static hjkl_ex::HostRegistry<App> {
&HOST_REGISTRY
}