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.rs279
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;