oxc
oxc copied to clipboard
feat(language_server): support multiple workspaces
ToDo:
- creating tests
- should we request
workspace/configurationwhen multiple folders have different configs (on initialization)?
- #10648

- #10515
👈 (View in Graphite) - #10572

- #10497

- #10507

- #10479

main
How to use the Graphite Merge Queue
Add either label to this PR to merge it via the merge queue:
- 0-merge - adds this PR to the back of the merge queue
- hotfix - for urgent hot fixes, skip the queue and merge this PR next
You must have a Graphite account in order to use the merge queue. Sign up using this link.
An organization admin has enabled the Graphite Merge Queue in this repository.
Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.
This stack of pull requests is managed by Graphite. Learn more about stacking.
CodSpeed Instrumentation Performance Report
Merging #10515 will not alter performance
Comparing 04-20-feat_language_server_support_multiple_workspaces (01cc2e4) with main (315143a)
Summary
✅ 36 untouched benchmarks
@nrayburn-tech can you make sure this still works for IntelliJ plugin ❤️ ?
I should be able to test some tonight. I think the only real scenario I know that I can test is opening a project that uses nested configs.
The IntelliJ plugin has bugs with specifying a config file. I don’t know that workspace folders are supported, I’ll try to find out how to test this.
I should be able to test some tonight. I think the only real scenario I know that I can test is opening a project that uses nested configs.
This should be enough. I do not expect that IntelliJ has support for this feature, I only want to make sure everything still works for you as expected.
I'm seeing warnings when I'm expecting errors. Testing with this folder, https://github.com/oxc-project/oxc-intellij-plugin/tree/main/sandbox and viewing the package-a/index.js file. Expecting an error for the debugger, instead it's a warning. Same in VS Code. idea.log contains all the language server communcation.
I didn't see any other problems.
I didn’t actually compare what I saw with the current version of the language server. It could be that I misinterpreted the Oxlint config and there’s actually nothing wrong. So, before you spend too much time troubleshooting you may want to verify that (or I will when I’m back at my laptop tomorrow).
Thanks for testing it out. There is a problem with nested configuration, which is already in the main branch. Created one test which passes for some reason, but fails in real world. Will look into it :)
EDIT: it is fixed in main and this PR is restacked
Re-tested within IntelliJ today and didn't notice any issues. Both nested config and specific config path support seems to be working. I don't think I'll be able to review the actual code changes here, but general usage seemed fine for me.
I did find that IntelliJ does have some support for the workspace concept, but I'm not sure if that means it supports LSP workspaces. https://blog.jetbrains.com/idea/2024/08/workspaces-in-intellij-idea/ In either case, I didn't do any workspace testing.
@camc314 can you merge after testing this PR manually?
@Sysix , mind clarifying how this is meant to work? I think i'm just doing something wront while testing though.
I tested on https://github.com/oxc-project/oxc-intellij-plugin/tree/main/sandbox, added a sandbox.code-workspace, opened the folder as a workspace, however On the left hand side, I've got no-debugger: allow, however the warning is still there? likewise, the warning for curly is missing on the left.
This is my sandbox.code-workspace file
{
"folders": [
{
"path": "custom-config"
},
{
"path": "nested-configs"
}
]
}
@Sysix , mind clarifying how this is meant to work? I think i'm just doing something wront while testing though.
I tested my changes with the following changes:
In C:\Users\sysix\.....\settings.json I use the debug server with extra information.
So I am sure I always use the debug language server for every editor instance.
{
"oxc.path.server": "/home/sysix/dev/oxc/editors/vscode/target/debug/oxc_language_server",
"oxc.trace.server": "verbose"
}
I am also updating the docs to clarify how to use the debug version: https://github.com/oxc-project/oxc-project.github.io/pull/357
Then add / remove folders to the workspace with the explorer:
Add another Git Repo:
When you're in single folder mode and adding a folder, VSCode will restart the window on my side. The real testing can only be done with 2 or more folders.
Every workspace folder can be configured by its own Options:
thanks - been debugging this trying to work out whats going on, I keep on seeing the following, when running this (i changed the code slightly to add more logging, but the lsp gets the workspace folders, but fails to get configuration - error server not initiaizlized
2025-05-01 17:53:18.005 [info] [2025-05-01T16:53:18Z INFO oxc_language_server] initialize Some(Object {"settings": Object {"run": String("onType"), "configPath": String("./custom-oxlint.jsonc"), "flags": Object {}}})
2025-05-01 17:53:18.005 [info] [2025-05-01T16:53:18Z INFO oxc_language_server] initialize: Options { run: OnType, config_path: Some("./custom-oxlint.jsonc"), flags: {} }
[2025-05-01T16:53:18Z INFO oxc_language_server] language server version: "0.16.8"
2025-05-01 17:53:18.005 [info] [2025-05-01T16:53:18Z INFO oxc_language_server] workspace folders: Some([WorkspaceFolder { uri: Uri(Uri { scheme: Some("file"), authority: Some(Authority { userinfo: None, host: Host { text: "", data: RegName("") }, port: None }), path: "/Users/cameron/github/tmp/oxc-intellij-plugin/sandbox/custom-config", query: None, fragment: None }), name: "custom-config" }, WorkspaceFolder { uri: Uri(Uri { scheme: Some("file"), authority: Some(Authority { userinfo: None, host: Host { text: "", data: RegName("") }, port: None }), path: "/Users/cameron/github/tmp/oxc-intellij-plugin/sandbox/nested-configs", query: None, fragment: None }), name: "nested-configs" }, WorkspaceFolder { uri: Uri(Uri { scheme: Some("file"), authority: Some(Authority { userinfo: None, host: Host { text: "", data: RegName("") }, port: None }), path: "/Users/cameron/github/tmp/nodejs-loaders", query: None, fragment: None }), name: "nodejs-loaders" }])
[2025-05-01T16:53:18Z INFO oxc_language_server] capabilities.workspace_configuration: true
2025-05-01 17:53:18.005 [info] [2025-05-01T16:53:18Z INFO oxc_language_server] failed to get configuration Error { code: ServerError(-32002), message: "Server not initialized", data: None }
[2025-05-01T16:53:18Z INFO oxc_language_server] configs [None, None, None]
2025-05-01 17:53:18.040 [info] [2025-05-01T16:53:18Z INFO oxc_language_server] workers [Some(Uri(Uri { scheme: Some("file"), authority: Some(Authority { userinfo: None, host: Host { text: "", data: RegName("") }, port: None }), path: "/Users/cameron/github/tmp/oxc-intellij-plugin/sandbox/custom-config", query: None, fragment: None }))Mutex { data: Options { run: OnType, config_path: None, flags: {} } }, Some(Uri(Uri { scheme: Some("file"), authority: Some(Authority { userinfo: None, host: Host { text: "", data: RegName("") }, port: None }), path: "/Users/cameron/github/tmp/oxc-intellij-plugin/sandbox/nested-configs", query: None, fragment: None }))Mutex { data: Options { run: OnType, config_path: None, flags: {} } }, Some(Uri(Uri { scheme: Some("file"), authority: Some(Authority { userinfo: None, host: Host { text: "", data: RegName("") }, port: None }), path: "/Users/cameron/github/tmp/nodejs-loaders", query: None, fragment: None }))Mutex { data: Options { run: OnType, config_path: None, flags: {} } }]
The error comes from tower_lsp_server, so it never actually goes to vscode. do we need to do this init logic after the server is initiialzied.
tower code:
/// Sends a custom request to the client.
///
/// # Initialization
///
/// If the request is sent to the client before the server has been initialized, this will
/// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
///
/// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
pub async fn send_request<R>(&self, params: R::Params) -> jsonrpc::Result<R::Result>
where
R: lsp_types::request::Request,
{
if let State::Initialized | State::ShutDown = self.inner.state.get() {
self.send_request_unchecked::<R>(params).await
} else {
let id = i64::from(self.inner.request_id.load(Ordering::SeqCst)) + 1;
let msg = Request::from_request::<R>(id.into(), params);
trace!("server not initialized, supressing message: {}", msg);
Err(jsonrpc::not_initialized_error())
}
}
Thanks for this info ❤️
I looked into the initialized request and hope to fix it with it. But I got some new problems on the way.
The workspace folders are only passed in the initialize request. We can pass a new object to initialization_options, which will include all configuration and will resolve the problem, but it is not expected from the clients (VSCode, Vim, JetBrains, ...). We need to make sure we get the right configuration fix_kind / nested_configs before creating the ServerLinter.
My Plan:
Check for the server's custom initialization_options configuration. If they passed a valid initialization_options, we will create the ServerLinter on initialize.
If the ServerLinter is not created, and we are in initialized request. Request the possible workspace configurations and create the ServerLinter. Use the fallback if not supported.
I guess we need to move WorkspaceWorker.options and WorkspaceWorker.nested_configs to WorkspaceWorker.server_linter. On the way, we can refactor the keys from oxc.flags to oxc.lint.flags. (Formatter flags in mind)
The configuration WorkspaceWorker.gitignore_glob will be interesting because with the inclusion of the
formatter it will change and should not include .eslintignore file.
This was mostly notes for me to get my problems / ideas structured. @camc314 and every other feel free to give some feedback.
Will start tomorrow and some new PRs to get the goal ❤️ If you guys want, you can close the 2 PRs and I will rebuild them. The changes will require some time.
that sounds great, thank you for working on this! The code for this change looked good to me, just the issue with the client request during startup.
It's up to you if you would like to close this PR/create a new one