use crate::config::SidebarSide;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Action {
Run {
sidebar: Option<SidebarSide>,
working_directory: Option<PathBuf>,
no_gpu: bool,
},
Help,
Version,
Check,
Install,
Uninstall,
}
pub(crate) fn parse(args: impl IntoIterator<Item = String>) -> Result<Action, String> {
let mut args = args.into_iter();
let mut sidebar = None;
let mut working_directory = None;
let mut no_gpu = false;
while let Some(arg) = args.next() {
match arg.as_str() {
"-h" | "--help" => return Ok(Action::Help),
"-V" | "--version" => return Ok(Action::Version),
"--check" | "--diagnostics" => {
if sidebar.is_some() || working_directory.is_some() || no_gpu {
return Err("--check cannot be combined with launch options".to_string());
}
ensure_no_more(args)?;
return Ok(Action::Check);
}
"install" | "--install" | "---install" => {
ensure_no_more(args)?;
return Ok(Action::Install);
}
"uninstall" | "--uninstall" | "---uninstall" => {
ensure_no_more(args)?;
return Ok(Action::Uninstall);
}
"--left" => set_sidebar(&mut sidebar, SidebarSide::Left)?,
"--no-gpu" => no_gpu = true,
"--right" => set_sidebar(&mut sidebar, SidebarSide::Right)?,
"--sidebar" => {
let value = args
.next()
.ok_or_else(|| "--sidebar requires left or right".to_string())?;
set_sidebar(&mut sidebar, SidebarSide::parse(&value)?)?;
}
"--working-directory" => {
let value = args
.next()
.ok_or_else(|| "--working-directory requires a path".to_string())?;
if working_directory.replace(PathBuf::from(value)).is_some() {
return Err("--working-directory may be supplied only once".to_string());
}
}
_ if arg.starts_with('-') => return Err(format!("unknown option: {arg}")),
_ => return Err(format!("unexpected argument: {arg}")),
}
}
Ok(Action::Run {
sidebar,
working_directory,
no_gpu,
})
}
fn set_sidebar(destination: &mut Option<SidebarSide>, value: SidebarSide) -> Result<(), String> {
if destination.replace(value).is_some() {
return Err("sidebar position may be supplied only once".to_string());
}
Ok(())
}
fn ensure_no_more(mut args: impl Iterator<Item = String>) -> Result<(), String> {
match args.next() {
Some(extra) => Err(format!("unexpected argument after action: {extra}")),
None => Ok(()),
}
}
pub(crate) fn help() -> &'static str {
"LiosBar — navigate live terminal sessions from a sidebar
Usage:
lios-bar [--sidebar left|right] [--working-directory PATH]
lios-bar --left
lios-bar --right
lios-bar --install
lios-bar --uninstall
lios-bar --check Report toolkit/display compatibility; no shell
lios-bar --no-gpu Use Cairo when GPU drivers cause rendering problems
Shortcuts:
Ctrl+Shift+T New session
Ctrl+PageDown Next session
Ctrl+PageUp Previous session
Ctrl+Shift+PageUp Move active session up (without switching)
Ctrl+Shift+PageDown Move active session down (without switching)
Ctrl+Shift+W Close selected session
Ctrl+Shift+R Rename selected session
Ctrl+Shift+B Move sidebar left/right
Ctrl+Shift+L Filter sessions by name/title (Enter opens, Esc returns)
Ctrl+Shift+F Find text in the selected terminal
Enter / Shift+Enter Next / previous match while editing the query
F3 / Shift+F3 Next / previous match while Find is open
Escape Close Find and return to the terminal
Ctrl+Shift+C Copy selection
Ctrl+Shift+V Guarded paste
Shift+Insert Guarded paste while the terminal owns focus
Sidebar layout: use the gear beside SESSIONS for saved compact rows, titles,
keyboard hints, and a layout-only reset.
Drag session rows to reorder within the current workspace.
"
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_to_run_without_overrides() {
assert_eq!(
parse(Vec::<String>::new()).unwrap(),
Action::Run {
sidebar: None,
working_directory: None,
no_gpu: false,
}
);
}
#[test]
fn parses_right_sidebar_and_directory() {
assert_eq!(
parse([
"--sidebar".to_string(),
"right".to_string(),
"--working-directory".to_string(),
"/tmp/project".to_string()
])
.unwrap(),
Action::Run {
sidebar: Some(SidebarSide::Right),
working_directory: Some(PathBuf::from("/tmp/project")),
no_gpu: false,
}
);
}
#[test]
fn rejects_commands_and_duplicate_position() {
assert!(parse(["bash".to_string()]).is_err());
assert!(parse(["--left".to_string(), "--right".to_string()]).is_err());
}
#[test]
fn install_rejects_trailing_arguments() {
assert!(parse(["--install".to_string(), "--right".to_string()]).is_err());
}
#[test]
fn check_is_read_only_and_rejects_launch_options() {
for alias in ["--check", "--diagnostics"] {
assert_eq!(parse([alias.to_string()]).unwrap(), Action::Check);
assert!(parse([alias.to_string(), "--right".to_string()]).is_err());
assert!(parse(["--right".to_string(), alias.to_string()]).is_err());
assert!(parse(["--no-gpu".to_string(), alias.to_string()]).is_err());
}
}
#[test]
fn software_rendering_is_an_opt_in_launch_override() {
assert_eq!(
parse(["--no-gpu".to_string(), "--right".to_string()]).unwrap(),
Action::Run {
sidebar: Some(SidebarSide::Right),
working_directory: None,
no_gpu: true,
}
);
}
}