Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/document.rs')
| -rw-r--r-- | helix-view/src/document.rs | 279 |
1 files changed, 113 insertions, 166 deletions
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index e52dbe0f..14526380 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -4,16 +4,15 @@ use arc_swap::ArcSwap; use futures_util::future::BoxFuture; use futures_util::FutureExt; use helix_core::auto_pairs::AutoPairs; -use helix_core::chars::char_is_word; use helix_core::command_line::Token; -use helix_core::diagnostic::DiagnosticProvider; +use helix_core::diagnostic::Severity; use helix_core::doc_formatter::TextFormat; use helix_core::encoding::Encoding; use helix_core::snippets::{ActiveSnippet, SnippetRenderCtx}; use helix_core::syntax::config::LanguageServerFeature; use helix_core::text_annotations::{InlineAnnotation, Overlay}; +use helix_core::RopeSlice; use helix_event::TaskController; -use helix_lsp::util::lsp_pos_to_pos; use helix_stdx::faccess::{copy_metadata, readonly}; use helix_vcs::{DiffHandle, DiffProviderRegistry}; use once_cell::sync::OnceCell; @@ -40,10 +39,11 @@ use helix_core::{ indent::{auto_detect_indent_style, IndentStyle}, line_ending::auto_detect_line_ending, syntax::{self, config::LanguageConfiguration}, - ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction, + ChangeSet, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction, }; use crate::{ + diagnostic::DiagnosticProvider, editor::Config, events::{DocumentDidChange, SelectionDidChange}, expansion, @@ -204,14 +204,11 @@ pub struct Document { pub readonly: bool, - pub previous_diagnostic_id: Option<String>, - /// Annotations for LSP document color swatches pub color_swatches: Option<DocumentColorSwatches>, // NOTE: ideally this would live on the handler for color swatches. This is blocked on a // large refactor that would make `&mut Editor` available on the `DocumentDidChange` event. pub color_swatch_controller: TaskController, - pub pull_diagnostic_controller: TaskController, // NOTE: this field should eventually go away - we should use the Editor's syn_loader instead // of storing a copy on every doc. Then we can remove the surrounding `Arc` and use the @@ -731,8 +728,6 @@ impl Document { color_swatches: None, color_swatch_controller: TaskController::new(), syn_loader, - previous_diagnostic_id: None, - pull_diagnostic_controller: TaskController::new(), } } @@ -980,12 +975,11 @@ impl Document { }; let identifier = self.path().map(|_| self.identifier()); - let language_servers: Vec<_> = self.language_servers.values().cloned().collect(); + let language_servers = self.language_servers.clone(); // mark changes up to now as saved let current_rev = self.get_current_revision(); let doc_id = self.id(); - let atomic_save = self.config.load().atomic_save; let encoding_with_bom_info = (self.encoding, self.has_bom); let last_saved_time = self.last_saved_time; @@ -1035,7 +1029,7 @@ impl Document { // Assume it is a hardlink to prevent data loss if the metadata cant be read (e.g. on certain Windows configurations) let is_hardlink = helix_stdx::faccess::hardlink_count(&write_path).unwrap_or(2) > 1; - let backup = if path.exists() && atomic_save { + let backup = if path.exists() { let path_ = write_path.clone(); // hacks: we use tempfile to handle the complex task of creating // non clobbered temporary path for us we don't want @@ -1124,7 +1118,7 @@ impl Document { text: text.clone(), }; - for language_server in language_servers { + for (_, language_server) in language_servers { if !language_server.is_initialized() { continue; } @@ -1178,7 +1172,7 @@ impl Document { } } - pub fn detect_editor_config(&mut self) { + pub(crate) fn detect_editor_config(&mut self) { if self.config.load().editor_config { if let Some(path) = self.path.as_ref() { self.editor_config = EditorConfig::find(path); @@ -1458,45 +1452,7 @@ impl Document { diff_handle.update_document(self.text.clone(), false); } - // map diagnostics over changes too - changes.update_positions(self.diagnostics.iter_mut().map(|diagnostic| { - let assoc = if diagnostic.starts_at_word { - Assoc::BeforeWord - } else { - Assoc::After - }; - (&mut diagnostic.range.start, assoc) - })); - changes.update_positions(self.diagnostics.iter_mut().filter_map(|diagnostic| { - if diagnostic.zero_width { - // for zero width diagnostics treat the diagnostic as a point - // rather than a range - return None; - } - let assoc = if diagnostic.ends_at_word { - Assoc::AfterWord - } else { - Assoc::Before - }; - Some((&mut diagnostic.range.end, assoc)) - })); - self.diagnostics.retain_mut(|diagnostic| { - if diagnostic.zero_width { - diagnostic.range.end = diagnostic.range.start - } else if diagnostic.range.start >= diagnostic.range.end { - return false; - } - diagnostic.line = self.text.char_to_line(diagnostic.range.start); - true - }); - - self.diagnostics.sort_by_key(|diagnostic| { - ( - diagnostic.range, - diagnostic.severity, - diagnostic.provider.clone(), - ) - }); + Diagnostic::apply_changes(&mut self.diagnostics, changes, self.text.slice(..)); // Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place let apply_inlay_hint_changes = |annotations: &mut Vec<InlineAnnotation>| { @@ -1660,7 +1616,7 @@ impl Document { let savepoint_idx = self .savepoints .iter() - .position(|savepoint_ref| std::ptr::eq(savepoint_ref.as_ptr(), savepoint)) + .position(|savepoint_ref| savepoint_ref.as_ptr() == savepoint as *const _) .expect("Savepoint must belong to this document"); let savepoint_ref = self.savepoints.remove(savepoint_idx); @@ -1781,6 +1737,10 @@ impl Document { current_revision } + pub fn spelling_language(&self) -> Option<helix_core::SpellingLanguage> { + Some(helix_core::SpellingLanguage::EN_US) + } + /// Corresponding language scope name. Usually `source.<lang>`. pub fn language_scope(&self) -> Option<&str> { self.language @@ -1815,12 +1775,6 @@ impl Document { self.version } - pub fn word_completion_enabled(&self) -> bool { - self.language_config() - .and_then(|lang_config| lang_config.word_completion.and_then(|c| c.enable)) - .unwrap_or_else(|| self.config.load().word_completion.enable) - } - pub fn path_completion_enabled(&self) -> bool { self.language_config() .and_then(|lang_config| lang_config.path_completion) @@ -1948,8 +1902,11 @@ impl Document { Url::from_file_path(self.path()?).ok() } - pub fn uri(&self) -> Option<helix_core::Uri> { - Some(self.path()?.clone().into()) + pub fn uri(&self) -> helix_core::Uri { + self.path + .clone() + .map(|path| path.into()) + .unwrap_or(helix_core::Uri::Scratch(self.id)) } #[inline] @@ -2031,94 +1988,6 @@ impl Document { ) } - pub fn lsp_diagnostic_to_diagnostic( - text: &Rope, - language_config: Option<&LanguageConfiguration>, - diagnostic: &helix_lsp::lsp::Diagnostic, - provider: DiagnosticProvider, - offset_encoding: helix_lsp::OffsetEncoding, - ) -> Option<Diagnostic> { - use helix_core::diagnostic::{Range, Severity::*}; - - // TODO: convert inside server - let start = - if let Some(start) = lsp_pos_to_pos(text, diagnostic.range.start, offset_encoding) { - start - } else { - log::warn!("lsp position out of bounds - {:?}", diagnostic); - return None; - }; - - let end = if let Some(end) = lsp_pos_to_pos(text, diagnostic.range.end, offset_encoding) { - end - } else { - log::warn!("lsp position out of bounds - {:?}", diagnostic); - return None; - }; - - let severity = diagnostic.severity.and_then(|severity| match severity { - lsp::DiagnosticSeverity::ERROR => Some(Error), - lsp::DiagnosticSeverity::WARNING => Some(Warning), - lsp::DiagnosticSeverity::INFORMATION => Some(Info), - lsp::DiagnosticSeverity::HINT => Some(Hint), - severity => { - log::error!("unrecognized diagnostic severity: {:?}", severity); - None - } - }); - - if let Some(lang_conf) = language_config { - if let Some(severity) = severity { - if severity < lang_conf.diagnostic_severity { - return None; - } - } - }; - use helix_core::diagnostic::{DiagnosticTag, NumberOrString}; - - let code = match diagnostic.code.clone() { - Some(x) => match x { - lsp::NumberOrString::Number(x) => Some(NumberOrString::Number(x)), - lsp::NumberOrString::String(x) => Some(NumberOrString::String(x)), - }, - None => None, - }; - - let tags = if let Some(tags) = &diagnostic.tags { - let new_tags = tags - .iter() - .filter_map(|tag| match *tag { - lsp::DiagnosticTag::DEPRECATED => Some(DiagnosticTag::Deprecated), - lsp::DiagnosticTag::UNNECESSARY => Some(DiagnosticTag::Unnecessary), - _ => None, - }) - .collect(); - - new_tags - } else { - Vec::new() - }; - - let ends_at_word = - start != end && end != 0 && text.get_char(end - 1).is_some_and(char_is_word); - let starts_at_word = start != end && text.get_char(start).is_some_and(char_is_word); - - Some(Diagnostic { - range: Range { start, end }, - ends_at_word, - starts_at_word, - zero_width: start == end, - line: diagnostic.range.start.line as usize, - message: diagnostic.message.clone(), - severity, - code, - tags, - source: diagnostic.source.clone(), - data: diagnostic.data.clone(), - provider, - }) - } - #[inline] pub fn diagnostics(&self) -> &[Diagnostic] { &self.diagnostics @@ -2133,17 +2002,17 @@ impl Document { if unchanged_sources.is_empty() { if let Some(provider) = provider { self.diagnostics - .retain(|diagnostic| &diagnostic.provider != provider); + .retain(|diagnostic| &diagnostic.inner.provider != provider); } else { self.diagnostics.clear(); } } else { self.diagnostics.retain(|d| { - if provider.is_some_and(|provider| provider != &d.provider) { + if provider.is_some_and(|provider| provider != &d.inner.provider) { return true; } - if let Some(source) = &d.source { + if let Some(source) = &d.inner.source { unchanged_sources.contains(source) } else { false @@ -2151,19 +2020,13 @@ impl Document { }); } self.diagnostics.extend(diagnostics); - self.diagnostics.sort_by_key(|diagnostic| { - ( - diagnostic.range, - diagnostic.severity, - diagnostic.provider.clone(), - ) - }); + self.diagnostics.sort(); } - /// clears diagnostics for a given language server id if set, otherwise all diagnostics are cleared + /// clears diagnostics for a given language server id pub fn clear_diagnostics_for_language_server(&mut self, id: LanguageServerId) { self.diagnostics - .retain(|d| d.provider.language_server_id() != Some(id)); + .retain(|d| d.inner.provider.language_server_id() != Some(id)); } /// Get the document's auto pairs. If the document has a recognized @@ -2289,10 +2152,6 @@ impl Document { pub fn reset_all_inlay_hints(&mut self) { self.inlay_hints = Default::default(); } - - pub fn has_language_server_with_feature(&self, feature: LanguageServerFeature) -> bool { - self.language_servers_with_feature(feature).next().is_some() - } } #[derive(Debug, Default)] @@ -2331,6 +2190,94 @@ impl Display for FormatterError { } } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Diagnostic { + pub inner: crate::Diagnostic, + pub range: helix_stdx::Range, + pub line: usize, + ends_at_word: bool, + starts_at_word: bool, + zero_width: bool, +} + +impl Diagnostic { + #[inline] + pub fn severity(&self) -> Severity { + self.inner.severity.unwrap_or(Severity::Warning) + } + + fn apply_changes(diagnostics: &mut Vec<Self>, changes: &ChangeSet, text: RopeSlice) { + use helix_core::Assoc; + + changes.update_positions(diagnostics.iter_mut().map(|diagnostic| { + let assoc = if diagnostic.starts_at_word { + Assoc::BeforeWord + } else { + Assoc::After + }; + (&mut diagnostic.range.start, assoc) + })); + changes.update_positions(diagnostics.iter_mut().filter_map(|diagnostic| { + if diagnostic.zero_width { + // for zero width diagnostics treat the diagnostic as a point + // rather than a range + return None; + } + let assoc = if diagnostic.ends_at_word { + Assoc::AfterWord + } else { + Assoc::Before + }; + Some((&mut diagnostic.range.end, assoc)) + })); + diagnostics.retain_mut(|diagnostic| { + if diagnostic.zero_width { + diagnostic.range.end = diagnostic.range.start; + } else if diagnostic.range.start >= diagnostic.range.end { + return false; + } + diagnostic.line = text.char_to_line(diagnostic.range.start); + true + }); + + diagnostics.sort(); + } +} + +impl crate::Diagnostic { + pub(crate) fn to_document_diagnostic(&self, text: &Rope) -> Option<Diagnostic> { + use helix_core::chars::char_is_word; + use helix_lsp::util; + + let (start, end, line) = match self.range { + crate::Range::Lsp { + range, + offset_encoding, + } => { + let start = util::lsp_pos_to_pos(text, range.start, offset_encoding)?; + let end = util::lsp_pos_to_pos(text, range.end, offset_encoding)?; + (start, end, range.start.line as usize) + } + crate::Range::Document(range) => { + (range.start, range.end, text.char_to_line(range.start)) + } + }; + + let ends_at_word = + start != end && end != 0 && text.get_char(end - 1).is_some_and(char_is_word); + let starts_at_word = start != end && text.get_char(start).is_some_and(char_is_word); + + Some(Diagnostic { + inner: self.clone(), + range: helix_stdx::Range { start, end }, + line, + starts_at_word, + ends_at_word, + zero_width: start == end, + }) + } +} + #[cfg(test)] mod test { use arc_swap::ArcSwap; |