helix/helix-view/src/handlers/diagnostics.rs

133 lines
4.0 KiB
Rust

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(self.active)
}
}
impl DiagnosticsHandler {
#[allow(clippy::new_without_default)]
pub fn new(enable_diagnostics: bool) -> 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: enable_diagnostics,
}
}
}
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
}
}
}