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_branches::BranchView, git_history::GitHistoryView, git_pull::GitPullView,
15 issue_view::IssueView, log_view::LogView,
16 task_archive::TaskArchiveView, terminal::TerminalView,
17};
18use crate::{file_index::SharedRegistry, issue_registry::IssueRegistry, project, settings, theme};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum ScreenKind {
24 FileSelector,
25 #[allow(dead_code)]
26 Editor,
27 CommandRunner,
28 LogView,
29 CommitWindow,
30 Terminal,
31 GitHistory,
32 GitBranches,
33 GitHistoryEditor,
34 ExtensionConfig,
35 TaskArchive,
36 IssueList,
37 GitPull,
38}
39
40#[derive(Debug)]
41pub enum Screen {
42 FileSelector(FileSelector),
43 Editor(Box<EditorView>),
44 CommandRunner(CommandRunner),
45 LogView(LogView),
46 CommitWindow(Box<CommitWindow>),
47 Terminal(Box<TerminalView>),
48 GitHistory(Box<GitHistoryView>),
49 GitBranches(Box<BranchView>),
50 GitHistoryEditor(Box<crate::views::git_history_editor::GitHistoryEditorView>),
51 ExtensionConfig(ExtensionConfigView),
52 TaskArchive(Box<TaskArchiveView>),
53 IssueList(Box<IssueView>),
54 GitPull(Box<GitPullView>),
55}
56
57impl Screen {
58 pub fn view_kind(&self) -> crate::views::ViewKind {
60 use crate::views::{
61 View, command_runner::CommandRunner, commit::CommitWindow, editor::EditorView,
62 extension_config::ExtensionConfigView, file_selector::FileSelector,
63 git_branches::BranchView, git_history::GitHistoryView,
64 git_history_editor::GitHistoryEditorView, git_pull::GitPullView,
65 issue_view::IssueView,
66 log_view::LogView, task_archive::TaskArchiveView, terminal::TerminalView,
67 };
68 match self {
69 Screen::Editor(_) => EditorView::KIND,
70 Screen::Terminal(_) => TerminalView::KIND,
71 Screen::CommitWindow(_) => CommitWindow::KIND,
72 Screen::GitHistory(_) => GitHistoryView::KIND,
73 Screen::GitBranches(_) => BranchView::KIND,
74 Screen::GitHistoryEditor(_) => GitHistoryEditorView::KIND,
75 Screen::GitPull(_) => GitPullView::KIND,
76 Screen::FileSelector(_) => FileSelector::KIND,
77 Screen::CommandRunner(_) => CommandRunner::KIND,
78 Screen::LogView(_) => LogView::KIND,
79 Screen::ExtensionConfig(_) => ExtensionConfigView::KIND,
80 Screen::TaskArchive(_) => TaskArchiveView::KIND,
81 Screen::IssueList(_) => IssueView::KIND,
82 }
83 }
84
85 pub fn kind(&self) -> ScreenKind {
87 match self {
88 Screen::FileSelector(_) => ScreenKind::FileSelector,
89 Screen::Editor(_) => ScreenKind::Editor,
90 Screen::CommandRunner(_) => ScreenKind::CommandRunner,
91 Screen::LogView(_) => ScreenKind::LogView,
92 Screen::CommitWindow(_) => ScreenKind::CommitWindow,
93 Screen::Terminal(_) => ScreenKind::Terminal,
94 Screen::GitHistory(_) => ScreenKind::GitHistory,
95 Screen::GitBranches(_) => ScreenKind::GitBranches,
96 Screen::GitHistoryEditor(_) => ScreenKind::GitHistoryEditor,
97 Screen::GitPull(_) => ScreenKind::GitPull,
98 Screen::ExtensionConfig(_) => ScreenKind::ExtensionConfig,
99 Screen::TaskArchive(_) => ScreenKind::TaskArchive,
100 Screen::IssueList(_) => ScreenKind::IssueList,
101 }
102 }
103}
104
105pub struct AppState {
106 pub screen: Screen,
107 pub status_msg: Option<String>,
108 pub should_quit: bool,
109 pub project: project::Project,
110 pub settings: settings::Settings,
111 pub theme: theme::Theme,
112 pub active_contexts: HashSet<String>,
114 pub registry: SharedRegistry,
116 pub stashed_commit_window: Option<Box<CommitWindow>>,
119 pub stashed_primary: Option<Screen>,
123 pub stashed_terminal: Option<Box<TerminalView>>,
126 pub registers: Registers,
128 pub clipboard: Option<arboard::Clipboard>,
131 pub schema_cache: HashMap<String, Arc<Value>>,
137 pub file_selector_search: Option<tokio::task::JoinHandle<()>>,
140 pub file_watcher: FileWatcher,
142 pub issue_registry: IssueRegistry,
146 pub task_registry: TaskRegistry,
149 pub task_executor: TaskExecutor,
151 pub log_store: LogStore,
153 pub task_config: Option<TasksFile>,
157 pub status_bar_clicks: crate::widgets::status_bar::StatusBarClickState,
160 pub context_menu: Option<crate::widgets::context_menu::ContextMenu>,
166 pub project_search_cancel: Option<tokio_util::sync::CancellationToken>,
170 pub project_search_generation_shared: std::sync::Arc<std::sync::atomic::AtomicU64>,
175 pub lsp_trigger_chars: HashMap<String, Vec<String>>,
177 pub hover_idle_generation: u64,
182 pub hover_idle_task: Option<tokio::task::JoinHandle<()>>,
185 pub project_search_generation: u64,
190 pub pending_search_state: Option<crate::views::editor::SearchState>,
193 pub pending_go_to_position: Option<(usize, usize)>,
196}
197
198impl AppState {
199 pub fn new(
200 screen: Screen,
201 project: project::Project,
202 settings: settings::Settings,
203 registry: SharedRegistry,
204 ) -> Self {
205 let theme = theme::Theme::resolve(&settings);
206 let log_dir = project.project_settings_path.join("cache").join("tasks");
207 let tasks_yaml = project.project_settings_path.join("tasks.yaml");
208 let task_config = match crate::task_config::load(&tasks_yaml) {
209 Ok(cfg) => cfg,
210 Err(errs) => {
211 for e in &errs {
212 log::warn!("tasks.yaml: {}", e);
213 }
214 None
215 }
216 };
217 let matcher_registry = project.extension_manager.matcher_registry_arc();
220
221 Self {
222 screen,
223 project,
224 settings,
225 theme,
226 registry,
227 status_msg: None,
228 should_quit: false,
229 active_contexts: HashSet::new(),
230 stashed_commit_window: None,
231 stashed_primary: None,
232 stashed_terminal: None,
233 registers: Registers::new(20),
234 clipboard: None,
235 schema_cache: HashMap::new(),
236 file_selector_search: None,
237 file_watcher: FileWatcher::new(),
238 issue_registry: IssueRegistry::new(),
239 task_registry: TaskRegistry::new(),
240 task_executor: TaskExecutor::with_matcher_registry(matcher_registry),
241 log_store: LogStore::new(log_dir, DEFAULT_QUOTA_BYTES),
242 task_config,
243 status_bar_clicks: crate::widgets::status_bar::StatusBarClickState::default(),
244 context_menu: None,
245 project_search_cancel: None,
246 project_search_generation_shared: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0)),
247 lsp_trigger_chars: HashMap::new(),
248 hover_idle_generation: 0,
249 hover_idle_task: None,
250 project_search_generation: 0,
251 pending_search_state: None,
252 pending_go_to_position: None,
253 }
254 }
255
256 pub fn recompute_contexts(&mut self) {
259 self.active_contexts.clear();
260 self.active_contexts.insert("global".into());
261
262 match &self.screen {
263 Screen::Editor(ed) => {
264 self.active_contexts.insert("editor".into());
265 if ed.buffer.is_dirty() {
266 self.active_contexts.insert("editor.dirty".into());
267 }
268 }
269 Screen::FileSelector(_) => {
270 self.active_contexts.insert("file_selector".into());
271 }
272 Screen::CommandRunner(_) => {
273 self.active_contexts.insert("command_runner".into());
274 }
275 Screen::LogView(_) => {
276 self.active_contexts.insert("log_view".into());
277 }
278 Screen::CommitWindow(_) => {
279 self.active_contexts.insert("commit".into());
280 }
281 Screen::Terminal(_) => {
282 self.active_contexts.insert("terminal".into());
283 }
284 Screen::GitHistory(_) => {
285 self.active_contexts.insert("git_history".into());
286 }
287 Screen::GitBranches(_) => {
288 self.active_contexts.insert("git_branches".into());
289 }
290 Screen::GitHistoryEditor(_) => {
291 self.active_contexts.insert("git_history_editor".into());
292 }
293 Screen::GitPull(_) => {
294 self.active_contexts.insert("git_pull".into());
295 }
296 Screen::ExtensionConfig(_) => {
297 self.active_contexts.insert("extension_config".into());
298 }
299 Screen::TaskArchive(_) => {
300 self.active_contexts.insert("task_archive".into());
301 }
302 Screen::IssueList(_) => {
303 self.active_contexts.insert("issue_list".into());
304 }
305 }
306
307 if self.project.has_git_repo() {
308 self.active_contexts.insert("git_repo".into());
309 }
310 }
311}
312
313impl AppState {
314 pub fn open_file_preserving_stash(&mut self, path: std::path::PathBuf) -> bool {
324 self.save_stashed_primary();
325 let folds = self.project.get_fold_state(&path);
326 match self.project.take_buffer(path) {
327 Ok(buf) => {
328 let ed = crate::views::editor::EditorView::open(buf, folds, &self.settings);
329 self.set_screen(Screen::Editor(Box::new(ed)));
330 true
331 }
332 Err(_) => false,
333 }
334 }
335
336 pub(crate) fn save_stashed_primary(&mut self) {
343 let Some(stashed) = self.stashed_primary.take() else {
344 return;
345 };
346 match stashed {
347 Screen::Editor(mut ed) => {
348 ed.stop_file_watching();
350 if let Some(ref path) = ed.buffer.path.clone() {
351 self.file_watcher.unwatch(path).ok();
352 }
353 let (buf, folds) = ed.into_state();
354 self.project.stash_buffer(buf, folds);
355 }
356 Screen::Terminal(tv) => {
357 self.stashed_terminal = Some(tv);
359 }
360 Screen::CommitWindow(cw) => {
361 self.stashed_commit_window = Some(cw);
363 }
364 _ => {
365 }
367 }
368 }
369
370 pub fn set_screen(&mut self, new_screen: Screen) -> Option<Screen> {
375 use crate::views::ViewKind;
376 let prev = std::mem::replace(&mut self.screen, new_screen);
377 let entering_modal = matches!(self.screen.view_kind(), ViewKind::Modal);
378 let was_primary = matches!(prev.view_kind(), ViewKind::Primary);
379 let now_primary = matches!(self.screen.view_kind(), ViewKind::Primary);
380
381 if entering_modal && was_primary {
383 self.stashed_primary = Some(prev);
385 None
387 } else {
388 if now_primary {
390 self.save_stashed_primary();
391 }
392 Some(prev)
393 }
394 }
395}