Refactors the `oxc_language_server::Tool` trait and associated LSP plumbing to pass `TextDocument` and `CodeActionParams` by value, aligning tool APIs with “consume” semantics and simplifying call sites that previously had to keep borrowed values alive.
<summary>Pull request overview</summary>
This PR refactors the language server’s `WorkspaceWorker::collect_diagnostics_with` to avoid building an intermediate map when collecting tool diagnostics, returning the tool’s `DiagnosticResult` directly.
This was an old code path when `oxc_language_server` did used both tools (`oxlint` & `oxfmt`) at the same time
With https://github.com/oxc-project/oxc/pull/23329 the conversion from offset to line/column position is now done by a helper function in `oxc_language_server`.
There was a helper function inside `oxlint` to call the function. Instead, call it directly now.
Moved the tests to the correct place, and removed duplicated ones.
> Adjusts the Oxc language server startup to scale Tower LSP request
handling concurrency based on the machine’s available parallelism,
aiming to improve throughput on higher-core systems without fully
saturating the host.
The saturation is from personal feeling and did not get benchmarked.
---------
Signed-off-by: Alexander S. <sysix@sysix-coding.de>
Signed-off-by: Yuji Sugiura <6259812+leaysgur@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Yuji Sugiura <6259812+leaysgur@users.noreply.github.com>
Updated the `LanguageId` docs, to reflect how the real implementation works.
We did a "rollback" to check for the file extension first, to align with the CLI.
This was specially a problem for jsonc LanguageId with json file extension, where a trailing comma was always added by the editor.
Related https://github.com/oxc-project/oxc/issues/25893
## What this PR does
Fixes the permanent language-server deadlock reported in #24955.
`workspace/diagnostic/refresh` is a server-to-client *request*, and a
spec-compliant client may re-pull `textDocument/diagnostic` from this
same server before replying to it. Three notification handlers
(`initialized`, `did_change_configuration`, `did_change_watched_files`)
awaited that round-trip inside the handler itself, pinning one of the
transport's concurrency slots (tower-lsp-server defaults to 4) for as
long as the client took to reply. Four rapid watcher events — e.g. four
saves of `.oxlintrc.json` or any `tsconfig*.json` in pull-diagnostic
mode — pinned all four slots on handlers waiting for the client, while
the client waited on diagnostic pulls the server could no longer
service. Circular wait, no timeout, no recovery until the process is
killed.
The refresh is advisory — nothing consumes its result beyond logging a
warning — so this PR detaches it: a new
`Backend::spawn_diagnostic_refresh()` clones the client and spawns the
round-trip, returning the handler's slot immediately. The server keeps
answering requests with any number of refresh replies outstanding. This
is the issue's first suggested fix; raising the concurrency level
(#25153) only widens the window rather than closing it, as the issue
notes.
## How it's tested
`test_outstanding_diagnostic_refreshes_do_not_wedge_the_server` runs in
the crate's existing wire-level `TestServer` harness — real LSP framing
over duplex streams, the real `Server` at its real default concurrency
of 4. It initializes in pull mode with dynamic watchers, fires four
watched-file events, reads each resulting `workspace/diagnostic/refresh`
request **without replying** (what a busy client looks like), then sends
`shutdown` and requires a response within a 10s timeout.
Verified both directions: against the previous code the test fails by
timeout at exactly the reported wedge point; with the fix, the full
crate suite passes (85/85). `cargo fmt` and `clippy --all-targets` are
clean for the changed files. The only other change is adding tokio's
`time` feature to dev-dependencies for the test's timeout.
## AI disclosure
Investigated, implemented, and verified with AI assistance (Claude
Code), per the contributing guide's disclosure policy. I traced the root
cause to the transport's concurrency accounting, confirmed the circular
wait matches the reporter's reproduction, and validated the fix with the
deadlock-reproducing regression test above before submission.
Co-authored-by: Alexander S. <sysix@sysix-coding.de>
> ## Pull request overview
>
> This PR refactors the language server’s client-notification flow to avoid flooding editors with too many `window/showMessage` notifications by capping the number of messages sent in a batch to 5.
>
Test generated with GPT-5.3-Codex 🤖
> ## Pull request overview
>
> Refactors the language-server tool initialization/restart plumbing to support emitting multiple `ClientMessage`s (instead of a single optional message), so tools can surface more than one warning/error to the client during startup and config changes.
>
Type-erase the shared LSP server future so CLI and NAPI reuse one async state machine. This removes duplicate LSP machinery and reduced the measured release binary by about 1.3 MiB.
AI assistance was used to implement this change.
> ## Pull request overview
>
> This PR updates the language server’s dynamic worker startup paths to surface `ClientMessage` warnings/errors to the client when a worker is created/started dynamically, and adds a regression test for single-file mode.
>
The newly created `ClientMessage` will now be passed into `Backend` and
sent to the client.
There is atm no real implementation in `oxlint` & `oxfmt`, but the LSP
integration is done (expect dynamic worker creation, see #25407).
Added a new `ToolBuildResult` struct, so an optional `ClientMessage` can
be passed to `Backend` (in a later PR).
This allows us later to send the custom message to the clients interface
when some config errors happened on the (re)creation.
There is no message sent yet, this will be integrated in another PR too.
This PR is just the base for the `ToolInterface`.
Working on the stack https://github.com/oxc-project/oxc/pull/23768 I created a local test with many responses in a quick time. This code change did resolve the issue.
Auto-merge because it only effects tests, even if not perfect :)
Autogenerated by Codex AI Agent 🤖
Some clients can send code actions for documents, which are not opened and already analyzed with a lint process.
A good example is the IntelliJ plugin, which allows the "fix all" code action from the file explorer.
> This PR refactors the language-server → tool code-action API to pass a single request struct (including an “is document open” flag), enabling the oxlint LSP linter to avoid re-running lint for code-action requests when the document is already open (and presumably already linted/cached).
This is mostly needed for https://github.com/oxc-project/oxc/pull/23768
When a user defined a custom config path, like `fmt.configPath` or `configPath`, the watch pattern returned the same path.
VS Code does not respect this as a valid pattern and does not watch for files defined like `./define-config.ts`.
Resulting into an LSP client, which will not send a watched-file-change requests to the server, when the user edited an important config path.
Fixes#23314
## Summary
- add an LSP-specific UTF-8 byte offset to `Position` converter
- use it for oxfmt formatter `TextEdit` ranges
- route oxlint LSP positions through the same converter
## Details
The existing conversion used the shared rope helper, which treats
Unicode line
and paragraph separators as line breaks. That is correct for some
JavaScript
source-location semantics, but LSP only treats CR, LF, and CRLF as line
breaks.
This keeps the shared rope helper unchanged and isolates the
protocol-specific
behavior in `oxc_language_server`.
## Tests
- `cargo fmt --all`
- `cargo test -p oxc_language_server position --lib`
- `cargo test -p oxfmt --lib lsp::server_formatter`
- `cargo test -p oxlint --lib lsp::error_with_position`
- `cargo clippy -p oxc_language_server -p oxfmt -p oxlint --lib --tests
--no-deps -- -D warnings`
- `git diff --check`
---------
Co-authored-by: xiaokhkh <10765730+xiaokhkh@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Sysix <sysix@sysix-coding.de>
When editors are sending absolute config paths like `configPath: "/home/user/.../.oxlintrc.json`, the relative information is not needed.
Do not know how editors will handle absolute path with a relative base URI. To make sure, everyone knows how to look it up, absolute patterns are not tied to base URI.
`init_watchers` was returning a `Vec<Registration>`, this was needed where the Worker need to manager multiple tools. Now it only owns one tool and can use `Option<Registration>` instead.
Because `DiagnosticMode` can only be evaluated with the `ClientCapabilities` from the editor, we need to delay the creation of the dynamic worker.
It works atm, because we know `oxfmt` is always `DiagnosticMode::None`, but I have plans to make `oxlint` compatible with the `ManagerMode::DynamicWithWorkspaces`.
Bonus: The dynamic worker is only created once, use `OnceCell` instead of a lock
closes https://github.com/oxc-project/oxc-vscode/issues/180
`oxfmt` config architecture allows running a second instance of the formatter. This worker is responsible now for single file mode and files outside the current workspace.
> In `oxfmt` LSP context, user want to use the server without a context of a project.
They want just open a file with `code /Users/local-settings/file.json`, which we already supported with the `single_file_mode` flag.
But they also expect to format such files when you have already opened a project.
It will not create watchers for this manager because they would be too expensive, atm the architecture only works with the root path of the workspace. In the example of the dynamic workspace, it would watch the complete file system.
This will "break" the single file mode in `oxfmt --lsp`, when a user edits the oxfmt config on the same level where an edited file is formatted.
Tested it with VS Code and WSL2 (linux) and Win10 native. Beside #21654 everything works as expected 🥳
This was a performance PR at first, but I am not sure if this will be noticeable.
Downsides first:
- We now always write to the file system, even if a worker could not be found
Upsides:
- Editing a file from another workspace, and then requesting formatting, did result into an invalid state (the in memory state was not set yet)
- on `textDocument/didOpen` & `textDocument/didSave` we avoid the worker lock completely if
- Diagnostic mode is "none" (for `oxfmt`)
- Diagnostic mode is "pull" (for `oxlint`, where the client does not support `textDocument/diagnostic` request)
Thought that the self reference will be used in a later stack, but the current implementation of the dynamic workspace worker looks good at the moment.
Little refactoring
Added logic for dynamic worker when first initializing the watch patterns. These will never change, not like other watch pattern which can be later be changed with `workspace/didChangeConfiguration`.
Dynamic Workers are not linked into this method, because it is not directly an LSP concept, instead something the LSP server needs to handle internally.
`workspace/executeCommand` is respected too. These are the only missing code paths :)
This method was used to create unique watcher IDs for each tool.
Because `oxc_language_server::run_server` now accepts only one tool, we can safely remove this method.
First code for the `DynamicWithWorkspaces`. This is just for `get_worker_for_uri` code path. File watchers and more are not supported atm.
This is all behind a test flag atm, later we use it inside `oxfmt`
The language server needs to support "modes" of workspaces management.
This could probably be refactored out later when we want to align `oxlint` with `oxfmt`.
The main reason why this check of "modes" is needed:
In `oxfmt` LSP context, user want to use the server without a context of a project.
They want just open a file with `code /Users/local-settings/file.json`, which we already supported with the `single_file_mode` flag.
But they also expect to format such files when you have already opened a project.
Created the enum so we can switch between 3 modes. One is not used atm, but simplifies the logic a bit. Maybe `oxlint` will drop its mode. VS Code will not request diagnostics in single file mode. Need to check what other editors do.
> Adds a `ResolvedPath` wrapper and new fixtures/tests intended to validate consistent path resolution across mixed-case `file://` URIs (notably from VS Code), to avoid mismatches when mapping URIs to filesystem/workspace entities.
Canonicalized only on windows macOS where case-sensitive file are present.
Not only linux, because `Text.txt` and `test.txt` in the same directory are different files.
This does not fix the stale diagnostics, but we now have at least a clean state on the linter side, when the document changed, but code actions are requested without a diagnostic request before.
Was originally found by https://github.com/oxc-project/oxc/pull/19793 and moved into an own PR for here.
Thank you, @wagenet
> ## Pull request overview
>
> Refactors the `oxc_language_server` LSP implementation to operate with a single `Tool`/`ToolBuilder` instead of managing multiple tools per workspace, simplifying worker creation and capability wiring.
The `Backend` struct in `backend.rs` was directly owning and managing all worker lifecycle logic — including the worker list, single-file mode flag, tool builders, dynamic worker creation, and workspace folder transitions — making the struct unwieldy and difficult to reason about.
## Changes
**New `WorkerManager` struct (`worker_manager.rs`)**
- Owns `workers: RwLock<Vec<WorkspaceWorker>>`, `single_file_mode: AtomicBool`, and `tool_builders: Arc<[Box<dyn ToolBuilder>]>`
- Manages two modes: *workspace mode* (root URI or workspace folders provided at `initialize`; workers created once and updated only on `didChangeWorkspaceFolders`) and *single-file mode* (no workspace provided; workers created dynamically on `didOpen` and torn down on `didClose`)
- Hosts all worker-lookup logic: `find_worker_for_uri`, `get_parent_dir_uri`, `assert_workspaces_are_valid_paths`
- Adds `WorkerGuard<'_>` — a RAII type that holds the read lock and dereferences to the matched `WorkspaceWorker`, returned by the new `get_worker_for_uri` async helper
- Encapsulates single-file-mode operations: `ensure_worker_for_file_uri` (returns `Option<Vec<Registration>>` for caller to forward to client), `try_shutdown_empty_workspace` (takes `open_uris` snapshot from caller to avoid cross-lock deadlocks)
- Encapsulates `update_workspace_folders` — the write-lock phase of `didChangeWorkspaceFolders` that handles mode transitions and worker extraction
**Simplified `Backend` (`backend.rs`)**
- Replaces the three fields `workspace_workers`, `single_file_mode`, `tool_builders` with a single `worker_manager: WorkerManager`
- All LSP handlers that look up a single worker now use `get_worker_for_uri` instead of the previous two-step `read_workers()` + `find_worker_for_uri` pattern:
```rust
// Before
let workers = self.worker_manager.read_workers().await;
let Some(worker) = WorkerManager::find_worker_for_uri(&workers, &uri) else { return; };
// After
let Some(worker) = self.worker_manager.get_worker_for_uri(&uri).await else { return; };
```
- All LSP handlers delegate worker lookup and lifecycle to `WorkerManager`; only client I/O (diagnostics publishing, capability registration) stays in `Backend`
```rust
// Before
pub struct Backend {
tool_builders: Arc<[Box<dyn ToolBuilder>]>,
workspace_workers: Arc<RwLock<Vec<WorkspaceWorker>>>,
single_file_mode: AtomicBool,
// ...
}
// After
pub struct Backend {
worker_manager: WorkerManager,
// ...
}
```
Workers are never cloned; ownership is transferred via `swap_remove` / `drain` as before.
<!-- START COPILOT CODING AGENT TIPS -->
---
⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with [Raycast](https://gh.io/cca-raycast-docs).
> ## Pull request overview
>
> Refactors the language-server tool interface to pass a single `TextDocument` struct (uri + language id + optional text) instead of multiple parameters, and updates LSP backend/worker call sites and tests accordingly.
>
> **Changes:**
>
> * Introduce `TextDocument` in `oxc_language_server` and thread it through `Tool`/`WorkspaceWorker` APIs.
>
> * Update `LSPFileSystem` to provide `get_document()` and migrate backend/worker/tool implementations to use it.
>
> * Update LSP-related tools/tests in `apps/oxlint` and `apps/oxfmt` to the new API.