Skip to content

Commit

Permalink
lsp: use task-based approach for pulling diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
vitallium committed Nov 7, 2024
1 parent 531c62c commit 27437c0
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 165 deletions.
54 changes: 54 additions & 0 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
#[doc(hidden)]
pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
#[doc(hidden)]
pub const DOCUMENT_DIAGNOSTICS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);

pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
Expand Down Expand Up @@ -662,6 +664,7 @@ pub struct Editor {
next_scroll_position: NextScrollCursorCenterTopBottom,
addons: HashMap<TypeId, Box<dyn Addon>>,
_scroll_cursor_center_top_bottom_task: Task<()>,
_pull_document_diagnostics_task: Task<()>,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
Expand Down Expand Up @@ -2081,6 +2084,7 @@ impl Editor {
addons: HashMap::default(),
_scroll_cursor_center_top_bottom_task: Task::ready(()),
text_style_refinement: None,
_pull_document_diagnostics_task: Task::ready(()),
};
this.tasks_update_task = Some(this.refresh_runnables(cx));
this._subscriptions.extend(project_subscriptions);
Expand Down Expand Up @@ -10709,6 +10713,32 @@ impl Editor {
}
}

fn refresh_diagnostics(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
let Some(project) = self.project.clone() else {
return None;
};
let buffer = self.buffer.read(cx);
let newest_selection = self.selections.newest_anchor().clone();
let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
let (end_buffer, _) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
if start_buffer != end_buffer {
return None;
}

self._pull_document_diagnostics_task = cx.spawn(|editor, mut cx| async move {
cx.background_executor()
.timer(DOCUMENT_DIAGNOSTICS_DEBOUNCE_TIMEOUT)
.await;

editor
.update(&mut cx, |_, cx| {
project.diagnostics(&start_buffer, start, cx);
})
.ok();
});
None
}

