Skip to main content

merman_lsp/
server.rs

1use crate::code_actions::code_actions_for_params_with_encoding;
2use crate::completion::{completion_for_snapshot, resolve_completion_item};
3use crate::diagnostics::analysis_payload_to_versioned_diagnostics;
4use crate::document_store::{
5    AnalyzerConfigurationChange, DiagnosticContext, DocumentDiagnosticState, DocumentStore,
6    DocumentSyncError, SemanticTokensState, SnapshotContext, StoredDocument,
7    WORKSPACE_SYMBOL_SNAPSHOT_BATCH_SIZE, analysis_options_with_lsp_resource_defaults,
8    default_lsp_analysis_options,
9};
10use crate::protocol::{
11    CONFIG_SCHEMA_METHOD, ConfigSchemaResponse, RULE_CATALOG_METHOD, RuleCatalogResponse,
12    WorkspaceEditEncoding, experimental_capabilities,
13};
14use crate::semantic_tokens::{
15    semantic_tokens_delta_result, semantic_tokens_for_snapshot, semantic_tokens_for_snapshot_range,
16    semantic_tokens_options, semantic_tokens_result_id,
17};
18use crate::snapshot::DocumentSnapshot;
19use crate::snapshot_context::{self, SnapshotContextKind};
20use crate::structure::{
21    document_symbols_with_hierarchy_support as structure_document_symbols_with_hierarchy_support,
22    folding_ranges as structure_folding_ranges, goto_definition as structure_goto_definition,
23    hover as structure_hover, prepare_rename as structure_prepare_rename,
24    references as structure_references,
25    rename_with_workspace_edit_encoding as structure_rename_with_workspace_edit_encoding,
26    selection_ranges as structure_selection_ranges,
27    workspace_symbols_for_snapshots as structure_workspace_symbols_for_snapshots,
28};
29use merman_analysis::{
30    AnalysisOptions, AnalysisPayload, Analyzer, SourceKind, document::analyze_document,
31    options_json::analysis_options_from_json_value, source_descriptor_for_kind,
32    source_discarded_after_limit_change_diagnostic, source_limit_diagnostic_for_len,
33};
34use merman_editor_core::DocumentKind;
35use std::hash::{Hash, Hasher};
36use std::sync::Arc;
37use std::sync::atomic::{AtomicBool, Ordering};
38use tokio::sync::Mutex;
39use tower_lsp::jsonrpc::Result;
40use tower_lsp::lsp_types::{
41    CodeActionKind, CodeActionOptions, CodeActionParams, CodeActionProviderCapability,
42    CodeActionResponse, CompletionItem, CompletionOptions, CompletionParams, CompletionResponse,
43    Diagnostic, DiagnosticOptions, DiagnosticServerCapabilities, DiagnosticSeverity,
44    DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
45    DidSaveTextDocumentParams, DocumentDiagnosticParams, DocumentDiagnosticReport,
46    DocumentDiagnosticReportResult, DocumentSymbolParams, DocumentSymbolResponse, FoldingRange,
47    FoldingRangeParams, FoldingRangeProviderCapability, FullDocumentDiagnosticReport,
48    GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverParams, HoverProviderCapability,
49    InitializeParams, InitializeResult, MessageType, NumberOrString, OneOf, Position,
50    PrepareRenameResponse, Range, ReferenceParams, RelatedFullDocumentDiagnosticReport,
51    RelatedUnchangedDocumentDiagnosticReport, RenameOptions, RenameParams, SelectionRange,
52    SelectionRangeParams, SelectionRangeProviderCapability, SemanticTokensDeltaParams,
53    SemanticTokensFullDeltaResult, SemanticTokensParams, SemanticTokensRangeParams,
54    SemanticTokensRangeResult, SemanticTokensResult, SemanticTokensServerCapabilities,
55    ServerCapabilities, TextDocumentPositionParams, TextDocumentSyncCapability,
56    TextDocumentSyncKind, UnchangedDocumentDiagnosticReport, WorkspaceEdit, WorkspaceSymbolParams,
57};
58use tower_lsp::{Client, ClientSocket, LanguageServer, LspService};
59
60const MAX_DIAGNOSTIC_RECOMPUTE_ATTEMPTS: usize = 3;
61
62#[derive(Debug)]
63pub struct MermanLanguageServer {
64    client: Client,
65    store: Arc<Mutex<DocumentStore>>,
66    semantic_tokens_refresh_supported: AtomicBool,
67    diagnostic_pull_supported: AtomicBool,
68    diagnostic_refresh_supported: AtomicBool,
69    workspace_edit_document_changes_supported: AtomicBool,
70    hierarchical_document_symbols_supported: AtomicBool,
71}
72
73impl MermanLanguageServer {
74    pub fn new(client: Client) -> Self {
75        Self {
76            client,
77            store: Arc::new(Mutex::new(DocumentStore::new())),
78            semantic_tokens_refresh_supported: AtomicBool::new(false),
79            diagnostic_pull_supported: AtomicBool::new(false),
80            diagnostic_refresh_supported: AtomicBool::new(false),
81            workspace_edit_document_changes_supported: AtomicBool::new(false),
82            hierarchical_document_symbols_supported: AtomicBool::new(false),
83        }
84    }
85
86    pub fn service() -> (LspService<Self>, ClientSocket) {
87        LspService::build(Self::new)
88            .custom_method(RULE_CATALOG_METHOD, Self::rule_catalog)
89            .custom_method(CONFIG_SCHEMA_METHOD, Self::config_schema)
90            .finish()
91    }
92
93    pub fn capabilities() -> ServerCapabilities {
94        ServerCapabilities {
95            text_document_sync: Some(TextDocumentSyncCapability::Kind(
96                TextDocumentSyncKind::INCREMENTAL,
97            )),
98            selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
99            hover_provider: Some(HoverProviderCapability::Simple(true)),
100            completion_provider: Some(CompletionOptions {
101                resolve_provider: Some(true),
102                trigger_characters: Some(vec![
103                    " ".to_string(),
104                    "\n".to_string(),
105                    "-".to_string(),
106                    ">".to_string(),
107                    "%".to_string(),
108                    "[".to_string(),
109                    "(".to_string(),
110                    "{".to_string(),
111                    "/".to_string(),
112                    "\\".to_string(),
113                    "@".to_string(),
114                    ":".to_string(),
115                ]),
116                ..CompletionOptions::default()
117            }),
118            definition_provider: Some(OneOf::Left(true)),
119            references_provider: Some(OneOf::Left(true)),
120            rename_provider: Some(OneOf::Right(RenameOptions {
121                prepare_provider: Some(true),
122                work_done_progress_options: Default::default(),
123            })),
124            document_symbol_provider: Some(OneOf::Left(true)),
125            workspace_symbol_provider: Some(OneOf::Left(true)),
126            diagnostic_provider: Some(DiagnosticServerCapabilities::Options(
127                Self::diagnostic_options(),
128            )),
129            code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
130                code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
131                work_done_progress_options: Default::default(),
132                resolve_provider: Some(false),
133            })),
134            folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
135            semantic_tokens_provider: Some(
136                SemanticTokensServerCapabilities::SemanticTokensOptions(semantic_tokens_options()),
137            ),
138            experimental: Some(experimental_capabilities()),
139            ..ServerCapabilities::default()
140        }
141    }
142
143    pub async fn rule_catalog(&self) -> Result<RuleCatalogResponse> {
144        Ok(RuleCatalogResponse::current())
145    }
146
147    pub async fn config_schema(&self) -> Result<ConfigSchemaResponse> {
148        Ok(ConfigSchemaResponse::current())
149    }
150
151    fn diagnostic_options() -> DiagnosticOptions {
152        DiagnosticOptions {
153            identifier: Some("merman".to_string()),
154            inter_file_dependencies: false,
155            workspace_diagnostics: false,
156            work_done_progress_options: Default::default(),
157        }
158    }
159
160    #[cfg(test)]
161    async fn snapshot_for_uri(
162        &self,
163        uri: &tower_lsp::lsp_types::Url,
164    ) -> Option<Arc<DocumentSnapshot>> {
165        snapshot_context::snapshot_context_for_uri(&self.store, uri, SnapshotContextKind::Structure)
166            .await
167            .ok()
168            .flatten()
169            .map(|context| context.snapshot)
170    }
171
172    async fn structure_snapshot_result<T>(
173        &self,
174        uri: &tower_lsp::lsp_types::Url,
175        compute: impl FnOnce(&DocumentSnapshot) -> Result<Option<T>>,
176    ) -> Result<Option<T>> {
177        snapshot_context::snapshot_result(&self.store, uri, SnapshotContextKind::Structure, compute)
178            .await
179    }
180
181    async fn semantic_snapshot_context_for_uri(
182        &self,
183        uri: &tower_lsp::lsp_types::Url,
184    ) -> Result<Option<SnapshotContext>> {
185        snapshot_context::snapshot_context_for_uri(
186            &self.store,
187            uri,
188            SnapshotContextKind::SemanticTokens,
189        )
190        .await
191    }
192
193    fn diagnostics_for_document(document: &StoredDocument, analyzer: &Analyzer) -> Vec<Diagnostic> {
194        let source = source_descriptor_for_document(&document.uri, document.kind);
195        let payload = if let Some(resource_limit) = document.resource_limit {
196            AnalysisPayload::new(
197                source,
198                vec![source_limit_diagnostic_for_len(
199                    resource_limit.source_len,
200                    resource_limit.max_source_bytes,
201                )],
202            )
203        } else if let Some(discarded_source) = document.discarded_source {
204            AnalysisPayload::new(
205                source,
206                vec![source_discarded_after_limit_change_diagnostic(
207                    discarded_source.source_len,
208                    discarded_source.previous_max_source_bytes,
209                )],
210            )
211        } else if let Some(sync_error) = document.sync_error {
212            return vec![document_sync_error_diagnostic(sync_error, document.version)];
213        } else {
214            analyze_document(document.text.as_ref(), analyzer, source)
215        };
216        analysis_payload_to_versioned_diagnostics(&payload, &document.uri, document.version)
217    }
218
219    fn diagnostic_result_id(diagnostics: &[tower_lsp::lsp_types::Diagnostic]) -> String {
220        let serialized = serde_json::to_vec(diagnostics).unwrap_or_default();
221        let mut hasher = std::collections::hash_map::DefaultHasher::new();
222        serialized.hash(&mut hasher);
223        format!("{:016x}", hasher.finish())
224    }
225
226    fn document_diagnostic_report(
227        diagnostics: Vec<tower_lsp::lsp_types::Diagnostic>,
228        result_id: Option<String>,
229        previous_result_id: Option<&str>,
230    ) -> DocumentDiagnosticReportResult {
231        if let Some(result_id) = result_id.clone()
232            && previous_result_id == Some(result_id.as_str())
233        {
234            return DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Unchanged(
235                RelatedUnchangedDocumentDiagnosticReport {
236                    related_documents: None,
237                    unchanged_document_diagnostic_report: UnchangedDocumentDiagnosticReport {
238                        result_id,
239                    },
240                },
241            ));
242        }
243
244        DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
245            RelatedFullDocumentDiagnosticReport {
246                related_documents: None,
247                full_document_diagnostic_report: FullDocumentDiagnosticReport {
248                    result_id,
249                    items: diagnostics,
250                },
251            },
252        ))
253    }
254
255    async fn diagnostics_for_current_context(
256        &self,
257        context: &DiagnosticContext,
258    ) -> Option<Vec<tower_lsp::lsp_types::Diagnostic>> {
259        let diagnostics = Self::diagnostics_for_document(&context.document, &context.analyzer);
260        let store = self.store.lock().await;
261        store
262            .is_diagnostic_context_current(context)
263            .then_some(diagnostics)
264    }
265
266    async fn diagnostics_or_recompute_latest(
267        &self,
268        mut context: DiagnosticContext,
269    ) -> Result<(DiagnosticContext, Vec<tower_lsp::lsp_types::Diagnostic>)> {
270        for _ in 0..MAX_DIAGNOSTIC_RECOMPUTE_ATTEMPTS {
271            if let Some(diagnostics) = self.diagnostics_for_current_context(&context).await {
272                return Ok((context, diagnostics));
273            }
274
275            let Some(latest_context) = ({
276                let store = self.store.lock().await;
277                store.diagnostic_context(&context.document.uri)
278            }) else {
279                return Ok((context, Vec::new()));
280            };
281            context = latest_context;
282        }
283
284        Err(stale_diagnostic_recompute_error())
285    }
286
287    async fn commit_diagnostic_state_if_current(
288        &self,
289        context: &DiagnosticContext,
290        state: DocumentDiagnosticState,
291    ) -> Result<()> {
292        let mut store = self.store.lock().await;
293        if store.set_diagnostic_state_if_current(context, state) {
294            Ok(())
295        } else {
296            Err(stale_diagnostic_recompute_error())
297        }
298    }
299
300    async fn publish_for_uri(&self, uri: &tower_lsp::lsp_types::Url) {
301        if self.diagnostic_pull_supported.load(Ordering::Relaxed) {
302            return;
303        }
304
305        let context = {
306            let store = self.store.lock().await;
307            store.diagnostic_context(uri)
308        };
309
310        let Some(context) = context else {
311            return;
312        };
313
314        self.publish_current_diagnostics(&context).await;
315    }
316
317    async fn publish_current_diagnostics(&self, context: &DiagnosticContext) {
318        if let Some(diagnostics) = self.diagnostics_for_current_context(context).await {
319            self.client
320                .publish_diagnostics(
321                    context.document.uri.clone(),
322                    diagnostics,
323                    Some(context.document.version),
324                )
325                .await;
326        }
327    }
328
329    async fn record_semantic_tokens_state(
330        &self,
331        context: &SnapshotContext,
332        tokens: Vec<tower_lsp::lsp_types::SemanticToken>,
333        result_id: Option<String>,
334    ) -> Result<()> {
335        let mut store = self.store.lock().await;
336        if store.set_semantic_tokens_state_if_current(
337            context,
338            SemanticTokensState::new(result_id, tokens),
339        ) {
340            Ok(())
341        } else {
342            Err(SnapshotContextKind::SemanticTokens.stale_error())
343        }
344    }
345
346    async fn ensure_workspace_symbol_snapshots_current(
347        &self,
348        contexts: &[SnapshotContext],
349    ) -> Result<()> {
350        let store = self.store.lock().await;
351        if store.workspace_symbol_snapshot_contexts_current(contexts) {
352            Ok(())
353        } else {
354            Err(SnapshotContextKind::WorkspaceSymbols.stale_error())
355        }
356    }
357
358    async fn workspace_symbol_snapshot_contexts(&self) -> Result<Vec<SnapshotContext>> {
359        loop {
360            let plan = {
361                let store = self.store.lock().await;
362                store.workspace_symbol_snapshot_build_plan(WORKSPACE_SYMBOL_SNAPSHOT_BATCH_SIZE)
363            };
364
365            if plan.batches.is_empty() {
366                return Ok(plan.contexts);
367            }
368
369            for batch in plan.batches {
370                let built = batch
371                    .into_iter()
372                    .map(|request| {
373                        let snapshot = request.build();
374                        (request, snapshot)
375                    })
376                    .collect();
377                let commit = self
378                    .store
379                    .lock()
380                    .await
381                    .snapshot_contexts_for_requests(built);
382                if commit.stale_open_documents {
383                    return Err(SnapshotContextKind::WorkspaceSymbols.stale_error());
384                }
385                // The current tower-lsp handler path exposes no explicit cancel token here.
386                tokio::task::yield_now().await;
387            }
388        }
389    }
390
391    async fn replace_analyzer(&self, options: AnalysisOptions) -> AnalyzerConfigurationChange {
392        let mut store = self.store.lock().await;
393        store.apply_analyzer_options(options)
394    }
395
396    fn client_supports_semantic_tokens_refresh(params: &InitializeParams) -> bool {
397        params
398            .capabilities
399            .workspace
400            .as_ref()
401            .and_then(|workspace| workspace.semantic_tokens.as_ref())
402            .and_then(|semantic_tokens| semantic_tokens.refresh_support)
403            .unwrap_or(false)
404    }
405
406    fn client_supports_diagnostic_pull(params: &InitializeParams) -> bool {
407        params
408            .capabilities
409            .text_document
410            .as_ref()
411            .and_then(|text_document| text_document.diagnostic.as_ref())
412            .is_some()
413    }
414
415    fn client_supports_diagnostic_refresh(params: &InitializeParams) -> bool {
416        params
417            .capabilities
418            .workspace
419            .as_ref()
420            .and_then(|workspace| workspace.diagnostic.as_ref())
421            .and_then(|diagnostic| diagnostic.refresh_support)
422            .unwrap_or(false)
423    }
424
425    fn client_supports_workspace_edit_document_changes(params: &InitializeParams) -> bool {
426        params
427            .capabilities
428            .workspace
429            .as_ref()
430            .and_then(|workspace| workspace.workspace_edit.as_ref())
431            .and_then(|workspace_edit| workspace_edit.document_changes)
432            .unwrap_or(false)
433    }
434
435    fn client_supports_hierarchical_document_symbols(params: &InitializeParams) -> bool {
436        params
437            .capabilities
438            .text_document
439            .as_ref()
440            .and_then(|text_document| text_document.document_symbol.as_ref())
441            .and_then(|document_symbol| document_symbol.hierarchical_document_symbol_support)
442            .unwrap_or(false)
443    }
444
445    async fn apply_initialization_options(
446        &self,
447        initialization_options: Option<serde_json::Value>,
448    ) -> tower_lsp::jsonrpc::Result<()> {
449        match initialization_options {
450            None => {
451                self.replace_analyzer(default_lsp_analysis_options()).await;
452                Ok(())
453            }
454            Some(value) => {
455                let options = analysis_options_with_lsp_resource_defaults(
456                    analysis_options_from_json_value(&value).map_err(|err| {
457                        tower_lsp::jsonrpc::Error::invalid_params(err.to_string())
458                    })?,
459                );
460                self.replace_analyzer(options).await;
461                Ok(())
462            }
463        }
464    }
465
466    async fn republish_all(&self) {
467        if self.diagnostic_pull_supported.load(Ordering::Relaxed) {
468            return;
469        }
470
471        let contexts = {
472            let store = self.store.lock().await;
473            store.diagnostic_contexts()
474        };
475
476        for context in contexts {
477            self.publish_current_diagnostics(&context).await;
478        }
479    }
480}
481
482#[tower_lsp::async_trait]
483impl LanguageServer for MermanLanguageServer {
484    async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
485        self.semantic_tokens_refresh_supported.store(
486            Self::client_supports_semantic_tokens_refresh(&params),
487            Ordering::Relaxed,
488        );
489        self.diagnostic_pull_supported.store(
490            Self::client_supports_diagnostic_pull(&params),
491            Ordering::Relaxed,
492        );
493        self.diagnostic_refresh_supported.store(
494            Self::client_supports_diagnostic_refresh(&params),
495            Ordering::Relaxed,
496        );
497        self.workspace_edit_document_changes_supported.store(
498            Self::client_supports_workspace_edit_document_changes(&params),
499            Ordering::Relaxed,
500        );
501        self.hierarchical_document_symbols_supported.store(
502            Self::client_supports_hierarchical_document_symbols(&params),
503            Ordering::Relaxed,
504        );
505        self.apply_initialization_options(params.initialization_options)
506            .await?;
507        Ok(InitializeResult {
508            capabilities: Self::capabilities(),
509            ..InitializeResult::default()
510        })
511    }
512
513    async fn initialized(&self, _: tower_lsp::lsp_types::InitializedParams) {
514        self.client
515            .log_message(MessageType::INFO, "merman-lsp initialized")
516            .await;
517    }
518
519    async fn shutdown(&self) -> Result<()> {
520        Ok(())
521    }
522
523    async fn did_open(&self, params: DidOpenTextDocumentParams) {
524        let doc = params.text_document;
525        let kind = document_kind_for_language_id(&doc.language_id, &doc.uri);
526        self.store
527            .lock()
528            .await
529            .open_text(doc.uri.clone(), doc.version, doc.text, kind);
530        self.publish_for_uri(&doc.uri).await;
531    }
532
533    async fn did_change(&self, params: DidChangeTextDocumentParams) {
534        let doc = params.text_document;
535        let update = self.store.lock().await.apply_text_changes(
536            doc.uri.clone(),
537            doc.version,
538            params.content_changes,
539        );
540        if update.affects_document_state() {
541            self.publish_for_uri(&doc.uri).await;
542        }
543    }
544
545    async fn did_save(&self, params: DidSaveTextDocumentParams) {
546        let uri = params.text_document.uri;
547        self.publish_for_uri(&uri).await;
548    }
549
550    async fn did_close(&self, params: DidCloseTextDocumentParams) {
551        let uri = params.text_document.uri;
552        self.store.lock().await.remove(&uri);
553        if !self.diagnostic_pull_supported.load(Ordering::Relaxed) {
554            self.client.publish_diagnostics(uri, Vec::new(), None).await;
555        }
556    }
557
558    async fn did_change_configuration(
559        &self,
560        params: tower_lsp::lsp_types::DidChangeConfigurationParams,
561    ) {
562        let options = if params.settings.is_null() {
563            default_lsp_analysis_options()
564        } else {
565            match analysis_options_from_json_value(&params.settings) {
566                Ok(options) => analysis_options_with_lsp_resource_defaults(options),
567                Err(err) => {
568                    self.client
569                        .log_message(
570                            MessageType::ERROR,
571                            format!("invalid merman analysis settings: {err}"),
572                        )
573                        .await;
574                    return;
575                }
576            }
577        };
578
579        let change = self.replace_analyzer(options).await;
580        if change.affects_diagnostics() {
581            self.republish_all().await;
582        }
583        if change.affects_snapshots()
584            && self
585                .semantic_tokens_refresh_supported
586                .load(Ordering::Relaxed)
587        {
588            let _ = self.client.semantic_tokens_refresh().await;
589        }
590        if change.affects_diagnostics()
591            && self.diagnostic_pull_supported.load(Ordering::Relaxed)
592            && self.diagnostic_refresh_supported.load(Ordering::Relaxed)
593        {
594            let _ = self.client.workspace_diagnostic_refresh().await;
595        }
596    }
597
598    async fn diagnostic(
599        &self,
600        params: DocumentDiagnosticParams,
601    ) -> Result<DocumentDiagnosticReportResult> {
602        let uri = params.text_document.uri;
603        let previous_result_id = params.previous_result_id.as_deref();
604        let (context, cached) = {
605            let store = self.store.lock().await;
606            (store.diagnostic_context(&uri), store.diagnostic_state(&uri))
607        };
608        if let Some(cached) = cached {
609            return Ok(Self::document_diagnostic_report(
610                cached.diagnostics,
611                Some(cached.result_id),
612                previous_result_id,
613            ));
614        }
615
616        let Some(context) = context else {
617            let diagnostics = Vec::new();
618            let result_id = Some(Self::diagnostic_result_id(&diagnostics));
619            return Ok(Self::document_diagnostic_report(
620                diagnostics,
621                result_id,
622                previous_result_id,
623            ));
624        };
625
626        let (context, diagnostics) = self.diagnostics_or_recompute_latest(context).await?;
627        let state = DocumentDiagnosticState {
628            result_id: Self::diagnostic_result_id(&diagnostics),
629            diagnostics,
630        };
631        self.commit_diagnostic_state_if_current(&context, state.clone())
632            .await?;
633        Ok(Self::document_diagnostic_report(
634            state.diagnostics,
635            Some(state.result_id),
636            previous_result_id,
637        ))
638    }
639
640    async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
641        let uri = params.text_document_position.text_document.uri;
642        let position = params.text_document_position.position;
643
644        self.structure_snapshot_result(&uri, |snapshot| {
645            Ok(Some(CompletionResponse::List(completion_for_snapshot(
646                snapshot, position,
647            ))))
648        })
649        .await
650    }
651
652    async fn completion_resolve(&self, item: CompletionItem) -> Result<CompletionItem> {
653        Ok(resolve_completion_item(item))
654    }
655
656    async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
657        let current_document_version = {
658            let store = self.store.lock().await;
659            store
660                .get(&params.text_document.uri)
661                .map(|document| document.version)
662        };
663        let workspace_edit_encoding = WorkspaceEditEncoding::from_document_changes_support(
664            self.workspace_edit_document_changes_supported
665                .load(Ordering::Relaxed),
666        );
667        Ok(code_actions_for_params_with_encoding(
668            &params,
669            current_document_version,
670            workspace_edit_encoding,
671        ))
672    }
673
674    async fn semantic_tokens_full(
675        &self,
676        params: SemanticTokensParams,
677    ) -> Result<Option<SemanticTokensResult>> {
678        let uri = params.text_document.uri;
679        let snapshot_context = self.semantic_snapshot_context_for_uri(&uri).await?;
680
681        let Some(snapshot_context) = snapshot_context else {
682            return Ok(None);
683        };
684        let snapshot = &snapshot_context.snapshot;
685
686        let mut tokens = semantic_tokens_for_snapshot(snapshot);
687        let result_id = semantic_tokens_result_id(snapshot, &tokens.data);
688        tokens.result_id = Some(result_id.clone());
689        self.record_semantic_tokens_state(&snapshot_context, tokens.data.clone(), Some(result_id))
690            .await?;
691
692        Ok(Some(SemanticTokensResult::Tokens(tokens)))
693    }
694
695    async fn semantic_tokens_full_delta(
696        &self,
697        params: SemanticTokensDeltaParams,
698    ) -> Result<Option<SemanticTokensFullDeltaResult>> {
699        let uri = params.text_document.uri;
700        let snapshot_context = self.semantic_snapshot_context_for_uri(&uri).await?;
701        let Some(snapshot_context) = snapshot_context else {
702            return Ok(None);
703        };
704        let snapshot = &snapshot_context.snapshot;
705
706        let current_tokens = semantic_tokens_for_snapshot(snapshot);
707        let current_result_id = semantic_tokens_result_id(snapshot, &current_tokens.data);
708        let previous = {
709            let store = self.store.lock().await;
710            store.semantic_tokens_state_for_delta(&uri, params.previous_result_id.as_str())
711        };
712        let delta = match previous {
713            Some(previous) => semantic_tokens_delta_result(
714                &previous.tokens,
715                &current_tokens.data,
716                current_result_id.clone(),
717            ),
718            _ => {
719                let mut tokens = current_tokens.clone();
720                tokens.result_id = Some(current_result_id.clone());
721                SemanticTokensFullDeltaResult::Tokens(tokens)
722            }
723        };
724
725        self.record_semantic_tokens_state(
726            &snapshot_context,
727            current_tokens.data,
728            Some(current_result_id),
729        )
730        .await?;
731
732        Ok(Some(delta))
733    }
734
735    async fn semantic_tokens_range(
736        &self,
737        params: SemanticTokensRangeParams,
738    ) -> Result<Option<SemanticTokensRangeResult>> {
739        let uri = params.text_document.uri;
740        let snapshot_context = self.semantic_snapshot_context_for_uri(&uri).await?;
741
742        let Some(snapshot_context) = snapshot_context else {
743            return Ok(None);
744        };
745        let result = semantic_tokens_for_snapshot_range(&snapshot_context.snapshot, params.range);
746        snapshot_context::ensure_snapshot_current(
747            &self.store,
748            &snapshot_context,
749            SnapshotContextKind::SemanticTokens,
750        )
751        .await?;
752
753        Ok(Some(result.into()))
754    }
755
756    async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
757        let uri = params.text_document_position_params.text_document.uri;
758        let position = params.text_document_position_params.position;
759
760        self.structure_snapshot_result(&uri, |snapshot| Ok(structure_hover(snapshot, position)))
761            .await
762    }
763
764    async fn selection_range(
765        &self,
766        params: SelectionRangeParams,
767    ) -> Result<Option<Vec<SelectionRange>>> {
768        let uri = params.text_document.uri;
769
770        self.structure_snapshot_result(&uri, |snapshot| {
771            Ok(structure_selection_ranges(snapshot, &params.positions))
772        })
773        .await
774    }
775
776    async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
777        let uri = params.text_document.uri;
778
779        self.structure_snapshot_result(&uri, |snapshot| {
780            Ok(Some(structure_folding_ranges(snapshot)))
781        })
782        .await
783    }
784
785    async fn document_symbol(
786        &self,
787        params: DocumentSymbolParams,
788    ) -> Result<Option<DocumentSymbolResponse>> {
789        let uri = params.text_document.uri;
790        let hierarchical_supported = self
791            .hierarchical_document_symbols_supported
792            .load(Ordering::Relaxed);
793
794        self.structure_snapshot_result(&uri, |snapshot| {
795            Ok(Some(structure_document_symbols_with_hierarchy_support(
796                snapshot,
797                hierarchical_supported,
798            )))
799        })
800        .await
801    }
802
803    async fn goto_definition(
804        &self,
805        params: GotoDefinitionParams,
806    ) -> Result<Option<GotoDefinitionResponse>> {
807        let uri = params.text_document_position_params.text_document.uri;
808        let position = params.text_document_position_params.position;
809
810        self.structure_snapshot_result(&uri, |snapshot| {
811            Ok(structure_goto_definition(snapshot, position))
812        })
813        .await
814    }
815
816    async fn references(
817        &self,
818        params: ReferenceParams,
819    ) -> Result<Option<Vec<tower_lsp::lsp_types::Location>>> {
820        let uri = params.text_document_position.text_document.uri;
821        let position = params.text_document_position.position;
822
823        self.structure_snapshot_result(&uri, |snapshot| {
824            Ok(structure_references(
825                snapshot,
826                position,
827                params.context.include_declaration,
828            ))
829        })
830        .await
831    }
832
833    async fn prepare_rename(
834        &self,
835        params: TextDocumentPositionParams,
836    ) -> Result<Option<PrepareRenameResponse>> {
837        let uri = params.text_document.uri;
838        let position = params.position;
839
840        self.structure_snapshot_result(&uri, |snapshot| {
841            Ok(structure_prepare_rename(snapshot, position))
842        })
843        .await
844    }
845
846    async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
847        let uri = params.text_document_position.text_document.uri.clone();
848        let workspace_edit_encoding = WorkspaceEditEncoding::from_document_changes_support(
849            self.workspace_edit_document_changes_supported
850                .load(Ordering::Relaxed),
851        );
852
853        self.structure_snapshot_result(&uri, |snapshot| {
854            structure_rename_with_workspace_edit_encoding(snapshot, params, workspace_edit_encoding)
855        })
856        .await
857    }
858
859    async fn symbol(
860        &self,
861        params: WorkspaceSymbolParams,
862    ) -> Result<Option<Vec<tower_lsp::lsp_types::SymbolInformation>>> {
863        let contexts = self.workspace_symbol_snapshot_contexts().await?;
864
865        let snapshots = contexts
866            .iter()
867            .map(|context| Arc::clone(&context.snapshot))
868            .collect::<Vec<_>>();
869        let symbols = structure_workspace_symbols_for_snapshots(&snapshots, &params.query);
870
871        self.ensure_workspace_symbol_snapshots_current(&contexts)
872            .await?;
873
874        Ok(Some(symbols))
875    }
876}
877
878fn stale_diagnostic_recompute_error() -> tower_lsp::jsonrpc::Error {
879    let mut error = tower_lsp::jsonrpc::Error::content_modified();
880    error.message = "diagnostic document changed while recomputing".into();
881    error
882}
883
884fn document_sync_error_diagnostic(
885    sync_error: DocumentSyncError,
886    document_version: i32,
887) -> Diagnostic {
888    let message = match sync_error {
889        DocumentSyncError::InvalidIncrementalRange => {
890            "document text is out of sync after an invalid incremental edit range; send a full document replacement or reopen the document"
891        }
892    };
893    Diagnostic {
894        range: Range::new(Position::new(0, 0), Position::new(0, 0)),
895        severity: Some(DiagnosticSeverity::ERROR),
896        code: Some(NumberOrString::String(
897            "merman.lsp.document_sync_lost".to_string(),
898        )),
899        source: Some("merman".to_string()),
900        message: message.to_string(),
901        related_information: None,
902        tags: None,
903        code_description: None,
904        data: Some(serde_json::json!({
905            "documentVersion": document_version,
906        })),
907    }
908}
909
910fn source_descriptor_for_document(
911    uri: &tower_lsp::lsp_types::Url,
912    kind: DocumentKind,
913) -> merman_analysis::SourceDescriptor {
914    let source_kind = match kind {
915        DocumentKind::Diagram => SourceKind::Diagram,
916        DocumentKind::Markdown => SourceKind::Markdown,
917        DocumentKind::Mdx => SourceKind::Mdx,
918    };
919    source_descriptor_for_kind(Some(uri.as_str()), source_kind)
920}
921
922fn document_kind_for_language_id(
923    language_id: &str,
924    uri: &tower_lsp::lsp_types::Url,
925) -> DocumentKind {
926    match language_id {
927        "markdown" => DocumentKind::Markdown,
928        "mdx" => DocumentKind::Mdx,
929        "mermaid" => DocumentKind::Diagram,
930        _ => DocumentKind::from_path(uri.path()),
931    }
932}
933
934#[cfg(test)]
935mod tests;