Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/annotations/diagnostics.rs')
| -rw-r--r-- | helix-view/src/annotations/diagnostics.rs | 313 |
1 files changed, 0 insertions, 313 deletions
diff --git a/helix-view/src/annotations/diagnostics.rs b/helix-view/src/annotations/diagnostics.rs deleted file mode 100644 index dc141462..00000000 --- a/helix-view/src/annotations/diagnostics.rs +++ /dev/null @@ -1,313 +0,0 @@ -use helix_core::diagnostic::Severity; -use helix_core::doc_formatter::{FormattedGrapheme, TextFormat}; -use helix_core::text_annotations::LineAnnotation; -use helix_core::{softwrapped_dimensions, Diagnostic, Position}; -use serde::{Deserialize, Serialize}; - -use crate::Document; - -/// Describes the severity level of a [`Diagnostic`]. -#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] -pub enum DiagnosticFilter { - Disable, - Enable(Severity), -} - -impl<'de> Deserialize<'de> for DiagnosticFilter { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: serde::Deserializer<'de>, - { - match &*String::deserialize(deserializer)? { - "disable" => Ok(DiagnosticFilter::Disable), - "hint" => Ok(DiagnosticFilter::Enable(Severity::Hint)), - "info" => Ok(DiagnosticFilter::Enable(Severity::Info)), - "warning" => Ok(DiagnosticFilter::Enable(Severity::Warning)), - "error" => Ok(DiagnosticFilter::Enable(Severity::Error)), - variant => Err(serde::de::Error::unknown_variant( - variant, - &["disable", "hint", "info", "warning", "error"], - )), - } - } -} - -impl Serialize for DiagnosticFilter { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - let filter = match self { - DiagnosticFilter::Disable => "disable", - DiagnosticFilter::Enable(Severity::Hint) => "hint", - DiagnosticFilter::Enable(Severity::Info) => "info", - DiagnosticFilter::Enable(Severity::Warning) => "warning", - DiagnosticFilter::Enable(Severity::Error) => "error", - }; - filter.serialize(serializer) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] -pub struct InlineDiagnosticsConfig { - pub cursor_line: DiagnosticFilter, - pub other_lines: DiagnosticFilter, - pub min_diagnostic_width: u16, - pub prefix_len: u16, - pub max_wrap: u16, - pub max_diagnostics: usize, -} - -impl InlineDiagnosticsConfig { - pub fn disabled(&self) -> bool { - matches!( - self, - Self { - cursor_line: DiagnosticFilter::Disable, - other_lines: DiagnosticFilter::Disable, - .. - } - ) - } - - pub fn prepare(&self, width: u16, enable_cursor_line: bool) -> Self { - let mut config = self.clone(); - if width < self.min_diagnostic_width + self.prefix_len { - config.cursor_line = DiagnosticFilter::Disable; - config.other_lines = DiagnosticFilter::Disable; - } else if !enable_cursor_line { - config.cursor_line = self.cursor_line.min(self.other_lines); - } - config - } - - pub fn max_diagnostic_start(&self, width: u16) -> u16 { - width - self.min_diagnostic_width - self.prefix_len - } - - pub fn text_fmt(&self, anchor_col: u16, width: u16) -> TextFormat { - let width = if anchor_col > self.max_diagnostic_start(width) { - self.min_diagnostic_width - } else { - width - anchor_col - self.prefix_len - }; - - TextFormat { - soft_wrap: true, - tab_width: 4, - max_wrap: self.max_wrap.min(width / 4), - max_indent_retain: 0, - wrap_indicator: "".into(), - wrap_indicator_highlight: None, - viewport_width: width, - soft_wrap_at_text_width: true, - } - } -} - -impl Default for InlineDiagnosticsConfig { - fn default() -> Self { - InlineDiagnosticsConfig { - cursor_line: DiagnosticFilter::Enable(Severity::Warning), - other_lines: DiagnosticFilter::Disable, - min_diagnostic_width: 40, - prefix_len: 1, - max_wrap: 20, - max_diagnostics: 10, - } - } -} - -pub struct InlineDiagnosticAccumulator<'a> { - idx: usize, - doc: &'a Document, - pub stack: Vec<(&'a Diagnostic, u16)>, - pub config: InlineDiagnosticsConfig, - cursor: usize, - cursor_line: bool, -} - -impl<'a> InlineDiagnosticAccumulator<'a> { - pub fn new(cursor: usize, doc: &'a Document, config: InlineDiagnosticsConfig) -> Self { - InlineDiagnosticAccumulator { - idx: 0, - doc, - stack: Vec::new(), - config, - cursor, - cursor_line: false, - } - } - - pub fn reset_pos(&mut self, char_idx: usize) -> usize { - self.idx = 0; - self.clear(); - self.skip_concealed(char_idx) - } - - pub fn skip_concealed(&mut self, conceal_end_char_idx: usize) -> usize { - let diagnostics = &self.doc.diagnostics[self.idx..]; - let idx = diagnostics.partition_point(|diag| diag.range.start < conceal_end_char_idx); - self.idx += idx; - self.next_anchor(conceal_end_char_idx) - } - - pub fn next_anchor(&self, current_char_idx: usize) -> usize { - let next_diag_start = self - .doc - .diagnostics - .get(self.idx) - .map_or(usize::MAX, |diag| diag.range.start); - if (current_char_idx..next_diag_start).contains(&self.cursor) { - self.cursor - } else { - next_diag_start - } - } - - pub fn clear(&mut self) { - self.cursor_line = false; - self.stack.clear(); - } - - fn process_anchor_impl( - &mut self, - grapheme: &FormattedGrapheme, - width: u16, - horizontal_off: usize, - ) -> bool { - // TODO: doing the cursor tracking here works well but is somewhat - // duplicate effort/tedious maybe centralize this somewhere? - // In the DocFormatter? - if grapheme.char_idx == self.cursor { - self.cursor_line = true; - if self - .doc - .diagnostics - .get(self.idx) - .is_none_or(|diag| diag.range.start != grapheme.char_idx) - { - return false; - } - } - - let Some(anchor_col) = grapheme.visual_pos.col.checked_sub(horizontal_off) else { - return true; - }; - if anchor_col >= width as usize { - return true; - } - - for diag in &self.doc.diagnostics[self.idx..] { - if diag.range.start != grapheme.char_idx { - break; - } - self.stack.push((diag, anchor_col as u16)); - self.idx += 1; - } - false - } - - pub fn proccess_anchor( - &mut self, - grapheme: &FormattedGrapheme, - width: u16, - horizontal_off: usize, - ) -> usize { - if self.process_anchor_impl(grapheme, width, horizontal_off) { - self.idx += self.doc.diagnostics[self.idx..] - .iter() - .take_while(|diag| diag.range.start == grapheme.char_idx) - .count(); - } - self.next_anchor(grapheme.char_idx + 1) - } - - pub fn filter(&self) -> DiagnosticFilter { - if self.cursor_line { - self.config.cursor_line - } else { - self.config.other_lines - } - } - - pub fn compute_line_diagnostics(&mut self) { - let filter = if self.cursor_line { - self.cursor_line = false; - self.config.cursor_line - } else { - self.config.other_lines - }; - let DiagnosticFilter::Enable(filter) = filter else { - self.stack.clear(); - return; - }; - self.stack.retain(|(diag, _)| diag.severity() >= filter); - self.stack.truncate(self.config.max_diagnostics) - } - - pub fn has_multi(&self, width: u16) -> bool { - self.stack - .last() - .is_some_and(|&(_, anchor)| anchor > self.config.max_diagnostic_start(width)) - } -} - -pub(crate) struct InlineDiagnostics<'a> { - state: InlineDiagnosticAccumulator<'a>, - width: u16, - horizontal_off: usize, -} - -impl<'a> InlineDiagnostics<'a> { - #[allow(clippy::new_ret_no_self)] - pub(crate) fn new( - doc: &'a Document, - cursor: usize, - width: u16, - horizontal_off: usize, - config: InlineDiagnosticsConfig, - ) -> Box<dyn LineAnnotation + 'a> { - Box::new(InlineDiagnostics { - state: InlineDiagnosticAccumulator::new(cursor, doc, config), - width, - horizontal_off, - }) - } -} - -impl LineAnnotation for InlineDiagnostics<'_> { - fn reset_pos(&mut self, char_idx: usize) -> usize { - self.state.reset_pos(char_idx) - } - - fn skip_concealed_anchors(&mut self, conceal_end_char_idx: usize) -> usize { - self.state.skip_concealed(conceal_end_char_idx) - } - - fn process_anchor(&mut self, grapheme: &FormattedGrapheme) -> usize { - self.state - .proccess_anchor(grapheme, self.width, self.horizontal_off) - } - - fn insert_virtual_lines( - &mut self, - _line_end_char_idx: usize, - _line_end_visual_pos: Position, - _doc_line: usize, - ) -> Position { - self.state.compute_line_diagnostics(); - let multi = self.state.has_multi(self.width); - let diagostic_height: usize = self - .state - .stack - .drain(..) - .map(|(diag, anchor)| { - let text_fmt = self.state.config.text_fmt(anchor, self.width); - softwrapped_dimensions(diag.message.as_str().trim().into(), &text_fmt).0 - }) - .sum(); - Position::new(multi as usize + diagostic_height, 0) - } -} |