pub fn set_selections_from_remote(
&mut self,
selections: Vec<Selection<Anchor>>,
Expand Down Expand Up @@ -12336,7 +12366,9 @@ impl Editor {
} => {
self.scrollbar_marker_state.dirty = true;
self.active_indent_guides_state.dirty = true;
self.refresh_diagnostics(cx);
self.refresh_active_diagnostics(cx);

self.refresh_code_actions(cx);
if self.has_active_inline_completion(cx) {
self.update_visible_inline_completion(cx);
Expand Down Expand Up @@ -13530,6 +13562,15 @@ pub trait CodeActionProvider {
) -> Task<Result<ProjectTransaction>>;
}

pub trait DiagnosticsProvider {
fn diagnostics(
&self,
buffer: &Model<Buffer>,
position: text::Anchor,
cx: &mut WindowContext,
) -> Option<Task<Result<Vec<lsp::Diagnostic>>>>;
}

impl CodeActionProvider for Model<Project> {
fn code_actions(
&self,
Expand Down Expand Up @@ -13813,6 +13854,19 @@ impl SemanticsProvider for Model<Project> {
}
}

impl DiagnosticsProvider for Model<Project> {
fn diagnostics(
&self,
buffer: &Model<Buffer>,
position: text::Anchor,
cx: &mut WindowContext,
) -> Option<Task<Result<Vec<lsp::Diagnostic>>>> {
Some(self.update(cx, |project, cx| {
project.document_diagnostics(buffer, position, cx)
}))
}
}

fn inlay_hint_settings(
location: Anchor,
snapshot: &MultiBufferSnapshot,
Expand Down
4 changes: 2 additions & 2 deletions crates/language/src/proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
value: message.lamport_timestamp,
},
server_id: LanguageServerId(message.server_id as usize),
diagnostics: Arc::from(deserialize_diagnostics(message.diagnostics)),
diagnostics: deserialize_diagnostics(message.diagnostics),
}
}
proto::operation::Variant::UpdateCompletionTriggers(message) => {
Expand Down Expand Up @@ -394,7 +394,7 @@ pub fn deserialize_selection(selection: proto::Selection) -> Option<Selection<An
/// Deserializes a list of diagnostics from the RPC representation.
pub fn deserialize_diagnostics(
diagnostics: Vec<proto::Diagnostic>,
) -> Vec<DiagnosticEntry<Anchor>> {
) -> Arc<[DiagnosticEntry<Anchor>]> {
diagnostics
.into_iter()
.filter_map(|diagnostic| {
Expand Down
143 changes: 38 additions & 105 deletions crates/project/src/lsp_command.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
mod signature_help;

use crate::{
lsp_store::{LanguageServerState, LspStore},
CodeAction, CoreCompletion, DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint,
InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
LocationLink, MarkupContent, ProjectTransaction, ResolveState,
lsp_store::LspStore, CodeAction, CoreCompletion, DocumentHighlight, Hover, HoverBlock,
HoverBlockKind, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip,
InlayHintTooltip, Location, LocationLink, MarkupContent, ProjectTransaction, ResolveState,
};
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
Expand All @@ -18,14 +17,13 @@ use language::{
point_from_lsp, point_to_lsp,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
Diagnostic, DiagnosticEntry, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
Unclipped,
OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
};
use lsp::{
AdapterServerCapabilities, CodeActionKind, CodeActionOptions, CompletionContext,
CompletionListItemDefaultsEditRange, CompletionTriggerKind, DiagnosticSeverity,
DocumentHighlightKind, LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities,
OneOf, ServerCapabilities,
CompletionListItemDefaultsEditRange, CompletionTriggerKind, Diagnostic, DocumentHighlightKind,
LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities, OneOf,
ServerCapabilities,
};
use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature};
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
Expand Down Expand Up @@ -183,10 +181,9 @@ pub(crate) struct LinkedEditingRange {
pub position: Anchor,
}

#[derive(Debug)]
#[derive(Clone, Debug)]
pub(crate) struct GetDocumentDiagnostics {
pub language_server_id: LanguageServerId,
pub previous_result_id: Option<String>,
pub position: Anchor,
}

#[async_trait(?Send)]
Expand Down Expand Up @@ -3031,26 +3028,9 @@ impl LspCommand for LinkedEditingRange {
}
}

fn lsp_diagnostic_to_diagnostic(diagnostic: lsp::Diagnostic) -> Diagnostic {
Diagnostic {
source: diagnostic.source.clone(),
code: diagnostic.code.as_ref().map(|code| match code {
lsp::NumberOrString::Number(code) => code.to_string(),
lsp::NumberOrString::String(code) => code.clone(),
}),
severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
message: diagnostic.message,
group_id: 0,
is_primary: true,
is_disk_based: false,
is_unnecessary: true,
data: diagnostic.data.clone(),
}
}

#[async_trait(?Send)]
impl LspCommand for GetDocumentDiagnostics {
type Response = Vec<DiagnosticEntry<Anchor>>;
type Response = Vec<Diagnostic>;
type LspRequest = lsp::request::DocumentDiagnosticRequest;
type ProtoRequest = proto::GetDocumentDiagnostics;

Expand Down Expand Up @@ -3081,7 +3061,7 @@ impl LspCommand for GetDocumentDiagnostics {
uri: lsp::Url::from_file_path(path).unwrap(),
},
identifier,
previous_result_id: self.previous_result_id.clone(),
previous_result_id: None,
partial_result_params: Default::default(),
work_done_progress_params: Default::default(),
}
Expand All @@ -3090,50 +3070,15 @@ impl LspCommand for GetDocumentDiagnostics {
async fn response_from_lsp(
self,
message: lsp::DocumentDiagnosticReportResult,
lsp_store: Model<LspStore>,
buffer: Model<Buffer>,
language_server_id: LanguageServerId,
mut cx: AsyncAppContext,
) -> Result<Vec<DiagnosticEntry<Anchor>>> {
_: Model<LspStore>,
_: Model<Buffer>,
_: LanguageServerId,
_: AsyncAppContext,
) -> Result<Vec<Diagnostic>> {
match message {
lsp::DocumentDiagnosticReportResult::Report(report) => match report {
lsp::DocumentDiagnosticReport::Full(report) => {
// TODO: Handle related_documents in the report
let previous_result_id =
report.full_document_diagnostic_report.result_id.clone();
lsp_store.update(&mut cx, |store, _| {
if let Some(LanguageServerState::Running {
previous_document_diagnostic_result_id,
..
}) = store.as_local_mut().and_then(|local_store| {
local_store.language_servers.get_mut(&language_server_id)
}) {
*previous_document_diagnostic_result_id = previous_result_id;
}
})?;

buffer.update(&mut cx, |buffer, _| {
report
.full_document_diagnostic_report
.items
.into_iter()
.map(|diagnostic| {
let start = buffer.clip_point_utf16(
point_from_lsp(diagnostic.range.start),
Bias::Left,
);
let end = buffer.clip_point_utf16(
point_from_lsp(diagnostic.range.end),
Bias::Left,
);

DiagnosticEntry {
range: buffer.anchor_after(start)..buffer.anchor_before(end),
diagnostic: lsp_diagnostic_to_diagnostic(diagnostic),
}
})
.collect()
})
Ok(report.full_document_diagnostic_report.items.clone())
}
lsp::DocumentDiagnosticReport::Unchanged(_) => Ok(vec![]),
},
Expand All @@ -3145,61 +3090,49 @@ impl LspCommand for GetDocumentDiagnostics {
proto::GetDocumentDiagnostics {
project_id,
buffer_id: buffer.remote_id().into(),
language_server_id: self.language_server_id.to_proto(),
position: Some(language::proto::serialize_anchor(
&buffer.anchor_before(self.position),
)),
version: serialize_version(&buffer.version()),
}
}

async fn from_proto(
message: proto::GetDocumentDiagnostics,
lsp_store: Model<LspStore>,
_: Model<Buffer>,
_: Model<LspStore>,
buffer: Model<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
let language_server_id = LanguageServerId::from_proto(message.language_server_id);
let previous_result_id = lsp_store
.update(&mut cx, |lsp_store, _| {
match lsp_store
.as_local()
.and_then(|local_store| local_store.language_servers.get(&language_server_id))
{
Some(LanguageServerState::Running {
previous_document_diagnostic_result_id,
..
}) => previous_document_diagnostic_result_id.clone(),
_ => None,
}
})
.expect("Failed to retrieve previous_result_id");

Ok(Self {
language_server_id,
previous_result_id,
})
let position = message
.position
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("invalid position"))?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&message.version))
})?
.await?;
Ok(Self { position })
}

fn response_to_proto(
diagnostics: Vec<DiagnosticEntry<Anchor>>,
_: Vec<Diagnostic>,
_: &mut LspStore,
_: PeerId,
_: &clock::Global,
_: &mut AppContext,
) -> proto::GetDocumentDiagnosticsResponse {
proto::GetDocumentDiagnosticsResponse {
diagnostics: language::proto::serialize_diagnostics(&diagnostics),
result_id: Default::default(),
}
todo!()
}

async fn response_from_proto(
self,
message: proto::GetDocumentDiagnosticsResponse,
_: proto::GetDocumentDiagnosticsResponse,
_: Model<LspStore>,
_: Model<Buffer>,
_: AsyncAppContext,
) -> Result<Vec<DiagnosticEntry<Anchor>>> {
Ok(language::proto::deserialize_diagnostics(
message.diagnostics,
))
) -> Result<Vec<Diagnostic>> {
todo!()
}

fn buffer_id_from_proto(message: &proto::GetDocumentDiagnostics) -> Result<BufferId> {
Expand Down
Loading

0 comments on commit 27437c0

Please sign in to comment.