feat(config): add zellij-style workspace configuration system
- commit
- ba9ac75d19cef8800b048e47ca97fa2d729ee7cc
- author
- bamen · · 2025-09-19 03:41 UTC
- parent
- a7f39d5
- signature
- unsigned
3 files · +104 -0
M internal/config/config.go +52 -0
| @@ -40,6 +40,8 @@ type Config struct { | |||
| 40 | 40 | RenderingMode RenderingMode `mapstructure:"rendering_mode" yaml:"rendering_mode"` | |
| 41 | 41 | // UseDomZoom toggles DOM-based zoom instead of native WebKit zoom. | |
| 42 | 42 | UseDomZoom bool `mapstructure:"use_dom_zoom" yaml:"use_dom_zoom"` | |
| 43 | + | // Workspace defines workspace, pane, and tab handling behaviour. | |
| 44 | + | Workspace WorkspaceConfig `mapstructure:"workspace" yaml:"workspace"` | |
| 43 | 45 | } | |
| 44 | 46 | ||
| 45 | 47 | // RenderingMode selects GPU vs CPU rendering. | |
| @@ -209,6 +211,9 @@ type DebugConfig struct { | |||
| 209 | 211 | ||
| 210 | 212 | // Enable general debug mode (equivalent to DUMBER_DEBUG env var) | |
| 211 | 213 | EnableGeneralDebug bool `mapstructure:"enable_general_debug" yaml:"enable_general_debug"` | |
| 214 | + | ||
| 215 | + | // Enable workspace navigation and focus debug logs | |
| 216 | + | EnableWorkspaceDebug bool `mapstructure:"enable_workspace_debug" yaml:"enable_workspace_debug"` | |
| 212 | 217 | } | |
| 213 | 218 | ||
| 214 | 219 | // APISecurityConfig holds optional API key protection for dumb://api endpoints | |
| @@ -219,6 +224,40 @@ type APISecurityConfig struct { | |||
| 219 | 224 | RequireToken bool `mapstructure:"require_token" yaml:"require_token"` | |
| 220 | 225 | } | |
| 221 | 226 | ||
| 227 | + | // WorkspaceConfig captures layout, pane, and tab behaviour preferences. | |
| 228 | + | type WorkspaceConfig struct { | |
| 229 | + | // EnableZellijControls toggles Zellij-inspired keybindings. | |
| 230 | + | EnableZellijControls bool `mapstructure:"enable_zellij_controls" yaml:"enable_zellij_controls" json:"enable_zellij_controls"` | |
| 231 | + | // PaneMode defines modal pane behaviour and bindings. | |
| 232 | + | PaneMode PaneModeConfig `mapstructure:"pane_mode" yaml:"pane_mode" json:"pane_mode"` | |
| 233 | + | // Tabs holds classic browser tab shortcuts. | |
| 234 | + | Tabs TabKeyConfig `mapstructure:"tabs" yaml:"tabs" json:"tabs"` | |
| 235 | + | // Popups configures default popup placement rules. | |
| 236 | + | Popups PopupBehaviorConfig `mapstructure:"popups" yaml:"popups" json:"popups"` | |
| 237 | + | } | |
| 238 | + | ||
| 239 | + | // PaneModeConfig defines modal behaviour for pane management. | |
| 240 | + | type PaneModeConfig struct { | |
| 241 | + | ActivationShortcut string `mapstructure:"activation_shortcut" yaml:"activation_shortcut" json:"activation_shortcut"` | |
| 242 | + | TimeoutMilliseconds int `mapstructure:"timeout_ms" yaml:"timeout_ms" json:"timeout_ms"` | |
| 243 | + | ActionBindings map[string]string `mapstructure:"action_bindings" yaml:"action_bindings" json:"action_bindings"` | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | // TabKeyConfig defines Zellij-inspired tab shortcuts. | |
| 247 | + | type TabKeyConfig struct { | |
| 248 | + | NewTab string `mapstructure:"new_tab" yaml:"new_tab" json:"new_tab"` | |
| 249 | + | CloseTab string `mapstructure:"close_tab" yaml:"close_tab" json:"close_tab"` | |
| 250 | + | NextTab string `mapstructure:"next_tab" yaml:"next_tab" json:"next_tab"` | |
| 251 | + | PreviousTab string `mapstructure:"previous_tab" yaml:"previous_tab" json:"previous_tab"` | |
| 252 | + | } | |
| 253 | + | ||
| 254 | + | // PopupBehaviorConfig defines handling for popup windows. | |
| 255 | + | type PopupBehaviorConfig struct { | |
| 256 | + | Placement string `mapstructure:"placement" yaml:"placement" json:"placement"` | |
| 257 | + | OpenInNewPane bool `mapstructure:"open_in_new_pane" yaml:"open_in_new_pane" json:"open_in_new_pane"` | |
| 258 | + | FollowPaneContext bool `mapstructure:"follow_pane_context" yaml:"follow_pane_context" json:"follow_pane_context"` | |
| 259 | + | } | |
| 260 | + | ||
| 222 | 261 | // Manager handles configuration loading, watching, and reloading. | |
| 223 | 262 | type Manager struct { | |
| 224 | 263 | config *Config | |
| @@ -612,6 +651,19 @@ func (m *Manager) setDefaults() { | |||
| 612 | 651 | // Rendering defaults | |
| 613 | 652 | m.viper.SetDefault("rendering_mode", string(RenderingModeGPU)) | |
| 614 | 653 | m.viper.SetDefault("use_dom_zoom", defaults.UseDomZoom) | |
| 654 | + | ||
| 655 | + | // Workspace defaults | |
| 656 | + | m.viper.SetDefault("workspace.enable_zellij_controls", defaults.Workspace.EnableZellijControls) | |
| 657 | + | m.viper.SetDefault("workspace.pane_mode.activation_shortcut", defaults.Workspace.PaneMode.ActivationShortcut) | |
| 658 | + | m.viper.SetDefault("workspace.pane_mode.timeout_ms", defaults.Workspace.PaneMode.TimeoutMilliseconds) | |
| 659 | + | m.viper.SetDefault("workspace.pane_mode.action_bindings", defaults.Workspace.PaneMode.ActionBindings) | |
| 660 | + | m.viper.SetDefault("workspace.tabs.new_tab", defaults.Workspace.Tabs.NewTab) | |
| 661 | + | m.viper.SetDefault("workspace.tabs.close_tab", defaults.Workspace.Tabs.CloseTab) | |
| 662 | + | m.viper.SetDefault("workspace.tabs.next_tab", defaults.Workspace.Tabs.NextTab) | |
| 663 | + | m.viper.SetDefault("workspace.tabs.previous_tab", defaults.Workspace.Tabs.PreviousTab) | |
| 664 | + | m.viper.SetDefault("workspace.popups.placement", defaults.Workspace.Popups.Placement) | |
| 665 | + | m.viper.SetDefault("workspace.popups.open_in_new_pane", defaults.Workspace.Popups.OpenInNewPane) | |
| 666 | + | m.viper.SetDefault("workspace.popups.follow_pane_context", defaults.Workspace.Popups.FollowPaneContext) | |
| 615 | 667 | } | |
| 616 | 668 | ||
| 617 | 669 | func (m *Manager) ensurePersistedDefaults(cfg *Config) { | |
| @@ -40,6 +40,8 @@ type Config struct { | |||||
| 40 | RenderingMode RenderingMode `mapstructure:"rendering_mode" yaml:"rendering_mode"` | 40 | RenderingMode RenderingMode `mapstructure:"rendering_mode" yaml:"rendering_mode"` | ||
| 41 | // UseDomZoom toggles DOM-based zoom instead of native WebKit zoom. | 41 | // UseDomZoom toggles DOM-based zoom instead of native WebKit zoom. | ||
| 42 | UseDomZoom bool `mapstructure:"use_dom_zoom" yaml:"use_dom_zoom"` | 42 | UseDomZoom bool `mapstructure:"use_dom_zoom" yaml:"use_dom_zoom"` | ||
| 43 | + | // Workspace defines workspace, pane, and tab handling behaviour. | |||
| 44 | + | Workspace WorkspaceConfig `mapstructure:"workspace" yaml:"workspace"` | |||
| 43 | } | 45 | } | ||
| 44 | 46 | ||||
| 45 | // RenderingMode selects GPU vs CPU rendering. | 47 | // RenderingMode selects GPU vs CPU rendering. | ||
| @@ -209,6 +211,9 @@ type DebugConfig struct { | |||||
| 209 | 211 | ||||
| 210 | // Enable general debug mode (equivalent to DUMBER_DEBUG env var) | 212 | // Enable general debug mode (equivalent to DUMBER_DEBUG env var) | ||
| 211 | EnableGeneralDebug bool `mapstructure:"enable_general_debug" yaml:"enable_general_debug"` | 213 | EnableGeneralDebug bool `mapstructure:"enable_general_debug" yaml:"enable_general_debug"` | ||
| 214 | + | ||||
| 215 | + | // Enable workspace navigation and focus debug logs | |||
| 216 | + | EnableWorkspaceDebug bool `mapstructure:"enable_workspace_debug" yaml:"enable_workspace_debug"` | |||
| 212 | } | 217 | } | ||
| 213 | 218 | ||||
| 214 | // APISecurityConfig holds optional API key protection for dumb://api endpoints | 219 | // APISecurityConfig holds optional API key protection for dumb://api endpoints | ||
| @@ -219,6 +224,40 @@ type APISecurityConfig struct { | |||||
| 219 | RequireToken bool `mapstructure:"require_token" yaml:"require_token"` | 224 | RequireToken bool `mapstructure:"require_token" yaml:"require_token"` | ||
| 220 | } | 225 | } | ||
| 221 | 226 | ||||
| 227 | + | // WorkspaceConfig captures layout, pane, and tab behaviour preferences. | |||
| 228 | + | type WorkspaceConfig struct { | |||
| 229 | + | // EnableZellijControls toggles Zellij-inspired keybindings. | |||
| 230 | + | EnableZellijControls bool `mapstructure:"enable_zellij_controls" yaml:"enable_zellij_controls" json:"enable_zellij_controls"` | |||
| 231 | + | // PaneMode defines modal pane behaviour and bindings. | |||
| 232 | + | PaneMode PaneModeConfig `mapstructure:"pane_mode" yaml:"pane_mode" json:"pane_mode"` | |||
| 233 | + | // Tabs holds classic browser tab shortcuts. | |||
| 234 | + | Tabs TabKeyConfig `mapstructure:"tabs" yaml:"tabs" json:"tabs"` | |||
| 235 | + | // Popups configures default popup placement rules. | |||
| 236 | + | Popups PopupBehaviorConfig `mapstructure:"popups" yaml:"popups" json:"popups"` | |||
| 237 | + | } | |||
| 238 | + | ||||
| 239 | + | // PaneModeConfig defines modal behaviour for pane management. | |||
| 240 | + | type PaneModeConfig struct { | |||
| 241 | + | ActivationShortcut string `mapstructure:"activation_shortcut" yaml:"activation_shortcut" json:"activation_shortcut"` | |||
| 242 | + | TimeoutMilliseconds int `mapstructure:"timeout_ms" yaml:"timeout_ms" json:"timeout_ms"` | |||
| 243 | + | ActionBindings map[string]string `mapstructure:"action_bindings" yaml:"action_bindings" json:"action_bindings"` | |||
| 244 | + | } | |||
| 245 | + | ||||
| 246 | + | // TabKeyConfig defines Zellij-inspired tab shortcuts. | |||
| 247 | + | type TabKeyConfig struct { | |||
| 248 | + | NewTab string `mapstructure:"new_tab" yaml:"new_tab" json:"new_tab"` | |||
| 249 | + | CloseTab string `mapstructure:"close_tab" yaml:"close_tab" json:"close_tab"` | |||
| 250 | + | NextTab string `mapstructure:"next_tab" yaml:"next_tab" json:"next_tab"` | |||
| 251 | + | PreviousTab string `mapstructure:"previous_tab" yaml:"previous_tab" json:"previous_tab"` | |||
| 252 | + | } | |||
| 253 | + | ||||
| 254 | + | // PopupBehaviorConfig defines handling for popup windows. | |||
| 255 | + | type PopupBehaviorConfig struct { | |||
| 256 | + | Placement string `mapstructure:"placement" yaml:"placement" json:"placement"` | |||
| 257 | + | OpenInNewPane bool `mapstructure:"open_in_new_pane" yaml:"open_in_new_pane" json:"open_in_new_pane"` | |||
| 258 | + | FollowPaneContext bool `mapstructure:"follow_pane_context" yaml:"follow_pane_context" json:"follow_pane_context"` | |||
| 259 | + | } | |||
| 260 | + | ||||
| 222 | // Manager handles configuration loading, watching, and reloading. | 261 | // Manager handles configuration loading, watching, and reloading. | ||
| 223 | type Manager struct { | 262 | type Manager struct { | ||
| 224 | config *Config | 263 | config *Config | ||
| @@ -612,6 +651,19 @@ func (m *Manager) setDefaults() { | |||||
| 612 | // Rendering defaults | 651 | // Rendering defaults | ||
| 613 | m.viper.SetDefault("rendering_mode", string(RenderingModeGPU)) | 652 | m.viper.SetDefault("rendering_mode", string(RenderingModeGPU)) | ||
| 614 | m.viper.SetDefault("use_dom_zoom", defaults.UseDomZoom) | 653 | m.viper.SetDefault("use_dom_zoom", defaults.UseDomZoom) | ||
| 654 | + | ||||
| 655 | + | // Workspace defaults | |||
| 656 | + | m.viper.SetDefault("workspace.enable_zellij_controls", defaults.Workspace.EnableZellijControls) | |||
| 657 | + | m.viper.SetDefault("workspace.pane_mode.activation_shortcut", defaults.Workspace.PaneMode.ActivationShortcut) | |||
| 658 | + | m.viper.SetDefault("workspace.pane_mode.timeout_ms", defaults.Workspace.PaneMode.TimeoutMilliseconds) | |||
| 659 | + | m.viper.SetDefault("workspace.pane_mode.action_bindings", defaults.Workspace.PaneMode.ActionBindings) | |||
| 660 | + | m.viper.SetDefault("workspace.tabs.new_tab", defaults.Workspace.Tabs.NewTab) | |||
| 661 | + | m.viper.SetDefault("workspace.tabs.close_tab", defaults.Workspace.Tabs.CloseTab) | |||
| 662 | + | m.viper.SetDefault("workspace.tabs.next_tab", defaults.Workspace.Tabs.NextTab) | |||
| 663 | + | m.viper.SetDefault("workspace.tabs.previous_tab", defaults.Workspace.Tabs.PreviousTab) | |||
| 664 | + | m.viper.SetDefault("workspace.popups.placement", defaults.Workspace.Popups.Placement) | |||
| 665 | + | m.viper.SetDefault("workspace.popups.open_in_new_pane", defaults.Workspace.Popups.OpenInNewPane) | |||
| 666 | + | m.viper.SetDefault("workspace.popups.follow_pane_context", defaults.Workspace.Popups.FollowPaneContext) | |||
| 615 | } | 667 | } | ||
| 616 | 668 | ||||
| 617 | func (m *Manager) ensurePersistedDefaults(cfg *Config) { | 669 | func (m *Manager) ensurePersistedDefaults(cfg *Config) { | ||
M internal/config/defaults.go +36 -0
| @@ -25,6 +25,11 @@ const ( | |||
| 25 | 25 | ||
| 26 | 26 | // Appearance defaults | |
| 27 | 27 | defaultFontSize = 16 // points | |
| 28 | + | ||
| 29 | + | // Workspace defaults | |
| 30 | + | defaultPaneActivationShortcut = "cmdorctrl+p" | |
| 31 | + | defaultPaneTimeoutMilliseconds = 3000 | |
| 32 | + | defaultPopupPlacement = "right" | |
| 28 | 33 | ) | |
| 29 | 34 | ||
| 30 | 35 | // getDefaultLogDir returns the default log directory, falls back to empty string on error | |
| @@ -146,6 +151,37 @@ func DefaultConfig() *Config { | |||
| 146 | 151 | }, | |
| 147 | 152 | RenderingMode: RenderingModeGPU, | |
| 148 | 153 | UseDomZoom: false, | |
| 154 | + | Workspace: WorkspaceConfig{ | |
| 155 | + | EnableZellijControls: true, | |
| 156 | + | PaneMode: PaneModeConfig{ | |
| 157 | + | ActivationShortcut: defaultPaneActivationShortcut, | |
| 158 | + | TimeoutMilliseconds: defaultPaneTimeoutMilliseconds, | |
| 159 | + | ActionBindings: map[string]string{ | |
| 160 | + | "arrowright": "split-right", | |
| 161 | + | "arrowleft": "split-left", | |
| 162 | + | "arrowup": "split-up", | |
| 163 | + | "arrowdown": "split-down", | |
| 164 | + | "r": "split-right", | |
| 165 | + | "l": "split-left", | |
| 166 | + | "u": "split-up", | |
| 167 | + | "d": "split-down", | |
| 168 | + | "x": "close-pane", | |
| 169 | + | "enter": "confirm", | |
| 170 | + | "escape": "cancel", | |
| 171 | + | }, | |
| 172 | + | }, | |
| 173 | + | Tabs: TabKeyConfig{ | |
| 174 | + | NewTab: "cmdorctrl+t", | |
| 175 | + | CloseTab: "cmdorctrl+w", | |
| 176 | + | NextTab: "cmdorctrl+tab", | |
| 177 | + | PreviousTab: "cmdorctrl+shift+tab", | |
| 178 | + | }, | |
| 179 | + | Popups: PopupBehaviorConfig{ | |
| 180 | + | Placement: defaultPopupPlacement, | |
| 181 | + | OpenInNewPane: true, | |
| 182 | + | FollowPaneContext: true, | |
| 183 | + | }, | |
| 184 | + | }, | |
| 149 | 185 | } | |
| 150 | 186 | } | |
| 151 | 187 | ||
| @@ -25,6 +25,11 @@ const ( | |||||
| 25 | 25 | ||||
| 26 | // Appearance defaults | 26 | // Appearance defaults | ||
| 27 | defaultFontSize = 16 // points | 27 | defaultFontSize = 16 // points | ||
| 28 | + | ||||
| 29 | + | // Workspace defaults | |||
| 30 | + | defaultPaneActivationShortcut = "cmdorctrl+p" | |||
| 31 | + | defaultPaneTimeoutMilliseconds = 3000 | |||
| 32 | + | defaultPopupPlacement = "right" | |||
| 28 | ) | 33 | ) | ||
| 29 | 34 | ||||
| 30 | // getDefaultLogDir returns the default log directory, falls back to empty string on error | 35 | // getDefaultLogDir returns the default log directory, falls back to empty string on error | ||
| @@ -146,6 +151,37 @@ func DefaultConfig() *Config { | |||||
| 146 | }, | 151 | }, | ||
| 147 | RenderingMode: RenderingModeGPU, | 152 | RenderingMode: RenderingModeGPU, | ||
| 148 | UseDomZoom: false, | 153 | UseDomZoom: false, | ||
| 154 | + | Workspace: WorkspaceConfig{ | |||
| 155 | + | EnableZellijControls: true, | |||
| 156 | + | PaneMode: PaneModeConfig{ | |||
| 157 | + | ActivationShortcut: defaultPaneActivationShortcut, | |||
| 158 | + | TimeoutMilliseconds: defaultPaneTimeoutMilliseconds, | |||
| 159 | + | ActionBindings: map[string]string{ | |||
| 160 | + | "arrowright": "split-right", | |||
| 161 | + | "arrowleft": "split-left", | |||
| 162 | + | "arrowup": "split-up", | |||
| 163 | + | "arrowdown": "split-down", | |||
| 164 | + | "r": "split-right", | |||
| 165 | + | "l": "split-left", | |||
| 166 | + | "u": "split-up", | |||
| 167 | + | "d": "split-down", | |||
| 168 | + | "x": "close-pane", | |||
| 169 | + | "enter": "confirm", | |||
| 170 | + | "escape": "cancel", | |||
| 171 | + | }, | |||
| 172 | + | }, | |||
| 173 | + | Tabs: TabKeyConfig{ | |||
| 174 | + | NewTab: "cmdorctrl+t", | |||
| 175 | + | CloseTab: "cmdorctrl+w", | |||
| 176 | + | NextTab: "cmdorctrl+tab", | |||
| 177 | + | PreviousTab: "cmdorctrl+shift+tab", | |||
| 178 | + | }, | |||
| 179 | + | Popups: PopupBehaviorConfig{ | |||
| 180 | + | Placement: defaultPopupPlacement, | |||
| 181 | + | OpenInNewPane: true, | |||
| 182 | + | FollowPaneContext: true, | |||
| 183 | + | }, | |||
| 184 | + | }, | |||
| 149 | } | 185 | } | ||
| 150 | } | 186 | } | ||
| 151 | 187 | ||||
M services/config_service.go +16 -0
| @@ -26,6 +26,7 @@ type ConfigInfo struct { | |||
| 26 | 26 | DmenuSettings *config.DmenuConfig `json:"dmenu_settings"` | |
| 27 | 27 | RenderingMode string `json:"rendering_mode"` | |
| 28 | 28 | UseDomZoom bool `json:"use_dom_zoom"` | |
| 29 | + | Workspace config.WorkspaceConfig `json:"workspace"` | |
| 29 | 30 | } | |
| 30 | 31 | ||
| 31 | 32 | // NewConfigService creates a new ConfigService instance. | |
| @@ -46,6 +47,7 @@ func (s *ConfigService) GetConfigInfo(ctx context.Context) (*ConfigInfo, error) | |||
| 46 | 47 | DmenuSettings: &s.config.Dmenu, | |
| 47 | 48 | RenderingMode: string(s.config.RenderingMode), | |
| 48 | 49 | UseDomZoom: s.config.UseDomZoom, | |
| 50 | + | Workspace: s.config.Workspace, | |
| 49 | 51 | }, nil | |
| 50 | 52 | } | |
| 51 | 53 | ||
| @@ -216,6 +218,20 @@ func (s *ConfigService) ValidateConfig(ctx context.Context) ([]string, error) { | |||
| 216 | 218 | errors = append(errors, "dmenu max_history_items must be at least 1") | |
| 217 | 219 | } | |
| 218 | 220 | ||
| 221 | + | // Validate workspace configuration when enabled | |
| 222 | + | if s.config.Workspace.EnableZellijControls { | |
| 223 | + | if strings.TrimSpace(s.config.Workspace.PaneMode.ActivationShortcut) == "" { | |
| 224 | + | errors = append(errors, "workspace pane_mode activation_shortcut is empty") | |
| 225 | + | } | |
| 226 | + | if len(s.config.Workspace.PaneMode.ActionBindings) == 0 { | |
| 227 | + | errors = append(errors, "workspace pane_mode action_bindings are empty") | |
| 228 | + | } | |
| 229 | + | } | |
| 230 | + | ||
| 231 | + | if strings.TrimSpace(s.config.Workspace.Popups.Placement) == "" { | |
| 232 | + | errors = append(errors, "workspace popups placement is empty") | |
| 233 | + | } | |
| 234 | + | ||
| 219 | 235 | return errors, nil | |
| 220 | 236 | } | |
| 221 | 237 | ||
| @@ -26,6 +26,7 @@ type ConfigInfo struct { | |||||
| 26 | DmenuSettings *config.DmenuConfig `json:"dmenu_settings"` | 26 | DmenuSettings *config.DmenuConfig `json:"dmenu_settings"` | ||
| 27 | RenderingMode string `json:"rendering_mode"` | 27 | RenderingMode string `json:"rendering_mode"` | ||
| 28 | UseDomZoom bool `json:"use_dom_zoom"` | 28 | UseDomZoom bool `json:"use_dom_zoom"` | ||
| 29 | + | Workspace config.WorkspaceConfig `json:"workspace"` | |||
| 29 | } | 30 | } | ||
| 30 | 31 | ||||
| 31 | // NewConfigService creates a new ConfigService instance. | 32 | // NewConfigService creates a new ConfigService instance. | ||
| @@ -46,6 +47,7 @@ func (s *ConfigService) GetConfigInfo(ctx context.Context) (*ConfigInfo, error) | |||||
| 46 | DmenuSettings: &s.config.Dmenu, | 47 | DmenuSettings: &s.config.Dmenu, | ||
| 47 | RenderingMode: string(s.config.RenderingMode), | 48 | RenderingMode: string(s.config.RenderingMode), | ||
| 48 | UseDomZoom: s.config.UseDomZoom, | 49 | UseDomZoom: s.config.UseDomZoom, | ||
| 50 | + | Workspace: s.config.Workspace, | |||
| 49 | }, nil | 51 | }, nil | ||
| 50 | } | 52 | } | ||
| 51 | 53 | ||||
| @@ -216,6 +218,20 @@ func (s *ConfigService) ValidateConfig(ctx context.Context) ([]string, error) { | |||||
| 216 | errors = append(errors, "dmenu max_history_items must be at least 1") | 218 | errors = append(errors, "dmenu max_history_items must be at least 1") | ||
| 217 | } | 219 | } | ||
| 218 | 220 | ||||
| 221 | + | // Validate workspace configuration when enabled | |||
| 222 | + | if s.config.Workspace.EnableZellijControls { | |||
| 223 | + | if strings.TrimSpace(s.config.Workspace.PaneMode.ActivationShortcut) == "" { | |||
| 224 | + | errors = append(errors, "workspace pane_mode activation_shortcut is empty") | |||
| 225 | + | } | |||
| 226 | + | if len(s.config.Workspace.PaneMode.ActionBindings) == 0 { | |||
| 227 | + | errors = append(errors, "workspace pane_mode action_bindings are empty") | |||
| 228 | + | } | |||
| 229 | + | } | |||
| 230 | + | ||||
| 231 | + | if strings.TrimSpace(s.config.Workspace.Popups.Placement) == "" { | |||
| 232 | + | errors = append(errors, "workspace popups placement is empty") | |||
| 233 | + | } | |||
| 234 | + | ||||
| 219 | return errors, nil | 235 | return errors, nil | ||
| 220 | } | 236 | } | ||
| 221 | 237 | ||||