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
use lsp_types::PublishDiagnosticsParams;
use lsp_types::notification::PublishDiagnostics;
use tokio_util::sync::CancellationToken;
use tracing::{Span, warn};
use crate::client::Client;
use crate::documents::{Documents, DocumentsView};
use crate::raw::RequestId;
use crate::workspace::Workspace;
/// Per-request handle to framework state (see ADR 0009).
///
/// The handle exposes connection-scoped capabilities such as [`Client`], the
/// read-only [`DocumentsView`], and [`Workspace`] without exposing
/// protocol-owned queues or registries.
#[derive(Debug, Clone)]
pub struct Context {
pub(crate) request_id: Option<RequestId>,
pub(crate) span: Span,
pub(crate) client: Client,
pub(crate) documents: DocumentsView,
/// The connection's established [`Workspace`], present once the initialize
/// transaction has run. Handlers only run after that point, so a handler
/// always observes `Some`; it is `None` only before the transaction
/// establishes one.
pub(crate) workspace: Option<Workspace>,
pub(crate) cancellation: Option<CancellationToken>,
}
impl Context {
pub(crate) fn for_request(
id: RequestId,
span: Span,
client: Client,
documents: Documents,
) -> Self {
Self {
request_id: Some(id),
span,
client,
documents: documents.view(),
workspace: None,
cancellation: None,
}
}
pub(crate) fn for_notification(span: Span, client: Client, documents: Documents) -> Self {
Self {
request_id: None,
span,
client,
documents: documents.view(),
workspace: None,
cancellation: None,
}
}
/// Attach the connection's established [`Workspace`] to this context. The
/// protocol engine calls this once the initialize transaction has run, so
/// every handler and lifecycle hook that observes a workspace sees the same
/// established state.
pub(crate) fn with_workspace(mut self, workspace: Workspace) -> Self {
self.workspace = Some(workspace);
self
}
pub(crate) fn with_cancellation(mut self, cancellation: CancellationToken) -> Self {
self.cancellation = Some(cancellation);
self
}
pub(crate) fn cancellation(&self) -> Option<&CancellationToken> {
self.cancellation.as_ref()
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
pub fn span(&self) -> &Span {
&self.span
}
/// The connection's documents, as a read-only [`DocumentsView`].
///
/// The framework owns and mutates the documents; a handler reads the retained
/// documents and converts positions through this view, and a registered
/// document hook sees it already carrying the built-in mutation
/// (ADR 0018).
pub fn documents(&self) -> &DocumentsView {
&self.documents
}
/// A cheap clone of the typed handle for this connection's LSP client.
pub fn client(&self) -> Client {
self.client.clone()
}
/// The connection's [`Workspace`], established from `InitializeParams`
/// during the initialize transaction (ADR 0017, ADR 0018).
///
/// Returns `None` only where no workspace has been established, which no
/// handler can observe: the engine attaches the established workspace
/// before `on_initialize` runs, and every later dispatch inherits it.
pub fn workspace(&self) -> Option<&Workspace> {
self.workspace.as_ref()
}
/// Push a `textDocument/publishDiagnostics` notification through the
/// connection's typed [`Client`] (fire-and-forget).
///
/// Errors during serialization or send (channel closed during
/// shutdown) are logged via `tracing::warn!` rather than surfaced —
/// the LSP semantics of `publishDiagnostics` is "best effort"; a
/// failed publish never invalidates the handler that triggered it.
pub fn publish_diagnostics(&self, params: PublishDiagnosticsParams) {
if let Err(error) = self.client.notify::<PublishDiagnostics>(params) {
warn!(%error, "publish_diagnostics: notification failed");
}
}
}