use crate::core::state::Mode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpenTarget {
Current,
Tab,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollDir {
Up,
Down,
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum YankWhat {
Url,
Title,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HintTarget {
#[default]
Current,
Tab,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Command {
Open { target: OpenTarget, input: String },
Back(u32),
Forward(u32),
Reload { bypass_cache: bool },
Stop,
Scroll(ScrollDir, u32),
ScrollPage { down: bool, half: bool },
ScrollToPercent(u8),
Hint(HintTarget),
TabClose,
TabNext(u32),
TabPrev(u32),
TabSelect(usize),
Undo,
TabClone,
TabMove(i32),
TabOnly,
ModeEnter(Mode),
ModeLeave,
SetCommandLine(String),
Accept,
Yank(YankWhat),
QuickmarkSave(String),
QuickmarkLoad(String),
QuickmarkDel(String),
BookmarkAdd,
BookmarkLoad(String),
BookmarkDel(String),
Set { key: String, value: String },
ConfigSource,
DarkMode,
SessionSave(String),
SessionLoad(String),
PluginReload,
Memory,
Quit,
Nop,
}
impl Command {
pub fn parse(input: &str) -> Result<Command, String> {
if let Some(text) = input.strip_prefix("cmd-set-text ") {
return Ok(Command::SetCommandLine(text.to_string()));
}
if input.trim() == "cmd-set-text" {
return Ok(Command::SetCommandLine(String::new()));
}
let mut parts = input.split_whitespace();
let Some(name) = parts.next() else {
return Err("empty command".to_string());
};
let rest: Vec<&str> = parts.collect();
let arg = rest.join(" ");
let count = |default: u32| rest.first().and_then(|s| s.parse::<u32>().ok()).unwrap_or(default);
let cmd = match name {
"open" | "o" => Command::Open {
target: OpenTarget::Current,
input: arg,
},
"tabopen" | "t" => Command::Open {
target: OpenTarget::Tab,
input: arg,
},
"back" => Command::Back(count(1)),
"forward" => Command::Forward(count(1)),
"reload" | "r" => Command::Reload {
bypass_cache: rest.contains(&"--force"),
},
"stop" => Command::Stop,
"scroll" => Command::Scroll(parse_dir(rest.first())?, 1),
"scroll-page" => Command::ScrollPage {
down: matches!(rest.first(), Some(&"down")),
half: rest.contains(&"half"),
},
"scroll-to-perc" => Command::ScrollToPercent(count(100).min(100) as u8),
"hint" => Command::Hint(HintTarget::Current),
"hint-tab" => Command::Hint(HintTarget::Tab),
"tab-close" | "d" => Command::TabClose,
"tab-next" => Command::TabNext(count(1)),
"tab-prev" => Command::TabPrev(count(1)),
"tab-clone" => Command::TabClone,
"tab-only" => Command::TabOnly,
"tab-move" => Command::TabMove(
rest.first()
.and_then(|s| s.parse::<i32>().ok())
.ok_or_else(|| format!("tab-move needs an offset: {arg}"))?,
),
"undo" => Command::Undo,
"tab-focus" | "tab-select" => {
let n = rest
.first()
.and_then(|s| s.parse::<usize>().ok())
.ok_or_else(|| format!("tab-focus needs an index: {arg}"))?;
Command::TabSelect(n)
}
"mode-enter" => Command::ModeEnter(parse_mode(rest.first())?),
"mode-leave" => Command::ModeLeave,
"yank" => Command::Yank(match rest.first() {
None | Some(&"url") => YankWhat::Url,
Some(&"title") => YankWhat::Title,
Some(other) => return Err(format!("unknown yank target: {other}")),
}),
"quickmark-save" => Command::QuickmarkSave(arg),
"quickmark-load" => Command::QuickmarkLoad(arg),
"quickmark-del" => Command::QuickmarkDel(arg),
"bookmark-add" => Command::BookmarkAdd,
"bookmark-load" => Command::BookmarkLoad(arg),
"bookmark-del" => Command::BookmarkDel(arg),
"set" => {
let key = rest
.first()
.ok_or_else(|| "set needs a key".to_string())?
.to_string();
let value = rest[1..].join(" ");
Command::Set { key, value }
}
"config-source" => Command::ConfigSource,
"darkmode" => Command::DarkMode,
"session-save" => Command::SessionSave(arg),
"session-load" => Command::SessionLoad(arg),
"plugin-reload" => Command::PluginReload,
"memory" => Command::Memory,
"quit" | "q" | "qa" => Command::Quit,
"nop" => Command::Nop,
other => return Err(format!("unknown command: {other}")),
};
Ok(cmd)
}
pub fn with_count(self, count: u32) -> Command {
match self {
Command::Scroll(dir, _) => Command::Scroll(dir, count),
Command::Back(_) => Command::Back(count),
Command::Forward(_) => Command::Forward(count),
Command::TabNext(_) => Command::TabNext(count),
Command::TabPrev(_) => Command::TabPrev(count),
other => other,
}
}
}
fn parse_dir(arg: Option<&&str>) -> Result<ScrollDir, String> {
match arg {
Some(&"up") => Ok(ScrollDir::Up),
Some(&"down") => Ok(ScrollDir::Down),
Some(&"left") => Ok(ScrollDir::Left),
Some(&"right") => Ok(ScrollDir::Right),
other => Err(format!("invalid scroll direction: {other:?}")),
}
}
fn parse_mode(arg: Option<&&str>) -> Result<Mode, String> {
match arg {
Some(&"normal") => Ok(Mode::Normal),
Some(&"insert") => Ok(Mode::Insert),
Some(&"command") => Ok(Mode::Command),
other => Err(format!("unknown mode: {other:?}")),
}
}
pub const COMMAND_CATALOG: &[(&str, &str)] = &[
("open", "Open a URL or search in the current tab"),
("tabopen", "Open a URL or search in a new tab"),
("back", "Go back in history"),
("forward", "Go forward in history"),
("reload", "Reload the page"),
("stop", "Stop loading"),
("scroll", "Scroll in a direction"),
("scroll-page", "Scroll by a page"),
("scroll-to-perc", "Scroll to a percentage of the page"),
("hint", "Follow a link by keyboard"),
("hint-tab", "Open a hinted link in a new tab"),
("tab-close", "Close the current tab"),
("tab-next", "Focus the next tab"),
("tab-prev", "Focus the previous tab"),
("tab-focus", "Focus a tab by index"),
("tab-clone", "Duplicate the current tab"),
("tab-move", "Move the current tab"),
("tab-only", "Close all other tabs"),
("undo", "Reopen the last closed tab"),
("mode-enter", "Enter an input mode"),
("mode-leave", "Return to normal mode"),
("yank", "Copy the page URL or title"),
("quickmark-save", "Save the page as a named quickmark"),
("quickmark-load", "Open a quickmark by name"),
("quickmark-del", "Delete a quickmark"),
("bookmark-add", "Bookmark the current page"),
("bookmark-load", "Open a bookmark"),
("bookmark-del", "Delete a bookmark"),
("set", "Set a configuration value"),
("config-source", "Reload the configuration file"),
("darkmode", "Toggle web-content dark mode"),
("session-save", "Save the current tabs as a session"),
("session-load", "Restore a saved session"),
("plugin-reload", "Recompile and reload plugins"),
("memory", "Report memory use and view count"),
("quit", "Quit the browser"),
];