Commit Graph

514 Commits

Author SHA1 Message Date
Boshen 794891d93a perf(lsp): deduplicate server future (#25767)
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.
2026-08-17 07:38:12 +00:00
Sysix a2619a9d78 refactor(lsp): show ClientMessage on dynamic worker creation (#25407)
> ## 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.
>
2026-08-09 12:02:18 +00:00
Sysix 2eb3916a45 fix(lsp): remove capability check for window/showMessage notification (#25402)
The `window/showMessage` notification is a baseline feature in LSP and does not need Client capability `window.showMessage`. This capability is only needed for the `window/showMessageRequest` request.

- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showMessage
- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showMessageRequest
2026-08-08 23:51:33 +00:00
Alexander S. 36d4d9b0cb feat(lsp): send tool client messages to lsp client (#25401)
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).
2026-08-08 22:24:33 +00:00
Alexander S. c7583238a0 refactor(lsp): add ToolBuildResult struct for optional client message (#25386)
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`.
2026-08-08 17:02:23 +00:00
leaysgur 725d106490 docs(language_server): update options doc (#24967) 2026-07-27 01:37:30 +00:00
Sysix 74b5b484c2 test(lsp): add response buffer (#24086)
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 🤖
2026-07-26 15:20:27 +00:00
Sysix 7be5cf08f3 perf(oxlint/lsp): only invoke lint on code actions when document is not opened (#24676)
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
2026-07-19 17:16:22 +00:00
camc314 66e1a25c0e chore(all): fix clippy::manual_assert_eq (#24351) 2026-07-10 09:57:05 +00:00
Sysix d86f60b61c fix(lsp): normalize user config path to watch pattern (#23723)
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.
2026-06-25 00:15:37 +00:00
Sysix a401de8a7b test(lsp): fix unused imports for non-unix platforms (#23399) 2026-06-14 11:59:06 +00:00
konh 956309aa26 fix(lsp): use protocol line breaks for positions (#23329)
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>
2026-06-12 14:33:53 +02:00
Sysix 5a2981bb43 fix(lsp): notifiy every workspace for a watched file change (#22839)
When a watched file changes, it can affect multiple workspace folders. On a nested workspace set up the root oxlint config could be used multiple times.
The same for nested workspaces where only "one" config path is targeted with LSP settings.

related https://github.com/oxc-project/oxc-vscode/issues/97
closes https://github.com/oxc-project/oxc/issues/17515
2026-05-31 20:36:29 +00:00
Sysix 668f0d72e4 fix(lsp): return absolute path patterns without relative base uri (#22832)
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.
2026-05-31 20:29:37 +00:00
Sysix a769b33b01 perf(lsp): avoid some clones (#22813)
Found some places where `clone()` was too much and the ownership could be moved instead.
2026-05-29 09:25:26 +00:00
Sysix bbc410c5df refactor(lsp): WorkspaceWorker.init_watchers return single registration (#22809)
`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.
2026-05-28 19:27:24 +00:00
Sysix 5a4414d0fa feat(oxlint/lsp): support rulesCustomization lsp option (#21858)
> Adds support for an LSP workspace option (`rulesCustomization`) that lets clients customize rule severity and control whether specific rules participate in “fix all” code actions.

related https://github.com/oxc-project/oxc-vscode/issues/22
closes https://github.com/oxc-project/oxc/issues/21538
2026-05-05 09:57:06 +00:00
Sysix 19c1d5bd8f chore(lsp): create dynamic worker on start_manager (#21656)
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
2026-04-27 10:03:02 +00:00
Sysix e37cfc46b3 feat(oxfmt/lsp): format files outside workspace folders (#21481)
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 🥳
2026-04-27 09:53:41 +00:00
Dragan Okanovic 71f3286cb4 docs(linter): Fix the FixKind default value (#21721)
Updates the README.md to match what's in the source code:

https://github.com/oxc-project/oxc/blob/main/apps/oxlint/src/lsp/options.rs#L40-L48


https://github.com/oxc-project/oxc/blob/2ccede2ea75b9d2c3d0b56f3ef58eca8363189a6/apps/oxlint/src/lsp/options.rs#L40-L48

---------

Signed-off-by: Dragan Okanovic <1355455+abstractalgo@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-04-24 20:56:23 +01:00
Sysix d46ab7a01a refactor(lsp): remove RwLock on file_system property (#21704)
`LSPFileSystem` only holds a `papaya::HashMap` which concurrent hash-table without any locks.
Having `RwLock` here does only creates overhead
2026-04-24 11:50:00 +00:00
Sysix cf38a0bffb refactor(lsp): delay/avoid worker lock on Backend (#21691)
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)
2026-04-24 09:05:20 +00:00
Sysix 7816aa2d6c refactor(lsp): remove self reference in find_worker_for_uri (#21688)
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
2026-04-23 20:06:27 +00:00
Sysix 63eb98b6f2 chore(lsp): don't register dynamic worker watch patterns (#21620) 2026-04-21 20:23:10 +00:00
Sysix 5a6dab2149 chore(lsp): dynamic worker: watch pattern & execute command (#21545)
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 :)
2026-04-20 09:06:25 +00:00
Sysix 428fa25e95 chore(lsp): add WorkerManager::start_manager and stop_manager (#21528) 2026-04-20 08:59:56 +00:00
Sysix b0817ae645 refactor(lsp): remove Tool::name trait method (#21530)
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.
2026-04-17 15:18:24 +00:00
Sysix 667ce50dc8 docs(lsp): document Errors on run_ worker methods (#21480)
Added some error docs, for later when we "maybe" make the Worker & Manager public.
Should not be wrong to have it even when we not make it public :)
2026-04-17 14:24:46 +00:00
Sysix 32c6f2665d test(lsp): implement ManagerMode::DynamicWithWorkspaces (#21479)
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`
2026-04-17 14:04:24 +00:00
Sysix 7c75e43aa6 chore(lsp): make find_worker_for_uri non-public (#21478)
We want to use `get_worker_for_uri` which can later return `ManagerMode::DynamicWithWorkspaces` worker.
2026-04-17 13:50:34 +00:00
Sysix 2c74edc4b5 chore(lsp): define ManagerMode enum (#21477)
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.
2026-04-17 13:24:58 +00:00
Sysix 6b8ab6a37d fix(lsp): check for real uri path when finding workspace worker (#21194)
fixes https://github.com/oxc-project/oxc-vscode/issues/197
2026-04-13 16:16:51 +00:00
overlookmotel 9a45a10d5c fix(language_server): fix clippy warning in MacOS (#21378)
Clippy was producing an error for this code, but only on MacOS - hence why CI lint task did not catch it.
2026-04-13 13:00:23 +00:00
Sysix f54325df8c chore(lsp): add ResolvedPath struct (#21192)
> 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.
2026-04-13 08:16:45 +00:00
Sysix 3746946973 perf(lsp): reduce worker lock on workspace/executeCommand (#21272)
Found by claude opus 4.6🤖, reviewed by me.
2026-04-10 20:49:09 +00:00
Sysix 1550561217 fix(oxlint/lsp): remove uri cache on document change (#21039)
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
2026-04-05 12:22:08 +00:00
Sysix da6874ad71 perf(lsp): avoid String cloning for fs.get_document (#21040) 2026-04-04 15:36:06 +00:00
Sysix 3ef37da1fc fix(oxlint/lsp): error on unknown command (#20841)
> ## Pull request overview
>
> Adjusts LSP workspace command execution so unknown/unsupported commands surface as errors (instead of silently returning `null`), by removing the pre-dispatch “responsible tool” check and updating tests accordingly.
2026-03-29 19:51:11 +00:00
Sysix 58012db7e7 refactor(lsp): accept only one Tool & ToolBuilder (#20840)
> ## 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.
2026-03-29 16:21:36 +00:00
copilot-swe-agent dbfb51d592 refactor(lsp): extract WorkerManager from Backend (#20576)
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).
2026-03-25 21:42:44 +00:00
Sysix f1ee22442a refactor(lsp): pass TextDocument struct to tools instead of multiple params (#20390)
> ## 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.
2026-03-24 20:47:43 +00:00
Sysix 9c9234e616 test(lsp): skip test_get_parent_dir_uri in windows because of UNIX paths (#20622)
https://github.com/oxc-project/oxc/actions/runs/23403866474/job/68079394830
2026-03-22 13:48:36 +00:00
Sysix 6ed971046e test(lsp): skip single file mode tests on windows (#20621)
These test are timing out on main.
My first guess is the convert from URI → file path → URI is failing on Windows with UNIX paths.
2026-03-22 13:13:22 +00:00
copilot-swe-agent 26b47c3a1f feat(lsp): dynamic workspace creation for single-file mode (#20504)
Added LSP support for Client connections without a workspace. Typically, it happens when the user just opens the file (e.g. `vscode /path/to/file.js` instead of `vscode /path/to/workspace`). Then the client will not send a workspace Folder or a root URI.
Before this PR the LSP backend did not create a worker when root URIs are provided. Requests for formatting or diagnostics did not find a worker and returned silently no result.

In this PR we try to create workspace folders on the fly and try to shut them down when no longer needed.
Currently, we use the directory of the file as a root URI for workspace. We can later improve this logic for search for a git project or config file. I just like to have the ground steps here to improve them later :)

This method is nice because we automatically have config search support by the tools. We should improve the config search for them in this single file mode. I just added basic testing in later PRs.

-----

When an LSP client initializes without providing any workspace folders or root URI, the language server previously created no workers and silently ignored all file notifications. This implements proper single-file mode support entirely within `oxc_language_server` — no changes to `apps/oxfmt` or `apps/oxlint`.

## Changes

- **`single_file_mode: AtomicBool`** on `Backend` — set to `true` in `initialize` when neither `workspace_folders` nor `root_uri` is provided.

- **`ensure_worker_for_file_uri`** — on `textDocument/didOpen`, if in single-file mode and the URI is `file://`, a `WorkspaceWorker` is created dynamically for the file's parent directory. Uses a read-lock fast path, requests `workspace/configuration` when the client supports it, double-checks under write lock, then starts the worker and registers watchers.

- **`try_shutdown_empty_workspace`** — on `textDocument/didClose`, if no other open files map to the same workspace, the worker is shut down, removed, push diagnostics cleared, and watchers unregistered.

- **`did_change_workspace_folders` mode transitions** — when workspace folders are added while in single-file mode, the server exits single-file mode and shuts down all dynamically-created workers before adding the explicit workspace workers. When all workspace folders are removed and no new ones are added, the server enters single-file mode so subsequent `didOpen` calls create workers dynamically again.

- **Non-`file://` schemes** (e.g. `untitled://`, `vscode-userdata://`) are skipped — no worker is created for them in single-file mode.

## Behavior

```
Client initializes with no workspaceFolder / rootUri
  → single_file_mode = true, workers = []

textDocument/didOpen  file:///home/user/project/foo.ts
  → workspace worker created for file:///home/user/project/

textDocument/didOpen  file:///home/user/project/bar.ts
  → same worker reused (path prefix match)

textDocument/didClose file:///home/user/project/foo.ts
  → sibling bar.ts still open → worker kept

textDocument/didClose file:///home/user/project/bar.ts
  → no open files left → worker shut down & removed

workspace/didChangeWorkspaceFolders (add folder)
  → exit single-file mode, shut down single-file workers,
    create explicit workspace worker

workspace/didChangeWorkspaceFolders (remove last folder)
  → enter single-file mode
```

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 Send tasks to Copilot coding agent from [Slack](https://gh.io/cca-slack-docs) and [Teams](https://gh.io/cca-teams-docs) to turn conversations into code. Copilot posts an update in your thread when it's finished.
2026-03-20 20:00:52 +00:00
Sysix 7acfd41554 refactor(lsp): pass complete CodeActionContext to Tool trait (#20048)
Little bit of refactoring for later PRs.
Pass the complete context to the tool trait so other parameter of it can be used like `diagnostics` or trigger kind.
There is a little optimization that can be done. When requesting a code action from trigger-kind=automatic or with a diagnostic, we can skip the cache-fallback of linting the file. Mostly the file is linted already by another thread.
2026-03-05 23:33:49 +00:00
Sysix 6095cdee01 docs(lsp): explain why language_id should be preferred (#19976)
> ## Pull request overview
>
> Updates the language server’s `LanguageId` trait documentation to clarify how formatters should determine the language for a document, emphasizing use of the LSP-provided `language_id`.
2026-03-03 21:57:35 +00:00
Sysix 74a30783a0 fix(lsp): handle other schemes (besides from file and untitled) (#19871)
> This PR updates workspace-worker selection in the language server so that documents using non-`file` URI schemes (e.g. VS Code in-memory/userdata URIs) can still be associated with a workspace worker.
2026-03-02 00:32:37 +00:00
camc314 694be7d211 feat(linter): introduce typeAware as config options (#19614) 2026-02-25 11:55:54 +00:00
Sysix 652c34602c feat(oxfmt/lsp): support untitled:// schema (#19287)
related https://github.com/oxc-project/oxc-vscode/issues/25

> This PR adds support for formatting unsaved documents using the untitled:// URI scheme in the oxfmt LSP server. This enables VS Code and other LSP clients to format in-memory documents that haven't been saved to disk yet.

Angular is not supported for the moment, could not find a VS Code extension which defines a language ID for it  :/
2026-02-17 01:49:45 +00:00
Sysix 0eb9a2dd37 refactor(oxfmt/lsp): pass LanguageId to run_format (#19286)
related https://github.com/oxc-project/oxc-vscode/issues/25

> This PR updates the language server formatting pipeline so that a document’s `LanguageId` (from `textDocument/didOpen`) is propagated down into tool formatting hooks, enabling formatters to make decisions even when file extensions are missing.
2026-02-17 01:49:45 +00:00