use crate::app::ExternalMsg; use crate::app::HelpMenuLine; use crate::app::NodeFilter; use crate::app::NodeSorter; use crate::app::NodeSorterApplicable; use crate::default_config; use crate::ui::Style; use anyhow::Result; use indexmap::IndexSet; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::collections::HashMap; use tui::layout::Constraint as TuiConstraint; #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Action { #[serde(default)] pub help: Option, #[serde(default)] pub messages: Vec, } impl Action { pub fn extend(mut self, other: Self) -> Self { self.help = other.help.or(self.help); self.messages = other.messages; self } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct NodeTypeConfig { #[serde(default)] pub style: Style, #[serde(default)] pub meta: HashMap, } impl NodeTypeConfig { pub fn extend(mut self, other: Self) -> Self { self.style = self.style.extend(other.style); self.meta.extend(other.meta); self } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct NodeTypesConfig { #[serde(default)] pub directory: NodeTypeConfig, #[serde(default)] pub file: NodeTypeConfig, #[serde(default)] pub symlink: NodeTypeConfig, #[serde(default)] pub mime_essence: HashMap, #[serde(default)] pub extension: HashMap, #[serde(default)] pub special: HashMap, } impl NodeTypesConfig { fn extend(mut self, other: Self) -> Self { self.directory = self.directory.extend(other.directory); self.file = self.file.extend(other.file); self.symlink = self.symlink.extend(other.symlink); self.mime_essence.extend(other.mime_essence); self.extension.extend(other.extension); self.special.extend(other.special); self } } #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct UiConfig { #[serde(default)] pub prefix: Option, #[serde(default)] pub suffix: Option, #[serde(default)] pub style: Style, } impl UiConfig { pub fn extend(mut self, other: Self) -> Self { self.prefix = other.prefix.or(self.prefix); self.suffix = other.suffix.or(self.suffix); self.style = self.style.extend(other.style); self } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct UiElement { #[serde(default)] pub format: Option, #[serde(default)] pub style: Style, } impl UiElement { fn extend(mut self, other: Self) -> Self { self.format = other.format.or(self.format); self.style = self.style.extend(other.style); self } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct TableRowConfig { #[serde(default)] pub cols: Option>, #[serde(default)] pub style: Style, #[serde(default)] pub height: Option, } impl TableRowConfig { fn extend(mut self, other: Self) -> Self { self.cols = other.cols.or(self.cols); self.style = self.style.extend(other.style); self.height = other.height.or(self.height); self } } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "snake_case", deny_unknown_fields)] pub enum Constraint { Percentage(u16), Ratio(u32, u32), Length(u16), Max(u16), Min(u16), } impl Default for Constraint { fn default() -> Self { Self::Min(1) } } impl Into for Constraint { fn into(self) -> TuiConstraint { match self { Self::Length(n) => TuiConstraint::Length(n), Self::Percentage(n) => TuiConstraint::Percentage(n), Self::Ratio(x, y) => TuiConstraint::Ratio(x, y), Self::Max(n) => TuiConstraint::Max(n), Self::Min(n) => TuiConstraint::Min(n), } } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct TableConfig { #[serde(default)] pub header: TableRowConfig, #[serde(default)] pub row: TableRowConfig, #[serde(default)] pub style: Style, #[serde(default)] pub tree: Option<(UiElement, UiElement, UiElement)>, #[serde(default)] pub col_spacing: Option, #[serde(default)] pub col_widths: Option>, } impl TableConfig { pub fn extend(mut self, other: Self) -> Self { self.header = self.header.extend(other.header); self.row = self.row.extend(other.row); self.style = self.style.extend(other.style); self.tree = other.tree.or(self.tree); self.col_spacing = other.col_spacing.or(self.col_spacing); self.col_widths = other.col_widths.or(self.col_widths); self } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct LogsConfig { #[serde(default)] pub info: UiElement, #[serde(default)] pub success: UiElement, #[serde(default)] pub error: UiElement, } impl LogsConfig { pub fn extend(mut self, other: Self) -> Self { self.info = self.info.extend(other.info); self.success = self.success.extend(other.success); self.error = self.error.extend(other.error); self } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct SortDirectionIdentifiersUi { #[serde(default)] pub forward: UiElement, #[serde(default)] pub reverse: UiElement, } impl SortDirectionIdentifiersUi { pub fn extend(mut self, other: Self) -> Self { self.forward = self.forward.extend(other.forward); self.reverse = self.reverse.extend(other.reverse); self } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct SortAndFilterUi { #[serde(default)] pub separator: UiElement, #[serde(default)] pub sort_direction_identifiers: SortDirectionIdentifiersUi, #[serde(default)] pub sorter_identifiers: HashMap, #[serde(default)] pub filter_identifiers: HashMap, } impl SortAndFilterUi { pub fn extend(mut self, other: Self) -> Self { self.separator = self.separator.extend(other.separator); self.sort_direction_identifiers = self .sort_direction_identifiers .extend(other.sort_direction_identifiers); self.sorter_identifiers.extend(other.sorter_identifiers); self.filter_identifiers.extend(other.filter_identifiers); self } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct GeneralConfig { #[serde(default)] pub show_hidden: Option, #[serde(default)] pub cursor: UiElement, #[serde(default)] pub prompt: UiElement, #[serde(default)] pub logs: LogsConfig, #[serde(default)] pub table: TableConfig, #[serde(default)] pub default_ui: UiConfig, #[serde(default)] pub focus_ui: UiConfig, #[serde(default)] pub selection_ui: UiConfig, #[serde(default)] pub sort_and_filter_ui: SortAndFilterUi, #[serde(default)] pub initial_sorting: Option>, } impl GeneralConfig { pub fn extend(mut self, other: Self) -> Self { self.show_hidden = other.show_hidden.or(self.show_hidden); self.cursor = self.cursor.extend(other.cursor); self.prompt = self.prompt.extend(other.prompt); self.logs = self.logs.extend(other.logs); self.table = self.table.extend(other.table); self.default_ui = self.default_ui.extend(other.default_ui); self.focus_ui = self.focus_ui.extend(other.focus_ui); self.selection_ui = self.selection_ui.extend(other.selection_ui); self.sort_and_filter_ui = self.sort_and_filter_ui.extend(other.sort_and_filter_ui); self.initial_sorting = other.initial_sorting.or(self.initial_sorting); self } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct KeyBindings { #[serde(default)] pub remaps: BTreeMap, #[serde(default)] pub on_key: BTreeMap, #[serde(default)] pub on_alphabet: Option, #[serde(default)] pub on_number: Option, #[serde(default)] pub on_special_character: Option, #[serde(default)] pub default: Option, } impl KeyBindings { pub fn extend(mut self, other: Self) -> Self { self.remaps.extend(other.remaps); self.on_key.extend(other.on_key); self.on_alphabet = other.on_alphabet.or(self.on_alphabet); self.on_number = other.on_number.or(self.on_number); self.on_special_character = other.on_special_character.or(self.on_special_character); self.default = other.default.or(self.default); self } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Mode { #[serde(default)] pub name: String, #[serde(default)] pub help: Option, #[serde(default)] pub extra_help: Option, #[serde(default)] pub key_bindings: KeyBindings, } impl Mode { pub fn extend(mut self, other: Self) -> Self { self.help = other.help.or(self.help); self.extra_help = other.extra_help.or(self.extra_help); self.key_bindings = self.key_bindings.extend(other.key_bindings); self } pub fn help_menu(&self) -> Vec { let extra_help_lines = self.extra_help.clone().map(|e| { e.lines() .map(|l| HelpMenuLine::Paragraph(l.into())) .collect::>() }); self.help .clone() .map(|h| { h.lines() .map(|l| HelpMenuLine::Paragraph(l.into())) .collect() }) .unwrap_or_else(|| { extra_help_lines .unwrap_or_default() .into_iter() .chain( self.key_bindings .on_key .iter() .filter(|(k, _)| !self.key_bindings.remaps.contains_key(&k.to_string())) .filter_map(|(k, a)| { a.help.clone().map(|h| HelpMenuLine::KeyMap(k.into(), h)) }), ) .chain( self.key_bindings .on_alphabet .iter() .map(|a| ("[a-Z]", a.help.clone())) .filter_map(|(k, mh)| mh.map(|h| HelpMenuLine::KeyMap(k.into(), h))), ) .chain( self.key_bindings .on_number .iter() .map(|a| ("[0-9]", a.help.clone())) .filter_map(|(k, mh)| mh.map(|h| HelpMenuLine::KeyMap(k.into(), h))), ) .chain( self.key_bindings .on_special_character .iter() .map(|a| ("[spcl chars]", a.help.clone())) .filter_map(|(k, mh)| mh.map(|h| HelpMenuLine::KeyMap(k.into(), h))), ) .chain( self.key_bindings .default .iter() .map(|a| ("[default]", a.help.clone())) .filter_map(|(k, mh)| mh.map(|h| HelpMenuLine::KeyMap(k.into(), h))), ) .collect() }) } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct BuiltinModesConfig { #[serde(default)] pub default: Mode, #[serde(default)] pub selection_ops: Mode, #[serde(default)] pub create: Mode, #[serde(default)] pub create_directory: Mode, #[serde(default)] pub create_file: Mode, #[serde(default)] pub number: Mode, #[serde(default)] pub go_to: Mode, #[serde(default)] pub rename: Mode, #[serde(default)] pub delete: Mode, #[serde(default)] pub action: Mode, #[serde(default)] pub search: Mode, #[serde(default)] pub filter: Mode, #[serde(default)] pub sort: Mode, } impl BuiltinModesConfig { pub fn extend(mut self, other: Self) -> Self { self.default = self.default.extend(other.default); self.selection_ops = self.selection_ops.extend(other.selection_ops); self.go_to = self.go_to.extend(other.go_to); self.create = self.create.extend(other.create); self.create_file = self.create_file.extend(other.create_file); self.create_directory = self.create_directory.extend(other.create_directory); self.rename = self.rename.extend(other.rename); self.delete = self.delete.extend(other.delete); self.number = self.number.extend(other.number); self.action = self.action.extend(other.action); self.search = self.search.extend(other.search); self.filter = self.filter.extend(other.filter); self.sort = self.sort.extend(other.sort); self } pub fn get(&self, name: &str) -> Option<&Mode> { match name { "default" => Some(&self.default), "selection ops" => Some(&self.selection_ops), "selection_ops" => Some(&self.selection_ops), "create" => Some(&self.create), "create file" => Some(&self.create_file), "create_file" => Some(&self.create_file), "create directory" => Some(&self.create_directory), "create_directory" => Some(&self.create_directory), "number" => Some(&self.number), "go to" => Some(&self.go_to), "go_to" => Some(&self.go_to), "rename" => Some(&self.rename), "delete" => Some(&self.delete), "action" => Some(&self.action), "search" => Some(&self.search), "sort" => Some(&self.sort), "filter" => Some(&self.filter), _ => None, } } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ModesConfig { #[serde(default)] pub builtin: BuiltinModesConfig, #[serde(default)] pub custom: HashMap, } impl ModesConfig { pub fn get(&self, name: &str) -> Option<&Mode> { self.builtin.get(name).or_else(|| self.custom.get(name)) } pub fn extend(mut self, other: Self) -> Self { self.builtin = self.builtin.extend(other.builtin); self.custom.extend(other.custom); self } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Config { pub version: String, #[serde(default)] pub general: GeneralConfig, #[serde(default)] pub node_types: NodeTypesConfig, #[serde(default)] pub modes: ModesConfig, } impl Default for Config { fn default() -> Self { Self { version: default_config::version(), general: default_config::general(), node_types: default_config::node_types(), modes: default_config::modes(), } } } impl Config { pub fn extended(mut self) -> Self { let default = Self::default(); self.general = default.general.extend(self.general); self.node_types = default.node_types.extend(self.node_types); self.modes = default.modes.extend(self.modes); self } fn parsed_version(&self) -> Result<(u16, u16, u16)> { let mut configv = self .version .strip_prefix('v') .unwrap_or_default() .split('.'); let major = configv.next().unwrap_or_default().parse::()?; let minor = configv.next().unwrap_or_default().parse::()?; let bugfix = configv.next().unwrap_or_default().parse::()?; Ok((major, minor, bugfix)) } pub fn is_compatible(&self) -> Result { let result = match self.parsed_version()? { (0, 5, 0) => true, (_, _, _) => false, }; Ok(result) } pub fn upgrade_notification(&self) -> Result> { Ok(None) /* let result = match self.parsed_version()? { (0, 5, 0) => None, (_, _, _) => Some("App version updated. New: added sorting support and some hacks: https://github.com/sayanarijit/xplr/wiki/Hacks"), }; Ok(result) */ } }