Skip to content

Commit 6e58077

Browse files
committed
update diff gutter synchronously, with a timeout
1 parent 65d176b commit 6e58077

File tree

7 files changed

+260
-93
lines changed

7 files changed

+260
-93
lines changed

helix-term/src/application.rs

+32-20
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::{
3131
use log::{debug, error, warn};
3232
use std::{
3333
io::{stdin, stdout, Write},
34-
sync::{atomic::Ordering, Arc},
34+
sync::Arc,
3535
time::{Duration, Instant},
3636
};
3737

@@ -274,17 +274,26 @@ impl Application {
274274
}
275275

276276
#[cfg(feature = "integration")]
277-
fn render(&mut self) {}
277+
async fn render(&mut self) {}
278278

279279
#[cfg(not(feature = "integration"))]
280-
fn render(&mut self) {
280+
async fn render(&mut self) {
281281
let mut cx = crate::compositor::Context {
282282
editor: &mut self.editor,
283283
jobs: &mut self.jobs,
284284
scroll: None,
285285
};
286286

287-
cx.editor.redraw_handle.store(false, Ordering::Relaxed);
287+
// Aquire mutable access to the redraw_handle lock
288+
// to ensure that there are no tasks running that want to block rendering
289+
drop(cx.editor.redraw_handle.1.write().await);
290+
cx.editor.needs_redraw = false;
291+
{
292+
// exhaust any leftover redraw notifications
293+
let notify = cx.editor.redraw_handle.0.notified();
294+
tokio::pin!(notify);
295+
notify.enable();
296+
}
288297

289298
let area = self
290299
.terminal
@@ -306,7 +315,7 @@ impl Application {
306315
where
307316
S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
308317
{
309-
self.render();
318+
self.render().await;
310319
self.last_render = Instant::now();
311320

312321
loop {
@@ -331,18 +340,18 @@ impl Application {
331340
biased;
332341

333342
Some(event) = input_stream.next() => {
334-
self.handle_terminal_events(event);
343+
self.handle_terminal_events(event).await;
335344
}
336345
Some(signal) = self.signals.next() => {
337346
self.handle_signals(signal).await;
338347
}
339348
Some(callback) = self.jobs.futures.next() => {
340349
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
341-
self.render();
350+
self.render().await;
342351
}
343352
Some(callback) = self.jobs.wait_futures.next() => {
344353
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
345-
self.render();
354+
self.render().await;
346355
}
347356
event = self.editor.wait_event() => {
348357
let _idle_handled = self.handle_editor_event(event).await;
@@ -447,25 +456,25 @@ impl Application {
447456
self.compositor.resize(area);
448457
self.terminal.clear().expect("couldn't clear terminal");
449458

450-
self.render();
459+
self.render().await;
451460
}
452461
signal::SIGUSR1 => {
453462
self.refresh_config();
454-
self.render();
463+
self.render().await;
455464
}
456465
_ => unreachable!(),
457466
}
458467
}
459468

460-
pub fn handle_idle_timeout(&mut self) {
469+
pub async fn handle_idle_timeout(&mut self) {
461470
let mut cx = crate::compositor::Context {
462471
editor: &mut self.editor,
463472
jobs: &mut self.jobs,
464473
scroll: None,
465474
};
466475
let should_render = self.compositor.handle_event(&Event::IdleTimeout, &mut cx);
467-
if should_render || self.editor.redraw_handle.load(Ordering::Relaxed) {
468-
self.render();
476+
if should_render || self.editor.needs_redraw {
477+
self.render().await;
469478
}
470479
}
471480

@@ -538,31 +547,31 @@ impl Application {
538547
match event {
539548
EditorEvent::DocumentSaved(event) => {
540549
self.handle_document_write(event);
541-
self.render();
550+
self.render().await;
542551
}
543552
EditorEvent::ConfigEvent(event) => {
544553
self.handle_config_events(event);
545-
self.render();
554+
self.render().await;
546555
}
547556
EditorEvent::LanguageServerMessage((id, call)) => {
548557
self.handle_language_server_message(call, id).await;
549558
// limit render calls for fast language server messages
550559
let last = self.editor.language_servers.incoming.is_empty();
551560

552561
if last || self.last_render.elapsed() > LSP_DEADLINE {
553-
self.render();
562+
self.render().await;
554563
self.last_render = Instant::now();
555564
}
556565
}
557566
EditorEvent::DebuggerEvent(payload) => {
558567
let needs_render = self.editor.handle_debugger_message(payload).await;
559568
if needs_render {
560-
self.render();
569+
self.render().await;
561570
}
562571
}
563572
EditorEvent::IdleTimer => {
564573
self.editor.clear_idle_timer();
565-
self.handle_idle_timeout();
574+
self.handle_idle_timeout().await;
566575

567576
#[cfg(feature = "integration")]
568577
{
@@ -574,7 +583,10 @@ impl Application {
574583
false
575584
}
576585

577-
pub fn handle_terminal_events(&mut self, event: Result<CrosstermEvent, crossterm::ErrorKind>) {
586+
pub async fn handle_terminal_events(
587+
&mut self,
588+
event: Result<CrosstermEvent, crossterm::ErrorKind>,
589+
) {
578590
let mut cx = crate::compositor::Context {
579591
editor: &mut self.editor,
580592
jobs: &mut self.jobs,
@@ -598,7 +610,7 @@ impl Application {
598610
};
599611

600612
if should_redraw && !self.editor.should_close() {
601-
self.render();
613+
self.render().await;
602614
}
603615
}
604616

helix-vcs/Cargo.toml

+7-3
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ homepage = "https://helix-editor.com"
1111
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1212

1313
[dependencies]
14-
git-repository = { version = "0.26", default-features = false , optional = true }
14+
15+
helix-core = { version = "0.6", path = "../helix-core" }
16+
1517
tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
18+
parking_lot = "0.12"
19+
20+
git-repository = { version = "0.26", default-features = false , optional = true }
1621
imara-diff = "0.1.5"
17-
helix-core = { version = "0.6", path = "../helix-core" }
22+
1823
log = "0.4"
19-
parking_lot = "0.12"
2024

2125
[features]
2226
git = ["git-repository"]

helix-vcs/src/diff.rs

+41-25
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
11
use std::ops::Range;
2-
use std::sync::atomic::AtomicBool;
32
use std::sync::Arc;
43

54
use helix_core::Rope;
65
use imara_diff::Algorithm;
76
use parking_lot::{Mutex, MutexGuard};
87
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
8+
use tokio::sync::{Notify, RwLock};
99
use tokio::task::JoinHandle;
1010

1111
use crate::diff::worker::DiffWorker;
1212

1313
mod line_cache;
1414
mod worker;
1515

16-
enum Event {
17-
UpdateDocument(Rope),
18-
UpdateDiffBase(Rope),
16+
type RedrawHandle = Arc<(Notify, RwLock<()>)>;
17+
18+
// The order of enum variants is used by the PartialOrd
19+
// derive macro, DO NOT REORDER
20+
#[derive(PartialEq, PartialOrd)]
21+
enum RenderStrategy {
22+
Async,
23+
SyncWithTimeout,
24+
Sync,
25+
}
26+
27+
struct Event {
28+
text: Rope,
29+
is_base: bool,
30+
render_strategy: RenderStrategy,
1931
}
2032

2133
#[derive(Clone, Debug)]
@@ -26,22 +38,23 @@ pub struct DiffHandle {
2638
}
2739

2840
impl DiffHandle {
29-
pub fn new(diff_base: Rope, doc: Rope, redraw_handle: Arc<AtomicBool>) -> DiffHandle {
41+
pub fn new(diff_base: Rope, doc: Rope, redraw_handle: RedrawHandle) -> DiffHandle {
3042
DiffHandle::new_with_handle(diff_base, doc, redraw_handle).0
3143
}
3244

3345
fn new_with_handle(
3446
diff_base: Rope,
3547
doc: Rope,
36-
notify: Arc<AtomicBool>,
48+
redraw_handle: RedrawHandle,
3749
) -> (DiffHandle, JoinHandle<()>) {
3850
let (sender, receiver) = unbounded_channel();
3951
let hunks: Arc<Mutex<Vec<Hunk>>> = Arc::default();
4052
let worker = DiffWorker {
4153
channel: receiver,
4254
hunks: hunks.clone(),
4355
new_hunks: Vec::default(),
44-
notify,
56+
redraw_handle,
57+
difff_finished_notify: Arc::default(),
4558
};
4659
let handle = tokio::spawn(worker.run(diff_base, doc));
4760
let differ = DiffHandle {
@@ -63,35 +76,38 @@ impl DiffHandle {
6376
}
6477
}
6578

66-
pub fn update_document(&self, doc: Rope) -> bool {
67-
if self.inverted {
68-
self.update_diff_base_impl(doc)
79+
pub fn update_document(&self, doc: Rope, block: bool) -> bool {
80+
let mode = if block {
81+
RenderStrategy::Sync
6982
} else {
70-
self.update_document_impl(doc)
71-
}
83+
RenderStrategy::SyncWithTimeout
84+
};
85+
self.update_document_impl(doc, self.inverted, mode)
7286
}
7387

7488
pub fn update_diff_base(&self, diff_base: Rope) -> bool {
75-
if self.inverted {
76-
self.update_document_impl(diff_base)
77-
} else {
78-
self.update_diff_base_impl(diff_base)
79-
}
89+
self.update_document_impl(diff_base, !self.inverted, RenderStrategy::Async)
8090
}
8191

82-
pub fn update_document_impl(&self, doc: Rope) -> bool {
83-
self.channel.send(Event::UpdateDocument(doc)).is_ok()
84-
}
85-
86-
pub fn update_diff_base_impl(&self, diff_base: Rope) -> bool {
87-
self.channel.send(Event::UpdateDiffBase(diff_base)).is_ok()
92+
fn update_document_impl(&self, text: Rope, is_base: bool, mode: RenderStrategy) -> bool {
93+
let event = Event {
94+
text,
95+
is_base,
96+
render_strategy: mode,
97+
};
98+
self.channel.send(event).is_ok()
8899
}
89100
}
90101

91102
// TODO configuration
92-
const DIFF_DEBOUNCE_TIME: u64 = 100;
103+
/// synchronus debounce value should be low
104+
/// so we can update synchrously most of the time
105+
const DIFF_DEBOUNCE_TIME_SYNC: u64 = 1;
106+
/// maximum time that rendering should be blocked until the diff finishes
107+
const SYNC_DIFF_TIMEOUT: u64 = 50;
108+
const DIFF_DEBOUNCE_TIME_ASYNC: u64 = 100;
93109
const ALGORITHM: Algorithm = Algorithm::Histogram;
94-
const MAX_DIFF_LINES: usize = u16::MAX as usize;
110+
const MAX_DIFF_LINES: usize = 64 * u16::MAX as usize;
95111
// cap average line length to 128 for files with MAX_DIFF_LINES
96112
const MAX_DIFF_BYTES: usize = MAX_DIFF_LINES * 128;
97113

0 commit comments

Comments
 (0)