Detailed changes
@@ -1410,6 +1410,17 @@ dependencies = [
"const-oid",
]
+[[package]]
+name = "diagnostics"
+version = "0.1.0"
+dependencies = [
+ "editor",
+ "gpui",
+ "postage",
+ "project",
+ "workspace",
+]
+
[[package]]
name = "digest"
version = "0.8.1"
@@ -0,0 +1,14 @@
+[package]
+name = "diagnostics"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+path = "src/diagnostics.rs"
+
+[dependencies]
+editor = { path = "../editor" }
+gpui = { path = "../gpui" }
+project = { path = "../project" }
+workspace = { path = "../workspace" }
+postage = { version = "0.4", features = ["futures-traits"] }
@@ -0,0 +1,45 @@
+use editor::{Editor, MultiBuffer};
+use gpui::{elements::*, Entity, ModelHandle, RenderContext, View, ViewContext, ViewHandle};
+use postage::watch;
+use project::Project;
+
+struct ProjectDiagnostics {
+ editor: ViewHandle<Editor>,
+ project: ModelHandle<Project>,
+}
+
+impl ProjectDiagnostics {
+ fn new(
+ project: ModelHandle<Project>,
+ settings: watch::Receiver<workspace::Settings>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
+ let mut buffer = cx.add_model(|cx| MultiBuffer::new(project.read(cx).replica_id(cx)));
+ for (path, diagnostics) in project.read(cx).diagnostics(cx) {}
+
+ Self {
+ editor: cx.add_view(|cx| {
+ Editor::for_buffer(
+ buffer.clone(),
+ editor::settings_builder(buffer.downgrade(), settings),
+ cx,
+ )
+ }),
+ project,
+ }
+ }
+}
+
+impl Entity for ProjectDiagnostics {
+ type Event = ();
+}
+
+impl View for ProjectDiagnostics {
+ fn ui_name() -> &'static str {
+ "ProjectDiagnostics"
+ }
+
+ fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
+ ChildView::new(self.editor.id()).boxed()
+ }
+}
@@ -15,10 +15,11 @@ pub use element::*;
use gpui::{
action,
elements::Text,
+ fonts::TextStyle,
geometry::vector::{vec2f, Vector2F},
keymap::Binding,
text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
- MutableAppContext, RenderContext, View, ViewContext, WeakViewHandle,
+ MutableAppContext, RenderContext, View, ViewContext, WeakModelHandle, WeakViewHandle,
};
use items::BufferItemHandle;
use language::{
@@ -29,6 +30,7 @@ pub use multi_buffer::MultiBuffer;
use multi_buffer::{
Anchor, AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot, ToOffset, ToPoint,
};
+use postage::watch;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use smol::Timer;
@@ -3787,6 +3789,48 @@ pub fn diagnostic_style(
}
}
+pub fn settings_builder(
+ buffer: WeakModelHandle<MultiBuffer>,
+ settings: watch::Receiver<workspace::Settings>,
+) -> impl Fn(&AppContext) -> EditorSettings {
+ move |cx| {
+ let settings = settings.borrow();
+ let font_cache = cx.font_cache();
+ let font_family_id = settings.buffer_font_family;
+ let font_family_name = cx.font_cache().family_name(font_family_id).unwrap();
+ let font_properties = Default::default();
+ let font_id = font_cache
+ .select_font(font_family_id, &font_properties)
+ .unwrap();
+ let font_size = settings.buffer_font_size;
+
+ let mut theme = settings.theme.editor.clone();
+ theme.text = TextStyle {
+ color: theme.text.color,
+ font_family_name,
+ font_family_id,
+ font_id,
+ font_size,
+ font_properties,
+ underline: None,
+ };
+ let language = buffer.upgrade(cx).and_then(|buf| buf.read(cx).language(cx));
+ let soft_wrap = match settings.soft_wrap(language) {
+ workspace::settings::SoftWrap::None => SoftWrap::None,
+ workspace::settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
+ workspace::settings::SoftWrap::PreferredLineLength => {
+ SoftWrap::Column(settings.preferred_line_length(language).saturating_sub(1))
+ }
+ };
+
+ EditorSettings {
+ tab_size: settings.tab_size,
+ soft_wrap,
+ style: theme,
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -1,10 +1,9 @@
-use crate::{Editor, EditorSettings, Event};
+use crate::{Editor, Event};
use crate::{MultiBuffer, ToPoint as _};
use anyhow::Result;
use gpui::{
- elements::*, fonts::TextStyle, AppContext, Entity, ModelContext, ModelHandle,
- MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
- WeakModelHandle,
+ elements::*, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, RenderContext,
+ Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle,
};
use language::{Diagnostic, File as _};
use postage::watch;
@@ -13,8 +12,7 @@ use std::fmt::Write;
use std::path::Path;
use text::{Point, Selection};
use workspace::{
- settings, EntryOpener, ItemHandle, ItemView, ItemViewHandle, Settings, StatusItemView,
- WeakItemHandle,
+ EntryOpener, ItemHandle, ItemView, ItemViewHandle, Settings, StatusItemView, WeakItemHandle,
};
pub struct BufferOpener;
@@ -53,42 +51,7 @@ impl ItemHandle for BufferItemHandle {
Box::new(cx.add_view(window_id, |cx| {
Editor::for_buffer(
self.0.clone(),
- move |cx| {
- let settings = settings.borrow();
- let font_cache = cx.font_cache();
- let font_family_id = settings.buffer_font_family;
- let font_family_name = cx.font_cache().family_name(font_family_id).unwrap();
- let font_properties = Default::default();
- let font_id = font_cache
- .select_font(font_family_id, &font_properties)
- .unwrap();
- let font_size = settings.buffer_font_size;
-
- let mut theme = settings.theme.editor.clone();
- theme.text = TextStyle {
- color: theme.text.color,
- font_family_name,
- font_family_id,
- font_id,
- font_size,
- font_properties,
- underline: None,
- };
- let language = buffer.upgrade(cx).and_then(|buf| buf.read(cx).language(cx));
- let soft_wrap = match settings.soft_wrap(language) {
- settings::SoftWrap::None => crate::SoftWrap::None,
- settings::SoftWrap::EditorWidth => crate::SoftWrap::EditorWidth,
- settings::SoftWrap::PreferredLineLength => crate::SoftWrap::Column(
- settings.preferred_line_length(language).saturating_sub(1),
- ),
- };
-
- EditorSettings {
- tab_size: settings.tab_size,
- soft_wrap,
- style: theme,
- }
- },
+ crate::settings_builder(buffer, settings),
cx,
)
}))
@@ -87,6 +87,8 @@ pub struct BufferSnapshot {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Diagnostic {
+ pub source: Option<String>,
+ pub code: Option<String>,
pub severity: DiagnosticSeverity,
pub message: String,
pub group_id: usize,
@@ -720,7 +722,7 @@ impl Buffer {
pub fn update_diagnostics(
&mut self,
version: Option<i32>,
- mut diagnostics: Vec<lsp::Diagnostic>,
+ mut diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
cx: &mut ModelContext<Self>,
) -> Result<Operation> {
diagnostics.sort_unstable_by_key(|d| (d.range.start, d.range.end));
@@ -736,7 +738,6 @@ impl Buffer {
} else {
self.deref()
};
- let abs_path = self.file.as_ref().and_then(|f| f.abs_path());
let empty_set = HashSet::new();
let disk_based_sources = self
@@ -750,26 +751,11 @@ impl Buffer {
.peekable();
let mut last_edit_old_end = PointUtf16::zero();
let mut last_edit_new_end = PointUtf16::zero();
- let mut group_ids_by_diagnostic_range = HashMap::new();
- let mut diagnostics_by_group_id = HashMap::new();
- let mut next_group_id = 0;
- 'outer: for diagnostic in &diagnostics {
- let mut start = diagnostic.range.start.to_point_utf16();
- let mut end = diagnostic.range.end.to_point_utf16();
- let source = diagnostic.source.as_ref();
- let code = diagnostic.code.as_ref();
- let group_id = diagnostic_ranges(&diagnostic, abs_path.as_deref())
- .find_map(|range| group_ids_by_diagnostic_range.get(&(source, code, range)))
- .copied()
- .unwrap_or_else(|| {
- let group_id = post_inc(&mut next_group_id);
- for range in diagnostic_ranges(&diagnostic, abs_path.as_deref()) {
- group_ids_by_diagnostic_range.insert((source, code, range), group_id);
- }
- group_id
- });
-
- if diagnostic
+ 'outer: for entry in &mut diagnostics {
+ let mut start = entry.range.start;
+ let mut end = entry.range.end;
+ if entry
+ .diagnostic
.source
.as_ref()
.map_or(false, |source| disk_based_sources.contains(source))
@@ -790,46 +776,20 @@ impl Buffer {
end = last_edit_new_end + (end - last_edit_old_end);
}
- let mut range = content.clip_point_utf16(start, Bias::Left)
+ entry.range = content.clip_point_utf16(start, Bias::Left)
..content.clip_point_utf16(end, Bias::Right);
- if range.start == range.end {
- range.end.column += 1;
- range.end = content.clip_point_utf16(range.end, Bias::Right);
- if range.start == range.end && range.end.column > 0 {
- range.start.column -= 1;
- range.start = content.clip_point_utf16(range.start, Bias::Left);
+ if entry.range.start == entry.range.end {
+ entry.range.end.column += 1;
+ entry.range.end = content.clip_point_utf16(entry.range.end, Bias::Right);
+ if entry.range.start == entry.range.end && entry.range.end.column > 0 {
+ entry.range.start.column -= 1;
+ entry.range.start = content.clip_point_utf16(entry.range.start, Bias::Left);
}
}
-
- diagnostics_by_group_id
- .entry(group_id)
- .or_insert(Vec::new())
- .push(DiagnosticEntry {
- range,
- diagnostic: Diagnostic {
- severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
- message: diagnostic.message.clone(),
- group_id,
- is_primary: false,
- },
- });
}
drop(edits_since_save);
- let new_diagnostics = DiagnosticSet::new(
- diagnostics_by_group_id
- .into_values()
- .flat_map(|mut diagnostics| {
- let primary = diagnostics
- .iter_mut()
- .min_by_key(|entry| entry.diagnostic.severity)
- .unwrap();
- primary.diagnostic.is_primary = true;
- diagnostics
- }),
- content,
- );
- self.diagnostics = new_diagnostics;
+ self.diagnostics = DiagnosticSet::new(diagnostics, content);
if let Some(version) = version {
let language_server = self.language_server.as_mut().unwrap();
@@ -1971,16 +1931,6 @@ impl ToTreeSitterPoint for Point {
}
}
-trait ToPointUtf16 {
- fn to_point_utf16(self) -> PointUtf16;
-}
-
-impl ToPointUtf16 for lsp::Position {
- fn to_point_utf16(self) -> PointUtf16 {
- PointUtf16::new(self.line, self.character)
- }
-}
-
impl operation_queue::Operation for Operation {
fn lamport_timestamp(&self) -> clock::Lamport {
match self {
@@ -2000,32 +1950,17 @@ impl operation_queue::Operation for Operation {
}
}
-fn diagnostic_ranges<'a>(
- diagnostic: &'a lsp::Diagnostic,
- abs_path: Option<&'a Path>,
-) -> impl 'a + Iterator<Item = Range<PointUtf16>> {
- diagnostic
- .related_information
- .iter()
- .flatten()
- .filter_map(move |info| {
- if info.location.uri.to_file_path().ok()? == abs_path? {
- let info_start = PointUtf16::new(
- info.location.range.start.line,
- info.location.range.start.character,
- );
- let info_end = PointUtf16::new(
- info.location.range.end.line,
- info.location.range.end.character,
- );
- Some(info_start..info_end)
- } else {
- None
- }
- })
- .chain(Some(
- diagnostic.range.start.to_point_utf16()..diagnostic.range.end.to_point_utf16(),
- ))
+impl Default for Diagnostic {
+ fn default() -> Self {
+ Self {
+ source: Default::default(),
+ code: Default::default(),
+ severity: DiagnosticSeverity::ERROR,
+ message: Default::default(),
+ group_id: Default::default(),
+ is_primary: Default::default(),
+ }
+ }
}
pub fn contiguous_ranges(
@@ -117,6 +117,8 @@ pub fn serialize_diagnostics<'a>(
} as i32,
group_id: entry.diagnostic.group_id as u64,
is_primary: entry.diagnostic.is_primary,
+ code: entry.diagnostic.code.clone(),
+ source: entry.diagnostic.source.clone(),
})
.collect()
}
@@ -269,6 +271,8 @@ pub fn deserialize_diagnostics(
message: diagnostic.message,
group_id: diagnostic.group_id as usize,
is_primary: diagnostic.is_primary,
+ code: diagnostic.code,
+ source: diagnostic.source,
},
})
})
@@ -516,23 +516,29 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
.update_diagnostics(
Some(open_notification.text_document.version),
vec![
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
- severity: Some(lsp::DiagnosticSeverity::ERROR),
- message: "undefined variable 'A'".to_string(),
- ..Default::default()
+ DiagnosticEntry {
+ range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "undefined variable 'A'".to_string(),
+ ..Default::default()
+ },
},
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
- severity: Some(lsp::DiagnosticSeverity::ERROR),
- message: "undefined variable 'BB'".to_string(),
- ..Default::default()
+ DiagnosticEntry {
+ range: PointUtf16::new(1, 9)..PointUtf16::new(1, 11),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "undefined variable 'BB'".to_string(),
+ ..Default::default()
+ },
},
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(2, 9), lsp::Position::new(2, 12)),
- severity: Some(lsp::DiagnosticSeverity::ERROR),
- message: "undefined variable 'CCC'".to_string(),
- ..Default::default()
+ DiagnosticEntry {
+ range: PointUtf16::new(2, 9)..PointUtf16::new(2, 12),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "undefined variable 'CCC'".to_string(),
+ ..Default::default()
+ },
},
],
cx,
@@ -553,6 +559,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
message: "undefined variable 'BB'".to_string(),
group_id: 1,
is_primary: true,
+ ..Default::default()
},
},
DiagnosticEntry {
@@ -562,6 +569,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
message: "undefined variable 'CCC'".to_string(),
group_id: 2,
is_primary: true,
+ ..Default::default()
}
}
]
@@ -592,17 +600,21 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
.update_diagnostics(
Some(open_notification.text_document.version),
vec![
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
- severity: Some(lsp::DiagnosticSeverity::ERROR),
- message: "undefined variable 'A'".to_string(),
- ..Default::default()
+ DiagnosticEntry {
+ range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "undefined variable 'A'".to_string(),
+ ..Default::default()
+ },
},
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)),
- severity: Some(lsp::DiagnosticSeverity::WARNING),
- message: "unreachable statement".to_string(),
- ..Default::default()
+ DiagnosticEntry {
+ range: PointUtf16::new(0, 9)..PointUtf16::new(0, 12),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::WARNING,
+ message: "unreachable statement".to_string(),
+ ..Default::default()
+ },
},
],
cx,
@@ -621,6 +633,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
message: "unreachable statement".to_string(),
group_id: 1,
is_primary: true,
+ ..Default::default()
}
},
DiagnosticEntry {
@@ -630,6 +643,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
message: "undefined variable 'A'".to_string(),
group_id: 0,
is_primary: true,
+ ..Default::default()
},
}
]
@@ -670,19 +684,23 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
.update_diagnostics(
Some(change_notification_2.text_document.version),
vec![
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
- severity: Some(lsp::DiagnosticSeverity::ERROR),
- message: "undefined variable 'BB'".to_string(),
- source: Some("disk".to_string()),
- ..Default::default()
+ DiagnosticEntry {
+ range: PointUtf16::new(1, 9)..PointUtf16::new(1, 11),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "undefined variable 'BB'".to_string(),
+ source: Some("disk".to_string()),
+ ..Default::default()
+ },
},
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
- severity: Some(lsp::DiagnosticSeverity::ERROR),
- message: "undefined variable 'A'".to_string(),
- source: Some("disk".to_string()),
- ..Default::default()
+ DiagnosticEntry {
+ range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "undefined variable 'A'".to_string(),
+ source: Some("disk".to_string()),
+ ..Default::default()
+ },
},
],
cx,
@@ -701,6 +719,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
message: "undefined variable 'A'".to_string(),
group_id: 0,
is_primary: true,
+ ..Default::default()
}
},
DiagnosticEntry {
@@ -710,6 +729,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
message: "undefined variable 'BB'".to_string(),
group_id: 1,
is_primary: true,
+ ..Default::default()
},
}
]
@@ -732,23 +752,21 @@ async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
.update_diagnostics(
None,
vec![
- lsp::Diagnostic {
- range: lsp::Range::new(
- lsp::Position::new(0, 10),
- lsp::Position::new(0, 10),
- ),
- severity: Some(lsp::DiagnosticSeverity::ERROR),
- message: "syntax error 1".to_string(),
- ..Default::default()
+ DiagnosticEntry {
+ range: PointUtf16::new(0, 10)..PointUtf16::new(0, 10),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "syntax error 1".to_string(),
+ ..Default::default()
+ },
},
- lsp::Diagnostic {
- range: lsp::Range::new(
- lsp::Position::new(1, 10),
- lsp::Position::new(1, 10),
- ),
- severity: Some(lsp::DiagnosticSeverity::ERROR),
- message: "syntax error 2".to_string(),
- ..Default::default()
+ DiagnosticEntry {
+ range: PointUtf16::new(1, 10)..PointUtf16::new(1, 10),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "syntax error 2".to_string(),
+ ..Default::default()
+ },
},
],
cx,
@@ -766,9 +784,9 @@ async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
.collect::<Vec<_>>(),
&[
("let one = ", None),
- (";", Some(lsp::DiagnosticSeverity::ERROR)),
+ (";", Some(DiagnosticSeverity::ERROR)),
("\nlet two =", None),
- (" ", Some(lsp::DiagnosticSeverity::ERROR)),
+ (" ", Some(DiagnosticSeverity::ERROR)),
("\nlet three = 3;\n", None)
]
);
@@ -776,224 +794,6 @@ async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
});
}
-#[gpui::test]
-async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) {
- cx.add_model(|cx| {
- let text = "
- fn foo(mut v: Vec<usize>) {
- for x in &v {
- v.push(1);
- }
- }
- "
- .unindent();
-
- let file = FakeFile::new("/example.rs");
- let mut buffer = Buffer::from_file(0, text, Box::new(file.clone()), cx);
- buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
- let diagnostics = vec![
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
- severity: Some(DiagnosticSeverity::WARNING),
- message: "error 1".to_string(),
- related_information: Some(vec![lsp::DiagnosticRelatedInformation {
- location: lsp::Location {
- uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
- range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
- },
- message: "error 1 hint 1".to_string(),
- }]),
- ..Default::default()
- },
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
- severity: Some(DiagnosticSeverity::HINT),
- message: "error 1 hint 1".to_string(),
- related_information: Some(vec![lsp::DiagnosticRelatedInformation {
- location: lsp::Location {
- uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
- range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
- },
- message: "original diagnostic".to_string(),
- }]),
- ..Default::default()
- },
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
- severity: Some(DiagnosticSeverity::ERROR),
- message: "error 2".to_string(),
- related_information: Some(vec![
- lsp::DiagnosticRelatedInformation {
- location: lsp::Location {
- uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
- range: lsp::Range::new(
- lsp::Position::new(1, 13),
- lsp::Position::new(1, 15),
- ),
- },
- message: "error 2 hint 1".to_string(),
- },
- lsp::DiagnosticRelatedInformation {
- location: lsp::Location {
- uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
- range: lsp::Range::new(
- lsp::Position::new(1, 13),
- lsp::Position::new(1, 15),
- ),
- },
- message: "error 2 hint 2".to_string(),
- },
- ]),
- ..Default::default()
- },
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
- severity: Some(DiagnosticSeverity::HINT),
- message: "error 2 hint 1".to_string(),
- related_information: Some(vec![lsp::DiagnosticRelatedInformation {
- location: lsp::Location {
- uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
- range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
- },
- message: "original diagnostic".to_string(),
- }]),
- ..Default::default()
- },
- lsp::Diagnostic {
- range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
- severity: Some(DiagnosticSeverity::HINT),
- message: "error 2 hint 2".to_string(),
- related_information: Some(vec![lsp::DiagnosticRelatedInformation {
- location: lsp::Location {
- uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
- range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
- },
- message: "original diagnostic".to_string(),
- }]),
- ..Default::default()
- },
- ];
- buffer.update_diagnostics(None, diagnostics, cx).unwrap();
- assert_eq!(
- buffer
- .snapshot()
- .diagnostics_in_range::<_, Point>(0..buffer.len())
- .collect::<Vec<_>>(),
- &[
- DiagnosticEntry {
- range: Point::new(1, 8)..Point::new(1, 9),
- diagnostic: Diagnostic {
- severity: DiagnosticSeverity::WARNING,
- message: "error 1".to_string(),
- group_id: 0,
- is_primary: true,
- }
- },
- DiagnosticEntry {
- range: Point::new(1, 8)..Point::new(1, 9),
- diagnostic: Diagnostic {
- severity: DiagnosticSeverity::HINT,
- message: "error 1 hint 1".to_string(),
- group_id: 0,
- is_primary: false,
- }
- },
- DiagnosticEntry {
- range: Point::new(1, 13)..Point::new(1, 15),
- diagnostic: Diagnostic {
- severity: DiagnosticSeverity::HINT,
- message: "error 2 hint 1".to_string(),
- group_id: 1,
- is_primary: false,
- }
- },
- DiagnosticEntry {
- range: Point::new(1, 13)..Point::new(1, 15),
- diagnostic: Diagnostic {
- severity: DiagnosticSeverity::HINT,
- message: "error 2 hint 2".to_string(),
- group_id: 1,
- is_primary: false,
- }
- },
- DiagnosticEntry {
- range: Point::new(2, 8)..Point::new(2, 17),
- diagnostic: Diagnostic {
- severity: DiagnosticSeverity::ERROR,
- message: "error 2".to_string(),
- group_id: 1,
- is_primary: true,
- }
- }
- ]
- );
-
- assert_eq!(
- buffer
- .snapshot()
- .diagnostic_group::<Point>(0)
- .collect::<Vec<_>>(),
- &[
- DiagnosticEntry {
- range: Point::new(1, 8)..Point::new(1, 9),
- diagnostic: Diagnostic {
- severity: DiagnosticSeverity::WARNING,
- message: "error 1".to_string(),
- group_id: 0,
- is_primary: true,
- }
- },
- DiagnosticEntry {
- range: Point::new(1, 8)..Point::new(1, 9),
- diagnostic: Diagnostic {
- severity: DiagnosticSeverity::HINT,
- message: "error 1 hint 1".to_string(),
- group_id: 0,
- is_primary: false,
- }
- },
- ]
- );
- assert_eq!(
- buffer
- .snapshot()
- .diagnostic_group::<Point>(1)
- .collect::<Vec<_>>(),
- &[
- DiagnosticEntry {
- range: Point::new(1, 13)..Point::new(1, 15),
- diagnostic: Diagnostic {
- severity: DiagnosticSeverity::HINT,
- message: "error 2 hint 1".to_string(),
- group_id: 1,
- is_primary: false,
- }
- },
- DiagnosticEntry {
- range: Point::new(1, 13)..Point::new(1, 15),
- diagnostic: Diagnostic {
- severity: DiagnosticSeverity::HINT,
- message: "error 2 hint 2".to_string(),
- group_id: 1,
- is_primary: false,
- }
- },
- DiagnosticEntry {
- range: Point::new(2, 8)..Point::new(2, 17),
- diagnostic: Diagnostic {
- severity: DiagnosticSeverity::ERROR,
- message: "error 2".to_string(),
- group_id: 1,
- is_primary: true,
- }
- }
- ]
- );
-
- buffer
- });
-}
-
fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
buffer: &Buffer,
range: Range<T>,
@@ -4,10 +4,11 @@ mod worktree;
use anyhow::Result;
use client::{Client, UserStore};
+use clock::ReplicaId;
use futures::Future;
use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
-use language::LanguageRegistry;
+use language::{DiagnosticEntry, LanguageRegistry, PointUtf16};
use std::{
path::Path,
sync::{atomic::AtomicBool, Arc},
@@ -62,6 +63,11 @@ impl Project {
}
}
+ pub fn replica_id(&self, cx: &AppContext) -> ReplicaId {
+ // TODO
+ self.worktrees.first().unwrap().read(cx).replica_id()
+ }
+
pub fn worktrees(&self) -> &[ModelHandle<Worktree>] {
&self.worktrees
}
@@ -159,6 +165,13 @@ impl Project {
}
}
+ pub fn diagnostics<'a>(
+ &'a self,
+ cx: &'a AppContext,
+ ) -> impl Iterator<Item = (&'a Path, &'a [DiagnosticEntry<PointUtf16>])> {
+ std::iter::empty()
+ }
+
pub fn active_entry(&self) -> Option<ProjectEntry> {
self.active_entry
}
@@ -12,7 +12,10 @@ use gpui::{
executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
Task, UpgradeModelHandle, WeakModelHandle,
};
-use language::{Buffer, Language, LanguageRegistry, Operation, Rope};
+use language::{
+ Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, Operation,
+ PointUtf16, Rope,
+};
use lazy_static::lazy_static;
use lsp::LanguageServer;
use parking_lot::Mutex;
@@ -30,7 +33,7 @@ use std::{
ffi::{OsStr, OsString},
fmt,
future::Future,
- ops::Deref,
+ ops::{Deref, Range},
path::{Path, PathBuf},
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
@@ -40,7 +43,7 @@ use std::{
};
use sum_tree::Bias;
use sum_tree::{Edit, SeekTarget, SumTree};
-use util::{ResultExt, TryFutureExt};
+use util::{post_inc, ResultExt, TryFutureExt};
lazy_static! {
static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore");
@@ -747,20 +750,67 @@ impl Worktree {
cx: &mut ModelContext<Worktree>,
) -> Result<()> {
let this = self.as_local_mut().ok_or_else(|| anyhow!("not local"))?;
- let file_path = params
+ let abs_path = params
.uri
.to_file_path()
- .map_err(|_| anyhow!("URI is not a file"))?
+ .map_err(|_| anyhow!("URI is not a file"))?;
+ let worktree_path = abs_path
.strip_prefix(&this.abs_path)
.context("path is not within worktree")?
.to_owned();
+ let mut group_ids_by_diagnostic_range = HashMap::new();
+ let mut diagnostics_by_group_id = HashMap::new();
+ let mut next_group_id = 0;
+ for diagnostic in ¶ms.diagnostics {
+ let source = diagnostic.source.as_ref();
+ let code = diagnostic.code.as_ref();
+ let group_id = diagnostic_ranges(&diagnostic, &abs_path)
+ .find_map(|range| group_ids_by_diagnostic_range.get(&(source, code, range)))
+ .copied()
+ .unwrap_or_else(|| {
+ let group_id = post_inc(&mut next_group_id);
+ for range in diagnostic_ranges(&diagnostic, &abs_path) {
+ group_ids_by_diagnostic_range.insert((source, code, range), group_id);
+ }
+ group_id
+ });
+
+ diagnostics_by_group_id
+ .entry(group_id)
+ .or_insert(Vec::new())
+ .push(DiagnosticEntry {
+ range: diagnostic.range.start.to_point_utf16()
+ ..diagnostic.range.end.to_point_utf16(),
+ diagnostic: Diagnostic {
+ source: diagnostic.source.clone(),
+ code: diagnostic.code.clone(),
+ severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
+ message: diagnostic.message.clone(),
+ group_id,
+ is_primary: false,
+ },
+ });
+ }
+
+ let diagnostics = diagnostics_by_group_id
+ .into_values()
+ .flat_map(|mut diagnostics| {
+ let primary = diagnostics
+ .iter_mut()
+ .min_by_key(|entry| entry.diagnostic.severity)
+ .unwrap();
+ primary.diagnostic.is_primary = true;
+ diagnostics
+ })
+ .collect::<Vec<_>>();
+
for buffer in this.open_buffers.values() {
if let Some(buffer) = buffer.upgrade(cx) {
if buffer
.read(cx)
.file()
- .map_or(false, |file| file.path().as_ref() == file_path)
+ .map_or(false, |file| file.path().as_ref() == worktree_path)
{
let (remote_id, operation) = buffer.update(cx, |buffer, cx| {
(
@@ -774,7 +824,7 @@ impl Worktree {
}
}
- this.diagnostics.insert(file_path, params.diagnostics);
+ this.diagnostics.insert(worktree_path, diagnostics);
Ok(())
}
@@ -838,7 +888,7 @@ pub struct LocalWorktree {
share: Option<ShareState>,
open_buffers: HashMap<usize, WeakModelHandle<Buffer>>,
shared_buffers: HashMap<PeerId, HashMap<u64, ModelHandle<Buffer>>>,
- diagnostics: HashMap<PathBuf, Vec<lsp::Diagnostic>>,
+ diagnostics: HashMap<PathBuf, Vec<DiagnosticEntry<PointUtf16>>>,
collaborators: HashMap<PeerId, Collaborator>,
queued_operations: Vec<(u64, Operation)>,
languages: Arc<LanguageRegistry>,
@@ -2998,6 +3048,44 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
}
}
+trait ToPointUtf16 {
+ fn to_point_utf16(self) -> PointUtf16;
+}
+
+impl ToPointUtf16 for lsp::Position {
+ fn to_point_utf16(self) -> PointUtf16 {
+ PointUtf16::new(self.line, self.character)
+ }
+}
+
+fn diagnostic_ranges<'a>(
+ diagnostic: &'a lsp::Diagnostic,
+ abs_path: &'a Path,
+) -> impl 'a + Iterator<Item = Range<PointUtf16>> {
+ diagnostic
+ .related_information
+ .iter()
+ .flatten()
+ .filter_map(move |info| {
+ if info.location.uri.to_file_path().ok()? == abs_path {
+ let info_start = PointUtf16::new(
+ info.location.range.start.line,
+ info.location.range.start.character,
+ );
+ let info_end = PointUtf16::new(
+ info.location.range.end.line,
+ info.location.range.end.character,
+ );
+ Some(info_start..info_end)
+ } else {
+ None
+ }
+ })
+ .chain(Some(
+ diagnostic.range.start.to_point_utf16()..diagnostic.range.end.to_point_utf16(),
+ ))
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -3740,6 +3828,224 @@ mod tests {
});
}
+ #[gpui::test]
+ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) {
+ cx.add_model(|cx| {
+ let text = "
+ fn foo(mut v: Vec<usize>) {
+ for x in &v {
+ v.push(1);
+ }
+ }
+ "
+ .unindent();
+
+ let file = FakeFile::new("/example.rs");
+ let mut buffer = Buffer::from_file(0, text, Box::new(file.clone()), cx);
+ buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
+ let diagnostics = vec![
+ DiagnosticEntry {
+ range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9),
+ diagnostic: Diagnostic {
+ severity: Some(DiagnosticSeverity::WARNING),
+ message: "error 1".to_string(),
+ related_information: Some(vec![lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9),
+ },
+ message: "error 1 hint 1".to_string(),
+ }]),
+ ..Default::default()
+ },
+ },
+ DiagnosticEntry {
+ range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9),
+ diagnostic: Diagnostic {},
+ severity: Some(DiagnosticSeverity::HINT),
+ message: "error 1 hint 1".to_string(),
+ related_information: Some(vec![lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9),
+ },
+ message: "original diagnostic".to_string(),
+ }]),
+ ..Default::default()
+ },
+ DiagnosticEntry {
+ range: PointUtf16::new(2, 8)..PointUtf16::new(2, 17),
+ diagnostic: Diagnostic {},
+ severity: Some(DiagnosticSeverity::ERROR),
+ message: "error 2".to_string(),
+ related_information: Some(vec![
+ lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: PointUtf16::new(1, 13)..PointUtf16::new(1, 15),
+ },
+ message: "error 2 hint 1".to_string(),
+ },
+ lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: PointUtf16::new(1, 13)..PointUtf16::new(1, 15),
+ },
+ message: "error 2 hint 2".to_string(),
+ },
+ ]),
+ ..Default::default()
+ },
+ DiagnosticEntry {
+ range: PointUtf16::new(1, 13)..PointUtf16::new(1, 15),
+ diagnostic: Diagnostic {},
+ severity: Some(DiagnosticSeverity::HINT),
+ message: "error 2 hint 1".to_string(),
+ related_information: Some(vec![lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: PointUtf16::new(2, 8)..PointUtf16::new(2, 17),
+ },
+ message: "original diagnostic".to_string(),
+ }]),
+ ..Default::default()
+ },
+ DiagnosticEntry {
+ range: PointUtf16::new(1, 13)..PointUtf16::new(1, 15),
+ diagnostic: Diagnostic {},
+ severity: Some(DiagnosticSeverity::HINT),
+ message: "error 2 hint 2".to_string(),
+ related_information: Some(vec![lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: PointUtf16::new(2, 8)..PointUtf16::new(2, 17),
+ },
+ message: "original diagnostic".to_string(),
+ }]),
+ ..Default::default()
+ },
+ ];
+ buffer.update_diagnostics(None, diagnostics, cx).unwrap();
+ assert_eq!(
+ buffer
+ .snapshot()
+ .diagnostics_in_range::<_, Point>(0..buffer.len())
+ .collect::<Vec<_>>(),
+ &[
+ DiagnosticEntry {
+ range: Point::new(1, 8)..Point::new(1, 9),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::WARNING,
+ message: "error 1".to_string(),
+ group_id: 0,
+ is_primary: true,
+ }
+ },
+ DiagnosticEntry {
+ range: Point::new(1, 8)..Point::new(1, 9),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 1 hint 1".to_string(),
+ group_id: 0,
+ is_primary: false,
+ }
+ },
+ DiagnosticEntry {
+ range: Point::new(1, 13)..Point::new(1, 15),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 2 hint 1".to_string(),
+ group_id: 1,
+ is_primary: false,
+ }
+ },
+ DiagnosticEntry {
+ range: Point::new(1, 13)..Point::new(1, 15),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 2 hint 2".to_string(),
+ group_id: 1,
+ is_primary: false,
+ }
+ },
+ DiagnosticEntry {
+ range: Point::new(2, 8)..Point::new(2, 17),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "error 2".to_string(),
+ group_id: 1,
+ is_primary: true,
+ }
+ }
+ ]
+ );
+
+ assert_eq!(
+ buffer
+ .snapshot()
+ .diagnostic_group::<Point>(0)
+ .collect::<Vec<_>>(),
+ &[
+ DiagnosticEntry {
+ range: Point::new(1, 8)..Point::new(1, 9),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::WARNING,
+ message: "error 1".to_string(),
+ group_id: 0,
+ is_primary: true,
+ }
+ },
+ DiagnosticEntry {
+ range: Point::new(1, 8)..Point::new(1, 9),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 1 hint 1".to_string(),
+ group_id: 0,
+ is_primary: false,
+ }
+ },
+ ]
+ );
+ assert_eq!(
+ buffer
+ .snapshot()
+ .diagnostic_group::<Point>(1)
+ .collect::<Vec<_>>(),
+ &[
+ DiagnosticEntry {
+ range: Point::new(1, 13)..Point::new(1, 15),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 2 hint 1".to_string(),
+ group_id: 1,
+ is_primary: false,
+ }
+ },
+ DiagnosticEntry {
+ range: Point::new(1, 13)..Point::new(1, 15),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 2 hint 2".to_string(),
+ group_id: 1,
+ is_primary: false,
+ }
+ },
+ DiagnosticEntry {
+ range: Point::new(2, 8)..Point::new(2, 17),
+ diagnostic: Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "error 2".to_string(),
+ group_id: 1,
+ is_primary: true,
+ }
+ }
+ ]
+ );
+
+ buffer
+ });
+ }
+
#[gpui::test(iterations = 100)]
fn test_random(mut rng: StdRng) {
let operations = env::var("OPERATIONS")
@@ -269,6 +269,8 @@ message Diagnostic {
string message = 4;
uint64 group_id = 5;
bool is_primary = 6;
+ optional string code = 7;
+ optional string source = 8;
enum Severity {
None = 0;
Error = 1;