Skip to main content

fresh/app/window/
buffers.rs

1//! Per-window buffer storage. The inner map and split tree are
2//! module-private so every add/remove and every multi-field mutation
3//! goes through this surface — that's the seam where the "every
4//! BufferId reachable from the split tree is in here" invariant
5//! (issue #1939) will eventually be enforced.
6
7use fresh_core::BufferId;
8use std::collections::{HashMap, HashSet};
9use std::path::PathBuf;
10
11use crate::model::event::LeafId;
12use crate::state::EditorState;
13use crate::view::split::{SplitManager, SplitViewState};
14
15type Splits = (SplitManager, HashMap<LeafId, SplitViewState>);
16
17pub struct WindowBuffers {
18    map: HashMap<BufferId, EditorState>,
19    splits: Option<Splits>,
20}
21
22impl WindowBuffers {
23    pub fn new() -> Self {
24        Self {
25            map: HashMap::new(),
26            splits: None,
27        }
28    }
29
30    // -- single-buffer access --------------------------------------------
31
32    pub fn get(&self, id: &BufferId) -> Option<&EditorState> {
33        self.map.get(id)
34    }
35
36    pub fn get_mut(&mut self, id: &BufferId) -> Option<&mut EditorState> {
37        self.map.get_mut(id)
38    }
39
40    pub fn insert(&mut self, id: BufferId, state: EditorState) -> Option<EditorState> {
41        self.map.insert(id, state)
42    }
43
44    pub fn remove(&mut self, id: &BufferId) -> Option<EditorState> {
45        self.map.remove(id)
46    }
47
48    pub fn contains_key(&self, id: &BufferId) -> bool {
49        self.map.contains_key(id)
50    }
51
52    pub fn len(&self) -> usize {
53        self.map.len()
54    }
55
56    pub fn iter(&self) -> std::collections::hash_map::Iter<'_, BufferId, EditorState> {
57        self.map.iter()
58    }
59
60    /// Read-only handle to the buffer state map, for rendering helpers
61    /// that take `&HashMap<BufferId, EditorState>`. Mutation is not
62    /// available through this path — go through one of the named
63    /// methods.
64    pub fn as_map(&self) -> &HashMap<BufferId, EditorState> {
65        &self.map
66    }
67
68    /// Mutable handle to the buffer state map, for the render path
69    /// which needs to walk every buffer and mutate per-render
70    /// scratch state. *Doesn't* hand out the splits, so the
71    /// "BufferId-in-splits-is-in-buffers" invariant is bounded by
72    /// the size of `insert`/`remove`: render won't add or remove
73    /// buffers, it just mutates their contents. Prefer the `with_*`
74    /// methods at any new call site.
75    pub fn as_map_mut(&mut self) -> &mut HashMap<BufferId, EditorState> {
76        &mut self.map
77    }
78
79    /// Owned snapshot of every buffer id — for callers that need to
80    /// mutate `self` while iterating.
81    pub fn ids(&self) -> Vec<BufferId> {
82        self.map.keys().copied().collect()
83    }
84
85    pub fn find_id<F>(&self, mut predicate: F) -> Option<BufferId>
86    where
87        F: FnMut(BufferId, &EditorState) -> bool,
88    {
89        self.map
90            .iter()
91            .find(|(id, state)| predicate(**id, state))
92            .map(|(id, _)| *id)
93    }
94
95    pub fn count_where<F>(&self, mut predicate: F) -> usize
96    where
97        F: FnMut(BufferId, &EditorState) -> bool,
98    {
99        self.map
100            .iter()
101            .filter(|(id, state)| predicate(**id, state))
102            .count()
103    }
104
105    pub fn paths(&self) -> Vec<PathBuf> {
106        self.map
107            .values()
108            .filter_map(|state| state.buffer.file_path().map(PathBuf::from))
109            .collect()
110    }
111
112    pub fn languages(&self) -> HashSet<String> {
113        self.map
114            .values()
115            .map(|state| state.language.clone())
116            .collect()
117    }
118
119    pub fn any_needs_semantic_redraw(&self) -> bool {
120        self.map.values().any(|state| {
121            state
122                .reference_highlight_overlay
123                .needs_redraw()
124                .is_some_and(|remaining| remaining.is_zero())
125        })
126    }
127
128    // -- splits (read) ---------------------------------------------------
129
130    pub fn splits(&self) -> Option<&Splits> {
131        self.splits.as_ref()
132    }
133
134    pub fn split_manager(&self) -> Option<&SplitManager> {
135        self.splits.as_ref().map(|(m, _)| m)
136    }
137
138    pub fn split_view_states(&self) -> Option<&HashMap<LeafId, SplitViewState>> {
139        self.splits.as_ref().map(|(_, vs)| vs)
140    }
141
142    pub fn has_splits(&self) -> bool {
143        self.splits.is_some()
144    }
145
146    // -- splits (mut, no buffer interaction) -----------------------------
147    //
148    // Used alone these are safe; the borrow checker prevents any caller
149    // from holding one of these alongside a `get_mut` / `insert` / etc.
150    // To touch a buffer state and a split together, use one of the
151    // `with_*` methods below.
152
153    pub fn splits_mut(&mut self) -> Option<&mut Splits> {
154        self.splits.as_mut()
155    }
156
157    pub fn split_manager_mut(&mut self) -> Option<&mut SplitManager> {
158        self.splits.as_mut().map(|(m, _)| m)
159    }
160
161    pub fn split_view_states_mut(&mut self) -> Option<&mut HashMap<LeafId, SplitViewState>> {
162        self.splits.as_mut().map(|(_, vs)| vs)
163    }
164
165    pub fn set_splits(&mut self, splits: Splits) {
166        self.splits = Some(splits);
167    }
168
169    pub fn clear_splits(&mut self) {
170        self.splits = None;
171    }
172
173    // -- combined mutation (closure-based) -------------------------------
174    //
175    // These methods own the disjoint sub-borrow internally so callers
176    // can't accidentally write a stale `BufferId` into the split tree
177    // (or remove a buffer the split tree still points at) — the
178    // closure's lifetime is tied to a single owning borrow.
179
180    /// Run `f` with the buffer state and the named split's view state.
181    /// Returns `None` if either the buffer or the split is missing.
182    pub fn with_buffer_and_split<F, R>(&mut self, buf: BufferId, split: LeafId, f: F) -> Option<R>
183    where
184        F: FnOnce(&mut EditorState, &mut SplitViewState) -> R,
185    {
186        let state = self.map.get_mut(&buf)?;
187        let (_, vs_map) = self.splits.as_mut()?;
188        let vs = vs_map.get_mut(&split)?;
189        Some(f(state, vs))
190    }
191
192    /// Run `f` with the buffer state and the full per-leaf view-state
193    /// map. Used by fold/cursor operations that touch every split
194    /// hosting `buf`.
195    pub fn with_buffer_and_view_states<F, R>(&mut self, buf: BufferId, f: F) -> Option<R>
196    where
197        F: FnOnce(&mut EditorState, &mut HashMap<LeafId, SplitViewState>) -> R,
198    {
199        let state = self.map.get_mut(&buf)?;
200        let (_, vs_map) = self.splits.as_mut()?;
201        Some(f(state, vs_map))
202    }
203
204    /// Run `f` with mutable refs to the buffer map, the split
205    /// manager, and the per-leaf view state map. The render path
206    /// and per-frame plugin-state snapshot need all three live at
207    /// once — closure scope bounds the joint borrow.
208    pub fn with_all_mut<F, R>(&mut self, f: F) -> Option<R>
209    where
210        F: FnOnce(
211            &mut HashMap<BufferId, EditorState>,
212            &mut SplitManager,
213            &mut HashMap<LeafId, SplitViewState>,
214        ) -> R,
215    {
216        let buffer_map = &mut self.map;
217        let (mgr, vs_map) = self.splits.as_mut()?;
218        Some(f(buffer_map, mgr, vs_map))
219    }
220}
221
222impl Default for WindowBuffers {
223    fn default() -> Self {
224        Self::new()
225    }
226}
227
228impl<'a> IntoIterator for &'a WindowBuffers {
229    type Item = (&'a BufferId, &'a EditorState);
230    type IntoIter = std::collections::hash_map::Iter<'a, BufferId, EditorState>;
231    fn into_iter(self) -> Self::IntoIter {
232        self.map.iter()
233    }
234}
235
236impl<'a> IntoIterator for &'a mut WindowBuffers {
237    type Item = (&'a BufferId, &'a mut EditorState);
238    type IntoIter = std::collections::hash_map::IterMut<'a, BufferId, EditorState>;
239    fn into_iter(self) -> Self::IntoIter {
240        self.map.iter_mut()
241    }
242}