mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-26 20:22:30 +00:00
WIP
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
6645e2820c
commit
85674ba506
11 changed files with 554 additions and 417 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -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"
|
||||
|
|
14
crates/diagnostics/Cargo.toml
Normal file
14
crates/diagnostics/Cargo.toml
Normal file
|
@ -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"] }
|
45
crates/diagnostics/src/diagnostics.rs
Normal file
45
crates/diagnostics/src/diagnostics.rs
Normal file
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue