Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/handlers/diagnostics.rs')
| -rw-r--r-- | helix-view/src/handlers/diagnostics.rs | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/helix-view/src/handlers/diagnostics.rs b/helix-view/src/handlers/diagnostics.rs new file mode 100644 index 00000000..2b8ff632 --- /dev/null +++ b/helix-view/src/handlers/diagnostics.rs @@ -0,0 +1,131 @@ +use std::cell::Cell; +use std::num::NonZeroUsize; +use std::sync::atomic::{self, AtomicUsize}; +use std::sync::Arc; +use std::time::Duration; + +use helix_event::{request_redraw, send_blocking, AsyncHook}; +use tokio::sync::mpsc::Sender; +use tokio::time::Instant; + +use crate::{Document, DocumentId, ViewId}; + +#[derive(Debug)] +pub enum DiagnosticEvent { + CursorLineChanged { generation: usize }, + Refresh, +} + +struct DiagnosticTimeout { + active_generation: Arc<AtomicUsize>, + generation: usize, +} + +const TIMEOUT: Duration = Duration::from_millis(350); + +impl AsyncHook for DiagnosticTimeout { + type Event = DiagnosticEvent; + + fn handle_event( + &mut self, + event: DiagnosticEvent, + timeout: Option<Instant>, + ) -> Option<Instant> { + match event { + DiagnosticEvent::CursorLineChanged { generation } => { + if generation > self.generation { + self.generation = generation; + Some(Instant::now() + TIMEOUT) + } else { + timeout + } + } + DiagnosticEvent::Refresh if timeout.is_some() => Some(Instant::now() + TIMEOUT), + DiagnosticEvent::Refresh => None, + } + } + + fn finish_debounce(&mut self) { + if self.active_generation.load(atomic::Ordering::Relaxed) < self.generation { + self.active_generation + .store(self.generation, atomic::Ordering::Relaxed); + request_redraw(); + } + } +} + +pub struct DiagnosticsHandler { + active_generation: Arc<AtomicUsize>, + generation: Cell<usize>, + last_doc: Cell<DocumentId>, + last_cursor_line: Cell<usize>, + pub active: bool, + pub events: Sender<DiagnosticEvent>, +} + +// make sure we never share handlers across multiple views this is a stop +// gap solution. We just shouldn't be cloneing a view to begin with (we do +// for :hsplit/vsplit) and really this should not be view specific to begin with +// but to fix that larger architecutre changes are needed +impl Clone for DiagnosticsHandler { + fn clone(&self) -> Self { + Self::new() + } +} + +impl DiagnosticsHandler { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + let active_generation = Arc::new(AtomicUsize::new(0)); + let events = DiagnosticTimeout { + active_generation: active_generation.clone(), + generation: 0, + } + .spawn(); + Self { + active_generation, + generation: Cell::new(0), + events, + last_doc: Cell::new(DocumentId(NonZeroUsize::new(usize::MAX).unwrap())), + last_cursor_line: Cell::new(usize::MAX), + active: true, + } + } +} + +impl DiagnosticsHandler { + pub fn immediately_show_diagnostic(&self, doc: &Document, view: ViewId) { + self.last_doc.set(doc.id()); + let cursor_line = doc + .selection(view) + .primary() + .cursor_line(doc.text().slice(..)); + self.last_cursor_line.set(cursor_line); + self.active_generation + .store(self.generation.get(), atomic::Ordering::Relaxed); + } + pub fn show_cursorline_diagnostics(&self, doc: &Document, view: ViewId) -> bool { + if !self.active { + return false; + } + let cursor_line = doc + .selection(view) + .primary() + .cursor_line(doc.text().slice(..)); + if self.last_cursor_line.get() == cursor_line && self.last_doc.get() == doc.id() { + let active_generation = self.active_generation.load(atomic::Ordering::Relaxed); + self.generation.get() == active_generation + } else { + self.last_doc.set(doc.id()); + self.last_cursor_line.set(cursor_line); + self.generation.set(self.generation.get() + 1); + send_blocking( + &self.events, + DiagnosticEvent::CursorLineChanged { + generation: self.generation.get(), + }, + ); + false + } + } +} |