Skip to content

Commit ef3d0b1

Browse files
committed
feat(language_server): support multiple workspaces
1 parent 246d6dc commit ef3d0b1

File tree

5 files changed

+691
-523
lines changed

5 files changed

+691
-523
lines changed

crates/oxc_language_server/src/capabilities.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use tower_lsp_server::lsp_types::{
55
WorkspaceServerCapabilities,
66
};
77

8-
use crate::{code_actions::CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, commands::LSP_COMMANDS};
8+
use crate::{code_actions::CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, commands::FIX_ALL_COMMAND_ID};
99

1010
#[derive(Clone, Default)]
1111
pub struct Capabilities {
@@ -45,11 +45,6 @@ impl From<ClientCapabilities> for Capabilities {
4545

4646
impl From<Capabilities> for ServerCapabilities {
4747
fn from(value: Capabilities) -> Self {
48-
let commands = LSP_COMMANDS
49-
.iter()
50-
.filter_map(|c| if c.available(value.clone()) { Some(c.command_id()) } else { None })
51-
.collect();
52-
5348
Self {
5449
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
5550
workspace: Some(WorkspaceServerCapabilities {
@@ -74,7 +69,10 @@ impl From<Capabilities> for ServerCapabilities {
7469
None
7570
},
7671
execute_command_provider: if value.workspace_execute_command {
77-
Some(ExecuteCommandOptions { commands, ..Default::default() })
72+
Some(ExecuteCommandOptions {
73+
commands: vec![FIX_ALL_COMMAND_ID.to_string()],
74+
..Default::default()
75+
})
7876
} else {
7977
None
8078
},
Lines changed: 10 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,21 @@
1-
use std::str::FromStr;
2-
3-
use log::error;
41
use serde::Deserialize;
5-
use tower_lsp_server::{
6-
jsonrpc::{self, Error},
7-
lsp_types::{
8-
ApplyWorkspaceEditParams, TextEdit, Uri, WorkspaceEdit, request::ApplyWorkspaceEdit,
9-
},
10-
};
11-
12-
use crate::{Backend, capabilities::Capabilities};
13-
14-
pub const LSP_COMMANDS: [WorkspaceCommands; 1] = [WorkspaceCommands::FixAll(FixAllCommand)];
15-
16-
pub trait WorkspaceCommand {
17-
fn command_id(&self) -> String;
18-
fn available(&self, cap: Capabilities) -> bool;
19-
type CommandArgs<'a>: serde::Deserialize<'a>;
20-
async fn execute(
21-
&self,
22-
backend: &Backend,
23-
args: Self::CommandArgs<'_>,
24-
) -> jsonrpc::Result<Option<serde_json::Value>>;
25-
}
26-
27-
pub enum WorkspaceCommands {
28-
FixAll(FixAllCommand),
29-
}
30-
31-
impl WorkspaceCommands {
32-
pub fn command_id(&self) -> String {
33-
match self {
34-
WorkspaceCommands::FixAll(c) => c.command_id(),
35-
}
36-
}
37-
pub fn available(&self, cap: Capabilities) -> bool {
38-
match self {
39-
WorkspaceCommands::FixAll(c) => c.available(cap),
40-
}
41-
}
42-
pub async fn execute(
43-
&self,
44-
backend: &Backend,
45-
args: Vec<serde_json::Value>,
46-
) -> jsonrpc::Result<Option<serde_json::Value>> {
47-
match self {
48-
WorkspaceCommands::FixAll(c) => {
49-
let arg: Result<
50-
<FixAllCommand as WorkspaceCommand>::CommandArgs<'_>,
51-
serde_json::Error,
52-
> = serde_json::from_value(serde_json::Value::Array(args));
53-
if let Err(e) = arg {
54-
error!("Invalid args passed to {:?}: {e}", c.command_id());
55-
return Err(Error::invalid_request());
56-
}
57-
let arg = arg.unwrap();
58-
59-
c.execute(backend, arg).await
60-
}
61-
}
62-
}
63-
}
642

65-
pub struct FixAllCommand;
3+
pub const FIX_ALL_COMMAND_ID: &str = "oxc.fixAll";
664

675
#[derive(Deserialize)]
68-
pub struct FixAllCommandArg {
69-
uri: String,
6+
pub struct FixAllCommandArgs {
7+
pub uri: String,
708
}
719

72-
impl WorkspaceCommand for FixAllCommand {
73-
fn command_id(&self) -> String {
74-
"oxc.fixAll".into()
75-
}
76-
fn available(&self, cap: Capabilities) -> bool {
77-
cap.workspace_apply_edit
78-
}
79-
type CommandArgs<'a> = (FixAllCommandArg,);
80-
81-
async fn execute(
82-
&self,
83-
backend: &Backend,
84-
args: Self::CommandArgs<'_>,
85-
) -> jsonrpc::Result<Option<serde_json::Value>> {
86-
let url = Uri::from_str(&args.0.uri);
87-
if let Err(e) = url {
88-
error!("Invalid uri passed to {:?}: {e}", self.command_id());
89-
return Err(Error::invalid_request());
90-
}
91-
let url = url.unwrap();
10+
impl TryFrom<Vec<serde_json::Value>> for FixAllCommandArgs {
11+
type Error = &'static str;
9212

93-
let mut edits = vec![];
94-
if let Some(value) = backend.diagnostics_report_map.pin_owned().get(&url.to_string()) {
95-
for report in value {
96-
if let Some(fixed) = &report.fixed_content {
97-
edits.push(TextEdit { range: fixed.range, new_text: fixed.code.clone() });
98-
}
99-
}
100-
let _ = backend
101-
.client
102-
.send_request::<ApplyWorkspaceEdit>(ApplyWorkspaceEditParams {
103-
label: Some(match edits.len() {
104-
1 => "Oxlint: 1 fix applied".into(),
105-
n => format!("Oxlint: {n} fixes applied"),
106-
}),
107-
edit: WorkspaceEdit {
108-
#[expect(clippy::disallowed_types)]
109-
changes: Some(std::collections::HashMap::from([(url, edits)])),
110-
..WorkspaceEdit::default()
111-
},
112-
})
113-
.await;
13+
fn try_from(value: Vec<serde_json::Value>) -> Result<Self, Self::Error> {
14+
if value.len() != 1 {
15+
return Err("Expected exactly one argument for FixAllCommandArgs");
11416
}
11517

116-
Ok(None)
18+
let first_value = value.into_iter().next().ok_or("Missing argument")?;
19+
serde_json::from_value(first_value).map_err(|_| "Failed to parse FixAllCommandArgs")
11720
}
11821
}

crates/oxc_language_server/src/linter/server_linter.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::sync::Arc;
22

33
use tower_lsp_server::{UriExt, lsp_types::Uri};
44

5-
use oxc_linter::{ConfigStoreBuilder, FixKind, LintOptions, Linter};
5+
use oxc_linter::Linter;
66

77
use crate::linter::error_with_position::DiagnosticReport;
88
use crate::linter::isolated_lint_handler::IsolatedLintHandler;
@@ -15,7 +15,10 @@ pub struct ServerLinter {
1515
}
1616

1717
impl ServerLinter {
18+
#[cfg(test)]
1819
pub fn new(options: IsolatedLintHandlerOptions) -> Self {
20+
use oxc_linter::{ConfigStoreBuilder, FixKind, LintOptions};
21+
1922
let config_store =
2023
ConfigStoreBuilder::default().build().expect("Failed to build config store");
2124
let linter = Linter::new(LintOptions::default(), config_store).with_fix(FixKind::SafeFix);
@@ -42,7 +45,7 @@ mod test {
4245

4346
use super::*;
4447
use crate::linter::tester::Tester;
45-
use oxc_linter::{LintFilter, LintFilterKind, Oxlintrc};
48+
use oxc_linter::{ConfigStoreBuilder, LintFilter, LintFilterKind, LintOptions, Oxlintrc};
4649
use rustc_hash::FxHashMap;
4750

4851
#[test]

0 commit comments

Comments
 (0)