opencode_provider_manager/app/
state.rs1use crate::config_core::{ConfigLayer, ConfigPaths, OpenCodeConfig, ProviderConfig};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum AppMode {
8 MergedView,
10 SplitView,
12 AddProvider,
14 EditProvider { provider_id: String },
16 ModelSelector { provider_id: String },
18 AuthStatus,
20 Import,
22}
23
24#[derive(Debug)]
26pub struct AppState {
27 pub global_config: Option<OpenCodeConfig>,
29 pub project_config: Option<OpenCodeConfig>,
31 pub custom_config: Option<OpenCodeConfig>,
33 pub merged_config: OpenCodeConfig,
35 pub paths: ConfigPaths,
37 pub mode: AppMode,
39 pub dirty: bool,
41 pub edit_layer: ConfigLayer,
43}
44
45impl AppState {
46 pub fn new() -> crate::config_core::Result<Self> {
48 let paths = ConfigPaths::discover()?;
49 Ok(Self {
50 global_config: None,
51 project_config: None,
52 custom_config: None,
53 merged_config: OpenCodeConfig::default(),
54 paths,
55 mode: AppMode::MergedView,
56 dirty: false,
57 edit_layer: ConfigLayer::Project,
58 })
59 }
60
61 pub fn load_configs(&mut self) -> crate::config_core::Result<()> {
66 self.global_config = if self.paths.global.exists() {
68 Some(crate::config_core::jsonc::read_config(&self.paths.global)?)
69 } else {
70 None
71 };
72
73 self.custom_config = if let Some(ref custom_path) = self.paths.custom {
75 if custom_path.exists() {
76 Some(crate::config_core::jsonc::read_config(custom_path)?)
77 } else {
78 None
79 }
80 } else {
81 None
82 };
83
84 self.project_config = if let Some(ref project_path) = self.paths.project {
86 if project_path.exists() {
87 Some(crate::config_core::jsonc::read_config(project_path)?)
88 } else {
89 None
90 }
91 } else {
92 None
93 };
94
95 let mut configs_to_merge = Vec::new();
97 if let Some(global) = &self.global_config {
98 configs_to_merge.push(global.clone());
99 }
100 if let Some(custom) = &self.custom_config {
101 configs_to_merge.push(custom.clone());
102 }
103 if let Some(project) = &self.project_config {
104 configs_to_merge.push(project.clone());
105 }
106
107 self.merged_config = crate::config_core::merge_configs(&configs_to_merge);
108 self.dirty = false;
109
110 Ok(())
111 }
112
113 pub fn get_provider(&self, provider_id: &str) -> Option<&ProviderConfig> {
115 self.merged_config.provider.as_ref()?.get(provider_id)
116 }
117
118 pub fn provider_ids(&self) -> Vec<String> {
120 self.merged_config
121 .provider
122 .as_ref()
123 .map(|p| p.keys().cloned().collect())
124 .unwrap_or_default()
125 }
126
127 pub fn mark_dirty(&mut self) {
129 self.dirty = true;
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_app_state_creation() {
139 let state = AppState::new();
140 assert!(state.is_ok());
141 let state = state.unwrap();
142 assert_eq!(state.mode, AppMode::MergedView);
143 assert!(!state.dirty);
144 }
145
146 #[test]
147 fn test_app_state_default_mode() {
148 let state = AppState::new().unwrap();
149 assert!(matches!(state.mode, AppMode::MergedView));
150 }
151
152 #[test]
153 fn test_provider_ids_empty() {
154 let state = AppState::new().unwrap();
155 assert!(state.provider_ids().is_empty());
156 }
157}