From 7bf30080f6aa8fc1f983e1d72bc91dd790f28d29 Mon Sep 17 00:00:00 2001 From: Petr Devyaterikov <22612354+utrumo@users.noreply.github.com> Date: Wed, 27 May 2026 14:31:57 +0300 Subject: [PATCH] typescript_vts: add initialization_options pass-through (#1507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * typescript_vts: add initialization_options pass-through Extends ls_specific_settings["typescript_vts"] with an optional initialization_options key — a dict forwarded to vtsls via all three LSP configuration channels: - the initialize request (initializationOptions field), - a workspace/didChangeConfiguration notification right after initialize, - and responses to workspace/configuration pulls, looked up per section (e.g. "typescript", "vtsls", "javascript", or dotted paths like "typescript.tsdk"). All three channels are necessary because different parts of vtsls read settings from different places — notably typescript.tsdk, which vtsls pulls via workspace/configuration rather than reading from initializationOptions. Without the pull-answer, setting tsdk had no effect. Primary use case: Yarn Plug'n'Play projects. Running `yarn dlx @yarnpkg/sdks vscode` in the project root generates .yarn/sdks/typescript/lib/tsserver.js, a PnP-aware tsserver. The user then points vtsls at it with: ls_specific_settings: typescript_vts: initialization_options: typescript: tsdk: "<...>/.yarn/sdks/typescript/lib" vtsls: autoUseWorkspaceTsdk: true autoUseWorkspaceTsdk is required alongside tsdk because, in a headless LSP context, there is no UI prompt to confirm switching to the workspace TypeScript version. See yioneko/vtsls#169 for the recipe. Default behaviour is unchanged — the field is optional, omitted from the initialize params and didChangeConfiguration when unset or empty, and workspace/configuration continues to answer empty sections. --- CHANGELOG.md | 6 ++ docs/02-usage/050_configuration.md | 24 +++++++ .../language_servers/vts_language_server.py | 63 ++++++++++++++++--- 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb02d092..0884b617 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Status of the `main` branch. Changes prior to the next official version change will appear here. +* Language Servers: + - `typescript_vts`: Add `initialization_options` setting in `ls_specific_settings.typescript_vts`. + The dict is forwarded to vtsls via `initializationOptions`, `workspace/didChangeConfiguration`, + and `workspace/configuration` pulls. Enables Yarn PnP setups with `typescript.tsdk` pointing + at the Yarn-generated SDK. + # v1.5.3 (2026-05-26) # v1.5.2 (2026-05-26) diff --git a/docs/02-usage/050_configuration.md b/docs/02-usage/050_configuration.md index 03731604..983924bb 100644 --- a/docs/02-usage/050_configuration.md +++ b/docs/02-usage/050_configuration.md @@ -1022,6 +1022,30 @@ Supported settings: |---|---|---| | `vtsls_version` | `0.2.9` | Override the `@vtsls/language-server` npm package version Serena installs. | | `npm_registry` | `null` | Override the npm registry Serena uses for the managed install. | +| `initialization_options` | `null` | Dict forwarded to vtsls on three LSP channels: the `initializationOptions` field of the `initialize` request, a `workspace/didChangeConfiguration` notification sent right after initialize, and as the response to `workspace/configuration` pull requests (section-scoped). Typical use is Yarn PnP: point `typescript.tsdk` at the Yarn-generated SDK and enable `vtsls.autoUseWorkspaceTsdk`. | + +Example (Yarn PnP project with TypeScript in a subdirectory; run `yarn dlx @yarnpkg/sdks vscode` in the project once to generate the SDK): + +```yaml +ls_specific_settings: + typescript_vts: + initialization_options: + typescript: + tsdk: "project/.yarn/sdks/typescript/lib" + vtsls: + autoUseWorkspaceTsdk: true +``` + +vtsls reads `typescript.tsdk` through the `workspace/configuration` pull, not through `initializationOptions`, so Serena answers those pulls from the same dict (and also pushes it on `workspace/didChangeConfiguration` for compatibility with servers that expect the notification). Without `autoUseWorkspaceTsdk: true`, vtsls falls back to its bundled TypeScript and ignores `tsdk` (there is no UI prompt to confirm the switch in a headless LSP). + +The dict is forwarded to vtsls verbatim — Serena does not validate its structure. For the list of supported keys and their expected types, refer to the vtsls [configuration schema](https://github.com/yioneko/vtsls/blob/main/packages/service/configuration.schema.json) and the underlying [VS Code TypeScript settings](https://code.visualstudio.com/docs/languages/typescript). `null` (the default) and `{}` are both treated as "unset": no `initializationOptions` are sent and no `workspace/didChangeConfiguration` notification is pushed. A non-dict value (e.g. a string or list) raises an error at server start. + +**Troubleshooting:** + +- *vtsls keeps using its bundled TypeScript and ignores `tsdk`* — ensure `vtsls.autoUseWorkspaceTsdk: true` is set alongside `typescript.tsdk`. Without it vtsls does not auto-switch to the workspace TS in a headless LSP. +- *tsserver fails to start after pointing at a custom `tsdk`* — verify the path resolves to a directory containing `tsserver.js` (e.g. `.yarn/sdks/typescript/lib`, not `.yarn/sdks/typescript`). Relative paths are interpreted relative to the project root. +- *Setting appears in `solidlsp` logs but vtsls does not react* — cross-check the key against the vtsls configuration schema linked above. The dict is forwarded as-is, so an unknown or wrong-typed key is silently ignored by vtsls. +- *Need to inspect what Serena is actually forwarding* — the dict is logged at INFO level via the `Forwarding user-provided initializationOptions to vtsls: …` line at language server startup. #### Vue diff --git a/src/solidlsp/language_servers/vts_language_server.py b/src/solidlsp/language_servers/vts_language_server.py index 2464338a..5525809b 100644 --- a/src/solidlsp/language_servers/vts_language_server.py +++ b/src/solidlsp/language_servers/vts_language_server.py @@ -34,7 +34,20 @@ DEFAULT_VTSLS_VERSION = "0.2.9" class VtsLanguageServer(SolidLanguageServer): """ Provides TypeScript specific instantiation of the LanguageServer class using vtsls. - Contains various configurations and settings specific to TypeScript via vtsls wrapper. + + Supported entries in ``ls_specific_settings["typescript_vts"]``: + - ``vtsls_version``: version of ``@vtsls/language-server`` to install (default: ``"0.2.9"``). + - ``npm_registry``: custom npm registry for the managed install. + - ``initialization_options``: optional dict forwarded verbatim as LSP + ``initializationOptions``. Useful for Yarn PnP projects, e.g.:: + + initialization_options: + typescript: + tsdk: "project/.yarn/sdks/typescript/lib" + vtsls: + autoUseWorkspaceTsdk: true + + See https://github.com/yioneko/vtsls/issues/169 for the PnP recipe. """ def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings): @@ -113,13 +126,30 @@ class VtsLanguageServer(SolidLanguageServer): assert os.path.exists(vts_executable_path), "vtsls executable not found. Please install @vtsls/language-server and try again." return f"{vts_executable_path} --stdio" - @staticmethod - def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: + @property + def _initialization_options(self) -> dict: + """ + Validated user-provided ``initializationOptions``. + + :raises ValueError: if ``ls_specific_settings.typescript_vts.initialization_options`` + is set to a value that is not a dict. + """ + opts = self._custom_settings.get("initialization_options") + if opts is None: + return {} + if not isinstance(opts, dict): + raise ValueError(f"ls_specific_settings.typescript_vts.initialization_options must be a dict, got {type(opts).__name__}") + return opts + + def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: """ Returns the initialize params for the VTS Language Server. + + If ``initialization_options`` is set in ``ls_specific_settings["typescript_vts"]``, + it is forwarded verbatim as LSP ``initializationOptions``. """ root_uri = pathlib.Path(repository_absolute_path).as_uri() - initialize_params = { + initialize_params: dict = { "locale": "en", "capabilities": { "textDocument": { @@ -152,6 +182,11 @@ class VtsLanguageServer(SolidLanguageServer): } ], } + + if self._initialization_options: + log.info("Forwarding user-provided initializationOptions to vtsls: %s", self._initialization_options) + initialize_params["initializationOptions"] = self._initialization_options + return cast(InitializeParams, initialize_params) def _start_server(self) -> None: @@ -179,12 +214,12 @@ class VtsLanguageServer(SolidLanguageServer): def execute_client_command_handler(params: dict) -> list: return [] - def workspace_configuration_handler(params: dict) -> list[dict] | dict: - # VTS may request workspace configuration - # Return empty configuration for each requested item - if "items" in params: - return [{}] * len(params["items"]) - return {} + init_options = self._initialization_options + + def workspace_configuration_handler(params: dict) -> list[object]: + # vtsls pulls settings for sections like "typescript", "vtsls", "javascript". + # Return the matching sub-dicts from the user-provided initialization_options. + return [init_options.get(item.get("section", ""), {}) for item in params["items"]] def do_nothing(params: dict) -> None: return @@ -227,6 +262,14 @@ class VtsLanguageServer(SolidLanguageServer): log.debug(f"completionProvider: {init_response['capabilities']['completionProvider']}") self.server.notify.initialized({}) + + # vtsls also reads settings via workspace/didChangeConfiguration (in addition + # to initializationOptions and workspace/configuration pulls). Push the same + # user-provided settings on all three channels for maximum compatibility, + # e.g. so that `typescript.tsdk` is honoured for Yarn PnP projects. + if init_options: + self.server.notify.workspace_did_change_configuration({"settings": init_options}) + if self.server_ready.wait(timeout=1.0): log.info("VTS server is ready") else: