1use crate::model::event::SplitId;
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct SyncAnchor {
20 pub left_line: usize,
22 pub right_line: usize,
24}
25
26pub type ScrollSyncGroupId = u32;
28
29#[derive(Debug, Clone)]
34pub struct ScrollSyncGroup {
35 pub id: ScrollSyncGroupId,
37 pub left_split: SplitId,
39 pub right_split: SplitId,
41 pub scroll_line: usize,
44 pub anchors: Vec<SyncAnchor>,
47}
48
49impl ScrollSyncGroup {
50 pub fn new(id: ScrollSyncGroupId, left_split: SplitId, right_split: SplitId) -> Self {
52 Self {
53 id,
54 left_split,
55 right_split,
56 scroll_line: 0,
57 anchors: vec![SyncAnchor {
58 left_line: 0,
59 right_line: 0,
60 }],
61 }
62 }
63
64 pub fn set_anchors(&mut self, anchors: Vec<SyncAnchor>) {
67 self.anchors = anchors;
68 if self.anchors.is_empty() {
70 self.anchors.push(SyncAnchor {
71 left_line: 0,
72 right_line: 0,
73 });
74 }
75 }
76
77 pub fn contains_split(&self, split_id: SplitId) -> bool {
79 self.left_split == split_id || self.right_split == split_id
80 }
81
82 pub fn is_left_split(&self, split_id: SplitId) -> bool {
84 self.left_split == split_id
85 }
86
87 pub fn left_to_right_line(&self, left_line: usize) -> usize {
89 let anchor = self
91 .anchors
92 .iter()
93 .rfind(|a| a.left_line <= left_line)
94 .unwrap_or(&self.anchors[0]);
95
96 let offset = left_line.saturating_sub(anchor.left_line);
98
99 anchor.right_line.saturating_add(offset)
101 }
102
103 pub fn right_to_left_line(&self, right_line: usize) -> usize {
105 let anchor = self
107 .anchors
108 .iter()
109 .rfind(|a| a.right_line <= right_line)
110 .unwrap_or(&self.anchors[0]);
111
112 let offset = right_line.saturating_sub(anchor.right_line);
114
115 anchor.left_line.saturating_add(offset)
117 }
118
119 pub fn apply_scroll_delta(&mut self, split_id: SplitId, delta_lines: isize) -> bool {
122 if !self.contains_split(split_id) {
123 return false;
124 }
125
126 let new_scroll = if delta_lines >= 0 {
128 self.scroll_line.saturating_add(delta_lines as usize)
129 } else {
130 self.scroll_line.saturating_sub(delta_lines.unsigned_abs())
131 };
132
133 self.scroll_line = new_scroll;
134 true
135 }
136
137 pub fn set_scroll_line(&mut self, line: usize) {
140 self.scroll_line = line;
141 }
142
143 pub fn left_scroll_line(&self) -> usize {
145 self.scroll_line
146 }
147
148 pub fn right_scroll_line(&self) -> usize {
150 self.left_to_right_line(self.scroll_line)
151 }
152
153 pub fn scroll_line_for_split(&self, split_id: SplitId) -> usize {
155 if split_id == self.left_split {
156 self.left_scroll_line()
157 } else {
158 self.right_scroll_line()
159 }
160 }
161}
162
163#[derive(Debug, Default)]
165pub struct ScrollSyncManager {
166 groups: Vec<ScrollSyncGroup>,
168 next_id: ScrollSyncGroupId,
170}
171
172impl ScrollSyncManager {
173 pub fn new() -> Self {
175 Self {
176 groups: Vec::new(),
177 next_id: 1,
178 }
179 }
180
181 pub fn create_group(&mut self, left_split: SplitId, right_split: SplitId) -> ScrollSyncGroupId {
183 let id = self.next_id;
184 self.next_id += 1;
185
186 let group = ScrollSyncGroup::new(id, left_split, right_split);
187 self.groups.push(group);
188 id
189 }
190
191 pub fn create_group_with_id(
194 &mut self,
195 id: ScrollSyncGroupId,
196 left_split: SplitId,
197 right_split: SplitId,
198 ) -> bool {
199 if self.groups.iter().any(|g| g.id == id) {
201 return false;
202 }
203
204 let group = ScrollSyncGroup::new(id, left_split, right_split);
205 self.groups.push(group);
206 true
207 }
208
209 pub fn remove_group(&mut self, id: ScrollSyncGroupId) -> bool {
211 if let Some(pos) = self.groups.iter().position(|g| g.id == id) {
212 self.groups.remove(pos);
213 true
214 } else {
215 false
216 }
217 }
218
219 pub fn remove_groups_for_split(&mut self, split_id: SplitId) {
221 self.groups.retain(|g| !g.contains_split(split_id));
222 }
223
224 pub fn get_group_mut(&mut self, id: ScrollSyncGroupId) -> Option<&mut ScrollSyncGroup> {
226 self.groups.iter_mut().find(|g| g.id == id)
227 }
228
229 pub fn get_group(&self, id: ScrollSyncGroupId) -> Option<&ScrollSyncGroup> {
231 self.groups.iter().find(|g| g.id == id)
232 }
233
234 pub fn find_group_for_split(&self, split_id: SplitId) -> Option<&ScrollSyncGroup> {
236 self.groups.iter().find(|g| g.contains_split(split_id))
237 }
238
239 pub fn find_group_for_split_mut(&mut self, split_id: SplitId) -> Option<&mut ScrollSyncGroup> {
241 self.groups.iter_mut().find(|g| g.contains_split(split_id))
242 }
243
244 pub fn is_split_synced(&self, split_id: SplitId) -> bool {
246 self.groups.iter().any(|g| g.contains_split(split_id))
247 }
248
249 pub fn groups(&self) -> &[ScrollSyncGroup] {
251 &self.groups
252 }
253
254 pub fn apply_scroll_delta(&mut self, split_id: SplitId, delta_lines: isize) -> bool {
257 if let Some(group) = self.find_group_for_split_mut(split_id) {
258 group.apply_scroll_delta(split_id, delta_lines);
259 true
260 } else {
261 false
262 }
263 }
264
265 pub fn set_anchors(&mut self, group_id: ScrollSyncGroupId, anchors: Vec<SyncAnchor>) {
267 if let Some(group) = self.get_group_mut(group_id) {
268 group.set_anchors(anchors);
269 }
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn test_left_to_right_line_simple() {
279 let mut group = ScrollSyncGroup::new(1, SplitId(1), SplitId(2));
280 group.set_anchors(vec![
281 SyncAnchor {
282 left_line: 0,
283 right_line: 0,
284 },
285 SyncAnchor {
286 left_line: 10,
287 right_line: 10,
288 },
289 SyncAnchor {
290 left_line: 20,
291 right_line: 25,
292 }, ]);
294
295 assert_eq!(group.left_to_right_line(0), 0);
297 assert_eq!(group.left_to_right_line(5), 5);
298
299 assert_eq!(group.left_to_right_line(10), 10);
301 assert_eq!(group.left_to_right_line(15), 15);
302
303 assert_eq!(group.left_to_right_line(20), 25);
305 assert_eq!(group.left_to_right_line(25), 30);
306 }
307
308 #[test]
309 fn test_right_to_left_line() {
310 let mut group = ScrollSyncGroup::new(1, SplitId(1), SplitId(2));
311 group.set_anchors(vec![
312 SyncAnchor {
313 left_line: 0,
314 right_line: 0,
315 },
316 SyncAnchor {
317 left_line: 10,
318 right_line: 15,
319 }, ]);
321
322 assert_eq!(group.right_to_left_line(0), 0);
324 assert_eq!(group.right_to_left_line(5), 5);
325
326 assert_eq!(group.right_to_left_line(15), 10);
328 assert_eq!(group.right_to_left_line(20), 15);
329 }
330
331 #[test]
332 fn test_scroll_delta() {
333 let mut group = ScrollSyncGroup::new(1, SplitId(1), SplitId(2));
334 group.set_anchors(vec![
335 SyncAnchor {
336 left_line: 0,
337 right_line: 0,
338 },
339 SyncAnchor {
340 left_line: 50,
341 right_line: 60,
342 },
343 ]);
344
345 assert_eq!(group.left_scroll_line(), 0);
347 assert_eq!(group.right_scroll_line(), 0);
348
349 group.apply_scroll_delta(SplitId(1), 10);
351 assert_eq!(group.left_scroll_line(), 10);
352 assert_eq!(group.right_scroll_line(), 10);
353
354 group.set_scroll_line(55);
356 assert_eq!(group.left_scroll_line(), 55);
357 assert_eq!(group.right_scroll_line(), 65); }
359}