fresh/app/window_resources.rs
1//! Editor-global resources shared into every `Window` by `Arc` clone.
2//!
3//! ## Why this exists
4//!
5//! `Window` was originally constructed with only its own per-project state
6//! (buffers, splits, file_explorer, lsp, …) and reached back to `Editor`
7//! for everything else (`config`, `theme`, `plugin_manager`, registries,
8//! filesystem authority). The reach-back forced almost every handler to
9//! live on `impl Editor` rather than `impl Window`, because the body
10//! needed `&self` *as Editor* to read those resources.
11//!
12//! `WindowResources` flips that: every editor-global service a handler
13//! could plausibly need is shared into `Window` as an `Arc<…>` clone (or
14//! `Clone`-by-value for handles that already carry their own `Arc`s,
15//! like `Authority`). A `Window` method now has direct access to
16//! `self.config.editor.line_wrap`, `self.authority.path_translation`,
17//! etc., without any `Editor` reference. Methods that previously had to
18//! sit on `impl Editor` to read these can move to `impl Window`.
19//!
20//! ## What stays on `Editor` (not in `WindowResources`)
21//!
22//! - `next_buffer_id` allocator (separate concept — see
23//! [`BufferIdAllocator`])
24//! - `theme: Theme` — direct value (not `Arc`); pending Tier-2 migration
25//! - `clipboard: Clipboard` — owned value; pending Tier-2 migration
26//! - `mode_registry: ModeRegistry` — owned value; pending wrap
27//! - `quick_open_registry: QuickOpenRegistry` — owned value; pending wrap
28//! - `recovery_service: RecoveryService` — owned value; needs `Arc`
29//! wrapping when `recovery_actions.rs` migrates
30//! - `event_broadcaster: EventBroadcaster` — owned value; needs check
31//! - `plugin_manager: PluginManager` — needs `Arc<Mutex<…>>` wrapping
32//! when the first hook-firing handler migrates to `impl Window`
33//! - All `*_registry` types currently owned by value
34//!
35//! These are deliberately deferred: they can be added to `WindowResources`
36//! incrementally as method migrations surface the need. Foundation PR
37//! lands what's cheap to share today; later PRs widen the surface as
38//! needed by each `impl Window` move.
39
40use crate::config::Config;
41use crate::config_io::DirectoryContext;
42use crate::input::command_registry::CommandRegistry;
43use crate::input::keybindings::KeybindingResolver;
44use crate::model::filesystem::FileSystem;
45use crate::primitives::grammar::GrammarRegistry;
46use crate::services::authority::Authority;
47use crate::services::fs::FsManager;
48use crate::services::time_source::SharedTimeSource;
49use crate::view::theme::ThemeRegistry;
50use fresh_core::BufferId;
51use std::collections::HashMap;
52use std::sync::atomic::{AtomicUsize, Ordering};
53use std::sync::{Arc, RwLock};
54
55/// Globally-unique `BufferId` allocator shared across every `Window`.
56///
57/// `BufferId`s must be unique editor-wide so plugin APIs that thread
58/// ids around (`editor.openFile(...) -> BufferId`, `setActiveBuffer(id)`,
59/// terminal `terminalId` correlation, recovery files keyed by id) don't
60/// have to disambiguate by `WindowId`. The allocator is a single
61/// `Arc<AtomicUsize>` cloned into every `Window` — concurrent
62/// `next()` calls return distinct ids without locking.
63#[derive(Debug, Clone)]
64pub struct BufferIdAllocator(Arc<AtomicUsize>);
65
66impl BufferIdAllocator {
67 /// Construct an allocator starting at `start`. The first `next()`
68 /// call returns `BufferId(start)`.
69 pub fn new(start: usize) -> Self {
70 Self(Arc::new(AtomicUsize::new(start)))
71 }
72
73 /// Allocate the next `BufferId`. Thread-safe and lock-free.
74 pub fn next(&self) -> BufferId {
75 BufferId(self.0.fetch_add(1, Ordering::Relaxed))
76 }
77
78 /// Peek at the value the next `next()` call would return without
79 /// advancing. Test-only — production code should always use `next()`.
80 #[doc(hidden)]
81 pub fn peek(&self) -> usize {
82 self.0.load(Ordering::Relaxed)
83 }
84
85 /// Restore the counter to a specific value (used by workspace
86 /// rehydration so persisted ids don't collide with freshly-allocated
87 /// ones after restart).
88 pub fn set(&self, value: usize) {
89 self.0.store(value, Ordering::Relaxed);
90 }
91}
92
93/// Editor-global resources that every `Window` holds an `Arc`-cloned
94/// reference to.
95///
96/// One instance is constructed in `editor_init` and cloned into the base
97/// `Window`. `Editor::create_window` and the first-dive seed path in
98/// `set_active_window` clone it into every subsequent `Window`. All
99/// fields are cheap to clone (`Arc` increment or `Clone`-by-value where
100/// the inner type already carries `Arc`s like `Authority`).
101///
102/// A `Window` handler that needs any of these reads it directly:
103/// `self.resources.config.editor.line_wrap`,
104/// `self.resources.authority.path_translation`, etc. The
105/// [`Window::config()`] / `Window::authority()` accessors are the
106/// canonical reading API; the field itself stays `pub(crate)` so call
107/// sites can split-borrow disjoint sub-fields when the borrow checker
108/// needs it.
109#[derive(Clone)]
110pub struct WindowResources {
111 /// Read-only editor configuration. Hot-reloaded by swapping the
112 /// `Arc`'s pointee when the user edits their config file.
113 pub config: Arc<Config>,
114
115 /// Tree-sitter grammar registry. `Arc` because grammar loading
116 /// can be expensive and is shared across windows.
117 pub grammar_registry: Arc<GrammarRegistry>,
118
119 /// Theme registry (the *catalogue* of available themes, not the
120 /// active theme — that's `Editor::theme` for now, pending the
121 /// Tier-2 wrap).
122 pub theme_registry: Arc<ThemeRegistry>,
123
124 /// Cache of plugin-supplied theme JSONs, populated by plugin
125 /// commands and read by the theme loader.
126 pub theme_cache: Arc<RwLock<HashMap<String, serde_json::Value>>>,
127
128 /// Keybinding resolver (mode → key → command map). `RwLock` because
129 /// plugin commands can mutate the resolver at runtime.
130 pub keybindings: Arc<RwLock<KeybindingResolver>>,
131
132 /// Command registry (named commands a plugin or user can invoke).
133 /// `RwLock` for the same reason as `keybindings`.
134 pub command_registry: Arc<RwLock<CommandRegistry>>,
135
136 /// Filesystem operation manager (background renames, deletes, etc.)
137 pub fs_manager: Arc<FsManager>,
138
139 /// Direct host-filesystem handle. Held alongside the active
140 /// authority's filesystem because some operations (recovery file
141 /// IO, history persistence) intentionally bypass the authority's
142 /// translation.
143 pub local_filesystem: Arc<dyn FileSystem + Send + Sync>,
144
145 /// Globally-unique `BufferId` allocator (see [`BufferIdAllocator`]).
146 pub buffer_id_alloc: BufferIdAllocator,
147
148 /// Active filesystem authority (local / devcontainer / remote).
149 /// `Authority` is `Clone` because it internally holds `Arc`s for
150 /// the filesystem and path-translation handles; cloning here gives
151 /// each window an independent handle that points at the same
152 /// underlying authority.
153 pub authority: Authority,
154
155 /// Editor-wide time source (real clock in production, controllable
156 /// in tests). Already `Arc`-internal.
157 pub time_source: SharedTimeSource,
158
159 /// Directory context (config dir, themes dir, plugins dir, etc.).
160 /// Cloned by value because it's a small struct of `PathBuf`s.
161 pub dir_context: DirectoryContext,
162
163 /// Tokio runtime for async I/O tasks (LSP, file watchers, git, etc.).
164 /// Single runtime shared across all windows via `Arc`. `None` means
165 /// the editor was constructed without async support (rare).
166 pub tokio_runtime: Option<Arc<tokio::runtime::Runtime>>,
167
168 /// Async-message bridge (Sender + Arc'd Receiver). Windows clone this
169 /// to publish messages back to the editor's main loop. The Receiver
170 /// is `Arc<Mutex<>>` internally, so all clones drain the same queue.
171 pub async_bridge: Option<crate::services::async_bridge::AsyncBridge>,
172
173 /// Plugin manager (single QuickJS instance), wrapped in
174 /// `Arc<RwLock<>>` so windows can fire hooks and read state
175 /// without going through `Editor`. Reads take a read lock; the
176 /// few `&mut self` methods (process_commands, check_thread_health,
177 /// test_inject_command) take a write lock.
178 pub plugin_manager: Arc<RwLock<crate::services::plugins::manager::PluginManager>>,
179
180 /// Active resolved theme, mirrored from `Editor.theme`. Each
181 /// `set_theme` / theme-reload pushes the new value into this
182 /// `Arc<RwLock<>>` so Window methods can read colors without
183 /// going through Editor.
184 pub theme: Arc<RwLock<crate::view::theme::Theme>>,
185
186 /// Editor-wide event broadcaster (cloneable, Arc inside).
187 pub event_broadcaster: crate::model::control_event::EventBroadcaster,
188}