1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0
use {
crate::{
TRACER,
Uri,
database::{
PartitionKey,
PartitionWriteContextRef,
},
protocol::{
jsonrpc,
lsp::{
FullDocumentDiagnosticReport,
PartialResultParams,
UnchangedDocumentDiagnosticReport,
WorkDoneProgressParams,
},
},
record::LaburnumRecordRef,
scheduler::task::TaskContext,
},
opentelemetry::trace::FutureExt,
serde::{
Deserialize,
Serialize,
},
};
/// Workspace client capabilities specific to diagnostic pull requests.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DiagnosticWorkspaceClientCapabilities {
/// Whether the client implementation supports a refresh request sent from
/// the server to the client.
///
/// Note that this event is global and will force the client to refresh all
/// pulled diagnostics currently shown. It should be used with absolute care
/// and is useful for situation where a server for example detects a project
/// wide change that requires such a calculation.
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_support: Option<bool>,
}
/// A previous result ID in a workspace pull request.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct PreviousResultId {
/// The URI for which the client knows a result ID.
pub uri: Uri,
/// The value of the previous result ID.
pub value: String,
}
/// Parameters of the workspace diagnostic request.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceDiagnosticParams {
/// The additional identifier provided during registration.
pub identifier: Option<String>,
/// The currently known diagnostic reports with their
/// previous result ids.
pub previous_result_ids: Vec<PreviousResultId>,
#[serde(flatten)]
pub work_done_progress_params: WorkDoneProgressParams,
#[serde(flatten)]
pub partial_result_params: PartialResultParams,
}
/// A full document diagnostic report for a workspace diagnostic result.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceFullDocumentDiagnosticReport {
/// The URI for which diagnostic information is reported.
pub uri: Uri,
/// The version number for which the diagnostics are reported.
///
/// If the document is not marked as open, `None` can be provided.
pub version: Option<i64>,
#[serde(flatten)]
pub full_document_diagnostic_report: FullDocumentDiagnosticReport,
}
/// An unchanged document diagnostic report for a workspace diagnostic result.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceUnchangedDocumentDiagnosticReport {
/// The URI for which diagnostic information is reported.
pub uri: Uri,
/// The version number for which the diagnostics are reported.
///
/// If the document is not marked as open, `None` can be provided.
pub version: Option<i64>,
#[serde(flatten)]
pub unchanged_document_diagnostic_report: UnchangedDocumentDiagnosticReport,
}
/// A workspace diagnostic document report.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum WorkspaceDocumentDiagnosticReport {
Full(WorkspaceFullDocumentDiagnosticReport),
Unchanged(WorkspaceUnchangedDocumentDiagnosticReport),
}
impl From<WorkspaceFullDocumentDiagnosticReport>
for WorkspaceDocumentDiagnosticReport
{
fn from(from: WorkspaceFullDocumentDiagnosticReport) -> Self {
Self::Full(from)
}
}
impl From<WorkspaceUnchangedDocumentDiagnosticReport>
for WorkspaceDocumentDiagnosticReport
{
fn from(from: WorkspaceUnchangedDocumentDiagnosticReport) -> Self {
Self::Unchanged(from)
}
}
/// A workspace diagnostic report.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct WorkspaceDiagnosticReport {
pub items: Vec<WorkspaceDocumentDiagnosticReport>,
}
/// A partial result for a workspace diagnostic report.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct WorkspaceDiagnosticReportPartialResult {
pub items: Vec<WorkspaceDocumentDiagnosticReport>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum WorkspaceDiagnosticReportResult {
Report(WorkspaceDiagnosticReport),
Partial(WorkspaceDiagnosticReportPartialResult),
}
impl From<WorkspaceDiagnosticReport> for WorkspaceDiagnosticReportResult {
fn from(from: WorkspaceDiagnosticReport) -> Self {
Self::Report(from)
}
}
impl From<WorkspaceDiagnosticReportPartialResult>
for WorkspaceDiagnosticReportResult
{
fn from(from: WorkspaceDiagnosticReportPartialResult) -> Self {
Self::Partial(from)
}
}
pub trait WorkspaceDiagnosticService<
P: crate::database::storage::Partitions,
T: crate::protocol::lsp::LanguageServer<P>,
>: Send + Sync + 'static
{
/// The [`workspace/diagnostic`] request is sent from the client to the server
/// to ask the server to compute workspace wide diagnostics which previously
/// where pushed from the server to the client.
///
/// In contrast to the [`textDocument/diagnostic`] request, the workspace
/// request can be long-running and is not bound to a specific workspace or
/// document state. If the client supports streaming for the workspace
/// diagnostic pull, it is legal to provide a `textDocument/diagnostic`
/// report multiple times for the same document URI. The last one
/// reported will win over previous reports.
///
/// [`textDocument/diagnostic`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_diagnostic
///
/// If a client receives a diagnostic report for a document in a workspace
/// diagnostic request for which the client also issues individual document
/// diagnostic pull requests, the client needs to decide which diagnostics
/// win and should be presented. In general:
///
/// * Diagnostics for a higher document version should win over those from a
/// lower document version (e.g. note that document versions are steadily
/// increasing).
/// * Diagnostics from a document pull should win over diagnostics from a
/// workspace pull.
///
/// The request doesn't define its own client and server capabilities. It is
/// only issued if a server registers for the [`workspace/diagnostic`]
/// request.
///
/// [`workspace/diagnostic`]: https://microsoft.github.io/language-server-protocol/specification#workspace_diagnostic
///
/// # Compatibility
///
/// This request was introduced in specification version 3.17.0.
fn workspace_diagnostic(
&self,
_params: WorkspaceDiagnosticParams,
ctx: &mut TaskContext<P, T>,
_writer: &mut PartitionWriteContextRef<'_, P>,
) -> impl std::future::Future<
Output = jsonrpc::Result<WorkspaceDiagnosticReportResult>,
> + Send {
let cx = otel::span!(^
"laburnum.lsp.workspace_diagnostic",
"identifier" = _params.identifier.as_ref().map(|s| s.to_string()).unwrap_or_default()
);
async move {
use crate::database::DynPartition;
let partition_key = crate::partitions::Diagnostics::KEY;
let query_client = ctx.query_client();
let query_results = query_client
.prefix_internal(partition_key, String::new())
.await;
let reader = ctx.source_cache_reader();
let encoding = ctx.position_encoding();
let mut diagnostics_by_file: std::collections::HashMap<
crate::Uri,
(crate::SourceKey, Vec<crate::protocol::lsp::Diagnostic>),
> = std::collections::HashMap::new();
for record_meta in query_results.records().iter() {
if let Some(record_ref) = query_results.get(record_meta)
&& let Some(diag) = record_ref.as_dyn_diagnostic()
&& let Some(source_key) = diag.source_key()
&& let Some(uri) = reader.get_uri(source_key.file_id())
{
let lsp_diag = diag.to_lsp_diagnostic(&reader, &encoding);
diagnostics_by_file
.entry(uri.clone())
.or_insert_with(|| (source_key, Vec::new()))
.1
.push(lsp_diag);
}
}
let items: Vec<WorkspaceDocumentDiagnosticReport> = diagnostics_by_file
.into_iter()
.map(|(uri, (source_key, items))| {
let version = reader
.current_version(source_key.file_id())
.map(|v| v as i64);
WorkspaceFullDocumentDiagnosticReport {
uri,
version,
full_document_diagnostic_report:
crate::protocol::lsp::FullDocumentDiagnosticReport {
result_id: None,
items,
},
}
.into()
})
.collect();
Ok(WorkspaceDiagnosticReportResult::Report(
WorkspaceDiagnosticReport { items },
))
}
.with_context(cx)
}
const WORKSPACE_DIAGNOSTIC_LANE: crate::scheduler::lanes::Lane =
crate::scheduler::lanes::DEFAULT_LANE;
}