aboutsummaryrefslogtreecommitdiff
path: root/src/config/model.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/config/model.rs')
-rw-r--r--src/config/model.rs216
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);
+ }
+}