|
| 1 | +use std::mem::take; |
| 2 | +use std::ops::Deref; |
| 3 | +use std::sync::Arc; |
| 4 | + |
| 5 | +use arc_swap::ArcSwap; |
| 6 | +use ropey::{Rope, RopeSlice}; |
| 7 | +use similar::{capture_diff_slices_deadline, Algorithm, DiffTag}; |
| 8 | +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; |
| 9 | +use tokio::task::JoinHandle; |
| 10 | +use tokio::time::{timeout_at, Duration, Instant}; |
| 11 | + |
| 12 | +use crate::rope_line_cache::RopeLineCache; |
| 13 | +use crate::{LineDiff, LineDiffs}; |
| 14 | + |
| 15 | +#[cfg(test)] |
| 16 | +mod test; |
| 17 | + |
| 18 | +#[derive(Clone, Debug)] |
| 19 | +pub struct Differ { |
| 20 | + channel: UnboundedSender<Event>, |
| 21 | + line_diffs: Arc<ArcSwap<LineDiffs>>, |
| 22 | +} |
| 23 | + |
| 24 | +impl Differ { |
| 25 | + pub fn new(diff_base: Rope, doc: Rope) -> Differ { |
| 26 | + Differ::new_with_handle(diff_base, doc).0 |
| 27 | + } |
| 28 | + |
| 29 | + fn new_with_handle(diff_base: Rope, doc: Rope) -> (Differ, JoinHandle<()>) { |
| 30 | + let (sender, reciver) = unbounded_channel(); |
| 31 | + let line_diffs: Arc<ArcSwap<LineDiffs>> = Arc::default(); |
| 32 | + let worker = DiffWorker { |
| 33 | + channel: reciver, |
| 34 | + line_diffs: line_diffs.clone(), |
| 35 | + new_line_diffs: LineDiffs::default(), |
| 36 | + }; |
| 37 | + let handle = tokio::spawn(worker.run(diff_base, doc)); |
| 38 | + let differ = Differ { |
| 39 | + channel: sender, |
| 40 | + line_diffs, |
| 41 | + }; |
| 42 | + (differ, handle) |
| 43 | + } |
| 44 | + pub fn get_line_diffs(&self) -> impl Deref<Target = impl Deref<Target = LineDiffs>> { |
| 45 | + self.line_diffs.load() |
| 46 | + } |
| 47 | + |
| 48 | + pub fn update_document(&self, doc: Rope) -> bool { |
| 49 | + self.channel.send(Event::UpdateDocument(doc)).is_ok() |
| 50 | + } |
| 51 | + |
| 52 | + pub fn update_diff_base(&self, diff_base: Rope) -> bool { |
| 53 | + self.channel.send(Event::UpdateDiffBase(diff_base)).is_ok() |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +// TODO configuration |
| 58 | +const DIFF_MAX_DEBOUNCE: u64 = 200; |
| 59 | +const DIFF_DEBOUNCE: u64 = 10; |
| 60 | +const DIFF_TIMEOUT: u64 = 200; |
| 61 | +const MAX_DIFF_LEN: usize = 40000; |
| 62 | +const ALGORITHM: Algorithm = Algorithm::Myers; |
| 63 | + |
| 64 | +struct DiffWorker { |
| 65 | + channel: UnboundedReceiver<Event>, |
| 66 | + line_diffs: Arc<ArcSwap<LineDiffs>>, |
| 67 | + new_line_diffs: LineDiffs, |
| 68 | +} |
| 69 | + |
| 70 | +impl DiffWorker { |
| 71 | + async fn run(mut self, diff_base: Rope, doc: Rope) { |
| 72 | + let mut diff_base = RopeLineCache::new(diff_base); |
| 73 | + let mut doc = RopeLineCache::new(doc); |
| 74 | + self.perform_diff(diff_base.lines(), doc.lines()); |
| 75 | + self.apply_line_diff(); |
| 76 | + while let Some(event) = self.channel.recv().await { |
| 77 | + let mut accumulator = EventAccumulator::new(); |
| 78 | + accumulator.handle_event(event); |
| 79 | + accumulator |
| 80 | + .accumualte_debounced_events(&mut self.channel) |
| 81 | + .await; |
| 82 | + |
| 83 | + if let Some(new_doc) = accumulator.doc { |
| 84 | + doc.update(new_doc) |
| 85 | + } |
| 86 | + if let Some(new_base) = accumulator.diff_base { |
| 87 | + diff_base.update(new_base) |
| 88 | + } |
| 89 | + |
| 90 | + self.perform_diff(diff_base.lines(), doc.lines()); |
| 91 | + self.apply_line_diff(); |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + /// update the line diff (used by the gutter) by replacing it with `self.new_line_diffs`. |
| 96 | + /// `self.new_line_diffs` is always empty after this function runs. |
| 97 | + /// To improve performance this function trys to reuse the allocation of the old diff previously stored in `self.line_diffs` |
| 98 | + fn apply_line_diff(&mut self) { |
| 99 | + let diff_to_apply = take(&mut self.new_line_diffs); |
| 100 | + let old_line_diff = self.line_diffs.swap(Arc::new(diff_to_apply)); |
| 101 | + if let Ok(mut cached_alloc) = Arc::try_unwrap(old_line_diff) { |
| 102 | + cached_alloc.clear(); |
| 103 | + self.new_line_diffs = cached_alloc; |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + fn perform_diff(&mut self, diff_base: &[RopeSlice<'_>], doc: &[RopeSlice<'_>]) { |
| 108 | + if diff_base.len() > MAX_DIFF_LEN || doc.len() > MAX_DIFF_LEN { |
| 109 | + return; |
| 110 | + } |
| 111 | + // TODO allow configuration algorithm |
| 112 | + // TODO configure diff deadline |
| 113 | + |
| 114 | + let diff = capture_diff_slices_deadline( |
| 115 | + ALGORITHM, |
| 116 | + diff_base, |
| 117 | + doc, |
| 118 | + Some(std::time::Instant::now() + std::time::Duration::from_millis(DIFF_TIMEOUT)), |
| 119 | + ); |
| 120 | + for op in diff { |
| 121 | + let (tag, _, line_range) = op.as_tag_tuple(); |
| 122 | + let op = match tag { |
| 123 | + DiffTag::Insert => LineDiff::Added, |
| 124 | + DiffTag::Replace => LineDiff::Modified, |
| 125 | + DiffTag::Delete => { |
| 126 | + self.add_line_diff(line_range.start, LineDiff::Deleted); |
| 127 | + continue; |
| 128 | + } |
| 129 | + DiffTag::Equal => continue, |
| 130 | + }; |
| 131 | + |
| 132 | + for line in line_range { |
| 133 | + self.add_line_diff(line, op) |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + fn add_line_diff(&mut self, line: usize, op: LineDiff) { |
| 139 | + self.new_line_diffs.insert(line, op); |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +struct EventAccumulator { |
| 144 | + diff_base: Option<Rope>, |
| 145 | + doc: Option<Rope>, |
| 146 | +} |
| 147 | +impl EventAccumulator { |
| 148 | + fn new() -> EventAccumulator { |
| 149 | + EventAccumulator { |
| 150 | + diff_base: None, |
| 151 | + doc: None, |
| 152 | + } |
| 153 | + } |
| 154 | + fn handle_event(&mut self, event: Event) { |
| 155 | + match event { |
| 156 | + Event::UpdateDocument(doc) => self.doc = Some(doc), |
| 157 | + Event::UpdateDiffBase(new_diff_base) => self.diff_base = Some(new_diff_base), |
| 158 | + } |
| 159 | + } |
| 160 | + async fn accumualte_debounced_events(&mut self, channel: &mut UnboundedReceiver<Event>) { |
| 161 | + let final_time = Instant::now() + Duration::from_millis(DIFF_MAX_DEBOUNCE); |
| 162 | + let debounce = Duration::from_millis(DIFF_DEBOUNCE); |
| 163 | + loop { |
| 164 | + let mut debounce = Instant::now() + debounce; |
| 165 | + if final_time < debounce { |
| 166 | + debounce = final_time; |
| 167 | + } |
| 168 | + if let Ok(Some(event)) = timeout_at(debounce, channel.recv()).await { |
| 169 | + self.handle_event(event) |
| 170 | + } else { |
| 171 | + break; |
| 172 | + } |
| 173 | + } |
| 174 | + } |
| 175 | +} |
| 176 | + |
| 177 | +enum Event { |
| 178 | + UpdateDocument(Rope), |
| 179 | + UpdateDiffBase(Rope), |
| 180 | +} |
0 commit comments