mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-31 02:21:57 +03:00
Allow retrieving a buffer's diagnostics
This commit is contained in:
parent
5bfbeb55c0
commit
ef4fc42d93
@ -175,10 +175,10 @@ impl<T: Clone> Default for AnchorRangeMultimap<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone> AnchorRangeMultimap<T> {
|
impl<T: Clone> AnchorRangeMultimap<T> {
|
||||||
fn intersecting_point_ranges<'a, O>(
|
pub fn intersecting_point_ranges<'a, O>(
|
||||||
&'a self,
|
&'a self,
|
||||||
range: Range<O>,
|
range: Range<O>,
|
||||||
content: &'a Content<'a>,
|
content: Content<'a>,
|
||||||
inclusive: bool,
|
inclusive: bool,
|
||||||
) -> impl Iterator<Item = (usize, Range<Point>, &T)> + 'a
|
) -> impl Iterator<Item = (usize, Range<Point>, &T)> + 'a
|
||||||
where
|
where
|
||||||
@ -187,10 +187,11 @@ impl<T: Clone> AnchorRangeMultimap<T> {
|
|||||||
use super::ToPoint as _;
|
use super::ToPoint as _;
|
||||||
|
|
||||||
let end_bias = if inclusive { Bias::Right } else { Bias::Left };
|
let end_bias = if inclusive { Bias::Right } else { Bias::Left };
|
||||||
let range = range.start.to_full_offset(content, Bias::Left)
|
let range = range.start.to_full_offset(&content, Bias::Left)
|
||||||
..range.end.to_full_offset(content, end_bias);
|
..range.end.to_full_offset(&content, end_bias);
|
||||||
let mut cursor = self.entries.filter::<_, usize>(
|
let mut cursor = self.entries.filter::<_, usize>(
|
||||||
{
|
{
|
||||||
|
let content = content.clone();
|
||||||
let mut endpoint = Anchor {
|
let mut endpoint = Anchor {
|
||||||
full_offset: 0,
|
full_offset: 0,
|
||||||
bias: Bias::Right,
|
bias: Bias::Right,
|
||||||
@ -199,12 +200,12 @@ impl<T: Clone> AnchorRangeMultimap<T> {
|
|||||||
move |summary: &AnchorRangeMultimapSummary| {
|
move |summary: &AnchorRangeMultimapSummary| {
|
||||||
endpoint.full_offset = summary.max_end;
|
endpoint.full_offset = summary.max_end;
|
||||||
endpoint.bias = self.end_bias;
|
endpoint.bias = self.end_bias;
|
||||||
let max_end = endpoint.to_full_offset(content, self.end_bias);
|
let max_end = endpoint.to_full_offset(&content, self.end_bias);
|
||||||
let start_cmp = range.start.cmp(&max_end);
|
let start_cmp = range.start.cmp(&max_end);
|
||||||
|
|
||||||
endpoint.full_offset = summary.min_start;
|
endpoint.full_offset = summary.min_start;
|
||||||
endpoint.bias = self.start_bias;
|
endpoint.bias = self.start_bias;
|
||||||
let min_start = endpoint.to_full_offset(content, self.start_bias);
|
let min_start = endpoint.to_full_offset(&content, self.start_bias);
|
||||||
let end_cmp = range.end.cmp(&min_start);
|
let end_cmp = range.end.cmp(&min_start);
|
||||||
|
|
||||||
if inclusive {
|
if inclusive {
|
||||||
@ -228,10 +229,10 @@ impl<T: Clone> AnchorRangeMultimap<T> {
|
|||||||
let ix = *cursor.start();
|
let ix = *cursor.start();
|
||||||
endpoint.full_offset = item.range.start;
|
endpoint.full_offset = item.range.start;
|
||||||
endpoint.bias = self.start_bias;
|
endpoint.bias = self.start_bias;
|
||||||
let start = endpoint.to_point(content);
|
let start = endpoint.to_point(&content);
|
||||||
endpoint.full_offset = item.range.end;
|
endpoint.full_offset = item.range.end;
|
||||||
endpoint.bias = self.end_bias;
|
endpoint.bias = self.end_bias;
|
||||||
let end = endpoint.to_point(content);
|
let end = endpoint.to_point(&content);
|
||||||
let value = &item.value;
|
let value = &item.value;
|
||||||
cursor.next(&());
|
cursor.next(&());
|
||||||
Some((ix, start..end, value))
|
Some((ix, start..end, value))
|
||||||
|
@ -1592,6 +1592,7 @@ impl Snapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Content<'a> {
|
pub struct Content<'a> {
|
||||||
visible_text: &'a Rope,
|
visible_text: &'a Rope,
|
||||||
deleted_text: &'a Rope,
|
deleted_text: &'a Rope,
|
||||||
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
test-support = ["rand", "buffer/test-support"]
|
test-support = ["rand", "buffer/test-support", "lsp/test-support"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
buffer = { path = "../buffer" }
|
buffer = { path = "../buffer" }
|
||||||
@ -29,6 +29,7 @@ tree-sitter = "0.19.5"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
buffer = { path = "../buffer", features = ["test-support"] }
|
buffer = { path = "../buffer", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
|
lsp = { path = "../lsp", features = ["test-support"] }
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
tree-sitter-rust = "0.19.0"
|
tree-sitter-rust = "0.19.0"
|
||||||
unindent = "0.1.7"
|
unindent = "0.1.7"
|
||||||
|
@ -13,7 +13,7 @@ use clock::ReplicaId;
|
|||||||
use futures::FutureExt as _;
|
use futures::FutureExt as _;
|
||||||
use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
|
use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use lsp::LanguageServer;
|
use lsp::{DiagnosticSeverity, LanguageServer};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use postage::{prelude::Stream, sink::Sink, watch};
|
use postage::{prelude::Stream, sink::Sink, watch};
|
||||||
use rpc::proto;
|
use rpc::proto;
|
||||||
@ -59,7 +59,7 @@ pub struct Buffer {
|
|||||||
syntax_tree: Mutex<Option<SyntaxTree>>,
|
syntax_tree: Mutex<Option<SyntaxTree>>,
|
||||||
parsing_in_background: bool,
|
parsing_in_background: bool,
|
||||||
parse_count: usize,
|
parse_count: usize,
|
||||||
diagnostics: AnchorRangeMultimap<()>,
|
diagnostics: AnchorRangeMultimap<(DiagnosticSeverity, String)>,
|
||||||
language_server: Option<LanguageServerState>,
|
language_server: Option<LanguageServerState>,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
operations: Vec<Operation>,
|
operations: Vec<Operation>,
|
||||||
@ -73,6 +73,13 @@ pub struct Snapshot {
|
|||||||
query_cursor: QueryCursorHandle,
|
query_cursor: QueryCursorHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct Diagnostic {
|
||||||
|
pub range: Range<Point>,
|
||||||
|
pub severity: DiagnosticSeverity,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
struct LanguageServerState {
|
struct LanguageServerState {
|
||||||
server: Arc<LanguageServer>,
|
server: Arc<LanguageServer>,
|
||||||
latest_snapshot: watch::Sender<Option<LanguageServerSnapshot>>,
|
latest_snapshot: watch::Sender<Option<LanguageServerSnapshot>>,
|
||||||
@ -613,43 +620,67 @@ impl Buffer {
|
|||||||
|
|
||||||
pub fn update_diagnostics(
|
pub fn update_diagnostics(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: lsp::PublishDiagnosticsParams,
|
version: Option<i32>,
|
||||||
|
diagnostics: Vec<lsp::Diagnostic>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
dbg!(¶ms);
|
let version = version.map(|version| version as usize);
|
||||||
let language_server = self.language_server.as_mut().unwrap();
|
let content = if let Some(version) = version {
|
||||||
let version = params.version.ok_or_else(|| anyhow!("missing version"))? as usize;
|
let language_server = self.language_server.as_mut().unwrap();
|
||||||
let snapshot = language_server
|
let snapshot = language_server
|
||||||
.pending_snapshots
|
.pending_snapshots
|
||||||
.get(&version)
|
.get(&version)
|
||||||
.ok_or_else(|| anyhow!("missing snapshot"))?;
|
.ok_or_else(|| anyhow!("missing snapshot"))?;
|
||||||
self.diagnostics = snapshot.buffer_snapshot.content().anchor_range_multimap(
|
snapshot.buffer_snapshot.content()
|
||||||
|
} else {
|
||||||
|
self.content()
|
||||||
|
};
|
||||||
|
self.diagnostics = content.anchor_range_multimap(
|
||||||
Bias::Left,
|
Bias::Left,
|
||||||
Bias::Right,
|
Bias::Right,
|
||||||
params.diagnostics.into_iter().map(|diagnostic| {
|
diagnostics.into_iter().map(|diagnostic| {
|
||||||
// TODO: Use UTF-16 positions.
|
// TODO: Use UTF-16 positions.
|
||||||
let start = Point::new(
|
let start = Point::new(
|
||||||
diagnostic.range.start.line,
|
diagnostic.range.start.line,
|
||||||
diagnostic.range.start.character,
|
diagnostic.range.start.character,
|
||||||
);
|
);
|
||||||
let end = Point::new(diagnostic.range.end.line, diagnostic.range.end.character);
|
let end = Point::new(diagnostic.range.end.line, diagnostic.range.end.character);
|
||||||
(start..end, ())
|
let severity = diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR);
|
||||||
|
(start..end, (severity, diagnostic.message))
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let versions_to_delete = language_server
|
if let Some(version) = version {
|
||||||
.pending_snapshots
|
let language_server = self.language_server.as_mut().unwrap();
|
||||||
.range(..version)
|
let versions_to_delete = language_server
|
||||||
.map(|(v, _)| *v)
|
.pending_snapshots
|
||||||
.collect::<Vec<_>>();
|
.range(..version)
|
||||||
for version in versions_to_delete {
|
.map(|(v, _)| *v)
|
||||||
language_server.pending_snapshots.remove(&version);
|
.collect::<Vec<_>>();
|
||||||
|
for version in versions_to_delete {
|
||||||
|
language_server.pending_snapshots.remove(&version);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn diagnostics_in_range<'a, T: ToOffset>(
|
||||||
|
&'a self,
|
||||||
|
range: Range<T>,
|
||||||
|
) -> impl Iterator<Item = Diagnostic> + 'a {
|
||||||
|
let content = self.content();
|
||||||
|
let range = range.start.to_offset(&content)..range.end.to_offset(&content);
|
||||||
|
self.diagnostics
|
||||||
|
.intersecting_point_ranges(range, content, true)
|
||||||
|
.map(move |(_, range, (severity, message))| Diagnostic {
|
||||||
|
range,
|
||||||
|
severity: *severity,
|
||||||
|
message: message.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
|
fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
if let Some(indent_columns) = self.compute_autoindents() {
|
if let Some(indent_columns) = self.compute_autoindents() {
|
||||||
let indent_columns = cx.background().spawn(indent_columns);
|
let indent_columns = cx.background().spawn(indent_columns);
|
||||||
@ -987,17 +1018,16 @@ impl Buffer {
|
|||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let file = if let Some(file) = self.file.as_ref() {
|
let abs_path = self
|
||||||
file
|
.file
|
||||||
} else {
|
.as_ref()
|
||||||
return;
|
.map_or(PathBuf::new(), |file| file.abs_path(cx).unwrap());
|
||||||
};
|
|
||||||
|
|
||||||
let version = post_inc(&mut language_server.next_version);
|
let version = post_inc(&mut language_server.next_version);
|
||||||
let snapshot = LanguageServerSnapshot {
|
let snapshot = LanguageServerSnapshot {
|
||||||
buffer_snapshot: self.text.snapshot(),
|
buffer_snapshot: self.text.snapshot(),
|
||||||
version,
|
version,
|
||||||
path: Arc::from(file.abs_path(cx).unwrap()),
|
path: Arc::from(abs_path),
|
||||||
};
|
};
|
||||||
language_server
|
language_server
|
||||||
.pending_snapshots
|
.pending_snapshots
|
||||||
|
@ -407,6 +407,78 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_diagnostics(mut cx: gpui::TestAppContext) {
|
||||||
|
let (language_server, mut fake) = lsp::LanguageServer::fake(&cx.background()).await;
|
||||||
|
|
||||||
|
let text = "
|
||||||
|
fn a() { A }
|
||||||
|
fn b() { BB }
|
||||||
|
fn c() { CCC }
|
||||||
|
"
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let buffer = cx.add_model(|cx| {
|
||||||
|
Buffer::new(0, text, cx).with_language(rust_lang(), Some(language_server), cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
let open_notification = fake
|
||||||
|
.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
buffer.update(&mut cx, |buffer, cx| {
|
||||||
|
// Edit the buffer, moving the content down
|
||||||
|
buffer.edit([0..0], "\n\n", cx);
|
||||||
|
|
||||||
|
// Receive diagnostics for an earlier version of the buffer.
|
||||||
|
buffer
|
||||||
|
.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()
|
||||||
|
},
|
||||||
|
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()
|
||||||
|
},
|
||||||
|
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()
|
||||||
|
},
|
||||||
|
],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// The diagnostics have moved down since they were created.
|
||||||
|
assert_eq!(
|
||||||
|
buffer
|
||||||
|
.diagnostics_in_range(Point::new(3, 0)..Point::new(5, 0))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&[
|
||||||
|
Diagnostic {
|
||||||
|
range: Point::new(3, 9)..Point::new(3, 11),
|
||||||
|
severity: DiagnosticSeverity::ERROR,
|
||||||
|
message: "undefined variable 'BB'".to_string()
|
||||||
|
},
|
||||||
|
Diagnostic {
|
||||||
|
range: Point::new(4, 9)..Point::new(4, 12),
|
||||||
|
severity: DiagnosticSeverity::ERROR,
|
||||||
|
message: "undefined variable 'CCC'".to_string()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_contiguous_ranges() {
|
fn test_contiguous_ranges() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
Loading…
Reference in New Issue
Block a user