diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1e858d0d83f458..7d90dfed651015 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -126,7 +126,7 @@ use project::{ lsp_store::{FormatTarget, FormatTrigger}, project_settings::{GitGutterSetting, ProjectSettings}, CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink, - Project, ProjectItem, ProjectTransaction, TaskSourceKind, + LspDiagnostics, Project, ProjectItem, ProjectTransaction, TaskSourceKind, }; use rand::prelude::*; use rpc::{proto::*, ErrorExt}; @@ -663,7 +663,7 @@ pub struct Editor { next_scroll_position: NextScrollCursorCenterTopBottom, addons: HashMap>, _scroll_cursor_center_top_bottom_task: Task<()>, - _pull_document_diagnostics_task: Task<()>, + _pull_document_diagnostics_task: Task>, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] @@ -2161,7 +2161,7 @@ impl Editor { addons: HashMap::default(), _scroll_cursor_center_top_bottom_task: Task::ready(()), text_style_refinement: None, - _pull_document_diagnostics_task: Task::ready(()), + _pull_document_diagnostics_task: Task::ready(Ok(())), }; this.tasks_update_task = Some(this.refresh_runnables(cx)); this._subscriptions.extend(project_subscriptions); @@ -10911,16 +10911,24 @@ impl Editor { return None; } - self._pull_document_diagnostics_task = cx.spawn(|editor, mut cx| async move { + self._pull_document_diagnostics_task = cx.spawn(|this, 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(); + let pull_diagnostics_task = this.update(&mut cx, |_, cx| { + project.pull_diagnostics(&start_buffer, start, cx) + })?; + + if let Some(pull_diagnostics_task) = pull_diagnostics_task { + let diagnostics = pull_diagnostics_task + .await + .context("Pull diagnostics task")?; + + let _ = this.update(&mut cx, |_, cx| project.update_diagnostics(diagnostics, cx)); + } + + anyhow::Ok(()) }); None } @@ -13886,12 +13894,18 @@ pub trait CodeActionProvider { } pub trait DiagnosticsProvider { - fn diagnostics( + fn pull_diagnostics( &self, buffer: &Model, position: text::Anchor, - cx: &mut WindowContext, - ) -> Option>>>; + cx: &mut AppContext, + ) -> Option>>>; + + fn update_diagnostics( + &self, + diagnostics: Vec, + cx: &mut AppContext, + ) -> Result<()>; } impl CodeActionProvider for Model { @@ -14232,16 +14246,40 @@ impl SemanticsProvider for Model { } impl DiagnosticsProvider for Model { - fn diagnostics( + fn pull_diagnostics( &self, buffer: &Model, position: text::Anchor, - cx: &mut WindowContext, - ) -> Option>>> { + cx: &mut AppContext, + ) -> Option>>> { Some(self.update(cx, |project, cx| { project.document_diagnostics(buffer, position, cx) })) } + + fn update_diagnostics( + &self, + diagnostics: Vec, + cx: &mut AppContext, + ) -> Result<()> { + self.update(cx, |project, cx| { + diagnostics + .into_iter() + .map(|diagnostic_set| { + project.update_diagnostics( + diagnostic_set.server_id, + lsp::PublishDiagnosticsParams { + uri: diagnostic_set.uri.unwrap(), + diagnostics: diagnostic_set.diagnostics.unwrap_or_else(|| vec![]), + version: None, + }, + &[], + cx, + ) + }) + .collect() + }) + } } fn inlay_hint_settings( diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index f95e6be4456627..5281dd7d91e335 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -3,7 +3,8 @@ mod signature_help; use crate::{ lsp_store::LspStore, CodeAction, CoreCompletion, DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, - InlayHintTooltip, Location, LocationLink, MarkupContent, ProjectTransaction, ResolveState, + InlayHintTooltip, Location, LocationLink, LspDiagnostics, MarkupContent, ProjectTransaction, + ResolveState, }; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; @@ -21,12 +22,13 @@ use language::{ }; use lsp::{ AdapterServerCapabilities, CodeActionKind, CodeActionOptions, CompletionContext, - CompletionListItemDefaultsEditRange, CompletionTriggerKind, Diagnostic, DocumentHighlightKind, + CompletionListItemDefaultsEditRange, CompletionTriggerKind, DocumentHighlightKind, LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities, OneOf, ServerCapabilities, }; +use serde_json::Value; use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature}; -use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; +use std::{cmp::Reverse, ops::Range, path::Path, str::FromStr, sync::Arc}; use text::{BufferId, LineEnding}; pub use signature_help::{ @@ -3075,9 +3077,91 @@ impl LspCommand for LinkedEditingRange { } } +impl GetDocumentDiagnostics { + pub fn deserialize_lsp_diagnostic(diagnostic: proto::LspDiagnostic) -> lsp::Diagnostic { + let start = diagnostic.start.unwrap(); + let end = diagnostic.end.unwrap(); + + let range = Range:: { + start: PointUtf16 { + row: start.row, + column: start.column, + }, + end: PointUtf16 { + row: end.row, + column: end.column, + }, + }; + + let data = if let Some(data) = diagnostic.data { + Value::from_str(&data).ok() + } else { + None + }; + + let code = if let Some(code) = diagnostic.code { + Some(lsp::NumberOrString::String(code)) + } else { + None + }; + + lsp::Diagnostic { + range: language::range_to_lsp(range), + severity: match proto::lsp_diagnostic::Severity::from_i32(diagnostic.severity).unwrap() + { + proto::lsp_diagnostic::Severity::Error => Some(lsp::DiagnosticSeverity::ERROR), + proto::lsp_diagnostic::Severity::Warning => Some(lsp::DiagnosticSeverity::WARNING), + proto::lsp_diagnostic::Severity::Information => { + Some(lsp::DiagnosticSeverity::INFORMATION) + } + proto::lsp_diagnostic::Severity::Hint => Some(lsp::DiagnosticSeverity::HINT), + _ => None, + }, + code, + code_description: None, + related_information: Some(vec![]), + tags: Some(vec![]), + source: diagnostic.source.clone(), + message: diagnostic.message, + data, + } + } + + pub fn serialize_lsp_diagnostic(diagnostic: lsp::Diagnostic) -> proto::LspDiagnostic { + let range = language::range_from_lsp(diagnostic.range); + + proto::LspDiagnostic { + start: Some(proto::PointUtf16 { + row: range.start.0.row, + column: range.start.0.column, + }), + end: Some(proto::PointUtf16 { + row: range.end.0.row, + column: range.end.0.column, + }), + severity: match diagnostic.severity { + Some(lsp::DiagnosticSeverity::ERROR) => proto::lsp_diagnostic::Severity::Error, + Some(lsp::DiagnosticSeverity::WARNING) => proto::lsp_diagnostic::Severity::Warning, + Some(lsp::DiagnosticSeverity::INFORMATION) => { + proto::lsp_diagnostic::Severity::Information + } + Some(lsp::DiagnosticSeverity::HINT) => proto::lsp_diagnostic::Severity::Hint, + _ => proto::lsp_diagnostic::Severity::None, + } as i32, + code: diagnostic.code.as_ref().map(|code| match code { + lsp::NumberOrString::Number(code) => code.to_string(), + lsp::NumberOrString::String(code) => code.clone(), + }), + source: diagnostic.source.clone(), + message: diagnostic.message, + data: diagnostic.data.as_ref().map(|data| data.to_string()), + } + } +} + #[async_trait(?Send)] impl LspCommand for GetDocumentDiagnostics { - type Response = Option>; + type Response = LspDiagnostics; type LspRequest = lsp::request::DocumentDiagnosticRequest; type ProtoRequest = proto::GetDocumentDiagnostics; @@ -3118,18 +3202,34 @@ impl LspCommand for GetDocumentDiagnostics { self, message: lsp::DocumentDiagnosticReportResult, _: Model, - _: Model, - _: LanguageServerId, - _: AsyncAppContext, + buffer: Model, + server_id: LanguageServerId, + cx: AsyncAppContext, ) -> Result { + let uri = buffer.read_with(&cx, |buffer, cx| { + let file = buffer.file().and_then(|file| file.as_local())?; + let uri = lsp::Url::from_file_path(file.abs_path(cx).clone()).unwrap(); + Some(uri) + })?; + match message { lsp::DocumentDiagnosticReportResult::Report(report) => match report { - lsp::DocumentDiagnosticReport::Full(report) => { - Ok(Some(report.full_document_diagnostic_report.items.clone())) - } - lsp::DocumentDiagnosticReport::Unchanged(_) => Ok(None), + lsp::DocumentDiagnosticReport::Full(report) => Ok(LspDiagnostics { + server_id, + uri, + diagnostics: Some(report.full_document_diagnostic_report.items.clone()), + }), + lsp::DocumentDiagnosticReport::Unchanged(_) => Ok(LspDiagnostics { + server_id, + uri, + diagnostics: None, + }), }, - lsp::DocumentDiagnosticReportResult::Partial(_) => Ok(None), + lsp::DocumentDiagnosticReportResult::Partial(_) => Ok(LspDiagnostics { + server_id, + uri, + diagnostics: None, + }), } } @@ -3180,6 +3280,7 @@ impl LspCommand for GetDocumentDiagnostics { proto::GetDocumentDiagnosticsResponse { server_id: LanguageServerId::to_proto(response.server_id), + uri: response.uri.unwrap().to_string(), diagnostics, } } @@ -3199,6 +3300,7 @@ impl LspCommand for GetDocumentDiagnostics { Ok(LspDiagnostics { server_id: LanguageServerId::from_proto(response.server_id), + uri: Some(lsp::Url::from_str(response.uri.as_str()).unwrap()), diagnostics: Some(diagnostics), }) } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index c1991f9e93090c..628a4aa24d848b 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -10,8 +10,9 @@ use crate::{ toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent}, worktree_store::{WorktreeStore, WorktreeStoreEvent}, yarn::YarnPathStore, - CodeAction, Completion, CoreCompletion, Hover, InlayHint, ProjectItem as _, ProjectPath, - ProjectTransaction, ResolveState, Symbol, ToolchainStore, + CodeAction, Completion, CoreCompletion, Hover, InlayHint, ProjectItem as _, + LspDiagnostics, ProjectPath, ProjectTransaction, ResolveState, Symbol, + ToolchainStore, }; use anyhow::{anyhow, Context as _, Result}; use async_trait::async_trait; @@ -3194,7 +3195,7 @@ impl LspStore { buffer_handle: &Model, position: Anchor, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { let buffer = buffer_handle.read(cx); let buffer_id = buffer.remote_id(); @@ -3244,7 +3245,6 @@ impl LspStore { .into_iter() .collect::>>()? .into_iter() - .flat_map(|diagnostics| diagnostics.into_iter().flatten()) .collect()) }) } else { @@ -3254,13 +3254,7 @@ impl LspStore { GetDocumentDiagnostics { position }, cx, ); - cx.spawn(|_, _| async move { - Ok(all_actions_task - .await - .into_iter() - .flat_map(|diagnostics| diagnostics.into_iter().flatten()) - .collect()) - }) + cx.spawn(|_, _| async move { Ok(all_actions_task.await.into_iter().collect()) }) } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7262a8b2ea5fff..4c11bb35d2af2f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -557,6 +557,27 @@ pub const DEFAULT_COMPLETION_CONTEXT: CompletionContext = CompletionContext { trigger_character: None, }; +/// An LSP diagnostics associated with a certain language server. +#[derive(Clone, Debug)] +pub struct LspDiagnostics { + /// The id of the language server that produced diagnostics. + pub server_id: LanguageServerId, + /// URI of thr resource, + pub uri: Option, + /// The diagnostics produced by this language server. + pub diagnostics: Option>, +} + +impl std::default::Default for LspDiagnostics { + fn default() -> Self { + Self { + server_id: LanguageServerId(0), + uri: None, + diagnostics: None, + } + } +} + impl Project { pub fn init_settings(cx: &mut AppContext) { WorktreeSettings::register(cx); @@ -3012,7 +3033,7 @@ impl Project { buffer_handle: &Model, position: Anchor, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { self.lsp_store.update(cx, |lsp_store, cx| { lsp_store.document_diagnostic(buffer_handle, position, cx) }) diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 7a2aa3bc3f10c7..1769f25e0198b7 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -2588,6 +2588,37 @@ message InstallExtension { string tmp_dir = 2; } +message LspDiagnosticRelatedInformation { + string location = 1; + string message = 2; +} + +message LspDiagnosticTag { + string name = 1; + optional string value = 2; +} + +message LspDiagnostic { + PointUtf16 start = 1; + PointUtf16 end = 2; + Severity severity = 3; + optional string code = 4; + // optional string code_description = 5; + optional string source = 6; + string message = 7; + // repeated LspDiagnosticRelatedInformation related_information = 8; + // repeated LspDiagnosticTag tags = 9; + optional string data = 10; + + enum Severity { + None = 0; + Error = 1; + Warning = 2; + Information = 3; + Hint = 4; + } +} + message GetDocumentDiagnostics { uint64 project_id = 1; uint64 buffer_id = 2; @@ -2596,5 +2627,7 @@ message GetDocumentDiagnostics { } message GetDocumentDiagnosticsResponse { - repeated Diagnostic diagnostics = 1; + uint64 server_id = 1; + string uri = 2; + repeated LspDiagnostic diagnostics = 3; }