5use helix_core::{Rope, RopeSlice};
6use imara_diff::intern::InternedInput;
8use tokio::sync::mpsc::UnboundedReceiver;
9use tokio::sync::Notify;
10use tokio::time::{timeout, timeout_at, Duration};
13 Event, RenderLock, ALGORITHM, DIFF_DEBOUNCE_TIME_ASYNC, DIFF_DEBOUNCE_TIME_SYNC,
16use super::line_cache::InternedRopeLines;
22pub(super) struct DiffWorker {
23 pub channel: UnboundedReceiver<Event>,
24 pub hunks: Arc<Mutex<Vec<Hunk>>>,
25 pub new_hunks: Vec<Hunk>,
26 pub redraw_notify: Arc<Notify>,
27 pub diff_finished_notify: Arc<Notify>,
31 async fn accumulate_events(&mut self, event: Event) -> (Option<Rope>, Option<Rope>) {
32 let mut accumulator = EventAccumulator::new();
33 accumulator.handle_event(event).await;
35 .accumulate_debounced_events(
37 self.redraw_notify.clone(),
38 self.diff_finished_notify.clone(),
41 (accumulator.doc, accumulator.diff_base)
44 pub async fn run(mut self, diff_base: Rope, doc: Rope) {
45 let mut interner = InternedRopeLines::new(diff_base, doc);
46 if let Some(lines) = interner.interned_lines() {
47 self.perform_diff(lines);
50 while let Some(event) = self.channel.recv().await {
51 let (doc, diff_base) = self.accumulate_events(event).await;
53 let process_accumulated_events = || {
54 if let Some(new_base) = diff_base {
55 interner.update_diff_base(new_base, doc)
57 interner.update_doc(doc.unwrap())
60 if let Some(lines) = interner.interned_lines() {
61 self.perform_diff(lines)
65 // Calculating diffs is computationally expensive and should
66 // not run inside an async function to avoid blocking other futures.
67 // Note: tokio::task::block_in_place does not work during tests
69 process_accumulated_events();
71 tokio::task::block_in_place(process_accumulated_events);
77 /// update the hunks (used by the gutter) by replacing it with `self.new_hunks`.
78 /// `self.new_hunks` is always empty after this function runs.
79 /// To improve performance this function tries to reuse the allocation of the old diff previously stored in `self.line_diffs`
80 fn apply_hunks(&mut self) {
81 swap(&mut *self.hunks.lock(), &mut self.new_hunks);
82 self.diff_finished_notify.notify_waiters();
83 self.new_hunks.clear();
86 fn perform_diff(&mut self, input: &InternedInput<RopeSlice>) {
87 imara_diff::diff(ALGORITHM, input, |before: Range<u32>, after: Range<u32>| {
88 self.new_hunks.push(Hunk { before, after })
93struct EventAccumulator {
94 diff_base: Option<Rope>,
96 render_lock: Option<RenderLock>,
99impl<'a> EventAccumulator {
100 fn new() -> EventAccumulator {
108 async fn handle_event(&mut self, event: Event) {
109 let dst = if event.is_base {
115 *dst = Some(event.text);
117 // always prefer the most synchronous requested render mode
118 if let Some(render_lock) = event.render_lock {
119 match &mut self.render_lock {
120 Some(RenderLock { timeout, .. }) => {
121 // A timeout of `None` means that the render should
122 // always wait for the diff to complete (so no timeout)
123 // remove the existing timeout, otherwise keep the previous timeout
124 // because it will be shorter then the current timeout
125 if render_lock.timeout.is_none() {
129 None => self.render_lock = Some(render_lock),
134 async fn accumulate_debounced_events(
136 channel: &mut UnboundedReceiver<Event>,
137 redraw_notify: Arc<Notify>,
138 diff_finished_notify: Arc<Notify>,
140 let async_debounce = Duration::from_millis(DIFF_DEBOUNCE_TIME_ASYNC);
141 let sync_debounce = Duration::from_millis(DIFF_DEBOUNCE_TIME_SYNC);
143 // if we are not blocking rendering use a much longer timeout
144 let debounce = if self.render_lock.is_none() {
150 if let Ok(Some(event)) = timeout(debounce, channel.recv()).await {
151 self.handle_event(event).await;
157 // setup task to trigger the rendering
158 match self.render_lock.take() {
159 // diff is performed outside of the rendering loop
160 // request a redraw after the diff is done
162 tokio::spawn(async move {
163 diff_finished_notify.notified().await;
164 redraw_notify.notify_one();
167 // diff is performed inside the rendering loop
168 // block redraw until the diff is done or the timeout is expired
171 timeout: Some(timeout),
173 tokio::spawn(async move {
175 // Acquire a lock on the redraw handle.
176 // The lock will block the rendering from occurring while held.
177 // The rendering waits for the diff if it doesn't time out
178 timeout_at(timeout, diff_finished_notify.notified()).await
180 // we either reached the timeout or the diff is finished, release the render lock
183 // Diff finished in time we are done.
186 // Diff failed to complete in time log the event
187 // and wait until the diff occurs to trigger an async redraw
188 log::info!("Diff computation timed out, update of diffs might appear delayed");
189 diff_finished_notify.notified().await;
190 redraw_notify.notify_one();
193 // a blocking diff is performed inside the rendering loop
194 // block redraw until the diff is done
199 tokio::spawn(async move {
200 diff_finished_notify.notified().await;
201 // diff is done release the lock
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list