typescript_vts: add initialization_options pass-through (#1507)

* 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.
This commit is contained in:
Petr Devyaterikov authored and GitHub committed 2026-05-27 13:31:57 +02:00
1 parent 9b292a6c8b
commit 7bf30080f6
3 files changed
+83 -10

No files matched your search

+6
View File
@@ -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)
+24
View File
@@ -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
@@ -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: