diff options
Diffstat (limited to 'src/config/model.rs')
| -rw-r--r-- | src/config/model.rs | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/src/config/model.rs b/src/config/model.rs new file mode 100644 index 00000000..70eede5a --- /dev/null +++ b/src/config/model.rs @@ -0,0 +1,216 @@ +use serde::Deserialize; + +use super::{CommandKeybindConfig, SoundConfig, ThemeConfig, DEFAULT_SCROLLBACK_LIMIT_BYTES}; + +#[derive(Debug, Clone, Deserialize)] +#[serde(default)] +pub struct ToastConfig { + pub enabled: bool, +} + +#[derive(Debug, Default, Deserialize)] +#[serde(default)] +pub struct Config { + pub onboarding: Option<bool>, + pub theme: ThemeConfig, + pub keys: KeysConfig, + pub ui: UiConfig, + pub advanced: AdvancedConfig, +} + +#[derive(Debug)] +pub struct LoadedConfig { + pub config: Config, + pub diagnostics: Vec<String>, +} + +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct KeysConfig { + /// Prefix key to toggle navigate mode (e.g. "ctrl+b", "f12", "esc"). + pub prefix: String, + /// Create a new workspace. Default: "n" + pub new_workspace: String, + /// Rename the selected workspace. Default: "shift+n" + pub rename_workspace: String, + /// Close the selected workspace. Default: "shift+d" + pub close_workspace: String, + /// Optional explicit detach shortcut in server/client mode. Unset by default. + pub detach: String, + /// Select the previous workspace. Unset by default. + pub previous_workspace: String, + /// Select the next workspace. Unset by default. + pub next_workspace: String, + /// Create a new tab in the active workspace. Default: "c" + pub new_tab: String, + /// Rename the active tab. Unset by default. + pub rename_tab: String, + /// Select the previous tab. Unset by default. + pub previous_tab: String, + /// Select the next tab. Unset by default. + pub next_tab: String, + /// Close the active tab. Unset by default. + pub close_tab: String, + /// Focus the pane to the left in terminal mode. Unset by default. + pub focus_pane_left: String, + /// Focus the pane below in terminal mode. Unset by default. + pub focus_pane_down: String, + /// Focus the pane above in terminal mode. Unset by default. + pub focus_pane_up: String, + /// Focus the pane to the right in terminal mode. Unset by default. + pub focus_pane_right: String, + /// Split pane vertically (side by side). Default: "v" + pub split_vertical: String, + /// Split pane horizontally (stacked). Default: "-" + pub split_horizontal: String, + /// Close the focused pane. Default: "x" + pub close_pane: String, + /// Toggle fullscreen for the focused pane. Default: "f" + pub fullscreen: String, + /// Enter resize mode. Default: "r" + pub resize_mode: String, + /// Toggle sidebar collapse. Default: "b" + pub toggle_sidebar: String, + /// Prefix-mode custom command bindings. + pub command: Vec<CommandKeybindConfig>, +} + +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct UiConfig { + pub sidebar_width: u16, + /// Ask for confirmation before closing a workspace. Default: true. + pub confirm_close: bool, + /// Accent color for highlights, borders, and navigation UI. + /// Accepts hex (#89b4fa), named colors (cyan, blue), or RGB (rgb(137,180,250)). + pub accent: String, + /// Optional visual toast notifications for background workspace events. + pub toast: ToastConfig, + /// Play sounds when agents change state in background workspaces. + pub sound: SoundConfig, +} + +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct AdvancedConfig { + /// Allow launching herdr inside an existing herdr pane. Default: false. + pub allow_nested: bool, + /// Maximum scrollback buffer size in bytes retained per pane terminal. Default: 10000000. + #[serde(alias = "scrollback_lines")] + pub scrollback_limit_bytes: usize, +} + +impl Default for KeysConfig { + fn default() -> Self { + Self { + prefix: "ctrl+b".into(), + new_workspace: "n".into(), + rename_workspace: "shift+n".into(), + close_workspace: "shift+d".into(), + detach: "".into(), + previous_workspace: "".into(), + next_workspace: "".into(), + new_tab: "c".into(), + rename_tab: "".into(), + previous_tab: "".into(), + next_tab: "".into(), + close_tab: "".into(), + focus_pane_left: "".into(), + focus_pane_down: "".into(), + focus_pane_up: "".into(), + focus_pane_right: "".into(), + split_vertical: "v".into(), + split_horizontal: "-".into(), + close_pane: "x".into(), + fullscreen: "f".into(), + resize_mode: "r".into(), + toggle_sidebar: "b".into(), + command: Vec::new(), + } + } +} + +impl Default for UiConfig { + fn default() -> Self { + Self { + sidebar_width: 26, + confirm_close: true, + accent: "cyan".into(), + toast: ToastConfig::default(), + sound: SoundConfig::default(), + } + } +} + +impl Default for ToastConfig { + fn default() -> Self { + Self { enabled: false } + } +} + +impl Default for AdvancedConfig { + fn default() -> Self { + Self { + allow_nested: false, + scrollback_limit_bytes: DEFAULT_SCROLLBACK_LIMIT_BYTES, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn toast_config_parses() { + let toml = r#" +[ui.toast] +enabled = true +"#; + let config: Config = toml::from_str(toml).unwrap(); + assert!(config.ui.toast.enabled); + } + + #[test] + fn missing_onboarding_shows_setup() { + let config = Config::default(); + assert!(config.should_show_onboarding()); + } + + #[test] + fn onboarding_false_skips_setup() { + let config: Config = toml::from_str("onboarding = false").unwrap(); + assert!(!config.should_show_onboarding()); + } + + #[test] + fn advanced_defaults_include_scrollback_limit_bytes() { + let config = Config::default(); + assert_eq!( + config.advanced.scrollback_limit_bytes, + DEFAULT_SCROLLBACK_LIMIT_BYTES + ); + } + + #[test] + fn advanced_config_parses() { + let toml = r#" +[advanced] +allow_nested = true +scrollback_limit_bytes = 12345 +"#; + let config: Config = toml::from_str(toml).unwrap(); + assert!(config.advanced.allow_nested); + assert_eq!(config.advanced.scrollback_limit_bytes, 12345); + } + + #[test] + fn advanced_legacy_scrollback_lines_alias_parses() { + let toml = r#" +[advanced] +scrollback_lines = 12345 +"#; + let config: Config = toml::from_str(toml).unwrap(); + assert_eq!(config.advanced.scrollback_limit_bytes, 12345); + } +} |
