Skip to content

Commit dbed6e4

Browse files
committed
Refactor and share code with publish diagnostics
1 parent dcd4d45 commit dbed6e4

File tree

3 files changed

+102
-168
lines changed

3 files changed

+102
-168
lines changed

helix-term/src/application.rs

Lines changed: 9 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ use helix_view::{
1111
align_view,
1212
document::{DocumentOpenError, DocumentSavedEventResult},
1313
editor::{ConfigEvent, EditorEvent},
14-
events::DiagnosticsDidChange,
1514
graphics::Rect,
1615
theme,
1716
tree::Layout,
18-
Align, Document, Editor,
17+
Align, Editor,
1918
};
2019
use serde_json::json;
2120
use tui::backend::Backend;
@@ -33,7 +32,7 @@ use crate::{
3332
use log::{debug, error, info, warn};
3433
#[cfg(not(feature = "integration"))]
3534
use std::io::stdout;
36-
use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc};
35+
use std::{io::stdin, path::Path, sync::Arc};
3736

3837
#[cfg(not(windows))]
3938
use anyhow::Context;
@@ -750,83 +749,20 @@ impl Application {
750749
log::error!("Discarding publishDiagnostic notification sent by an uninitialized server: {}", language_server.name());
751750
return;
752751
}
753-
// have to inline the function because of borrow checking...
754-
let doc = self.editor.documents.values_mut()
755-
.find(|doc| doc.uri().is_some_and(|u| u == uri))
756-
.filter(|doc| {
757-
if let Some(version) = params.version {
758-
if version != doc.version() {
759-
log::info!("Version ({version}) is out of date for {uri:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
760-
return false;
761-
}
762-
}
763-
true
764-
});
765752

766753
let diagnostics: Vec<(lsp::Diagnostic, LanguageServerId)> = params
767754
.diagnostics
768755
.into_iter()
769756
.map(|d| (d, server_id))
770757
.collect();
771758

772-
let mut unchanged_diag_sources = Vec::new();
773-
if let Some(doc) = &doc {
774-
if let Some(old_diagnostics) = self.editor.diagnostics.get(&uri) {
775-
unchanged_diag_sources = get_unchanged_diagnostic_sources(
776-
doc,
777-
&diagnostics,
778-
old_diagnostics,
779-
server_id,
780-
);
781-
}
782-
}
783-
784-
// Insert the original lsp::Diagnostics here because we may have no open document
785-
// for diagnosic message and so we can't calculate the exact position.
786-
// When using them later in the diagnostics picker, we calculate them on-demand.
787-
let diagnostics = match self.editor.diagnostics.entry(uri) {
788-
Entry::Occupied(o) => {
789-
let current_diagnostics = o.into_mut();
790-
// there may entries of other language servers, which is why we can't overwrite the whole entry
791-
current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
792-
current_diagnostics.extend(diagnostics);
793-
current_diagnostics
794-
// Sort diagnostics first by severity and then by line numbers.
795-
}
796-
Entry::Vacant(v) => v.insert(diagnostics),
797-
};
798-
799-
// Sort diagnostics first by severity and then by line numbers.
800-
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
801-
diagnostics
802-
.sort_by_key(|(d, server_id)| (d.severity, d.range.start, *server_id));
803-
804-
if let Some(doc) = doc {
805-
let diagnostic_of_language_server_and_not_in_unchanged_sources =
806-
|diagnostic: &lsp::Diagnostic, ls_id| {
807-
ls_id == server_id
808-
&& diagnostic.source.as_ref().map_or(true, |source| {
809-
!unchanged_diag_sources.contains(source)
810-
})
811-
};
812-
let diagnostics = Editor::doc_diagnostics_with_filter(
813-
&self.editor.language_servers,
814-
&self.editor.diagnostics,
815-
doc,
816-
diagnostic_of_language_server_and_not_in_unchanged_sources,
817-
);
818-
doc.replace_diagnostics(
819-
diagnostics,
820-
&unchanged_diag_sources,
821-
Some(server_id),
822-
);
823-
824-
let doc = doc.id();
825-
helix_event::dispatch(DiagnosticsDidChange {
826-
editor: &mut self.editor,
827-
doc,
828-
});
829-
}
759+
self.editor.add_diagnostics(
760+
diagnostics,
761+
server_id,
762+
uri,
763+
params.version,
764+
None,
765+
);
830766
}
831767
Notification::ShowMessage(params) => {
832768
log::warn!("unhandled window/showMessage: {:?}", params);
@@ -1240,30 +1176,3 @@ impl Application {
12401176
errs
12411177
}
12421178
}
1243-
1244-
pub fn get_unchanged_diagnostic_sources(
1245-
doc: &Document,
1246-
diagnostics: &[(lsp::Diagnostic, LanguageServerId)],
1247-
old_diagnostics: &[(lsp::Diagnostic, LanguageServerId)],
1248-
server_id: LanguageServerId,
1249-
) -> Vec<String> {
1250-
let mut unchanged_diag_sources = Vec::new();
1251-
let lang_conf = doc.language.clone();
1252-
1253-
if let Some(lang_conf) = &lang_conf {
1254-
for source in &lang_conf.persistent_diagnostic_sources {
1255-
let new_diagnostics = diagnostics
1256-
.iter()
1257-
.filter(|d| d.0.source.as_ref() == Some(source));
1258-
let old_diagnostics = old_diagnostics
1259-
.iter()
1260-
.filter(|(d, d_server)| *d_server == server_id && d.source.as_ref() == Some(source))
1261-
.map(|(d, _)| d);
1262-
if new_diagnostics.map(|x| &x.0).eq(old_diagnostics) {
1263-
unchanged_diag_sources.push(source.clone())
1264-
}
1265-
}
1266-
}
1267-
1268-
unchanged_diag_sources
1269-
}

