Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:00:56.859Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

dynamic_registration.rs

1055 lines · 47.1 KB · rust
1//! Handling of `client/registerCapability` and `client/unregisterCapability` requests,
2//! which let language servers toggle their capabilities at runtime.
3//!
4//! Multiple registrations of the same capability may coexist: the most recent one is
5//! "active" and mirrored into the server's [`lsp::ServerCapabilities`], while the rest
6//! (including the statically declared capability, if any) are kept to be restored when
7//! the active registration is unregistered.
8
9use anyhow::{Context as _, Result};
10use collections::{BTreeSet, HashSet};
11use gpui::{Context, SharedString};
12use lsp::{
13    CompletionOptions, DiagnosticServerCapabilities, LanguageServer, LanguageServerId, OneOf,
14    TextDocumentSyncSaveOptions,
15};
16
17use crate::lsp_store::{
18    LanguageServerState, LspStore, RenamePathsWatchedForServer, lsp_workspace_diagnostics_refresh,
19    notify_server_capabilities_updated,
20};
21
22#[derive(Debug)]
23pub(super) enum RegistrationSource {
24    Static,
25    Dynamic(String),
26}
27
28impl RegistrationSource {
29    pub(super) fn registration_id(&self) -> Option<&str> {
30        match self {
31            RegistrationSource::Static => None,
32            RegistrationSource::Dynamic(id) => Some(id),
33        }
34    }
35}
36
37pub(super) type CapabilityRegistrations<T> = Vec<(RegistrationSource, T)>;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40enum CapabilityUnregistration {
41    NotFound,
42    Removed { active_capability_changed: bool },
43}
44
45impl CapabilityUnregistration {
46    fn removed(self) -> bool {
47        self != CapabilityUnregistration::NotFound
48    }
49
50    fn capability_changed(self) -> bool {
51        self == CapabilityUnregistration::Removed {
52            active_capability_changed: true,
53        }
54    }
55}
56
57#[derive(Default, Debug)]
58pub(super) struct DynamicRegistrations {
59    pub(super) did_change_watched_files: HashSet<String>,
60    pub(super) diagnostics: CapabilityRegistrations<DiagnosticServerCapabilities>,
61    workspace_folders: CapabilityRegistrations<lsp::WorkspaceFoldersServerCapabilities>,
62    workspace_symbol: CapabilityRegistrations<OneOf<bool, lsp::WorkspaceSymbolOptions>>,
63    file_operations: CapabilityRegistrations<lsp::WorkspaceFileOperationsServerCapabilities>,
64    execute_command: CapabilityRegistrations<lsp::ExecuteCommandOptions>,
65    range_formatting: CapabilityRegistrations<OneOf<bool, lsp::DocumentRangeFormattingOptions>>,
66    on_type_formatting: CapabilityRegistrations<lsp::DocumentOnTypeFormattingOptions>,
67    formatting: CapabilityRegistrations<OneOf<bool, lsp::DocumentFormattingOptions>>,
68    rename: CapabilityRegistrations<OneOf<bool, lsp::RenameOptions>>,
69    code_action: CapabilityRegistrations<lsp::CodeActionProviderCapability>,
70    definition: CapabilityRegistrations<OneOf<bool, lsp::DefinitionOptions>>,
71    completion: CapabilityRegistrations<lsp::CompletionOptions>,
72    hover: CapabilityRegistrations<lsp::HoverProviderCapability>,
73    signature_help: CapabilityRegistrations<lsp::SignatureHelpOptions>,
74    color: CapabilityRegistrations<lsp::ColorProviderCapability>,
75    folding_range: CapabilityRegistrations<lsp::FoldingRangeProviderCapability>,
76    document_link: CapabilityRegistrations<lsp::DocumentLinkOptions>,
77    semantic_tokens: CapabilityRegistrations<lsp::SemanticTokensServerCapabilities>,
78    inlay_hint: CapabilityRegistrations<OneOf<bool, lsp::InlayHintServerCapabilities>>,
79    code_lens: CapabilityRegistrations<lsp::CodeLensOptions>,
80    document_symbol: CapabilityRegistrations<OneOf<bool, lsp::DocumentSymbolOptions>>,
81}
82
83impl LspStore {
84    /// Returns `true` when the registration changed the server's active capability value:
85    /// duplicate-ID replacements of non-active registrations and (re-)registrations with
86    /// options identical to the active ones do not.
87    fn register_dynamic_capability<T: Clone + PartialEq>(
88        &mut self,
89        server: &LanguageServer,
90        method: &str,
91        registration_id: String,
92        options: T,
93        cx: &mut Context<Self>,
94        registrations_of: impl FnOnce(&mut DynamicRegistrations) -> &mut CapabilityRegistrations<T>,
95        capability_of: impl Fn(&mut lsp::ServerCapabilities) -> &mut Option<T>,
96    ) -> anyhow::Result<bool> {
97        let server_id = server.server_id();
98        let local = self
99            .as_local_mut()
100            .context("Expected LSP Store to be local")?;
101        let registrations = registrations_of(
102            local
103                .language_server_dynamic_registrations
104                .entry(server_id)
105                .or_default(),
106        );
107        if registrations.is_empty() {
108            let mut initial_capabilities = server.capabilities();
109            if let Some(static_options) = capability_of(&mut initial_capabilities).take() {
110                registrations.push((RegistrationSource::Static, static_options));
111            }
112        }
113        let previously_active = registrations.last().map(|(_, options)| options.clone());
114        if let Some(index) = registrations
115            .iter()
116            .position(|(source, _)| source.registration_id() == Some(registration_id.as_str()))
117        {
118            log::warn!(
119                "Received a duplicate {method} registration with ID {registration_id}, replacing the previous one"
120            );
121            registrations[index].1 = options;
122        } else {
123            registrations.push((RegistrationSource::Dynamic(registration_id), options));
124        }
125        let active = registrations.last().map(|(_, options)| options.clone());
126        let active_changed = active != previously_active;
127        if active_changed {
128            server.update_capabilities(|capabilities| *capability_of(capabilities) = active);
129            notify_server_capabilities_updated(server, cx);
130        }
131        Ok(active_changed)
132    }
133
134    fn unregister_dynamic_capability<T: Clone + PartialEq>(
135        &mut self,
136        server: &LanguageServer,
137        unregistration: &lsp::Unregistration,
138        cx: &mut Context<Self>,
139        registrations_of: impl FnOnce(&mut DynamicRegistrations) -> &mut CapabilityRegistrations<T>,
140        capability_of: impl FnOnce(&mut lsp::ServerCapabilities) -> &mut Option<T>,
141    ) -> anyhow::Result<CapabilityUnregistration> {
142        let server_id = server.server_id();
143        let local = self
144            .as_local_mut()
145            .context("Expected LSP Store to be local")?;
146        let registrations = local
147            .language_server_dynamic_registrations
148            .get_mut(&server_id)
149            .map(registrations_of);
150        let index = registrations.as_ref().and_then(|registrations| {
151            registrations.iter().position(|(source, _)| {
152                source.registration_id() == Some(unregistration.id.as_str())
153            })
154        });
155        let (Some(registrations), Some(index)) = (registrations, index) else {
156            log::warn!(
157                "Attempted to unregister non-existent {} registration with ID {}",
158                unregistration.method,
159                unregistration.id
160            );
161            return Ok(CapabilityUnregistration::NotFound);
162        };
163        let removed_active = index + 1 == registrations.len();
164        let (_, removed_options) = registrations.remove(index);
165        let restored = registrations.last().map(|(_, options)| options.clone());
166        if !removed_active || restored.as_ref() == Some(&removed_options) {
167            return Ok(CapabilityUnregistration::Removed {
168                active_capability_changed: false,
169            });
170        }
171        server.update_capabilities(|capabilities| *capability_of(capabilities) = restored);
172        notify_server_capabilities_updated(server, cx);
173        Ok(CapabilityUnregistration::Removed {
174            active_capability_changed: true,
175        })
176    }
177
178    fn update_paths_watched_for_rename(&mut self, server: &LanguageServer) {
179        let Some(local) = self.as_local_mut() else {
180            return;
181        };
182        let watcher = server
183            .capabilities()
184            .workspace
185            .and_then(|workspace| workspace.file_operations)
186            .and_then(|file_operations| {
187                let did_rename_caps = file_operations.did_rename.as_ref();
188                let will_rename_caps = file_operations.will_rename.as_ref();
189                did_rename_caps.or(will_rename_caps)?;
190                Some(
191                    RenamePathsWatchedForServer::default()
192                        .with_did_rename_patterns(did_rename_caps)
193                        .with_will_rename_patterns(will_rename_caps),
194                )
195            });
196        match watcher {
197            Some(watcher) => {
198                local
199                    .language_server_paths_watched_for_rename
200                    .insert(server.server_id(), watcher);
201            }
202            None => {
203                local
204                    .language_server_paths_watched_for_rename
205                    .remove(&server.server_id());
206            }
207        }
208    }
209
210    fn apply_completion_triggers(
211        &self,
212        server_id: LanguageServerId,
213        options: Option<CompletionOptions>,
214        cx: &mut Context<Self>,
215    ) {
216        let Some(local) = self.as_local() else {
217            return;
218        };
219        let mut buffers_with_language_server = Vec::new();
220        for handle in self.buffer_store.read(cx).buffers() {
221            let buffer_id = handle.read(cx).remote_id();
222            if local
223                .buffers_opened_in_servers
224                .get(&buffer_id)
225                .filter(|s| s.contains(&server_id))
226                .is_some()
227            {
228                buffers_with_language_server.push(handle);
229            }
230        }
231        let triggers = options
232            .and_then(|options| options.trigger_characters)
233            .unwrap_or_default()
234            .into_iter()
235            .collect::<BTreeSet<_>>();
236        for handle in buffers_with_language_server {
237            let triggers = triggers.clone();
238            handle.update(cx, move |buffer, cx| {
239                buffer.set_completion_triggers(server_id, triggers, cx);
240            });
241        }
242    }
243
244    pub(super) fn register_server_capabilities(
245        &mut self,
246        server_id: LanguageServerId,
247        params: lsp::RegistrationParams,
248        cx: &mut Context<Self>,
249    ) -> anyhow::Result<()> {
250        let server = self
251            .language_server_for_id(server_id)
252            .with_context(|| format!("no server {server_id} found"))?;
253        for reg in params.registrations {
254            match reg.method.as_str() {
255                "workspace/didChangeWatchedFiles" => {
256                    if let Some(options) = reg.register_options {
257                        let notify = if let Some(local_lsp_store) = self.as_local_mut() {
258                            let caps = serde_json::from_value(options)?;
259                            local_lsp_store
260                                .on_lsp_did_change_watched_files(server_id, &reg.id, caps, cx);
261                            true
262                        } else {
263                            false
264                        };
265                        if notify {
266                            notify_server_capabilities_updated(&server, cx);
267                        }
268                    }
269                }
270                "workspace/didChangeConfiguration" => {
271                    // Ignore payload since we notify clients of setting changes unconditionally, relying on them pulling the latest settings.
272                }
273                "workspace/didChangeWorkspaceFolders" => {
274                    // In this case register options is an empty object, we can ignore it
275                    let caps = lsp::WorkspaceFoldersServerCapabilities {
276                        supported: Some(true),
277                        change_notifications: Some(OneOf::Right(reg.id.clone())),
278                    };
279                    self.register_dynamic_capability(
280                        &server,
281                        &reg.method,
282                        reg.id,
283                        caps,
284                        cx,
285                        |registrations| &mut registrations.workspace_folders,
286                        |capabilities| {
287                            &mut capabilities
288                                .workspace
289                                .get_or_insert_default()
290                                .workspace_folders
291                        },
292                    )?;
293                }
294                "workspace/symbol" => {
295                    let options = parse_register_capabilities(reg.register_options)?;
296                    self.register_dynamic_capability(
297                        &server,
298                        &reg.method,
299                        reg.id,
300                        options,
301                        cx,
302                        |registrations| &mut registrations.workspace_symbol,
303                        |capabilities| &mut capabilities.workspace_symbol_provider,
304                    )?;
305                }
306                "workspace/fileOperations" => {
307                    if let Some(options) = reg.register_options {
308                        let caps = serde_json::from_value(options)?;
309                        if self.register_dynamic_capability(
310                            &server,
311                            &reg.method,
312                            reg.id,
313                            caps,
314                            cx,
315                            |registrations| &mut registrations.file_operations,
316                            |capabilities| {
317                                &mut capabilities
318                                    .workspace
319                                    .get_or_insert_default()
320                                    .file_operations
321                            },
322                        )? {
323                            self.update_paths_watched_for_rename(&server);
324                        }
325                    }
326                }
327                "workspace/executeCommand" => {
328                    if let Some(options) = reg.register_options {
329                        let options = serde_json::from_value(options)?;
330                        self.register_dynamic_capability(
331                            &server,
332                            &reg.method,
333                            reg.id,
334                            options,
335                            cx,
336                            |registrations| &mut registrations.execute_command,
337                            |capabilities| &mut capabilities.execute_command_provider,
338                        )?;
339                    }
340                }
341                "textDocument/rangeFormatting" => {
342                    let options = parse_register_capabilities(reg.register_options)?;
343                    self.register_dynamic_capability(
344                        &server,
345                        &reg.method,
346                        reg.id,
347                        options,
348                        cx,
349                        |registrations| &mut registrations.range_formatting,
350                        |capabilities| &mut capabilities.document_range_formatting_provider,
351                    )?;
352                }
353                "textDocument/onTypeFormatting" => {
354                    if let Some(options) = reg
355                        .register_options
356                        .map(serde_json::from_value)
357                        .transpose()?
358                    {
359                        self.register_dynamic_capability(
360                            &server,
361                            &reg.method,
362                            reg.id,
363                            options,
364                            cx,
365                            |registrations| &mut registrations.on_type_formatting,
366                            |capabilities| &mut capabilities.document_on_type_formatting_provider,
367                        )?;
368                    }
369                }
370                "textDocument/formatting" => {
371                    let options = parse_register_capabilities(reg.register_options)?;
372                    self.register_dynamic_capability(
373                        &server,
374                        &reg.method,
375                        reg.id,
376                        options,
377                        cx,
378                        |registrations| &mut registrations.formatting,
379                        |capabilities| &mut capabilities.document_formatting_provider,
380                    )?;
381                }
382                "textDocument/rename" => {
383                    let options = parse_register_capabilities(reg.register_options)?;
384                    self.register_dynamic_capability(
385                        &server,
386                        &reg.method,
387                        reg.id,
388                        options,
389                        cx,
390                        |registrations| &mut registrations.rename,
391                        |capabilities| &mut capabilities.rename_provider,
392                    )?;
393                }
394                "textDocument/inlayHint" => {
395                    let options = parse_register_capabilities(reg.register_options)?;
396                    if self.register_dynamic_capability(
397                        &server,
398                        &reg.method,
399                        reg.id,
400                        options,
401                        cx,
402                        |registrations| &mut registrations.inlay_hint,
403                        |capabilities| &mut capabilities.inlay_hint_provider,
404                    )? {
405                        self.refresh_inlay_hints(server_id, cx);
406                    }
407                }
408                "textDocument/documentSymbol" => {
409                    let options = parse_register_capabilities(reg.register_options)?;
410                    if self.register_dynamic_capability(
411                        &server,
412                        &reg.method,
413                        reg.id,
414                        options,
415                        cx,
416                        |registrations| &mut registrations.document_symbol,
417                        |capabilities| &mut capabilities.document_symbol_provider,
418                    )? {
419                        self.refresh_document_symbols(Some(server_id), cx);
420                    }
421                }
422                "textDocument/codeAction" => {
423                    let options = parse_register_capabilities(reg.register_options)?;
424                    let provider = match options {
425                        OneOf::Left(value) => lsp::CodeActionProviderCapability::Simple(value),
426                        OneOf::Right(caps) => caps,
427                    };
428                    self.register_dynamic_capability(
429                        &server,
430                        &reg.method,
431                        reg.id,
432                        provider,
433                        cx,
434                        |registrations| &mut registrations.code_action,
435                        |capabilities| &mut capabilities.code_action_provider,
436                    )?;
437                }
438                "textDocument/definition" => {
439                    let options = parse_register_capabilities(reg.register_options)?;
440                    self.register_dynamic_capability(
441                        &server,
442                        &reg.method,
443                        reg.id,
444                        options,
445                        cx,
446                        |registrations| &mut registrations.definition,
447                        |capabilities| &mut capabilities.definition_provider,
448                    )?;
449                }
450                "textDocument/completion" => {
451                    if let Some(caps) = reg
452                        .register_options
453                        .map(serde_json::from_value::<CompletionOptions>)
454                        .transpose()?
455                    {
456                        if self.register_dynamic_capability(
457                            &server,
458                            &reg.method,
459                            reg.id,
460                            caps,
461                            cx,
462                            |registrations| &mut registrations.completion,
463                            |capabilities| &mut capabilities.completion_provider,
464                        )? {
465                            let active = server.capabilities().completion_provider;
466                            self.apply_completion_triggers(server_id, active, cx);
467                        }
468                    }
469                }
470                "textDocument/hover" => {
471                    let options = parse_register_capabilities(reg.register_options)?;
472                    let provider = match options {
473                        OneOf::Left(value) => lsp::HoverProviderCapability::Simple(value),
474                        OneOf::Right(caps) => caps,
475                    };
476                    self.register_dynamic_capability(
477                        &server,
478                        &reg.method,
479                        reg.id,
480                        provider,
481                        cx,
482                        |registrations| &mut registrations.hover,
483                        |capabilities| &mut capabilities.hover_provider,
484                    )?;
485                }
486                "textDocument/signatureHelp" => {
487                    if let Some(caps) = reg
488                        .register_options
489                        .map(serde_json::from_value)
490                        .transpose()?
491                    {
492                        self.register_dynamic_capability(
493                            &server,
494                            &reg.method,
495                            reg.id,
496                            caps,
497                            cx,
498                            |registrations| &mut registrations.signature_help,
499                            |capabilities| &mut capabilities.signature_help_provider,
500                        )?;
501                    }
502                }
503                "textDocument/didChange" => {
504                    if let Some(sync_kind) = reg
505                        .register_options
506                        .and_then(|opts| opts.get("syncKind").cloned())
507                        .map(serde_json::from_value::<lsp::TextDocumentSyncKind>)
508                        .transpose()?
509                    {
510                        server.update_capabilities(|capabilities| {
511                            let mut sync_options = take_text_document_sync_options(capabilities);
512                            sync_options.change = Some(sync_kind);
513                            capabilities.text_document_sync =
514                                Some(lsp::TextDocumentSyncCapability::Options(sync_options));
515                        });
516                        notify_server_capabilities_updated(&server, cx);
517                    }
518                }
519                "textDocument/didSave" => {
520                    if let Some(include_text) = reg
521                        .register_options
522                        .map(|opts| {
523                            let transpose = opts
524                                .get("includeText")
525                                .cloned()
526                                .map(serde_json::from_value::<Option<bool>>)
527                                .transpose();
528                            match transpose {
529                                Ok(value) => Ok(value.flatten()),
530                                Err(e) => Err(e),
531                            }
532                        })
533                        .transpose()?
534                    {
535                        server.update_capabilities(|capabilities| {
536                            let mut sync_options = take_text_document_sync_options(capabilities);
537                            sync_options.save =
538                                Some(TextDocumentSyncSaveOptions::SaveOptions(lsp::SaveOptions {
539                                    include_text,
540                                }));
541                            capabilities.text_document_sync =
542                                Some(lsp::TextDocumentSyncCapability::Options(sync_options));
543                        });
544                        notify_server_capabilities_updated(&server, cx);
545                    }
546                }
547                "textDocument/codeLens" => {
548                    if let Some(options) = reg
549                        .register_options
550                        .map(serde_json::from_value)
551                        .transpose()?
552                    {
553                        if self.register_dynamic_capability(
554                            &server,
555                            &reg.method,
556                            reg.id,
557                            options,
558                            cx,
559                            |registrations| &mut registrations.code_lens,
560                            |capabilities| &mut capabilities.code_lens_provider,
561                        )? {
562                            self.refresh_code_lens(Some(server_id), cx);
563                        }
564                    }
565                }
566                "textDocument/diagnostic" => {
567                    if let Some(caps) = reg
568                        .register_options
569                        .map(serde_json::from_value::<DiagnosticServerCapabilities>)
570                        .transpose()?
571                    {
572                        let registration_id = reg.id.clone();
573                        self.register_dynamic_capability(
574                            &server,
575                            &reg.method,
576                            reg.id,
577                            caps.clone(),
578                            cx,
579                            |registrations| &mut registrations.diagnostics,
580                            |capabilities| &mut capabilities.diagnostic_provider,
581                        )?;
582
583                        let supports_workspace_diagnostics =
584                            |capabilities: &DiagnosticServerCapabilities| match capabilities {
585                                DiagnosticServerCapabilities::Options(diagnostic_options) => {
586                                    diagnostic_options.workspace_diagnostics
587                                }
588                                DiagnosticServerCapabilities::RegistrationOptions(
589                                    diagnostic_registration_options,
590                                ) => {
591                                    diagnostic_registration_options
592                                        .diagnostic_options
593                                        .workspace_diagnostics
594                                }
595                            };
596
597                        let local = self
598                            .as_local_mut()
599                            .context("Expected LSP Store to be local")?;
600                        let state = local
601                            .language_servers
602                            .get_mut(&server_id)
603                            .context("Could not obtain Language Servers state")?;
604                        if let LanguageServerState::Running {
605                            workspace_diagnostics_refresh_tasks,
606                            ..
607                        } = state
608                        {
609                            workspace_diagnostics_refresh_tasks
610                                .remove(&Some(registration_id.clone()));
611                            if supports_workspace_diagnostics(&caps)
612                                && let Some(task) = lsp_workspace_diagnostics_refresh(
613                                    Some(registration_id.clone()),
614                                    caps,
615                                    server.clone(),
616                                    cx,
617                                )
618                            {
619                                workspace_diagnostics_refresh_tasks
620                                    .insert(Some(registration_id), task);
621                            }
622                        }
623
624                        let _ = self.pull_document_diagnostics_for_server(server_id, None, cx);
625                    }
626                }
627                "textDocument/documentColor" => {
628                    let options = parse_register_capabilities(reg.register_options)?;
629                    let provider = match options {
630                        OneOf::Left(value) => lsp::ColorProviderCapability::Simple(value),
631                        OneOf::Right(caps) => caps,
632                    };
633                    if self.register_dynamic_capability(
634                        &server,
635                        &reg.method,
636                        reg.id,
637                        provider,
638                        cx,
639                        |registrations| &mut registrations.color,
640                        |capabilities| &mut capabilities.color_provider,
641                    )? {
642                        self.refresh_document_colors(Some(server_id), cx);
643                    }
644                }
645                "textDocument/foldingRange" => {
646                    let options = parse_register_capabilities(reg.register_options)?;
647                    let provider = match options {
648                        OneOf::Left(value) => lsp::FoldingRangeProviderCapability::Simple(value),
649                        OneOf::Right(caps) => caps,
650                    };
651                    if self.register_dynamic_capability(
652                        &server,
653                        &reg.method,
654                        reg.id,
655                        provider,
656                        cx,
657                        |registrations| &mut registrations.folding_range,
658                        |capabilities| &mut capabilities.folding_range_provider,
659                    )? {
660                        self.refresh_folding_ranges(Some(server_id), cx);
661                    }
662                }
663                "textDocument/documentLink" => {
664                    if let Some(caps) = reg
665                        .register_options
666                        .map(serde_json::from_value)
667                        .transpose()?
668                    {
669                        if self.register_dynamic_capability(
670                            &server,
671                            &reg.method,
672                            reg.id,
673                            caps,
674                            cx,
675                            |registrations| &mut registrations.document_link,
676                            |capabilities| &mut capabilities.document_link_provider,
677                        )? {
678                            self.refresh_document_links(Some(server_id), cx);
679                        }
680                    }
681                }
682                "textDocument/semanticTokens" => {
683                    if let Some(caps) = reg
684                        .register_options
685                        .map(serde_json::from_value::<lsp::SemanticTokensRegistrationOptions>)
686                        .transpose()?
687                    {
688                        if self.register_dynamic_capability(
689                            &server,
690                            &reg.method,
691                            reg.id,
692                            lsp::SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(caps),
693                            cx,
694                            |registrations| &mut registrations.semantic_tokens,
695                            |capabilities| &mut capabilities.semantic_tokens_provider,
696                        )? {
697                            // Re-query already-open buffers, which would otherwise keep
698                            // tree-sitter-only highlighting until edited.
699                            self.refresh_semantic_tokens(server_id, cx);
700                        }
701                    }
702                }
703                _ => log::warn!("unhandled capability registration: {reg:?}"),
704            }
705        }
706
707        Ok(())
708    }
709
710    pub(super) fn unregister_server_capabilities(
711        &mut self,
712        server_id: LanguageServerId,
713        params: lsp::UnregistrationParams,
714        cx: &mut Context<Self>,
715    ) -> anyhow::Result<()> {
716        let server = self
717            .language_server_for_id(server_id)
718            .with_context(|| format!("no server {server_id} found"))?;
719        for unreg in params.unregisterations.iter() {
720            match unreg.method.as_str() {
721                "workspace/didChangeWatchedFiles" => {
722                    let notify = if let Some(local_lsp_store) = self.as_local_mut() {
723                        local_lsp_store
724                            .on_lsp_unregister_did_change_watched_files(server_id, &unreg.id, cx);
725                        true
726                    } else {
727                        false
728                    };
729                    if notify {
730                        notify_server_capabilities_updated(&server, cx);
731                    }
732                }
733                "workspace/didChangeConfiguration" => {
734                    // Ignore payload since we notify clients of setting changes unconditionally, relying on them pulling the latest settings.
735                }
736                "workspace/didChangeWorkspaceFolders" => {
737                    self.unregister_dynamic_capability(
738                        &server,
739                        unreg,
740                        cx,
741                        |registrations| &mut registrations.workspace_folders,
742                        |capabilities| {
743                            &mut capabilities
744                                .workspace
745                                .get_or_insert_default()
746                                .workspace_folders
747                        },
748                    )?;
749                }
750                "workspace/symbol" => {
751                    self.unregister_dynamic_capability(
752                        &server,
753                        unreg,
754                        cx,
755                        |registrations| &mut registrations.workspace_symbol,
756                        |capabilities| &mut capabilities.workspace_symbol_provider,
757                    )?;
758                }
759                "workspace/fileOperations" => {
760                    if self
761                        .unregister_dynamic_capability(
762                            &server,
763                            unreg,
764                            cx,
765                            |registrations| &mut registrations.file_operations,
766                            |capabilities| {
767                                &mut capabilities
768                                    .workspace
769                                    .get_or_insert_default()
770                                    .file_operations
771                            },
772                        )?
773                        .capability_changed()
774                    {
775                        self.update_paths_watched_for_rename(&server);
776                    }
777                }
778                "workspace/executeCommand" => {
779                    self.unregister_dynamic_capability(
780                        &server,
781                        unreg,
782                        cx,
783                        |registrations| &mut registrations.execute_command,
784                        |capabilities| &mut capabilities.execute_command_provider,
785                    )?;
786                }
787                "textDocument/rangeFormatting" => {
788                    self.unregister_dynamic_capability(
789                        &server,
790                        unreg,
791                        cx,
792                        |registrations| &mut registrations.range_formatting,
793                        |capabilities| &mut capabilities.document_range_formatting_provider,
794                    )?;
795                }
796                "textDocument/onTypeFormatting" => {
797                    self.unregister_dynamic_capability(
798                        &server,
799                        unreg,
800                        cx,
801                        |registrations| &mut registrations.on_type_formatting,
802                        |capabilities| &mut capabilities.document_on_type_formatting_provider,
803                    )?;
804                }
805                "textDocument/formatting" => {
806                    self.unregister_dynamic_capability(
807                        &server,
808                        unreg,
809                        cx,
810                        |registrations| &mut registrations.formatting,
811                        |capabilities| &mut capabilities.document_formatting_provider,
812                    )?;
813                }
814                "textDocument/rename" => {
815                    self.unregister_dynamic_capability(
816                        &server,
817                        unreg,
818                        cx,
819                        |registrations| &mut registrations.rename,
820                        |capabilities| &mut capabilities.rename_provider,
821                    )?;
822                }
823                "textDocument/codeAction" => {
824                    self.unregister_dynamic_capability(
825                        &server,
826                        unreg,
827                        cx,
828                        |registrations| &mut registrations.code_action,
829                        |capabilities| &mut capabilities.code_action_provider,
830                    )?;
831                }
832                "textDocument/definition" => {
833                    self.unregister_dynamic_capability(
834                        &server,
835                        unreg,
836                        cx,
837                        |registrations| &mut registrations.definition,
838                        |capabilities| &mut capabilities.definition_provider,
839                    )?;
840                }
841                "textDocument/completion" => {
842                    if self
843                        .unregister_dynamic_capability(
844                            &server,
845                            unreg,
846                            cx,
847                            |registrations| &mut registrations.completion,
848                            |capabilities| &mut capabilities.completion_provider,
849                        )?
850                        .capability_changed()
851                    {
852                        let restored = server.capabilities().completion_provider;
853                        self.apply_completion_triggers(server_id, restored, cx);
854                    }
855                }
856                "textDocument/hover" => {
857                    self.unregister_dynamic_capability(
858                        &server,
859                        unreg,
860                        cx,
861                        |registrations| &mut registrations.hover,
862                        |capabilities| &mut capabilities.hover_provider,
863                    )?;
864                }
865                "textDocument/signatureHelp" => {
866                    self.unregister_dynamic_capability(
867                        &server,
868                        unreg,
869                        cx,
870                        |registrations| &mut registrations.signature_help,
871                        |capabilities| &mut capabilities.signature_help_provider,
872                    )?;
873                }
874                "textDocument/semanticTokens" => {
875                    if self
876                        .unregister_dynamic_capability(
877                            &server,
878                            unreg,
879                            cx,
880                            |registrations| &mut registrations.semantic_tokens,
881                            |capabilities| &mut capabilities.semantic_tokens_provider,
882                        )?
883                        .capability_changed()
884                    {
885                        self.refresh_semantic_tokens(server_id, cx);
886                    }
887                }
888                "textDocument/didChange" => {
889                    server.update_capabilities(|capabilities| {
890                        let mut sync_options = take_text_document_sync_options(capabilities);
891                        sync_options.change = None;
892                        capabilities.text_document_sync =
893                            Some(lsp::TextDocumentSyncCapability::Options(sync_options));
894                    });
895                    notify_server_capabilities_updated(&server, cx);
896                }
897                "textDocument/didSave" => {
898                    server.update_capabilities(|capabilities| {
899                        let mut sync_options = take_text_document_sync_options(capabilities);
900                        sync_options.save = None;
901                        capabilities.text_document_sync =
902                            Some(lsp::TextDocumentSyncCapability::Options(sync_options));
903                    });
904                    notify_server_capabilities_updated(&server, cx);
905                }
906                "textDocument/inlayHint" => {
907                    if self
908                        .unregister_dynamic_capability(
909                            &server,
910                            unreg,
911                            cx,
912                            |registrations| &mut registrations.inlay_hint,
913                            |capabilities| &mut capabilities.inlay_hint_provider,
914                        )?
915                        .capability_changed()
916                    {
917                        self.refresh_inlay_hints(server_id, cx);
918                    }
919                }
920                "textDocument/documentSymbol" => {
921                    if self
922                        .unregister_dynamic_capability(
923                            &server,
924                            unreg,
925                            cx,
926                            |registrations| &mut registrations.document_symbol,
927                            |capabilities| &mut capabilities.document_symbol_provider,
928                        )?
929                        .capability_changed()
930                    {
931                        self.refresh_document_symbols(Some(server_id), cx);
932                    }
933                }
934                "textDocument/codeLens" => {
935                    if self
936                        .unregister_dynamic_capability(
937                            &server,
938                            unreg,
939                            cx,
940                            |registrations| &mut registrations.code_lens,
941                            |capabilities| &mut capabilities.code_lens_provider,
942                        )?
943                        .capability_changed()
944                    {
945                        self.refresh_code_lens(Some(server_id), cx);
946                    }
947                }
948                "textDocument/diagnostic" => {
949                    if self
950                        .unregister_dynamic_capability(
951                            &server,
952                            unreg,
953                            cx,
954                            |registrations| &mut registrations.diagnostics,
955                            |capabilities| &mut capabilities.diagnostic_provider,
956                        )?
957                        .removed()
958                    {
959                        let local = self
960                            .as_local_mut()
961                            .context("Expected LSP Store to be local")?;
962                        let state = local
963                            .language_servers
964                            .get_mut(&server_id)
965                            .context("Could not obtain Language Servers state")?;
966                        if let LanguageServerState::Running {
967                            workspace_diagnostics_refresh_tasks,
968                            ..
969                        } = state
970                        {
971                            workspace_diagnostics_refresh_tasks.remove(&Some(unreg.id.clone()));
972                        }
973
974                        self.clear_unregistered_diagnostics(
975                            server_id,
976                            SharedString::from(unreg.id.clone()),
977                            cx,
978                        )?;
979                    }
980                }
981                "textDocument/documentColor" => {
982                    if self
983                        .unregister_dynamic_capability(
984                            &server,
985                            unreg,
986                            cx,
987                            |registrations| &mut registrations.color,
988                            |capabilities| &mut capabilities.color_provider,
989                        )?
990                        .capability_changed()
991                    {
992                        self.refresh_document_colors(Some(server_id), cx);
993                    }
994                }
995                "textDocument/foldingRange" => {
996                    if self
997                        .unregister_dynamic_capability(
998                            &server,
999                            unreg,
1000                            cx,
1001                            |registrations| &mut registrations.folding_range,
1002                            |capabilities| &mut capabilities.folding_range_provider,
1003                        )?
1004                        .capability_changed()
1005                    {
1006                        self.refresh_folding_ranges(Some(server_id), cx);
1007                    }
1008                }
1009                "textDocument/documentLink" => {
1010                    if self
1011                        .unregister_dynamic_capability(
1012                            &server,
1013                            unreg,
1014                            cx,
1015                            |registrations| &mut registrations.document_link,
1016                            |capabilities| &mut capabilities.document_link_provider,
1017                        )?
1018                        .capability_changed()
1019                    {
1020                        self.refresh_document_links(Some(server_id), cx);
1021                    }
1022                }
1023                _ => log::warn!("unhandled capability unregistration: {unreg:?}"),
1024            }
1025        }
1026
1027        Ok(())
1028    }
1029}
1030
1031// Registration with registerOptions as null, should fallback to true.
1032// https://github.com/microsoft/vscode-languageserver-node/blob/d90a87f9557a0df9142cfb33e251cfa6fe27d970/client/src/common/client.ts#L2133
1033fn parse_register_capabilities<T: serde::de::DeserializeOwned>(
1034    register_options: Option<serde_json::Value>,
1035) -> Result<OneOf<bool, T>> {
1036    Ok(match register_options {
1037        Some(options) => OneOf::Right(serde_json::from_value::<T>(options)?),
1038        None => OneOf::Left(true),
1039    })
1040}
1041
1042fn take_text_document_sync_options(
1043    capabilities: &mut lsp::ServerCapabilities,
1044) -> lsp::TextDocumentSyncOptions {
1045    match capabilities.text_document_sync.take() {
1046        Some(lsp::TextDocumentSyncCapability::Options(sync_options)) => sync_options,
1047        Some(lsp::TextDocumentSyncCapability::Kind(sync_kind)) => {
1048            let mut sync_options = lsp::TextDocumentSyncOptions::default();
1049            sync_options.change = Some(sync_kind);
1050            sync_options
1051        }
1052        None => lsp::TextDocumentSyncOptions::default(),
1053    }
1054}
1055
Served at tenant.openagents/omega Member data and write actions are omitted.