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::SharedFileIndex, 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 file_index: SharedFileIndex,
117 pub stashed_commit_window: Option<Box<CommitWindow>>,
120 pub stashed_primary: Option<Screen>,
124 pub stashed_terminal: Option<Box<TerminalView>>,
127 pub registers: Registers,
129 pub clipboard: Option<arboard::Clipboard>,
132 pub schema_cache: HashMap<String, Arc<Value>>,
138 pub file_selector_search: Option<tokio::task::JoinHandle<()>>,
141 pub file_watcher: FileWatcher,
143 pub issue_registry: IssueRegistry,
147 pub task_registry: TaskRegistry,
150 pub task_executor: TaskExecutor,
152 pub log_store: LogStore,
154 pub task_config: Option<TasksFile>,
158 pub status_bar_clicks: crate::widgets::status_bar::StatusBarClickState,
161 pub context_menu: Option<crate::widgets::context_menu::ContextMenu>,
167 pub project_search_cancel: Option<tokio_util::sync::CancellationToken>,
171 pub project_search_generation_shared: std::sync::Arc<std::sync::atomic::AtomicU64>,
176 pub lsp_trigger_chars: HashMap<String, Vec<String>>,
178 pub hover_idle_generation: u64,
183 pub hover_idle_task: Option<tokio::task::JoinHandle<()>>,
186 pub project_search_generation: u64,
191 pub pending_search_state: Option<crate::views::editor::SearchState>,
194 pub pending_go_to_position: Option<(usize, usize)>,
197}
198
199impl AppState {
200 pub fn new(
201 screen: Screen,
202 project: project::Project,
203 settings: settings::Settings,
204 file_index: SharedFileIndex,
205 ) -> Self {
206 let theme = theme::Theme::resolve(&settings);
207 let log_dir = project.project_settings_path.join("cache").join("tasks");
208 let tasks_yaml = project.project_settings_path.join("tasks.yaml");
209 let task_config = match crate::task_config::load(&tasks_yaml) {
210 Ok(cfg) => cfg,
211 Err(errs) => {
212 for e in &errs {
213 log::warn!("tasks.yaml: {}", e);
214 }
215 None
216 }
217 };
218 let matcher_registry = project.extension_manager.matcher_registry_arc();
221
222 Self {
223 screen,
224 project,
225 settings,
226 theme,
227 file_index,
228 status_msg: None,
229 should_quit: false,
230 active_contexts: HashSet::new(),
231 stashed_commit_window: None,
232 stashed_primary: None,
233 stashed_terminal: None,
234 registers: Registers::new(20),
235 clipboard: None,
236 schema_cache: HashMap::new(),
237 file_selector_search: None,
238 file_watcher: FileWatcher::new(),
239 issue_registry: IssueRegistry::new(),
240 task_registry: TaskRegistry::new(),
241 task_executor: TaskExecutor::with_matcher_registry(matcher_registry),
242 log_store: LogStore::new(log_dir, DEFAULT_QUOTA_BYTES),
243 task_config,
244 status_bar_clicks: crate::widgets::status_bar::StatusBarClickState::default(),
245 context_menu: None,
246 project_search_cancel: None,
247 project_search_generation_shared: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0)),
248 lsp_trigger_chars: HashMap::new(),
249 hover_idle_generation: 0,
250 hover_idle_task: None,
251 project_search_generation: 0,
252 pending_search_state: None,
253 pending_go_to_position: None,
254 }
255 }
256
257 pub fn recompute_contexts(&mut self) {
260 self.active_contexts.clear();
261 self.active_contexts.insert("global".into());
262
263 match &self.screen {
264 Screen::Editor(ed) => {
265 self.active_contexts.insert("editor".into());
266 if ed.buffer.is_dirty() {
267 self.active_contexts.insert("editor.dirty".into());
268 }
269 }
270 Screen::FileSelector(_) => {
271 self.active_contexts.insert("file_selector".into());
272 }
273 Screen::CommandRunner(_) => {
274 self.active_contexts.insert("command_runner".into());
275 }
276 Screen::LogView(_) => {
277 self.active_contexts.insert("log_view".into());
278 }
279 Screen::CommitWindow(_) => {
280 self.active_contexts.insert("commit".into());
281 }
282 Screen::Terminal(_) => {
283 self.active_contexts.insert("terminal".into());
284 }
285 Screen::GitHistory(_) => {
286 self.active_contexts.insert("git_history".into());
287 }
288 Screen::GitBranches(_) => {
289 self.active_contexts.insert("git_branches".into());
290 }
291 Screen::GitHistoryEditor(_) => {
292 self.active_contexts.insert("git_history_editor".into());
293 }
294 Screen::GitPull(_) => {
295 self.active_contexts.insert("git_pull".into());
296 }
297 Screen::ExtensionConfig(_) => {
298 self.active_contexts.insert("extension_config".into());
299 }
300 Screen::TaskArchive(_) => {
301 self.active_contexts.insert("task_archive".into());
302 }
303 Screen::IssueList(_) => {
304 self.active_contexts.insert("issue_list".into());
305 }
306 }
307
308 if self.project.has_git_repo() {
309 self.active_contexts.insert("git_repo".into());
310 }
311 }
312}
313
314impl AppState {
315 pub fn open_file_preserving_stash(&mut self, path: std::path::PathBuf) -> bool {
325 self.save_stashed_primary();
326 let folds = self.project.get_fold_state(&path);
327 match self.project.take_buffer(path) {
328 Ok(buf) => {
329 let ed = crate::views::editor::EditorView::open(buf, folds, &self.settings);
330 self.set_screen(Screen::Editor(Box::new(ed)));
331 true
332 }
333 Err(_) => false,
334 }
335 }
336
337 pub(crate) fn save_stashed_primary(&mut self) {
344 let Some(stashed) = self.stashed_primary.take() else {
345 return;
346 };
347 match stashed {
348 Screen::Editor(mut ed) => {
349 ed.stop_file_watching();
351 if let Some(ref path) = ed.buffer.path.clone() {
352 self.file_watcher.unwatch(path).ok();
353 }
354 let (buf, folds) = ed.into_state();
355 self.project.stash_buffer(buf, folds);
356 }
357 Screen::Terminal(tv) => {
358 self.stashed_terminal = Some(tv);
360 }
361 Screen::CommitWindow(cw) => {
362 self.stashed_commit_window = Some(cw);
364 }
365 _ => {
366 }
368 }
369 }
370
371 pub fn set_screen(&mut self, new_screen: Screen) -> Option<Screen> {
376 use crate::views::ViewKind;
377 let prev = std::mem::replace(&mut self.screen, new_screen);
378 let entering_modal = matches!(self.screen.view_kind(), ViewKind::Modal);
379 let was_primary = matches!(prev.view_kind(), ViewKind::Primary);
380 let now_primary = matches!(self.screen.view_kind(), ViewKind::Primary);
381
382 if entering_modal && was_primary {
384 self.stashed_primary = Some(prev);
386 None
388 } else {
389 if now_primary {
391 self.save_stashed_primary();
392 }
393 Some(prev)
394 }
395 }
396}