duat_jump_list/
lib.rs

1//! This plugin is supposed to be a jump list for [`Selections`]
2//! movements to be used by text editing paradigms like `duat-kak` and
3//! `duat-vim`.
4//!
5//! It works by recording the [`Selections`] and can retrieve previous
6//! `Selections` values, taking [`Change`]s that took place into
7//! account.
8//!
9//! [`Selections`]: duat_core::mode::Selections
10//! [`Change`]: duat_core::text::Change
11#![feature(decl_macro, iter_order_by)]
12
13use std::ops::Range;
14
15use duat_core::{
16    Plugin, Plugins,
17    buffer::{Buffer, BufferTracker, Parser},
18    hook,
19};
20use gapbuf::GapBuffer;
21
22/// [`Plugin`]: Adds a `Jumps` parser to every [`Buffer`]
23///
24/// This `Jumps` parser can be used to retrieve previous
25/// [`Selections`] values, "jumping" around in the history.
26///
27/// [`Selections`]: duat_core::mode::Selections
28#[derive(Default)]
29pub struct JumpList;
30
31impl Plugin for JumpList {
32    fn plug(self, _: &Plugins) {
33        hook::add::<Buffer>(|pa, handle| handle.add_parser(pa, Jumps::new));
34    }
35}
36
37/// The state of the [`Selections`] at some point in time
38///
39/// It can either be a single selection, represented by an exclusive
40/// [`Range<usize>`] of bytes, or a list of selections, also
41/// represented by `Range<usize>`, alongside the index for the main
42/// selection.
43///
44/// [`Selections`]: duat_core::mode::Selections
45#[derive(Clone, Debug)]
46pub enum Jump {
47    Single(Range<usize>),
48    Multiple(Vec<Range<usize>>, usize),
49}
50
51impl Jump {
52    fn shift(&mut self, changes: &Changes) -> bool {
53        match self {
54            Jump::Single(selection) => {
55                if let Some(new) = changes.shift_selection(selection.clone()) {
56                    *selection = new;
57                    true
58                } else {
59                    false
60                }
61            }
62            Jump::Multiple(selections, _) => {
63                changes.shift_selections(selections);
64                !selections.is_empty()
65            }
66        }
67    }
68}
69
70#[derive(Debug)]
71struct Jumps {
72    list: GapBuffer<Saved>,
73    tracker: BufferTracker,
74    cur: usize,
75}
76
77impl Jumps {
78    fn new(tracker: BufferTracker) -> Self {
79        Self { list: GapBuffer::new(), tracker, cur: 0 }
80    }
81}
82
83impl Parser for Jumps {
84    fn parse(&mut self) -> bool {
85        false
86    }
87
88    fn before_get(&mut self) {
89        self.tracker.update();
90
91        // If there are no elements, every future jump is already correctly
92        // shifted, so no need to add these Changes lists
93        if self.list.is_empty() || self.tracker.moment().is_empty() {
94            return;
95        }
96        self.list.truncate(self.cur);
97
98        if self.cur == 0 {
99            return;
100        }
101
102        let changes = if let Saved::Changes(changes) = &mut self.list[self.cur - 1] {
103            changes
104        } else {
105            self.list.insert(self.cur, Saved::Changes(Box::default()));
106            self.cur += 1;
107            let Some(Saved::Changes(changes)) = self.list.get_mut(self.cur - 1) else {
108                unreachable!();
109            };
110            changes
111        };
112
113        for change in self.tracker.moment().changes() {
114            let change = (
115                change.start().byte() as i32,
116                change.taken_end().byte() as i32,
117                change.added_end().byte() as i32,
118            );
119            changes.add_change(change);
120        }
121    }
122
123    fn before_try_get(&mut self) -> bool {
124        self.before_get();
125        true
126    }
127}
128
129#[derive(Debug)]
130enum Saved {
131    Jump(Jump),
132    Changes(Box<Changes>),
133}
134
135/// A trait for recording and jumping [`Selections`]
136///
137/// This trait makes use of an internal [`Parser`], so it
138/// automatically takes into consideration any [`Change`]s that may
139/// have taken place in between jumps.
140///
141/// [`Selections`]: duat_core::mode::Selections
142/// [`Change`]: duat_core::text::Change
143pub trait BufferJumps {
144    /// Record the [`Buffer`]'s [`Selections`]
145    ///
146    /// If `allow_duplicates` is set to `false`, then the selections
147    /// will not be recorded if that would mean two identical jumps in
148    /// sequence.
149    ///
150    /// This function will return `false` if no jump was recorded.
151    ///
152    /// [`Selections`]: duat_core::mode::Selections
153    fn record_selections(&mut self, allow_duplicates: bool) -> bool;
154
155    /// Jumps forwards or backwards through the [`Jump`]s on the list
156    ///
157    /// The `Jump` can either be a single selection, represented by
158    /// an exclusive [`Range<usize>`], or it can be multiple
159    /// selections, also represented by a `Range<usize>`, with the
160    /// main selection's index included.
161    ///
162    /// This will return [`None`] if the [`JumpList`] plugin was not
163    /// plugged, or if no jumps have been saved/all jumps have been
164    /// removed.
165    fn jump_selections_by(&mut self, by: i32) -> Option<Jump>;
166
167    /// Jumps to the `n`th [`Jump`]
168    ///
169    /// The `Jump` can either be a single selection, represented by
170    /// an exclusive [`Range<usize>`], or it can be multiple
171    /// selections, also represented by a `Range<usize>`, with the
172    /// main selection's index included.
173    ///
174    /// This will return [`None`] if the [`JumpList`] plugin was not
175    /// plugged, or if no jumps have been saved/all jumps have been
176    /// removed.
177    fn jump_to_selections(&mut self, n: usize) -> Option<Jump>;
178}
179
180impl BufferJumps for Buffer {
181    fn record_selections(&mut self, allow_duplicates: bool) -> bool {
182        self.write_parser(|jumps: &mut Jumps| {
183            let selections = self.selections();
184
185            if !allow_duplicates {
186                for i in [Some(jumps.cur), jumps.cur.checked_sub(1)]
187                    .into_iter()
188                    .flatten()
189                {
190                    let Some(Saved::Jump(jump)) = jumps.list.get(i) else {
191                        continue;
192                    };
193
194                    match jump {
195                        Jump::Single(sel) => {
196                            if selections.len() == 1
197                                && selections.get_main().unwrap().byte_range(self.bytes()) == *sel
198                            {
199                                return false;
200                            }
201                        }
202                        Jump::Multiple(sels, main) => {
203                            if *main == selections.main_index()
204                                && sels.iter().eq_by(selections.iter(), |lhs, (rhs, _)| {
205                                    *lhs == rhs.byte_range(self.bytes())
206                                })
207                            {
208                                return false;
209                            }
210                        }
211                    }
212                }
213            }
214
215            if selections.len() == 1 {
216                jumps.list.insert(
217                    jumps.cur,
218                    Saved::Jump(Jump::Single(
219                        selections.get_main().unwrap().byte_range(self.bytes()),
220                    )),
221                );
222            } else if selections.len() > 1 {
223                jumps.list.insert(
224                    jumps.cur,
225                    Saved::Jump(Jump::Multiple(
226                        selections
227                            .iter()
228                            .map(|(sel, _)| sel.byte_range(self.bytes()))
229                            .collect(),
230                        selections.main_index(),
231                    )),
232                );
233            }
234            jumps.cur += 1;
235
236            true
237        })
238        .unwrap()
239    }
240
241    fn jump_selections_by(&mut self, mut by: i32) -> Option<Jump> {
242        self.write_parser(|jumps: &mut Jumps| {
243            let mut changes = Changes::default();
244            let mut last_seen = None;
245
246            let jump = if by >= 0 {
247                loop {
248                    match jumps.list.get_mut(jumps.cur) {
249                        Some(Saved::Jump(jump)) => {
250                            if by == 0 {
251                                break Some(jump.clone());
252                            } else if jumps.cur + 1 < jumps.list.len() {
253                                last_seen = Some(jumps.cur);
254                                by -= 1;
255                                jumps.cur += 1;
256                            } else {
257                                break None;
258                            }
259                        }
260                        Some(Saved::Changes(_)) => unreachable!(),
261                        None => break None,
262                    }
263                }
264            } else {
265                loop {
266                    match jumps.cur.checked_sub(1).and_then(|j| jumps.list.get_mut(j)) {
267                        Some(Saved::Jump(jump)) => {
268                            jumps.cur -= 1;
269                            if jump.shift(&changes) {
270                                by += 1;
271                                if by == 0 {
272                                    let jump = jump.clone();
273                                    if !changes.list.is_empty() && jumps.cur > 0 {
274                                        jumps
275                                            .list
276                                            .insert(jumps.cur, Saved::Changes(Box::new(changes)));
277                                        jumps.cur += 1;
278                                    }
279                                    break Some(jump);
280                                } else {
281                                    last_seen = Some(jumps.cur);
282                                }
283                            } else {
284                                if let Some(last_seen) = last_seen.as_mut() {
285                                    *last_seen -= 1;
286                                }
287                                jumps.list.remove(jumps.cur);
288                            }
289                        }
290                        Some(Saved::Changes(_)) => {
291                            if let Some(last_seen) = last_seen.as_mut() {
292                                *last_seen -= 1;
293                            }
294                            jumps.cur -= 1;
295                            let Saved::Changes(rev) = jumps.list.remove(jumps.cur) else {
296                                unreachable!();
297                            };
298                            changes.merge(*rev);
299                        }
300                        None => break None,
301                    }
302                }
303            };
304
305            jump.or_else(|| {
306                last_seen.map(|i| {
307                    let Some(Saved::Jump(jump)) = jumps.list.get(i) else {
308                        unreachable!();
309                    };
310                    jump.clone()
311                })
312            })
313        })
314        .flatten()
315    }
316
317    fn jump_to_selections(&mut self, n: usize) -> Option<Jump> {
318        let cur_n = self.write_parser(|jumps: &mut Jumps| {
319            jumps
320                .list
321                .iter()
322                .take(jumps.cur)
323                .filter(|s| matches!(s, Saved::Jump(_)))
324                .count()
325        })?;
326
327        self.jump_selections_by(n as i32 - cur_n as i32)
328    }
329}
330
331#[derive(Default, Debug)]
332struct Changes {
333    list: GapBuffer<(i32, i32, i32)>,
334    from: usize,
335    by: i32,
336}
337
338impl Changes {
339    fn add_change(&mut self, (start, taken_end, added_end): (i32, i32, i32)) {
340        let m_range = duat_core::utils::merging_range_by_guess_and_lazy_shift(
341            (&self.list, self.list.len()),
342            (0, [start, taken_end]),
343            (self.from, self.by, 0, std::ops::Add::add),
344            (|(start, ..)| *start, |(.., added_end)| *added_end),
345        );
346
347        if self.by != 0 && self.from < m_range.end {
348            for (start, taken_end, added_end) in &mut self.list.range_mut(self.from..m_range.end) {
349                *start += self.by;
350                *taken_end += self.by;
351                *added_end += self.by;
352            }
353        } else if self.by != 0 && m_range.end < self.from {
354            for (start, taken_end, added_end) in &mut self.list.range_mut(self.from..m_range.end) {
355                *start -= self.by;
356                *taken_end -= self.by;
357                *added_end -= self.by;
358            }
359        }
360
361        let (start, taken_end, added_end) = if m_range.start < m_range.end {
362            let (first_start, ..) = self.list[m_range.start];
363            let (_, last_taken_end, last_added_end) = self.list[m_range.end - 1];
364            (
365                start.min(first_start),
366                taken_end.max(last_taken_end),
367                added_end.max(last_added_end),
368            )
369        } else {
370            (start, taken_end, added_end)
371        };
372
373        self.list.splice(
374            m_range.clone(),
375            (taken_end != added_end).then_some((start, taken_end, added_end)),
376        );
377
378        let new_from = m_range.start + (taken_end != added_end) as usize;
379        if new_from < self.list.len() {
380            self.from = new_from;
381        } else {
382            self.from = 0;
383            self.by = 0;
384        }
385    }
386
387    fn merge(&mut self, other: Self) {
388        for change in other.iter() {
389            self.add_change(change);
390        }
391    }
392
393    fn iter(&self) -> impl Iterator<Item = (i32, i32, i32)> {
394        self.list
395            .iter()
396            .enumerate()
397            .map(|(i, &(start, taken_end, added_end))| {
398                if self.by != 0 && i >= self.from {
399                    (start + self.by, taken_end + self.by, added_end + self.by)
400                } else {
401                    (start, taken_end, added_end)
402                }
403            })
404    }
405
406    fn shift_selection(&self, mut selection: Range<usize>) -> Option<Range<usize>> {
407        for change in self.iter() {
408            selection = shift_selection(selection.clone(), change)?;
409
410            if change.0 as usize >= selection.end {
411                break;
412            }
413        }
414
415        Some(selection)
416    }
417
418    fn shift_selections(&self, selections: &mut Vec<Range<usize>>) {
419        let mut total_shift = 0;
420        let mut changes = self.iter().peekable();
421
422        selections.retain_mut(|selection| {
423            selection.start = (selection.start as i32 + total_shift) as usize;
424            selection.end = (selection.end as i32 + total_shift) as usize;
425
426            while let Some(&change) = changes.peek() {
427                let Some(new) = shift_selection(selection.clone(), change) else {
428                    return false;
429                };
430                *selection = new;
431
432                if change.1 as usize <= selection.start {
433                    changes.next();
434                    total_shift += change.2 - change.1;
435                } else if change.0 as usize >= selection.end {
436                    break;
437                }
438            }
439
440            true
441        });
442    }
443}
444
445fn shift_selection(
446    mut selection: Range<usize>,
447    (start, taken_end, added_end): (i32, i32, i32),
448) -> Option<Range<usize>> {
449    let shift = added_end - taken_end;
450    let (start, taken_end) = (start as usize, taken_end as usize);
451
452    if taken_end <= selection.start {
453        selection.start = (selection.start as i32 + shift) as usize;
454        selection.end = (selection.end as i32 + shift) as usize;
455    } else if taken_end < selection.end {
456        selection.start = added_end as usize;
457        selection.end = (selection.end as i32 + shift) as usize;
458    } else if start <= selection.start {
459        return None;
460    } else if start < selection.end {
461        selection.end = start
462    }
463    Some(selection)
464}