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::buffer::Change
11use std::{
12    collections::HashMap,
13    ops::Range,
14    sync::atomic::{AtomicUsize, Ordering},
15};
16
17use duat_core::{
18    Plugin, Plugins,
19    buffer::{Buffer, BufferParts, BufferTracker, PerBuffer},
20    context::Handle,
21    data::Pass,
22    hook::{self, BufferClosed, BufferOpened},
23};
24use gapbuf::GapBuffer;
25
26static TRACKER: BufferTracker = BufferTracker::new();
27static PARSERS: PerBuffer<Parser> = PerBuffer::new();
28
29/// [`Plugin`]: Adds a `Jumps` parser to every [`Buffer`]
30///
31/// This `Jumps` parser can be used to retrieve previous
32/// [`Selections`] values, "jumping" around in the history.
33///
34/// [`Selections`]: duat_core::mode::Selections
35#[derive(Default)]
36pub struct JumpList;
37
38impl Plugin for JumpList {
39    fn plug(self, _: &Plugins) {
40        hook::add::<BufferOpened>(|pa, handle| {
41            TRACKER.register_buffer(handle.write(pa));
42            PARSERS.register(pa, handle, Parser::new());
43        });
44
45        hook::add::<BufferClosed>(|pa, handle| {
46            PARSERS.unregister(pa, handle);
47        });
48    }
49}
50
51/// The state of the [`Selections`] at some point in time
52///
53/// It can either be a single selection, represented by an exclusive
54/// [`Range<usize>`] of bytes, or a list of selections, also
55/// represented by `Range<usize>`, alongside the index for the main
56/// selection.
57///
58/// [`Selections`]: duat_core::mode::Selections
59#[derive(Clone, Debug)]
60pub enum Jump {
61    Single(Range<usize>),
62    Multiple(Vec<Range<usize>>, usize),
63}
64
65impl Jump {
66    /// Wether the `Jump`'s selections are the same as those of the
67    /// [`Selections`]
68    ///
69    /// [`Selections`]: duat_core::mode::Selections
70    pub fn is_eq(&self, buf: &Buffer) -> bool {
71        match self {
72            Jump::Single(range) => {
73                buf.selections().len() == 1
74                    && buf.selections().main().byte_range(buf.bytes()) == *range
75            }
76            Jump::Multiple(ranges, main_i) => {
77                buf.selections().len() == ranges.len()
78                    && buf.selections().iter().zip(ranges.iter()).enumerate().all(
79                        |(i, ((sel, is_main), range))| {
80                            sel.byte_range(buf.bytes()) == *range && ((i == *main_i) == is_main)
81                        },
82                    )
83            }
84        }
85    }
86
87    /// Applies the `Jump` to the [`Selections`] of a [`Buffer`]
88    ///
89    /// [`Selections`]: duat_core::mode::Selections
90    pub fn apply(&self, pa: &mut Pass, handle: &Handle) {
91        match self {
92            Jump::Single(selection) => {
93                handle.write(pa).selections_mut().remove_extras();
94                handle.edit_main(pa, |mut c| {
95                    let start = c.text().point_at_byte(selection.start);
96                    let end = c.text().point_at_byte(selection.end);
97                    c.move_to(start..end)
98                });
99            }
100            Jump::Multiple(selections, main) => {
101                handle.write(pa).selections_mut().remove_extras();
102
103                handle.edit_main(pa, |mut c| {
104                    let mut is_first = true;
105                    for selection in selections {
106                        if !is_first {
107                            c.copy();
108                        }
109
110                        let start = c.text().point_at_byte(selection.start);
111                        let end = c.text().point_at_byte(selection.end);
112                        c.move_to(start..end);
113                        is_first = false;
114                    }
115                });
116                handle.write(pa).selections_mut().set_main(*main);
117            }
118        }
119    }
120
121    fn shift(&mut self, changes: &Changes) -> bool {
122        match self {
123            Jump::Single(selection) => {
124                if let Some(new) = changes.shift_selection(selection.clone()) {
125                    *selection = new;
126                    true
127                } else {
128                    false
129                }
130            }
131            Jump::Multiple(selections, _) => {
132                changes.shift_selections(selections);
133                !selections.is_empty()
134            }
135        }
136    }
137}
138
139#[derive(Debug)]
140struct Parser(HashMap<JumpListId, (GapBuffer<Saved>, usize)>);
141
142impl Parser {
143    fn new() -> Self {
144        Self(HashMap::new())
145    }
146
147    fn update(&mut self, parts: BufferParts) {
148        // If there are no elements, every future jump is already correctly
149        // shifted, so no need to add these Changes lists
150        if parts.changes.len() == 0 || self.0.values().all(|(list, _)| list.is_empty()) {
151            return;
152        }
153
154        for (list, cur) in self.0.values_mut() {
155            list.truncate(*cur);
156
157            if *cur == 0 {
158                continue;
159            }
160
161            let changes = if let Saved::Changes(changes) = &mut list[*cur - 1] {
162                changes
163            } else {
164                list.insert(*cur, Saved::Changes(Box::default()));
165                *cur += 1;
166                let Some(Saved::Changes(changes)) = list.get_mut(*cur - 1) else {
167                    unreachable!();
168                };
169                changes
170            };
171
172            for change in parts.changes.clone() {
173                let change = (
174                    change.start().byte() as i32,
175                    change.taken_end().byte() as i32,
176                    change.added_end().byte() as i32,
177                );
178                changes.add_change(change);
179            }
180        }
181    }
182}
183
184#[derive(Debug)]
185enum Saved {
186    Jump(Jump, JumpId),
187    Changes(Box<Changes>),
188}
189
190/// A trait for recording and jumping [`Selections`]
191///
192/// This trait makes use of an internal [`BufferTracker`], so it
193/// automatically takes into consideration any [`Change`]s that may
194/// have taken place in between jumps.
195///
196/// [`Selections`]: duat_core::mode::Selections
197/// [`Change`]: duat_core::buffer::Change
198pub trait BufferJumps {
199    /// Record the [`Buffer`]'s [`Selections`]
200    ///
201    /// If `allow_duplicates` is set to `false`, then the selections
202    /// will not be recorded if that would mean two identical jumps in
203    /// sequence.
204    ///
205    /// This function will return `None` if no jump was recorded,
206    /// otherwise it will return a `Some(JumptId)`], which can be used
207    /// to jump to specific selections via [`Handle::go_to_jump`].
208    ///
209    /// [`Selections`]: duat_core::mode::Selections
210    fn record_jump(
211        &self,
212        pa: &mut Pass,
213        jump_list_id: JumpListId,
214        allow_duplicates: bool,
215    ) -> Option<JumpId>;
216
217    /// Jumps forwards or backwards through the [`Jump`]s on the list
218    ///
219    /// The `Jump` can either be a single selection, represented by
220    /// an exclusive [`Range<usize>`], or it can be multiple
221    /// selections, also represented by a `Range<usize>`, with the
222    /// main selection's index included.
223    ///
224    /// This will return [`None`] if the [`JumpList`] plugin was not
225    /// plugged, or if no jumps have been saved/all jumps have been
226    /// removed.
227    fn move_jumps_by(&self, pa: &mut Pass, jump_list_id: JumpListId, by: i32) -> Option<Jump>;
228
229    /// Jumps to the [`Jump`] specified by a [`JumpId`]
230    ///
231    /// The `Jump` can either be a single selection, represented by
232    /// an exclusive [`Range<usize>`], or it can be multiple
233    /// selections, also represented by a `Range<usize>`, with the
234    /// main selection's index included.
235    ///
236    /// This will return [`None`] if the [`JumpList`] plugin was not
237    /// plugged, or if the [`JumpId`] in question doesn't belong to
238    /// this [`Buffer`].
239    ///
240    /// If you want the `Jump` without actually jumping, see
241    /// [`Handle::get_jump`].
242    fn go_to_jump(&self, pa: &mut Pass, jump_list_id: JumpListId, id: JumpId) -> Option<Jump>;
243
244    /// Gets the [`Jump`] specified by a [`JumpId`]
245    ///
246    /// The `Jump` can either be a single selection, represented by
247    /// an exclusive [`Range<usize>`], or it can be multiple
248    /// selections, also represented by a `Range<usize>`, with the
249    /// main selection's index included.
250    ///
251    /// This will return [`None`] if the [`JumpList`] plugin was not
252    /// plugged, or if the [`JumpId`] in question doesn't belong to
253    /// this [`Buffer`].
254    fn get_jump(&self, pa: &mut Pass, jump_list_id: JumpListId, id: JumpId) -> Option<Jump>;
255
256    /// Records a non duplicated selection if there is none, returning
257    /// it if successful. Otherwise returns the current [`JumpId`]
258    fn record_or_get_current_jump(&self, pa: &mut Pass, jump_list_id: JumpListId) -> JumpId;
259}
260
261impl BufferJumps for Handle {
262    fn record_jump(
263        &self,
264        pa: &mut Pass,
265        jump_list_id: JumpListId,
266        allow_duplicates: bool,
267    ) -> Option<JumpId> {
268        let (parser, buffer) = PARSERS.write(pa, self).unwrap();
269        parser.update(TRACKER.parts(buffer).unwrap());
270
271        let selections = buffer.selections();
272        let (list, cur) = parser.0.entry(jump_list_id).or_default();
273
274        if !allow_duplicates {
275            for i in [Some(*cur), cur.checked_sub(1)].into_iter().flatten() {
276                let Some(Saved::Jump(jump, _)) = list.get(i) else {
277                    continue;
278                };
279
280                match jump {
281                    Jump::Single(sel) => {
282                        if selections.len() == 1
283                            && selections.main().byte_range(buffer.bytes()) == *sel
284                        {
285                            return None;
286                        }
287                    }
288                    Jump::Multiple(sels, main) => {
289                        if *main == selections.main_index()
290                            && sels.len() == selections.len()
291                            && sels
292                                .iter()
293                                .zip(selections.iter())
294                                .all(|(lhs, (rhs, _))| *lhs == rhs.byte_range(buffer.bytes()))
295                        {
296                            return None;
297                        }
298                    }
299                }
300            }
301        }
302
303        list.truncate(*cur);
304        let jump_id = JumpId::new();
305
306        if selections.len() == 1 {
307            list.push_back(Saved::Jump(
308                Jump::Single(selections.main().byte_range(buffer.bytes())),
309                jump_id,
310            ));
311            *cur += 1;
312        } else if selections.len() > 1 {
313            list.push_back(Saved::Jump(
314                Jump::Multiple(
315                    selections
316                        .iter()
317                        .map(|(sel, _)| sel.byte_range(buffer.bytes()))
318                        .collect(),
319                    selections.main_index(),
320                ),
321                jump_id,
322            ));
323            *cur += 1;
324        }
325
326        Some(jump_id)
327    }
328
329    fn move_jumps_by(&self, pa: &mut Pass, jump_list_id: JumpListId, mut by: i32) -> Option<Jump> {
330        let (parser, buffer) = PARSERS.write(pa, self).unwrap();
331        parser.update(TRACKER.parts(buffer).unwrap());
332
333        let mut changes = Changes::default();
334        let mut last_seen = None;
335
336        let (list, cur) = parser.0.entry(jump_list_id).or_default();
337
338        let jump = if by >= 0 {
339            loop {
340                match list.get_mut(*cur) {
341                    Some(Saved::Jump(jump, _)) => {
342                        if by == 0 {
343                            break Some(jump.clone());
344                        } else if *cur + 1 < list.len() {
345                            last_seen = Some(*cur);
346                            by -= 1;
347                            *cur += 1;
348                        } else {
349                            break None;
350                        }
351                    }
352                    Some(Saved::Changes(_)) => unreachable!(),
353                    None => break None,
354                }
355            }
356        } else {
357            loop {
358                match cur.checked_sub(1).and_then(|j| list.get_mut(j)) {
359                    Some(Saved::Jump(jump, _)) => {
360                        *cur -= 1;
361                        if jump.shift(&changes) {
362                            by += 1;
363                            if by == 0 {
364                                let jump = jump.clone();
365                                if !changes.list.is_empty() && *cur > 0 {
366                                    list.insert(*cur, Saved::Changes(Box::new(changes)));
367                                    *cur += 1;
368                                }
369                                break Some(jump);
370                            } else {
371                                last_seen = Some(*cur);
372                            }
373                        } else {
374                            if let Some(last_seen) = last_seen.as_mut() {
375                                *last_seen -= 1;
376                            }
377                            list.remove(*cur);
378                        }
379                    }
380                    Some(Saved::Changes(_)) => {
381                        if let Some(last_seen) = last_seen.as_mut() {
382                            *last_seen -= 1;
383                        }
384                        *cur -= 1;
385                        let Saved::Changes(rev) = list.remove(*cur) else {
386                            unreachable!();
387                        };
388                        changes.merge(*rev);
389                    }
390                    None => break None,
391                }
392            }
393        };
394
395        jump.or_else(|| {
396            last_seen.map(|i| {
397                let Some(Saved::Jump(jump, _)) = list.get(i) else {
398                    unreachable!();
399                };
400                jump.clone()
401            })
402        })
403    }
404
405    fn go_to_jump(&self, pa: &mut Pass, jump_list_id: JumpListId, id: JumpId) -> Option<Jump> {
406        get_jump(self, pa, jump_list_id, id, true)
407    }
408
409    fn get_jump(&self, pa: &mut Pass, jump_list_id: JumpListId, id: JumpId) -> Option<Jump> {
410        get_jump(self, pa, jump_list_id, id, false)
411    }
412
413    fn record_or_get_current_jump(&self, pa: &mut Pass, jump_list_id: JumpListId) -> JumpId {
414        if let Some(jump_id) = self.record_jump(pa, jump_list_id, false) {
415            jump_id
416        } else {
417            let (parser, buffer) = PARSERS.write(pa, self).unwrap();
418            parser.update(TRACKER.parts(buffer).unwrap());
419
420            let (list, cur) = parser.0.get_mut(&jump_list_id).unwrap();
421
422            list.range(..(*cur + 1).min(list.len()))
423                .iter()
424                .rev()
425                .find_map(|saved| {
426                    if let Saved::Jump(_, jump_id) = saved {
427                        Some(*jump_id)
428                    } else {
429                        None
430                    }
431                })
432                .unwrap()
433        }
434    }
435}
436
437fn get_jump(
438    handle: &Handle,
439    pa: &mut Pass,
440    jump_list_id: JumpListId,
441    id: JumpId,
442    do_jump: bool,
443) -> Option<Jump> {
444    let (parser, buffer) = PARSERS.write(pa, handle).unwrap();
445    parser.update(TRACKER.parts(buffer).unwrap());
446
447    let (list, cur) = parser.0.entry(jump_list_id).or_default();
448
449    let mut changes = Changes::default();
450    let mut new_cur = *cur;
451
452    let jump = (|| {
453        // For better readability, consider `jumps.cur <= new_cur` to mean
454        // "moving forwards", and `jumps.cur >= new_cur` to mean "moving
455        // backwards".
456        loop {
457            match list.get_mut(new_cur) {
458                Some(Saved::Jump(jump, other)) => {
459                    if jump.shift(&changes) {
460                        if *other == id {
461                            let jump = jump.clone();
462                            if !changes.list.is_empty() && new_cur > 0 {
463                                list.insert(*cur, Saved::Changes(Box::new(changes)));
464                                *cur += 1;
465                            }
466                            break Some(jump);
467                        } else if *other < id && *cur <= new_cur {
468                            new_cur += 1;
469                        } else if *other > id && *cur >= new_cur {
470                            new_cur = new_cur.checked_sub(1)?;
471                        } else {
472                            if !changes.list.is_empty() && new_cur > 0 {
473                                list.insert(*cur, Saved::Changes(Box::new(changes)));
474                                *cur += 1;
475                            }
476                            return None;
477                        }
478                    } else {
479                        list.remove(new_cur);
480                        *cur = cur.checked_sub(1)?;
481                        new_cur -= 1;
482                    }
483                }
484                Some(Saved::Changes(_)) => {
485                    let Saved::Changes(rev) = list.remove(new_cur) else {
486                        unreachable!();
487                    };
488                    *cur -= 1;
489                    new_cur -= 1;
490                    changes.merge(*rev);
491                }
492                None if *cur >= new_cur => new_cur = new_cur.checked_sub(1)?,
493                None => break None,
494            }
495        }
496    })();
497
498    if jump.is_some() && do_jump {
499        *cur = new_cur
500    }
501
502    jump
503}
504
505#[derive(Default, Debug)]
506struct Changes {
507    list: GapBuffer<(i32, i32, i32)>,
508    from: usize,
509    by: i32,
510}
511
512impl Changes {
513    fn add_change(&mut self, (start, taken_end, added_end): (i32, i32, i32)) {
514        let m_range = duat_core::utils::merging_range_by_guess_and_lazy_shift(
515            (&self.list, self.list.len()),
516            (0, [start, taken_end]),
517            (self.from, self.by, 0, std::ops::Add::add),
518            (|(start, ..)| *start, |(.., added_end)| *added_end),
519        );
520
521        if self.by != 0 && self.from < m_range.end {
522            for (start, taken_end, added_end) in &mut self.list.range_mut(self.from..m_range.end) {
523                *start += self.by;
524                *taken_end += self.by;
525                *added_end += self.by;
526            }
527        } else if self.by != 0 && m_range.end < self.from {
528            for (start, taken_end, added_end) in &mut self.list.range_mut(self.from..m_range.end) {
529                *start -= self.by;
530                *taken_end -= self.by;
531                *added_end -= self.by;
532            }
533        }
534
535        let (start, taken_end, added_end) = if m_range.start < m_range.end {
536            let (first_start, ..) = self.list[m_range.start];
537            let (_, last_taken_end, last_added_end) = self.list[m_range.end - 1];
538            (
539                start.min(first_start),
540                taken_end.max(last_taken_end),
541                added_end.max(last_added_end),
542            )
543        } else {
544            (start, taken_end, added_end)
545        };
546
547        self.list.splice(
548            m_range.clone(),
549            (taken_end != added_end).then_some((start, taken_end, added_end)),
550        );
551
552        let new_from = m_range.start + (taken_end != added_end) as usize;
553        if new_from < self.list.len() {
554            self.from = new_from;
555        } else {
556            self.from = 0;
557            self.by = 0;
558        }
559    }
560
561    fn merge(&mut self, other: Self) {
562        for change in other.iter() {
563            self.add_change(change);
564        }
565    }
566
567    fn iter(&self) -> impl Iterator<Item = (i32, i32, i32)> {
568        self.list
569            .iter()
570            .enumerate()
571            .map(|(i, &(start, taken_end, added_end))| {
572                if self.by != 0 && i >= self.from {
573                    (start + self.by, taken_end + self.by, added_end + self.by)
574                } else {
575                    (start, taken_end, added_end)
576                }
577            })
578    }
579
580    fn shift_selection(&self, mut selection: Range<usize>) -> Option<Range<usize>> {
581        for change in self.iter() {
582            selection = shift_selection(selection.clone(), change)?;
583
584            if change.0 as usize >= selection.end {
585                break;
586            }
587        }
588
589        Some(selection)
590    }
591
592    fn shift_selections(&self, selections: &mut Vec<Range<usize>>) {
593        let mut total_shift = 0;
594        let mut changes = self.iter().peekable();
595
596        selections.retain_mut(|selection| {
597            selection.start = (selection.start as i32 + total_shift) as usize;
598            selection.end = (selection.end as i32 + total_shift) as usize;
599
600            while let Some(&change) = changes.peek() {
601                let Some(new) = shift_selection(selection.clone(), change) else {
602                    return false;
603                };
604                *selection = new;
605
606                if change.1 as usize <= selection.start {
607                    changes.next();
608                    total_shift += change.2 - change.1;
609                } else if change.0 as usize >= selection.end {
610                    break;
611                }
612            }
613
614            true
615        });
616    }
617}
618
619fn shift_selection(
620    mut selection: Range<usize>,
621    (start, taken_end, added_end): (i32, i32, i32),
622) -> Option<Range<usize>> {
623    let shift = added_end - taken_end;
624    let (start, taken_end) = (start as usize, taken_end as usize);
625
626    if taken_end <= selection.start {
627        selection.start = (selection.start as i32 + shift) as usize;
628        selection.end = (selection.end as i32 + shift) as usize;
629    } else if taken_end < selection.end {
630        selection.start = added_end as usize;
631        selection.end = (selection.end as i32 + shift) as usize;
632    } else if start <= selection.start {
633        return None;
634    } else if start < selection.end {
635        selection.end = start
636    }
637    Some(selection)
638}
639
640/// A struct representing a specific jump's position
641#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
642pub struct JumpId(usize);
643
644impl JumpId {
645    /// Returns a new unique `JumpId`
646    fn new() -> Self {
647        static COUNT: AtomicUsize = AtomicUsize::new(0);
648        Self(COUNT.fetch_add(1, Ordering::Relaxed))
649    }
650}
651
652/// A struct representing a list of jumps
653///
654/// You can use this to maintain multiple separate jump lists for a
655/// single [`Buffer`]
656#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
657pub struct JumpListId(usize);
658
659impl JumpListId {
660    /// Returns a new unique `JumpListId`
661    ///
662    /// Each id represents a unique sequence of [`Jump`] recordings
663    /// for a given [`Buffer`]. You can use this to modify jump lists
664    /// without invalidating other jumps. As an example, the
665    /// `duatmode` crate uses this feature to keep track of all
666    /// selection changes in a `Buffer`, as well as a separate list
667    /// for specific jumps with the `g` key.
668    pub fn new() -> Self {
669        static COUNT: AtomicUsize = AtomicUsize::new(0);
670        Self(COUNT.fetch_add(1, Ordering::Relaxed))
671    }
672}
673
674impl Default for JumpListId {
675    fn default() -> Self {
676        Self::new()
677    }
678}