Skip to main content

omena_lsp_server/
state.rs

1use crate::workspace_runtime_registry::WorkspaceRuntimeRegistry;
2use omena_incremental::IncrementalCancellationRegistryV0;
3use omena_query::{
4    OmenaQueryExternalSifInputV0, OmenaQuerySourceSyntaxIndexV0 as SourceSyntaxIndex,
5    OmenaQueryStylePackageManifestV0, OmenaQueryStyleResolutionInputsV0, ParserPositionV0,
6    ParserRangeV0,
7};
8use omena_tsgo_client::TsgoWorkspaceProcessPoolV0;
9use serde::Serialize;
10use std::collections::{BTreeMap, BTreeSet};
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
13#[serde(rename_all = "camelCase")]
14pub struct LspTextDocumentState {
15    pub uri: String,
16    pub workspace_folder_uri: Option<String>,
17    pub language_id: String,
18    pub version: i64,
19    pub text: String,
20    pub style_summary: Option<LspStyleDocumentSummary>,
21    #[serde(skip)]
22    pub style_candidates: Vec<LspStyleHoverCandidate>,
23    #[serde(skip)]
24    pub(crate) source_syntax_index: SourceSyntaxIndex,
25    #[serde(skip)]
26    pub source_selector_candidates: Vec<LspStyleHoverCandidate>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
30#[serde(rename_all = "camelCase")]
31pub struct LspStyleDocumentSummary {
32    pub language: &'static str,
33    pub selector_names: Vec<String>,
34    pub custom_property_decl_names: Vec<String>,
35    pub custom_property_ref_names: Vec<String>,
36    pub sass_module_use_sources: Vec<String>,
37    pub sass_module_forward_sources: Vec<String>,
38    pub diagnostic_count: usize,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
42#[serde(rename_all = "camelCase")]
43pub struct LspStyleHoverCandidatesResult {
44    pub schema_version: &'static str,
45    pub product: &'static str,
46    pub document_uri: String,
47    pub workspace_folder_uri: Option<String>,
48    pub language: Option<&'static str>,
49    pub query_position: Option<ParserPositionV0>,
50    pub candidate_count: usize,
51    pub candidates: Vec<LspStyleHoverCandidate>,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
55#[serde(rename_all = "camelCase")]
56pub struct LspStyleHoverCandidate {
57    pub kind: &'static str,
58    pub name: String,
59    pub range: ParserRangeV0,
60    pub source: &'static str,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub target_style_uri: Option<String>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub namespace: Option<String>,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
68#[serde(rename_all = "camelCase")]
69pub struct LspWorkspaceFolderState {
70    pub uri: String,
71    pub name: String,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
75#[serde(rename_all = "camelCase")]
76pub struct LspWatchedFileChangeState {
77    pub uri: String,
78    pub change_type: u64,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
82#[serde(rename_all = "camelCase")]
83pub struct LspShellStateSnapshot {
84    pub shutdown_requested: bool,
85    pub should_exit: bool,
86    pub features: LspFeatureSettings,
87    pub diagnostics: LspDiagnosticSettings,
88    pub resolution: LspResolutionSettings,
89    pub cancelled_request_count: usize,
90    pub workspace_style_index_exhausted_count: usize,
91    pub document_count: usize,
92    pub workspace_folder_count: usize,
93    pub configuration_change_count: usize,
94    pub watched_file_event_count: usize,
95    pub cached_workspace_resolution_input_count: usize,
96    pub documents: Vec<LspTextDocumentState>,
97    pub workspace_folders: Vec<LspWorkspaceFolderState>,
98    pub watched_file_changes: Vec<LspWatchedFileChangeState>,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
102#[serde(rename_all = "camelCase")]
103pub struct LspFeatureSettings {
104    pub definition: bool,
105    pub hover: bool,
106    pub completion: bool,
107    pub references: bool,
108    pub rename: bool,
109}
110
111impl Default for LspFeatureSettings {
112    fn default() -> Self {
113        Self {
114            definition: true,
115            hover: true,
116            completion: true,
117            references: true,
118            rename: true,
119        }
120    }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
124#[serde(rename_all = "camelCase")]
125pub struct LspDiagnosticSettings {
126    pub severity: u8,
127}
128
129impl Default for LspDiagnosticSettings {
130    fn default() -> Self {
131        Self { severity: 2 }
132    }
133}
134
135#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
136#[serde(rename_all = "camelCase")]
137pub struct LspResolutionSettings {
138    pub package_manifest_paths: Vec<String>,
139    #[serde(skip)]
140    pub package_manifests: Vec<OmenaQueryStylePackageManifestV0>,
141    #[serde(skip)]
142    pub workspace_style_resolution_inputs: BTreeMap<String, OmenaQueryStyleResolutionInputsV0>,
143    /// External Sass-module SIF artifacts sourced from the lock/bridge (#32/#33). When non-empty
144    /// the style-diagnostics path runs in `ExternalModuleModeV0::Sif`, which surfaces the
145    /// boundary lattice and the `@omena-strict:` sigil (#35); empty preserves the legacy
146    /// `Ignored` behaviour.
147    #[serde(skip)]
148    pub external_sifs: Vec<OmenaQueryExternalSifInputV0>,
149}
150
151#[derive(Debug, Default)]
152pub struct LspShellState {
153    pub shutdown_requested: bool,
154    pub should_exit: bool,
155    pub(crate) features: LspFeatureSettings,
156    pub(crate) diagnostics: LspDiagnosticSettings,
157    pub(crate) resolution: LspResolutionSettings,
158    pub(crate) cancelled_request_ids: IncrementalCancellationRegistryV0,
159    pub(crate) workspace_style_index_exhausted_count: usize,
160    pub(crate) configuration_change_count: usize,
161    pub(crate) documents: BTreeMap<String, LspTextDocumentState>,
162    pub(crate) open_document_uris: BTreeSet<String>,
163    pub(crate) workspace_runtime_registry: WorkspaceRuntimeRegistry,
164    pub(crate) tsgo_workspace_process_pool: TsgoWorkspaceProcessPoolV0,
165    pub(crate) watched_file_changes: Vec<LspWatchedFileChangeState>,
166}
167
168impl LspShellState {
169    pub fn document_count(&self) -> usize {
170        self.documents.len()
171    }
172
173    pub fn workspace_folder_count(&self) -> usize {
174        self.workspace_runtime_registry.len()
175    }
176
177    pub fn document(&self, uri: &str) -> Option<&LspTextDocumentState> {
178        let storage_uri = Self::document_storage_uri(uri);
179        self.documents.get(storage_uri.as_str()).or_else(|| {
180            self.documents
181                .iter()
182                .find(|(document_uri, _)| crate::protocol::file_uri_equivalent(document_uri, uri))
183                .map(|(_, document)| document)
184        })
185    }
186
187    pub(crate) fn document_mut(&mut self, uri: &str) -> Option<&mut LspTextDocumentState> {
188        let storage_uri = Self::document_storage_uri(uri);
189        if self.documents.contains_key(storage_uri.as_str()) {
190            return self.documents.get_mut(storage_uri.as_str());
191        }
192        let equivalent_uri = self
193            .documents
194            .keys()
195            .find(|document_uri| crate::protocol::file_uri_equivalent(document_uri, uri))
196            .cloned();
197        equivalent_uri.and_then(|document_uri| self.documents.get_mut(document_uri.as_str()))
198    }
199
200    pub(crate) fn document_storage_uri(uri: &str) -> String {
201        crate::protocol::canonical_file_uri(uri).unwrap_or_else(|| uri.to_string())
202    }
203
204    pub(crate) fn insert_open_document_uri(&mut self, uri: &str) -> String {
205        let storage_uri = Self::document_storage_uri(uri);
206        self.remove_open_document_uri(uri);
207        self.open_document_uris.insert(storage_uri.clone());
208        storage_uri
209    }
210
211    pub(crate) fn remove_open_document_uri(&mut self, uri: &str) {
212        let storage_uri = Self::document_storage_uri(uri);
213        self.open_document_uris.remove(storage_uri.as_str());
214        let equivalent_uris = self
215            .open_document_uris
216            .iter()
217            .filter(|candidate| crate::protocol::file_uri_equivalent(candidate, uri))
218            .cloned()
219            .collect::<Vec<_>>();
220        for candidate in equivalent_uris {
221            self.open_document_uris.remove(candidate.as_str());
222        }
223    }
224
225    pub(crate) fn has_open_document_uri(&self, uri: &str) -> bool {
226        let storage_uri = Self::document_storage_uri(uri);
227        self.open_document_uris.contains(storage_uri.as_str())
228            || self
229                .open_document_uris
230                .iter()
231                .any(|candidate| crate::protocol::file_uri_equivalent(candidate, uri))
232    }
233
234    pub(crate) fn insert_document(&mut self, uri: &str, document: LspTextDocumentState) {
235        let storage_uri = Self::document_storage_uri(uri);
236        self.remove_document_uri(uri);
237        self.documents.insert(storage_uri, document);
238    }
239
240    pub(crate) fn remove_document_uri(&mut self, uri: &str) -> Option<LspTextDocumentState> {
241        let storage_uri = Self::document_storage_uri(uri);
242        let removed = self.documents.remove(storage_uri.as_str());
243        let equivalent_uris = self
244            .documents
245            .keys()
246            .filter(|candidate| crate::protocol::file_uri_equivalent(candidate, uri))
247            .cloned()
248            .collect::<Vec<_>>();
249        for candidate in equivalent_uris {
250            self.documents.remove(candidate.as_str());
251        }
252        removed
253    }
254
255    pub(crate) fn contains_document_uri(&self, uri: &str) -> bool {
256        self.document(uri).is_some()
257    }
258
259    pub fn workspace_folder(&self, uri: &str) -> Option<&LspWorkspaceFolderState> {
260        self.workspace_runtime_registry.get(uri)
261    }
262
263    pub fn snapshot(&self) -> LspShellStateSnapshot {
264        LspShellStateSnapshot {
265            shutdown_requested: self.shutdown_requested,
266            should_exit: self.should_exit,
267            features: self.features.clone(),
268            diagnostics: self.diagnostics.clone(),
269            resolution: self.resolution.clone(),
270            cancelled_request_count: self.cancelled_request_ids.len(),
271            workspace_style_index_exhausted_count: self.workspace_style_index_exhausted_count,
272            document_count: self.document_count(),
273            workspace_folder_count: self.workspace_folder_count(),
274            configuration_change_count: self.configuration_change_count,
275            watched_file_event_count: self.watched_file_changes.len(),
276            cached_workspace_resolution_input_count: self
277                .resolution
278                .workspace_style_resolution_inputs
279                .len(),
280            documents: self.documents.values().cloned().collect(),
281            workspace_folders: self.workspace_runtime_registry.folder_snapshots(),
282            watched_file_changes: self.watched_file_changes.clone(),
283        }
284    }
285}