Add config to mark diagnostic sources as persistent
This commit is contained in:
parent
c874a896a5
commit
8653e1b02f
5 changed files with 91 additions and 41 deletions
|
@ -69,6 +69,7 @@ These configuration keys are available:
|
||||||
| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
|
| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
|
||||||
| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
|
| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
|
||||||
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
|
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
|
||||||
|
| `persistent-diagnostic-sources` | An array of LSP diagnostic sources assumed unchanged when the language server resends the same set of diagnostics. Helix can track the position for these diagnostics internally instead. Useful for diagnostics that are recomputed on save.
|
||||||
|
|
||||||
### File-type detection and the `file-types` key
|
### File-type detection and the `file-types` key
|
||||||
|
|
||||||
|
|
|
@ -155,6 +155,8 @@ pub struct LanguageConfiguration {
|
||||||
/// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
|
/// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
|
||||||
/// Falling back to the current working directory if none are configured.
|
/// Falling back to the current working directory if none are configured.
|
||||||
pub workspace_lsp_roots: Option<Vec<PathBuf>>,
|
pub workspace_lsp_roots: Option<Vec<PathBuf>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub persistent_diagnostic_sources: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
|
|
@ -717,7 +717,7 @@ impl Application {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Notification::PublishDiagnostics(params) => {
|
Notification::PublishDiagnostics(mut params) => {
|
||||||
let path = match params.uri.to_file_path() {
|
let path = match params.uri.to_file_path() {
|
||||||
Ok(path) => path,
|
Ok(path) => path,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -731,31 +731,69 @@ impl Application {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let offset_encoding = language_server.offset_encoding();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
let doc = self.editor.document_by_path_mut(&path).filter(|doc| {
|
// have to inline the function because of borrow checking...
|
||||||
|
let doc = self.editor.documents.values_mut()
|
||||||
|
.find(|doc| doc.path().map(|p| p == &path).unwrap_or(false))
|
||||||
|
.filter(|doc| {
|
||||||
if let Some(version) = params.version {
|
if let Some(version) = params.version {
|
||||||
if version != doc.version() {
|
if version != doc.version() {
|
||||||
log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
|
log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(doc) = doc {
|
if let Some(doc) = doc {
|
||||||
let lang_conf = doc.language_config();
|
let lang_conf = doc.language.clone();
|
||||||
let text = doc.text();
|
let text = doc.text().clone();
|
||||||
|
|
||||||
let diagnostics = params
|
let mut unchaged_diag_sources_ = Vec::new();
|
||||||
|
if let Some(lang_conf) = &lang_conf {
|
||||||
|
if let Some(old_diagnostics) =
|
||||||
|
self.editor.diagnostics.get(¶ms.uri)
|
||||||
|
{
|
||||||
|
if !lang_conf.persistent_diagnostic_sources.is_empty() {
|
||||||
|
// Sort diagnostics first by severity and then by line numbers.
|
||||||
|
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
|
||||||
|
params
|
||||||
|
.diagnostics
|
||||||
|
.sort_unstable_by_key(|d| (d.severity, d.range.start));
|
||||||
|
}
|
||||||
|
for source in &lang_conf.persistent_diagnostic_sources {
|
||||||
|
let new_diagnostics = params
|
||||||
.diagnostics
|
.diagnostics
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|diagnostic| {
|
.filter(|d| d.source.as_ref() == Some(source));
|
||||||
|
let old_diagnostics = old_diagnostics
|
||||||
|
.iter()
|
||||||
|
.filter(|(d, d_server)| {
|
||||||
|
*d_server == server_id
|
||||||
|
&& d.source.as_ref() == Some(source)
|
||||||
|
})
|
||||||
|
.map(|(d, _)| d);
|
||||||
|
if new_diagnostics.eq(old_diagnostics) {
|
||||||
|
unchaged_diag_sources_.push(source.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let unchaged_diag_sources = &unchaged_diag_sources_;
|
||||||
|
let diagnostics =
|
||||||
|
params.diagnostics.iter().filter_map(move |diagnostic| {
|
||||||
use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
|
use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
|
||||||
use lsp::DiagnosticSeverity;
|
use lsp::DiagnosticSeverity;
|
||||||
|
|
||||||
|
if diagnostic.source.as_ref().map_or(false, |source| {
|
||||||
|
unchaged_diag_sources.contains(source)
|
||||||
|
}) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: convert inside server
|
// TODO: convert inside server
|
||||||
let start = if let Some(start) = lsp_pos_to_pos(
|
let start = if let Some(start) = lsp_pos_to_pos(
|
||||||
text,
|
&text,
|
||||||
diagnostic.range.start,
|
diagnostic.range.start,
|
||||||
offset_encoding,
|
offset_encoding,
|
||||||
) {
|
) {
|
||||||
|
@ -766,7 +804,7 @@ impl Application {
|
||||||
};
|
};
|
||||||
|
|
||||||
let end = if let Some(end) =
|
let end = if let Some(end) =
|
||||||
lsp_pos_to_pos(text, diagnostic.range.end, offset_encoding)
|
lsp_pos_to_pos(&text, diagnostic.range.end, offset_encoding)
|
||||||
{
|
{
|
||||||
end
|
end
|
||||||
} else {
|
} else {
|
||||||
|
@ -786,7 +824,7 @@ impl Application {
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(lang_conf) = lang_conf {
|
if let Some(lang_conf) = &lang_conf {
|
||||||
if let Some(severity) = severity {
|
if let Some(severity) = severity {
|
||||||
if severity < lang_conf.diagnostic_severity {
|
if severity < lang_conf.diagnostic_severity {
|
||||||
return None;
|
return None;
|
||||||
|
@ -836,38 +874,31 @@ impl Application {
|
||||||
data: diagnostic.data.clone(),
|
data: diagnostic.data.clone(),
|
||||||
language_server_id: server_id,
|
language_server_id: server_id,
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
.collect();
|
|
||||||
|
|
||||||
doc.replace_diagnostics(diagnostics, server_id);
|
doc.replace_diagnostics(diagnostics, unchaged_diag_sources, server_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut diagnostics = params
|
let diagnostics = params.diagnostics.into_iter().map(|d| (d, server_id));
|
||||||
.diagnostics
|
|
||||||
.into_iter()
|
|
||||||
.map(|d| (d, server_id))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Insert the original lsp::Diagnostics here because we may have no open document
|
// Insert the original lsp::Diagnostics here because we may have no open document
|
||||||
// for diagnosic message and so we can't calculate the exact position.
|
// for diagnosic message and so we can't calculate the exact position.
|
||||||
// When using them later in the diagnostics picker, we calculate them on-demand.
|
// When using them later in the diagnostics picker, we calculate them on-demand.
|
||||||
match self.editor.diagnostics.entry(params.uri) {
|
let diagnostics = match self.editor.diagnostics.entry(params.uri) {
|
||||||
Entry::Occupied(o) => {
|
Entry::Occupied(o) => {
|
||||||
let current_diagnostics = o.into_mut();
|
let current_diagnostics = o.into_mut();
|
||||||
// there may entries of other language servers, which is why we can't overwrite the whole entry
|
// there may entries of other language servers, which is why we can't overwrite the whole entry
|
||||||
current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
|
current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
|
||||||
current_diagnostics.append(&mut diagnostics);
|
current_diagnostics.extend(diagnostics);
|
||||||
|
current_diagnostics
|
||||||
|
// Sort diagnostics first by severity and then by line numbers.
|
||||||
|
}
|
||||||
|
Entry::Vacant(v) => v.insert(diagnostics.collect()),
|
||||||
|
};
|
||||||
|
|
||||||
// Sort diagnostics first by severity and then by line numbers.
|
// Sort diagnostics first by severity and then by line numbers.
|
||||||
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
|
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
|
||||||
current_diagnostics
|
diagnostics.sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
|
||||||
.sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
|
|
||||||
}
|
|
||||||
Entry::Vacant(v) => {
|
|
||||||
diagnostics
|
|
||||||
.sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
|
|
||||||
v.insert(diagnostics);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
Notification::ShowMessage(params) => {
|
Notification::ShowMessage(params) => {
|
||||||
log::warn!("unhandled window/showMessage: {:?}", params);
|
log::warn!("unhandled window/showMessage: {:?}", params);
|
||||||
|
|
|
@ -1706,11 +1706,26 @@ impl Document {
|
||||||
|
|
||||||
pub fn replace_diagnostics(
|
pub fn replace_diagnostics(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut diagnostics: Vec<Diagnostic>,
|
diagnostics: impl IntoIterator<Item = Diagnostic>,
|
||||||
|
unchanged_sources: &[String],
|
||||||
language_server_id: usize,
|
language_server_id: usize,
|
||||||
) {
|
) {
|
||||||
|
if unchanged_sources.is_empty() {
|
||||||
self.clear_diagnostics(language_server_id);
|
self.clear_diagnostics(language_server_id);
|
||||||
self.diagnostics.append(&mut diagnostics);
|
} else {
|
||||||
|
self.diagnostics.retain(|d| {
|
||||||
|
if d.language_server_id != language_server_id {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(source) = &d.source {
|
||||||
|
unchanged_sources.contains(source)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.diagnostics.extend(diagnostics);
|
||||||
self.diagnostics
|
self.diagnostics
|
||||||
.sort_unstable_by_key(|diagnostic| diagnostic.range);
|
.sort_unstable_by_key(|diagnostic| diagnostic.range);
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,6 +191,7 @@ auto-format = true
|
||||||
comment-token = "//"
|
comment-token = "//"
|
||||||
language-servers = [ "rust-analyzer" ]
|
language-servers = [ "rust-analyzer" ]
|
||||||
indent = { tab-width = 4, unit = " " }
|
indent = { tab-width = 4, unit = " " }
|
||||||
|
persistent-diagnostic-sources = ["rustc", "clippy"]
|
||||||
|
|
||||||
[language.auto-pairs]
|
[language.auto-pairs]
|
||||||
'(' = ')'
|
'(' = ')'
|
||||||
|
|
Loading…
Add table
Reference in a new issue