|
|
|
use crate::app::ExternalMsg;
|
|
|
|
use crate::app::HelpMenuLine;
|
|
|
|
use crate::app::NodeFilter;
|
|
|
|
use crate::app::NodeSorter;
|
|
|
|
use crate::app::NodeSorterApplicable;
|
|
|
|
use crate::ui::Border;
|
|
|
|
use crate::ui::BorderType;
|
|
|
|
use crate::ui::Constraint;
|
|
|
|
use crate::ui::Layout;
|
|
|
|
use crate::ui::Style;
|
|
|
|
use indexmap::IndexSet;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct Action {
|
|
|
|
#[serde(default)]
|
|
|
|
pub help: Option<String>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub messages: Vec<ExternalMsg>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Action {
|
|
|
|
pub fn sanitized(self, read_only: bool) -> Option<Self> {
|
|
|
|
if self.messages.is_empty() {
|
|
|
|
None
|
|
|
|
} else if read_only {
|
|
|
|
if self.messages.iter().all(ExternalMsg::is_read_only) {
|
|
|
|
Some(self)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Some(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct NodeTypeConfig {
|
|
|
|
#[serde(default)]
|
|
|
|
pub style: Style,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub meta: HashMap<String, String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NodeTypeConfig {
|
|
|
|
pub fn extend(mut self, other: &Self) -> Self {
|
|
|
|
self.style = self.style.extend(&other.style);
|
|
|
|
self.meta.extend(other.meta.to_owned());
|
|
|
|
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<String, HashMap<String, NodeTypeConfig>>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub extension: HashMap<String, NodeTypeConfig>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub special: HashMap<String, NodeTypeConfig>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct UiConfig {
|
|
|
|
#[serde(default)]
|
|
|
|
pub prefix: Option<String>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub suffix: Option<String>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub style: Style,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct UiElement {
|
|
|
|
#[serde(default)]
|
|
|
|
pub format: Option<String>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub style: Style,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl UiElement {
|
|
|
|
pub fn extend(mut self, other: &Self) -> Self {
|
|
|
|
self.format = other.format.to_owned().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<Vec<UiElement>>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub style: Style,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub height: Option<u16>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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<u16>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub col_widths: Option<Vec<Constraint>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 warning: UiElement,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub error: UiElement,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct SortDirectionIdentifiersUi {
|
|
|
|
#[serde(default)]
|
|
|
|
pub forward: UiElement,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub reverse: UiElement,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct SortAndFilterUi {
|
|
|
|
#[serde(default)]
|
|
|
|
pub separator: UiElement,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub default_identifier: UiElement,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub sort_direction_identifiers: SortDirectionIdentifiersUi,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub sorter_identifiers: HashMap<NodeSorter, UiElement>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub filter_identifiers: HashMap<NodeFilter, UiElement>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub search_identifier: Option<UiElement>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct PanelUi {
|
|
|
|
#[serde(default)]
|
|
|
|
pub default: PanelUiConfig,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub table: PanelUiConfig,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub sort_and_filter: PanelUiConfig,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub selection: PanelUiConfig,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub input_and_logs: PanelUiConfig,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub help_menu: PanelUiConfig,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct GeneralConfig {
|
|
|
|
#[serde(default)]
|
|
|
|
pub disable_debug_error_mode: bool,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub enable_mouse: bool,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub show_hidden: bool,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub read_only: bool,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub enable_recover_mode: bool,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub hide_remaps_in_help_menu: bool,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub enforce_bounded_index_navigation: bool,
|
|
|
|
|
|
|
|
#[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 focus_selection_ui: UiConfig,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub sort_and_filter_ui: SortAndFilterUi,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub panel_ui: PanelUi,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub initial_sorting: Option<IndexSet<NodeSorterApplicable>>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub initial_mode: Option<String>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub initial_layout: Option<String>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub start_fifo: Option<String>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub global_key_bindings: KeyBindings,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct KeyBindings {
|
|
|
|
#[serde(default)]
|
|
|
|
pub on_key: BTreeMap<String, Action>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub on_alphabet: Option<Action>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub on_number: Option<Action>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub on_alphanumeric: Option<Action>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub on_special_character: Option<Action>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub on_character: Option<Action>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub on_navigation: Option<Action>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub on_function: Option<Action>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub default: Option<Action>,
|
|
|
|
// Checklist for adding new field:
|
|
|
|
// - [ ] Update App::handle_key
|
|
|
|
// - [ ] Update KeyBindings::sanitized
|
|
|
|
// - [ ] Update Mode::help_menu
|
|
|
|
// - [ ] Update configure-key-bindings.md
|
|
|
|
// - [ ] Update debug-key-bindings.md
|
|
|
|
}
|
|
|
|
|
|
|
|
impl KeyBindings {
|
|
|
|
pub fn sanitized(mut self, read_only: bool) -> Self {
|
|
|
|
if read_only {
|
|
|
|
self.on_key = self
|
|
|
|
.on_key
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|(k, a)| a.sanitized(read_only).map(|a| (k, a)))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
self.on_alphabet = self.on_alphabet.and_then(|a| a.sanitized(read_only));
|
|
|
|
self.on_number = self.on_number.and_then(|a| a.sanitized(read_only));
|
|
|
|
self.on_alphanumeric =
|
|
|
|
self.on_alphanumeric.and_then(|a| a.sanitized(read_only));
|
|
|
|
self.on_special_character = self
|
|
|
|
.on_special_character
|
|
|
|
.and_then(|a| a.sanitized(read_only));
|
|
|
|
self.on_character = self.on_character.and_then(|a| a.sanitized(read_only));
|
|
|
|
self.on_navigation = self.on_navigation.and_then(|a| a.sanitized(read_only));
|
|
|
|
self.on_function = self.on_function.and_then(|a| a.sanitized(read_only));
|
|
|
|
self.default = self.default.and_then(|a| a.sanitized(read_only));
|
|
|
|
};
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn extend(mut self, other: Self) -> Self {
|
|
|
|
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_alphanumeric = other.on_alphanumeric.or(self.on_alphanumeric);
|
|
|
|
self.on_special_character =
|
|
|
|
other.on_special_character.or(self.on_special_character);
|
|
|
|
self.on_character = other.on_character.or(self.on_character);
|
|
|
|
self.on_navigation = other.on_navigation.or(self.on_navigation);
|
|
|
|
self.on_function = other.on_function.or(self.on_function);
|
|
|
|
self.default = other.default.or(self.default);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct Mode {
|
|
|
|
#[serde(default)]
|
|
|
|
pub name: String,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub help: Option<String>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub extra_help: Option<String>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub key_bindings: KeyBindings,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub layout: Option<Layout>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub prompt: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Mode {
|
|
|
|
pub fn sanitized(
|
|
|
|
mut self,
|
|
|
|
read_only: bool,
|
|
|
|
global_key_bindings: KeyBindings,
|
|
|
|
) -> Self {
|
|
|
|
self.key_bindings = global_key_bindings
|
|
|
|
.sanitized(read_only)
|
|
|
|
.extend(self.key_bindings.sanitized(read_only));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn help_menu(&self) -> Vec<HelpMenuLine> {
|
|
|
|
let extra_help_lines = self.extra_help.clone().map(|e| {
|
|
|
|
e.lines()
|
|
|
|
.map(|l| HelpMenuLine::Paragraph(l.into()))
|
|
|
|
.collect::<Vec<HelpMenuLine>>()
|
|
|
|
});
|
|
|
|
|
|
|
|
self.help
|
|
|
|
.clone()
|
|
|
|
.map(|h| {
|
|
|
|
h.lines()
|
|
|
|
.map(|l| HelpMenuLine::Paragraph(l.into()))
|
|
|
|
.collect()
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
let lines = extra_help_lines
|
|
|
|
.unwrap_or_default()
|
|
|
|
.into_iter()
|
|
|
|
.chain(self.key_bindings.on_key.iter().filter_map(|(k, a)| {
|
|
|
|
let remaps = self
|
|
|
|
.key_bindings
|
|
|
|
.on_key
|
|
|
|
.iter()
|
|
|
|
.filter_map(|(rk, ra)| {
|
|
|
|
if rk == k {
|
|
|
|
None
|
|
|
|
} else if a == ra {
|
|
|
|
Some(rk.clone())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
a.help
|
|
|
|
.clone()
|
|
|
|
.map(|h| HelpMenuLine::KeyMap(k.into(), remaps, 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(), vec![], 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(), vec![], h))
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.chain(
|
|
|
|
self.key_bindings
|
|
|
|
.on_alphanumeric
|
|
|
|
.iter()
|
|
|
|
.map(|a| ("[0-Z]", a.help.clone()))
|
|
|
|
.filter_map(|(k, mh)| {
|
|
|
|
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.chain(
|
|
|
|
self.key_bindings
|
|
|
|
.on_special_character
|
|
|
|
.iter()
|
|
|
|
.map(|a| ("[^0-Z]", a.help.clone()))
|
|
|
|
.filter_map(|(k, mh)| {
|
|
|
|
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.chain(
|
|
|
|
self.key_bindings
|
|
|
|
.on_character
|
|
|
|
.iter()
|
|
|
|
.map(|a| ("[*]", a.help.clone()))
|
|
|
|
.filter_map(|(k, mh)| {
|
|
|
|
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.chain(
|
|
|
|
self.key_bindings
|
|
|
|
.on_navigation
|
|
|
|
.iter()
|
|
|
|
.map(|a| ("[nav]", a.help.clone()))
|
|
|
|
.filter_map(|(k, mh)| {
|
|
|
|
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.chain(
|
|
|
|
self.key_bindings
|
|
|
|
.on_function
|
|
|
|
.iter()
|
|
|
|
.map(|a| ("[f1-f12]", a.help.clone()))
|
|
|
|
.filter_map(|(k, mh)| {
|
|
|
|
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.chain(
|
|
|
|
self.key_bindings
|
|
|
|
.default
|
|
|
|
.iter()
|
|
|
|
.map(|a| ("[default]", a.help.clone()))
|
|
|
|
.filter_map(|(k, mh)| {
|
|
|
|
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut remapped = HashSet::new();
|
|
|
|
let mut result = vec![];
|
|
|
|
|
|
|
|
for line in lines {
|
|
|
|
match line {
|
|
|
|
HelpMenuLine::Paragraph(p) => {
|
|
|
|
result.push(HelpMenuLine::Paragraph(p))
|
|
|
|
}
|
|
|
|
HelpMenuLine::KeyMap(k, r, d) => {
|
|
|
|
if !remapped.contains(&k) {
|
|
|
|
for k in r.iter() {
|
|
|
|
remapped.insert(k.clone());
|
|
|
|
}
|
|
|
|
result.push(HelpMenuLine::KeyMap(k, r, d));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct ModesConfig {
|
|
|
|
#[serde(default)]
|
|
|
|
pub builtin: HashMap<String, Mode>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub custom: HashMap<String, Mode>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ModesConfig {
|
|
|
|
pub fn get(&self, name: &str) -> Option<&Mode> {
|
|
|
|
self.builtin.get(name).or_else(|| self.custom.get(name))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct PanelUiConfig {
|
|
|
|
#[serde(default)]
|
|
|
|
pub title: UiElement,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub style: Style,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub borders: Option<IndexSet<Border>>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub border_type: Option<BorderType>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub border_style: Style,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PanelUiConfig {
|
|
|
|
pub fn extend(mut self, other: &Self) -> Self {
|
|
|
|
self.title = self.title.extend(&other.title);
|
|
|
|
self.style = self.style.extend(&other.style);
|
|
|
|
self.borders = other.borders.to_owned().or(self.borders);
|
|
|
|
self.border_type = other.border_type.to_owned().or(self.border_type);
|
|
|
|
self.border_style = self.border_style.extend(&other.border_style);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct LayoutsConfig {
|
|
|
|
#[serde(default)]
|
|
|
|
pub builtin: HashMap<String, Layout>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub custom: HashMap<String, Layout>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl LayoutsConfig {
|
|
|
|
pub fn get_builtin(&self, name: &str) -> Option<&Layout> {
|
|
|
|
self.builtin.get(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_custom(&self, name: &str) -> Option<&Layout> {
|
|
|
|
self.custom.get(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get(&self, name: &str) -> Option<&Layout> {
|
|
|
|
self.get_builtin(name).or_else(|| self.get_custom(name))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct Config {
|
|
|
|
#[serde(default)]
|
|
|
|
pub general: GeneralConfig,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub node_types: NodeTypesConfig,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub layouts: LayoutsConfig,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub modes: ModesConfig,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct Hooks {
|
|
|
|
#[serde(default)]
|
|
|
|
pub on_load: Vec<ExternalMsg>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub on_directory_change: Vec<ExternalMsg>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub on_focus_change: Vec<ExternalMsg>,
|
|
|
|
// TODO After cleanup or Runner::run
|
|
|
|
// #[serde(default)]
|
|
|
|
// pub before_quit: Vec<ExternalMsg>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Hooks {
|
|
|
|
pub fn extend(mut self, other: Self) -> Self {
|
|
|
|
self.on_load.extend(other.on_load);
|
|
|
|
self.on_directory_change.extend(other.on_directory_change);
|
|
|
|
self.on_focus_change.extend(other.on_focus_change);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|