1use serde_json::Value;
2use std::collections::{HashMap, HashSet};
3use std::sync::Arc;
4
5use crate::editor::file_watcher::FileWatcher;
6use crate::log_store::{DEFAULT_QUOTA_BYTES, LogStore};
7use crate::registers::Registers;
8use crate::task_config::TasksFile;
9use crate::task_executor::TaskExecutor;
10use crate::task_registry::TaskRegistry;
11use crate::views::{
12 command_runner::CommandRunner, commit::CommitWindow, editor::EditorView,
13 extension_config::ExtensionConfigView, file_selector::FileSelector,
14 git_history::GitHistoryView, issue_view::IssueView, log_view::LogView,
15 task_archive::TaskArchiveView, terminal::TerminalView,
16};
17use crate::{file_index::SharedFileIndex, issue_registry::IssueRegistry, project, settings, theme};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum ScreenKind {
23 FileSelector,
24 #[allow(dead_code)]
25 Editor,
26 CommandRunner,
27 LogView,
28 CommitWindow,
29 Terminal,
30 GitHistory,
31 ExtensionConfig,
32 TaskArchive,
33 IssueList,
34}
35
36#[derive(Debug)]
37pub enum Screen {
38 FileSelector(FileSelector),
39 Editor(Box<EditorView>),
40 CommandRunner(CommandRunner),
41 LogView(LogView),
42 CommitWindow(Box<CommitWindow>),
43 Terminal(Box<TerminalView>),
44 GitHistory(Box<GitHistoryView>),
45 ExtensionConfig(ExtensionConfigView),
46 TaskArchive(Box<TaskArchiveView>),
47 IssueList(Box<IssueView>),
48}
49
50impl Screen {
51 pub fn view_kind(&self) -> crate::views::ViewKind {
53 use crate::views::{
54 View, command_runner::CommandRunner, commit::CommitWindow, editor::EditorView,
55 extension_config::ExtensionConfigView, file_selector::FileSelector,
56 git_history::GitHistoryView, issue_view::IssueView, log_view::LogView,
57 task_archive::TaskArchiveView, terminal::TerminalView,
58 };
59 match self {
60 Screen::Editor(_) => EditorView::KIND,
61 Screen::Terminal(_) => TerminalView::KIND,
62 Screen::CommitWindow(_) => CommitWindow::KIND,
63 Screen::GitHistory(_) => GitHistoryView::KIND,
64 Screen::FileSelector(_) => FileSelector::KIND,
65 Screen::CommandRunner(_) => CommandRunner::KIND,
66 Screen::LogView(_) => LogView::KIND,
67 Screen::ExtensionConfig(_) => ExtensionConfigView::KIND,
68 Screen::TaskArchive(_) => TaskArchiveView::KIND,
69 Screen::IssueList(_) => IssueView::KIND,
70 }
71 }
72
73 pub fn kind(&self) -> ScreenKind {
75 match self {
76 Screen::FileSelector(_) => ScreenKind::FileSelector,
77 Screen::Editor(_) => ScreenKind::Editor,
78 Screen::CommandRunner(_) => ScreenKind::CommandRunner,
79 Screen::LogView(_) => ScreenKind::LogView,
80 Screen::CommitWindow(_) => ScreenKind::CommitWindow,
81 Screen::Terminal(_) => ScreenKind::Terminal,
82 Screen::GitHistory(_) => ScreenKind::GitHistory,
83 Screen::ExtensionConfig(_) => ScreenKind::ExtensionConfig,
84 Screen::TaskArchive(_) => ScreenKind::TaskArchive,
85 Screen::IssueList(_) => ScreenKind::IssueList,
86 }
87 }
88}
89
90pub struct AppState {
91 pub screen: Screen,
92 pub status_msg: Option<String>,
93 pub should_quit: bool,
94 pub project: project::Project,
95 pub settings: settings::Settings,
96 pub theme: theme::Theme,
97 pub active_contexts: HashSet<String>,
99 pub file_index: SharedFileIndex,
102 pub stashed_commit_window: Option<Box<CommitWindow>>,
105 pub stashed_primary: Option<Screen>,
109 pub stashed_terminal: Option<Box<TerminalView>>,
112 pub registers: Registers,
114 pub clipboard: Option<arboard::Clipboard>,
117 pub schema_cache: HashMap<String, Arc<Value>>,
123 pub file_selector_search: Option<tokio::task::JoinHandle<()>>,
126 pub file_watcher: FileWatcher,
128 pub issue_registry: IssueRegistry,
132 pub task_registry: TaskRegistry,
135 pub task_executor: TaskExecutor,
137 pub log_store: LogStore,
139 pub task_config: Option<TasksFile>,
143 pub status_bar_clicks: crate::widgets::status_bar::StatusBarClickState,
146 pub context_menu: Option<crate::widgets::context_menu::ContextMenu>,
152 pub project_search_cancel: Option<tokio_util::sync::CancellationToken>,
156}
157
158impl AppState {
159 pub fn new(
160 screen: Screen,
161 project: project::Project,
162 settings: settings::Settings,
163 file_index: SharedFileIndex,
164 ) -> Self {
165 let theme = theme::Theme::resolve(&settings);
166 let log_dir = project.project_settings_path.join("cache").join("tasks");
167 let tasks_yaml = project.project_settings_path.join("tasks.yaml");
168 let task_config = match crate::task_config::load(&tasks_yaml) {
169 Ok(cfg) => cfg,
170 Err(errs) => {
171 for e in &errs {
172 log::warn!("tasks.yaml: {}", e);
173 }
174 None
175 }
176 };
177 let matcher_registry = project.extension_manager.matcher_registry_arc();
180
181 Self {
182 screen,
183 project,
184 settings,
185 theme,
186 file_index,
187 status_msg: None,
188 should_quit: false,
189 active_contexts: HashSet::new(),
190 stashed_commit_window: None,
191 stashed_primary: None,
192 stashed_terminal: None,
193 registers: Registers::new(20),
194 clipboard: None,
195 schema_cache: HashMap::new(),
196 file_selector_search: None,
197 file_watcher: FileWatcher::new(),
198 issue_registry: IssueRegistry::new(),
199 task_registry: TaskRegistry::new(),
200 task_executor: TaskExecutor::with_matcher_registry(matcher_registry),
201 log_store: LogStore::new(log_dir, DEFAULT_QUOTA_BYTES),
202 task_config,
203 status_bar_clicks: crate::widgets::status_bar::StatusBarClickState::default(),
204 context_menu: None,
205 project_search_cancel: None,
206 }
207 }
208
209 pub fn recompute_contexts(&mut self) {
212 self.active_contexts.clear();
213 self.active_contexts.insert("global".into());
214
215 match &self.screen {
216 Screen::Editor(ed) => {
217 self.active_contexts.insert("editor".into());
218 if ed.buffer.is_dirty() {
219 self.active_contexts.insert("editor.dirty".into());
220 }
221 }
222 Screen::FileSelector(_) => {
223 self.active_contexts.insert("file_selector".into());
224 }
225 Screen::CommandRunner(_) => {
226 self.active_contexts.insert("command_runner".into());
227 }
228 Screen::LogView(_) => {
229 self.active_contexts.insert("log_view".into());
230 }
231 Screen::CommitWindow(_) => {
232 self.active_contexts.insert("commit".into());
233 }
234 Screen::Terminal(_) => {
235 self.active_contexts.insert("terminal".into());
236 }
237 Screen::GitHistory(_) => {
238 self.active_contexts.insert("git_history".into());
239 }
240 Screen::ExtensionConfig(_) => {
241 self.active_contexts.insert("extension_config".into());
242 }
243 Screen::TaskArchive(_) => {
244 self.active_contexts.insert("task_archive".into());
245 }
246 Screen::IssueList(_) => {
247 self.active_contexts.insert("issue_list".into());
248 }
249 }
250
251 if self.project.has_git_repo() {
252 self.active_contexts.insert("git_repo".into());
253 }
254 }
255}
256
257impl AppState {
258 pub fn open_file_preserving_stash(&mut self, path: std::path::PathBuf) -> bool {
268 self.save_stashed_primary();
269 let folds = self.project.get_fold_state(&path);
270 match self.project.take_buffer(path) {
271 Ok(buf) => {
272 let ed = crate::views::editor::EditorView::open(buf, folds, &self.settings);
273 self.set_screen(Screen::Editor(Box::new(ed)));
274 true
275 }
276 Err(_) => false,
277 }
278 }
279
280 pub(crate) fn save_stashed_primary(&mut self) {
287 let Some(stashed) = self.stashed_primary.take() else {
288 return;
289 };
290 match stashed {
291 Screen::Editor(mut ed) => {
292 ed.stop_file_watching();
294 if let Some(ref path) = ed.buffer.path.clone() {
295 self.file_watcher.unwatch(path).ok();
296 }
297 let (buf, folds) = ed.into_state();
298 self.project.stash_buffer(buf, folds);
299 }
300 Screen::Terminal(tv) => {
301 self.stashed_terminal = Some(tv);
303 }
304 Screen::CommitWindow(cw) => {
305 self.stashed_commit_window = Some(cw);
307 }
308 _ => {
309 }
311 }
312 }
313
314 pub fn set_screen(&mut self, new_screen: Screen) -> Option<Screen> {
319 use crate::views::ViewKind;
320 let prev = std::mem::replace(&mut self.screen, new_screen);
321 let entering_modal = matches!(self.screen.view_kind(), ViewKind::Modal);
322 let was_primary = matches!(prev.view_kind(), ViewKind::Primary);
323 let now_primary = matches!(self.screen.view_kind(), ViewKind::Primary);
324
325 if entering_modal && was_primary {
327 self.stashed_primary = Some(prev);
329 None
331 } else {
332 if now_primary {
334 self.save_stashed_primary();
335 }
336 Some(prev)
337 }
338 }
339}