use super::command::RunCommandAction;
use super::layout::{Layout, PaneLayout};
use crate::cli::CliAction;
use crate::data::InputMode;
use crate::data::{Direction, Resize};
use crate::input::config::{ConfigError, KdlError};
use crate::input::options::OnForceClose;
use miette::{NamedSource, Report};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::str::FromStr;
use crate::position::Position;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum ResizeDirection {
Left,
Right,
Up,
Down,
Increase,
Decrease,
}
impl FromStr for ResizeDirection {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Left" | "left" => Ok(ResizeDirection::Left),
"Right" | "right" => Ok(ResizeDirection::Right),
"Up" | "up" => Ok(ResizeDirection::Up),
"Down" | "down" => Ok(ResizeDirection::Down),
"Increase" | "increase" | "+" => Ok(ResizeDirection::Increase),
"Decrease" | "decrease" | "-" => Ok(ResizeDirection::Decrease),
_ => Err(format!(
"Failed to parse ResizeDirection. Unknown ResizeDirection: {}",
s
)),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum SearchDirection {
Down,
Up,
}
impl FromStr for SearchDirection {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Down" | "down" => Ok(SearchDirection::Down),
"Up" | "up" => Ok(SearchDirection::Up),
_ => Err(format!(
"Failed to parse SearchDirection. Unknown SearchDirection: {}",
s
)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum SearchOption {
CaseSensitivity,
WholeWord,
Wrap,
}
impl FromStr for SearchOption {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"CaseSensitivity" | "casesensitivity" | "Casesensitivity" => {
Ok(SearchOption::CaseSensitivity)
},
"WholeWord" | "wholeword" | "Wholeword" => Ok(SearchOption::WholeWord),
"Wrap" | "wrap" => Ok(SearchOption::Wrap),
_ => Err(format!(
"Failed to parse SearchOption. Unknown SearchOption: {}",
s
)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum Action {
Quit,
Write(Vec<u8>),
WriteChars(String),
SwitchToMode(InputMode),
SwitchModeForAllClients(InputMode),
Resize(Resize, Option<Direction>),
FocusNextPane,
FocusPreviousPane,
SwitchFocus,
MoveFocus(Direction),
MoveFocusOrTab(Direction),
MovePane(Option<Direction>),
DumpScreen(String, bool),
EditScrollback,
ScrollUp,
ScrollUpAt(Position),
ScrollDown,
ScrollDownAt(Position),
ScrollToBottom,
PageScrollUp,
PageScrollDown,
HalfPageScrollUp,
HalfPageScrollDown,
ToggleFocusFullscreen,
TogglePaneFrames,
ToggleActiveSyncTab,
NewPane(Option<Direction>, Option<String>), EditFile(PathBuf, Option<usize>, Option<Direction>, bool), NewFloatingPane(Option<RunCommandAction>, Option<String>), NewTiledPane(Option<Direction>, Option<RunCommandAction>, Option<String>), TogglePaneEmbedOrFloating,
ToggleFloatingPanes,
CloseFocus,
PaneNameInput(Vec<u8>),
UndoRenamePane,
NewTab(Option<PaneLayout>, Option<String>), NoOp,
GoToNextTab,
GoToPreviousTab,
CloseTab,
GoToTab(u32),
ToggleTab,
TabNameInput(Vec<u8>),
UndoRenameTab,
Run(RunCommandAction),
Detach,
LeftClick(Position),
RightClick(Position),
MiddleClick(Position),
LeftMouseRelease(Position),
RightMouseRelease(Position),
MiddleMouseRelease(Position),
MouseHoldLeft(Position),
MouseHoldRight(Position),
MouseHoldMiddle(Position),
Copy,
Confirm,
Deny,
SkipConfirm(Box<Action>),
SearchInput(Vec<u8>),
Search(SearchDirection),
SearchToggleOption(SearchOption),
ToggleMouseMode,
}
impl Action {
pub fn actions_from_cli(
cli_action: CliAction,
get_current_dir: Box<dyn Fn() -> PathBuf>,
) -> Result<Vec<Action>, String> {
match cli_action {
CliAction::Write { bytes } => Ok(vec![Action::Write(bytes)]),
CliAction::WriteChars { chars } => Ok(vec![Action::WriteChars(chars)]),
CliAction::Resize { resize, direction } => Ok(vec![Action::Resize(resize, direction)]),
CliAction::FocusNextPane => Ok(vec![Action::FocusNextPane]),
CliAction::FocusPreviousPane => Ok(vec![Action::FocusPreviousPane]),
CliAction::MoveFocus { direction } => Ok(vec![Action::MoveFocus(direction)]),
CliAction::MoveFocusOrTab { direction } => Ok(vec![Action::MoveFocusOrTab(direction)]),
CliAction::MovePane { direction } => Ok(vec![Action::MovePane(Some(direction))]),
CliAction::DumpScreen { path, full } => Ok(vec![Action::DumpScreen(
path.as_os_str().to_string_lossy().into(),
full,
)]),
CliAction::EditScrollback => Ok(vec![Action::EditScrollback]),
CliAction::ScrollUp => Ok(vec![Action::ScrollUp]),
CliAction::ScrollDown => Ok(vec![Action::ScrollDown]),
CliAction::ScrollToBottom => Ok(vec![Action::ScrollToBottom]),
CliAction::PageScrollUp => Ok(vec![Action::PageScrollUp]),
CliAction::PageScrollDown => Ok(vec![Action::PageScrollDown]),
CliAction::HalfPageScrollUp => Ok(vec![Action::HalfPageScrollUp]),
CliAction::HalfPageScrollDown => Ok(vec![Action::HalfPageScrollDown]),
CliAction::ToggleFullscreen => Ok(vec![Action::ToggleFocusFullscreen]),
CliAction::TogglePaneFrames => Ok(vec![Action::TogglePaneFrames]),
CliAction::ToggleActiveSyncTab => Ok(vec![Action::ToggleActiveSyncTab]),
CliAction::NewPane {
direction,
command,
cwd,
floating,
name,
close_on_exit,
start_suspended,
} => {
if !command.is_empty() {
let mut command = command.clone();
let (command, args) = (PathBuf::from(command.remove(0)), command);
let current_dir = get_current_dir();
let cwd = cwd
.map(|cwd| current_dir.join(cwd))
.or_else(|| Some(current_dir));
let hold_on_start = start_suspended;
let hold_on_close = !close_on_exit;
let run_command_action = RunCommandAction {
command,
args,
cwd,
direction,
hold_on_close,
hold_on_start,
};
if floating {
Ok(vec![Action::NewFloatingPane(
Some(run_command_action),
name,
)])
} else {
Ok(vec![Action::NewTiledPane(
direction,
Some(run_command_action),
name,
)])
}
} else {
if floating {
Ok(vec![Action::NewFloatingPane(None, name)])
} else {
Ok(vec![Action::NewTiledPane(direction, None, name)])
}
}
},
CliAction::Edit {
direction,
file,
line_number,
floating,
cwd,
} => {
let mut file = file;
let current_dir = get_current_dir();
let cwd = cwd
.map(|cwd| current_dir.join(cwd))
.or_else(|| Some(current_dir));
if file.is_relative() {
if let Some(cwd) = cwd {
file = cwd.join(file);
}
}
Ok(vec![Action::EditFile(
file,
line_number,
direction,
floating,
)])
},
CliAction::SwitchMode { input_mode } => {
Ok(vec![Action::SwitchModeForAllClients(input_mode)])
},
CliAction::TogglePaneEmbedOrFloating => Ok(vec![Action::TogglePaneEmbedOrFloating]),
CliAction::ToggleFloatingPanes => Ok(vec![Action::ToggleFloatingPanes]),
CliAction::ClosePane => Ok(vec![Action::CloseFocus]),
CliAction::RenamePane { name } => Ok(vec![
Action::UndoRenamePane,
Action::PaneNameInput(name.as_bytes().to_vec()),
]),
CliAction::UndoRenamePane => Ok(vec![Action::UndoRenamePane]),
CliAction::GoToNextTab => Ok(vec![Action::GoToNextTab]),
CliAction::GoToPreviousTab => Ok(vec![Action::GoToPreviousTab]),
CliAction::CloseTab => Ok(vec![Action::CloseTab]),
CliAction::GoToTab { index } => Ok(vec![Action::GoToTab(index)]),
CliAction::RenameTab { name } => Ok(vec![
Action::TabNameInput(vec![0]),
Action::TabNameInput(name.as_bytes().to_vec()),
]),
CliAction::UndoRenameTab => Ok(vec![Action::UndoRenameTab]),
CliAction::NewTab { name, layout, cwd } => {
let current_dir = get_current_dir();
let cwd = cwd
.map(|cwd| current_dir.join(cwd))
.or_else(|| Some(current_dir));
if let Some(layout_path) = layout {
let (path_to_raw_layout, raw_layout) =
Layout::stringified_from_path_or_default(Some(&layout_path), None)
.map_err(|e| format!("Failed to load layout: {}", e))?;
let layout = Layout::from_str(&raw_layout, path_to_raw_layout, cwd).map_err(|e| {
let stringified_error = match e {
ConfigError::KdlError(kdl_error) => {
let error = kdl_error.add_src(layout_path.as_path().as_os_str().to_string_lossy().to_string(), String::from(raw_layout));
let report: Report = error.into();
format!("{:?}", report)
}
ConfigError::KdlDeserializationError(kdl_error) => {
let error_message = match kdl_error.kind {
kdl::KdlErrorKind::Context("valid node terminator") => {
format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}",
"- Missing `;` after a node name, eg. { node; another_node; }",
"- Missing quotations (\") around an argument node eg. { first_node \"argument_node\"; }",
"- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"",
"- Found an extraneous equal sign (=) between node child arguments and their values. eg. { argument=\"value\" }")
},
_ => String::from(kdl_error.help.unwrap_or("Kdl Deserialization Error")),
};
let kdl_error = KdlError {
error_message,
src: Some(NamedSource::new(layout_path.as_path().as_os_str().to_string_lossy().to_string(), String::from(raw_layout))),
offset: Some(kdl_error.span.offset()),
len: Some(kdl_error.span.len()),
help_message: None,
};
let report: Report = kdl_error.into();
format!("{:?}", report)
},
e => format!("{}", e)
};
stringified_error
})?;
let mut tabs = layout.tabs();
if tabs.len() > 1 {
return Err(format!("Tab layout cannot itself have tabs"));
} else if !tabs.is_empty() {
let (tab_name, layout) = tabs.drain(..).next().unwrap();
let name = tab_name.or(name);
Ok(vec![Action::NewTab(Some(layout), name)])
} else {
let layout = layout.new_tab();
Ok(vec![Action::NewTab(Some(layout), name)])
}
} else {
Ok(vec![Action::NewTab(None, name)])
}
},
}
}
}
impl From<OnForceClose> for Action {
fn from(ofc: OnForceClose) -> Action {
match ofc {
OnForceClose::Quit => Action::Quit,
OnForceClose::Detach => Action::Detach,
}
}
}