helix-term/src/commands/lsp.rs

Lines changed: 7 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@ use helix_view::{
2626
};
2727

2828
use crate::{
29-
application::get_unchanged_diagnostic_sources,
3029
compositor::{self, Compositor},
3130
job::Callback,
3231
ui::{self, overlay::overlaid, FileLocation, Picker, Popup, PromptEvent},
3332
};
3433

3534
use std::{
3635
cmp::Ordering,
37-
collections::{btree_map::Entry, BTreeMap, HashMap, HashSet},
36+
collections::{BTreeMap, HashMap, HashSet},
3837
fmt::{Display, Write},
3938
future::Future,
4039
path::{Path, PathBuf},
@@ -1447,79 +1446,15 @@ pub fn pull_diagnostic_for_current_doc(editor: &Editor, jobs: &mut crate::job::J
14471446
let callback = super::make_job_callback(
14481447
future.expect("safety: language server supports pull diagnostics"),
14491448
move |editor, _compositor, response: Option<lsp::DocumentDiagnosticReport>| {
1450-
let doc = match editor.document_by_path_mut(&original_path) {
1451-
Some(doc) => doc,
1452-
None => return,
1453-
};
1454-
let Some(language_server) = doc.language_servers().find(|ls| ls.id() == server_id)
1455-
else {
1456-
return;
1457-
};
1458-
// Pass them separately to satisfy borrow-checker
1459-
let offset_encoding = language_server.offset_encoding();
1460-
let server_id = language_server.id();
1461-
14621449
let parse_diagnostic = |editor: &mut Editor,
14631450
path: PathBuf,
14641451
report: Vec<lsp::Diagnostic>,
14651452
result_id: Option<String>| {
14661453
let uri = helix_core::Uri::try_from(path.clone()).unwrap();
1467-
let mut diagnostics: Vec<(Diagnostic, LanguageServerId)> =
1454+
let diagnostics: Vec<(Diagnostic, LanguageServerId)> =
14681455
report.into_iter().map(|d| (d, server_id)).collect();
14691456

1470-
let old_diagnostics = editor.diagnostics.get(&uri).cloned();
1471-
1472-
if let Some(doc) = editor.document_by_path_mut(&path) {
1473-
let new_diagnostics: Vec<helix_core::Diagnostic> = diagnostics
1474-
.iter()
1475-
.map(|d| {
1476-
Document::lsp_diagnostic_to_diagnostic(
1477-
doc.text(),
1478-
doc.language_config(),
1479-
&d.0,
1480-
server_id,
1481-
offset_encoding,
1482-
)
1483-
.unwrap()
1484-
})
1485-
.collect();
1486-
1487-
doc.previous_diagnostic_id = result_id;
1488-
1489-
let mut unchanged_diag_sources = Vec::new();
1490-
if let Some(old_diagnostics) = old_diagnostics {
1491-
unchanged_diag_sources = get_unchanged_diagnostic_sources(
1492-
doc,
1493-
&diagnostics,
1494-
&old_diagnostics,
1495-
server_id,
1496-
);
1497-
}
1498-
1499-
doc.replace_diagnostics(
1500-
new_diagnostics,
1501-
&unchanged_diag_sources,
1502-
Some(server_id),
1503-
);
1504-
}
1505-
1506-
// TODO: Maybe share code with application.rs:802
1507-
match editor.diagnostics.entry(uri) {
1508-
Entry::Occupied(o) => {
1509-
let current_diagnostics = o.into_mut();
1510-
// there may entries of other language servers, which is why we can't overwrite the whole entry
1511-
current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
1512-
current_diagnostics.append(&mut diagnostics);
1513-
// Sort diagnostics first by severity and then by line numbers.
1514-
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
1515-
current_diagnostics
1516-
.sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
1517-
}
1518-
Entry::Vacant(v) => {
1519-
diagnostics.sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
1520-
v.insert(diagnostics);
1521-
}
1522-
};
1457+
editor.add_diagnostics(diagnostics, server_id, uri, None, result_id);
15231458
};
15241459

15251460
let handle_document_diagnostic_report_kind = |editor: &mut Editor,
@@ -1543,6 +1478,10 @@ pub fn pull_diagnostic_for_current_doc(editor: &Editor, jobs: &mut crate::job::J
15431478
};
15441479

15451480
if let Some(response) = response {
1481+
let doc = match editor.document_by_path_mut(&original_path) {
1482+
Some(doc) => doc,
1483+
None => return,
1484+
};
15461485
match response {
15471486
lsp::DocumentDiagnosticReport::Full(report) => {
15481487
// Original file diagnostic

helix-view/src/editor.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2154,6 +2154,92 @@ impl Editor {
21542154
current_view.id
21552155
}
21562156
}
2157+
2158+
pub fn add_diagnostics(
2159+
&mut self,
2160+
diagnostics: Vec<(lsp::Diagnostic, LanguageServerId)>,
2161+
server_id: LanguageServerId,
2162+
uri: helix_core::Uri,
2163+
document_version: Option<i32>,
2164+
result_id: Option<String>,
2165+
) {
2166+
let doc = self.documents.values_mut()
2167+
.find(|doc| doc.uri().is_some_and(|u| u == uri))
2168+
.filter(|doc| {
2169+
if let Some(version) = document_version{
2170+
if version != doc.version() {
2171+
log::info!("Version ({version}) is out of date for {uri:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
2172+
return false;
2173+
}
2174+
}
2175+
true
2176+
});
2177+
2178+
if let Some(doc) = doc {
2179+
let mut unchanged_diag_sources = Vec::new();
2180+
if let Some(old_diagnostics) = self.diagnostics.get(&uri) {
2181+
if let Some(lang_conf) = doc.language_config() {
2182+
for source in &lang_conf.persistent_diagnostic_sources {
2183+
let new_diagnostics = diagnostics
2184+
.iter()
2185+
.filter(|d| d.0.source.as_ref() == Some(source));
2186+
let old_diagnostics = old_diagnostics
2187+
.iter()
2188+
.filter(|(d, d_server)| {
2189+
*d_server == server_id && d.source.as_ref() == Some(source)
2190+
})
2191+
.map(|(d, _)| d);
2192+
if new_diagnostics.map(|x| &x.0).eq(old_diagnostics) {
2193+
unchanged_diag_sources.push(source.clone())
2194+
}
2195+
}
2196+
}
2197+
}
2198+
2199+
// Insert the original lsp::Diagnostics here because we may have no open document
2200+
// for diagnosic message and so we can't calculate the exact position.
2201+
// When using them later in the diagnostics picker, we calculate them on-demand.
2202+
let diagnostics = match self.diagnostics.entry(uri) {
2203+
std::collections::btree_map::Entry::Occupied(o) => {
2204+
let current_diagnostics = o.into_mut();
2205+
// there may entries of other language servers, which is why we can't overwrite the whole entry
2206+
current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
2207+
current_diagnostics.extend(diagnostics);
2208+
current_diagnostics
2209+
// Sort diagnostics first by severity and then by line numbers.
2210+
}
2211+
std::collections::btree_map::Entry::Vacant(v) => v.insert(diagnostics),
2212+
};
2213+
2214+
// Sort diagnostics first by severity and then by line numbers.
2215+
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
2216+
diagnostics.sort_by_key(|(d, server_id)| (d.severity, d.range.start, *server_id));
2217+
2218+
let diagnostic_of_language_server_and_not_in_unchanged_sources =
2219+
|diagnostic: &lsp::Diagnostic, ls_id| {
2220+
ls_id == server_id
2221+
&& diagnostic
2222+
.source
2223+
.as_ref()
2224+
.map_or(true, |source| !unchanged_diag_sources.contains(source))
2225+
};
2226+
let diagnostics = Editor::doc_diagnostics_with_filter(
2227+
&self.language_servers,
2228+
&self.diagnostics,
2229+
doc,
2230+
diagnostic_of_language_server_and_not_in_unchanged_sources,
2231+
);
2232+
doc.replace_diagnostics(diagnostics, &unchanged_diag_sources, Some(server_id));
2233+
2234+
if result_id.is_some() {
2235+
doc.previous_diagnostic_id = result_id;
2236+
}
2237+
2238+
let doc = doc.id();
2239+
2240+
helix_event::dispatch(crate::events::DiagnosticsDidChange { editor: self, doc });
2241+
}
2242+
}
21572243
}
21582244

21592245
fn try_restore_indent(doc: &mut Document, view: &mut View) {

0 commit comments

Comments
 (0)