diff --git a/docs/src/client_configuration.md b/docs/src/client_configuration.md index 14fbbe834..3712501d4 100644 --- a/docs/src/client_configuration.md +++ b/docs/src/client_configuration.md @@ -41,7 +41,7 @@ Below is an example of the `LSP.sublime-settings` file with configurations for t | settings | per-project settings (equivalent to VS Code's Workspace Settings) | | initializationOptions | options to send to the server at startup (rarely used) | | selector | This is _the_ connection between your files and language servers. It's a selector that is matched against the current view's base scope. If the selector matches with the base scope of the the file, the associated language server is started. For more information, see https://www.sublimetext.com/docs/3/selectors.html | -| priority_selector | Used to prioritize a certain language server when choosing which one to query on views with multiple servers active. Certain LSP actions have to pick which server to query and this setting can be used to decide which one to pick based on the current scopes at the cursor location. For example when having both HTML and PHP servers running on a PHP file, this can be used to give priority to the HTML one in HTML blocks and to PHP one otherwise. That would be done by setting "priority_selector" to `text.html` for HTML server and `source.php` to PHP server. Note: when the "priority_selector" is missing, it will be the same as the "document_selector". +| priority_selector | Used to prioritize a certain language server when choosing which one to query on views with multiple servers active. Certain LSP actions have to pick which server to query and this setting can be used to decide which one to pick based on the current scopes at the cursor location. For example when having both HTML and PHP servers running on a PHP file, this can be used to give priority to the HTML one in HTML blocks and to PHP one otherwise. That would be done by setting "priority_selector" to `text.html` for HTML server and `source.php` to PHP server. Note: when the "priority_selector" is missing, it will be the same as the "document_selector". This setting also accepts an object where the key is the name of the provider (for example `renameProvider`) and the value is the selector to use for this provider. In that case a `*` key can also be used which will act as a fallback for providers that are not explicitly specified. | diagnostics_mode | Set to `"workspace"` (default is `"open_files"`) to ignore diagnostics for files that are not within the project (window) folders. If project has no folders then this option has no effect and diagnostics are shown for all files. If the server supports _pull diagnostics_ (`diagnosticProvider`), this setting also controls whether diagnostics are requested only for open files (`"open_files"`), or for all files in the project folders (`"workspace"`). | | tcp_port | see instructions below | | experimental_capabilities | Turn on experimental capabilities of a language server. This is a dictionary and differs per language server | diff --git a/plugin/core/registry.py b/plugin/core/registry.py index 56c0f1ed9..92a0a9347 100644 --- a/plugin/core/registry.py +++ b/plugin/core/registry.py @@ -21,14 +21,17 @@ windows = WindowRegistry() -def best_session(view: sublime.View, sessions: Iterable[Session], point: int | None = None) -> Session | None: +def best_session( + view: sublime.View, sessions: Iterable[Session], capability_path: str | None = None, point: int | None = None +) -> Session | None: if point is None: try: point = view.sel()[0].b except IndexError: return None try: - return max(sessions, key=lambda s: view.score_selector(point, s.config.priority_selector)) + return max(sessions, + key=lambda s: view.score_selector(point, s.config.priority_selector_for_capability(capability_path))) except ValueError: return None diff --git a/plugin/core/types.py b/plugin/core/types.py index 08c5ee8bd..456b9e739 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -682,7 +682,7 @@ class ClientConfig: def __init__(self, name: str, selector: str, - priority_selector: str | None = None, + priority_selector: str | dict[str, str] | None = None, schemes: list[str] | None = None, command: list[str] | None = None, binary_args: list[str] | None = None, # DEPRECATED @@ -910,6 +910,17 @@ def filter_out_disabled_capabilities(self, capability_path: str, options: dict[s result[k] = v return result + def priority_selector_for_capability(self, capability_path: str | None = None) -> str: + if isinstance(self.priority_selector, str): + return self.priority_selector + if capability_path: + capability = capability_path.split('.')[0] + if capability in self.priority_selector: + return self.priority_selector[capability] + if '*' in self.priority_selector: + return self.priority_selector['*'] + return self.selector + def __repr__(self) -> str: items: list[str] = [] for k, v in self.__dict__.items(): @@ -973,10 +984,10 @@ def _read_selector(config: sublime.Settings | dict[str, Any]) -> str: return "" -def _read_priority_selector(config: sublime.Settings | dict[str, Any]) -> str: +def _read_priority_selector(config: sublime.Settings | dict[str, Any]) -> str | dict[str, str]: # Best case scenario selector = config.get("priority_selector") - if isinstance(selector, str): + if isinstance(selector, (str, dict)): return selector # Otherwise, look for "languages": [...] languages = config.get("languages") diff --git a/plugin/documents.py b/plugin/documents.py index 49d09ab0f..7bb5b9ea4 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -869,7 +869,7 @@ def _on_initial_folding_ranges(self, kinds: list[str], response: list[FoldingRan # --- Public utility methods --------------------------------------------------------------------------------------- def session_async(self, capability: str, point: int | None = None) -> Session | None: - return best_session(self.view, self.sessions_async(capability), point) + return best_session(self.view, self.sessions_async(capability), capability, point) def sessions_async(self, capability: str | None = None) -> list[Session]: return [ diff --git a/plugin/tooling.py b/plugin/tooling.py index acfb42c0b..f765d97b2 100644 --- a/plugin/tooling.py +++ b/plugin/tooling.py @@ -361,7 +361,10 @@ def line(s: str) -> None: line(f' - command\n{self.json_dump(config.command)}') line(' - shell command\n{}'.format(self.code_block(list2cmdline(resolved_command), 'sh'))) line(f' - selector\n{self.code_block(config.selector)}') - line(f' - priority_selector\n{self.code_block(config.priority_selector)}') + priority_selector = self.code_block(config.priority_selector) \ + if isinstance(config.priority_selector, str) \ + else self.json_dump(config.priority_selector) + line(f' - priority_selector\n{priority_selector}') line(' - init_options') line(self.json_dump(config.init_options.get())) line(' - settings') diff --git a/sublime-package.json b/sublime-package.json index c9cea21f9..ba4022688 100644 --- a/sublime-package.json +++ b/sublime-package.json @@ -317,6 +317,109 @@ "default": "open_files" }, "ClientPrioritySelector": { + "oneOf": [ + { + "type": "object", + "properties": { + "*": { + "type": "string" + }, + "callHierarchyProvider": { + "type": "string" + }, + "codeActionProvider": { + "type": "string" + }, + "codeLensProvider": { + "type": "string" + }, + "colorProvider": { + "type": "string" + }, + "completionProvider": { + "type": "string" + }, + "declarationProvider": { + "type": "string" + }, + "definitionProvider": { + "type": "string" + }, + "diagnosticProvider": { + "type": "string" + }, + "documentFormattingProvider": { + "type": "string" + }, + "documentHighlightProvider": { + "type": "string" + }, + "documentLinkProvider": { + "type": "string" + }, + "documentOnTypeFormattingProvider": { + "type": "string" + }, + "documentRangeFormattingProvider": { + "type": "string" + }, + "documentSymbolProvider": { + "type": "string" + }, + "executeCommandProvider": { + "type": "string" + }, + "foldingRangeProvider": { + "type": "string" + }, + "hoverProvider": { + "type": "string" + }, + "implementationProvider": { + "type": "string" + }, + "inlayHintProvider": { + "type": "string" + }, + "inlineValueProvider": { + "type": "string" + }, + "linkedEditingRangeProvider": { + "type": "string" + }, + "monikerProvider": { + "type": "string" + }, + "referencesProvider": { + "type": "string" + }, + "renameProvider": { + "type": "string" + }, + "selectionRangeProvider": { + "type": "string" + }, + "semanticTokensProvider": { + "type": "string" + }, + "signatureHelpProvider": { + "type": "string" + }, + "typeDefinitionProvider": { + "type": "string" + }, + "typeHierarchyProvider": { + "type": "string" + }, + "workspaceSymbolProvider": { + "type": "string" + }, + } + }, + { + "type": "string", + } + ], "markdownDescription": "While the `\"selector\"` is used to determine which views belong to which language server configuration, the `\"priority_selector\"` is used to determine which language server wins at the caret position in case there are multiple language servers attached to a view. For instance, there can only be one signature help popup visible at any given time. This selector is use to decide which one to use for such capabilities. This setting is optional and you won't need to set it if you're planning on using a single language server for a particular type of view." }, // The ClientConfig class