Skip to content

Commit db05a15

Browse files
committed
refactor(language_server): do not request for worspace configuration when the client does not support it (#10507)
Currently working on supporting multiple workspace folder. `workspace/didChangeConfiguratoin` does not specific the Workspace, so some clients expect to request them. We need to check if the client support this or fallback to a global configuration change (changing all workspaces). ChatGPT: > what if workspace/configuration is not supported by the clinet ChatGPT: Ah, good follow-up — if the client does not support workspace/configuration, things get more limited and require some careful handling. 🔧 First: Check If the Client Supports It When your language server receives the initialize request from the client, it includes its capabilities. Look for: ``` "capabilities": { "workspace": { "configuration": true } } ``` > If that configuration field is false or missing, then you cannot use workspace/configuration, and the client will only send global settings via workspace/didChangeConfiguration.
1 parent 9f9e0e5 commit db05a15

File tree

2 files changed

+36
-4
lines changed

2 files changed

+36
-4
lines changed

crates/oxc_language_server/src/capabilities.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ use tower_lsp_server::lsp_types::{
77

88
use crate::{code_actions::CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, commands::LSP_COMMANDS};
99

10-
#[derive(Clone)]
10+
#[derive(Clone, Default)]
1111
pub struct Capabilities {
1212
pub code_action_provider: bool,
1313
pub workspace_apply_edit: bool,
1414
pub workspace_execute_command: bool,
15+
pub workspace_configuration: bool,
1516
}
1617

1718
impl From<ClientCapabilities> for Capabilities {
@@ -28,8 +29,17 @@ impl From<ClientCapabilities> for Capabilities {
2829
value.workspace.as_ref().is_some_and(|workspace| workspace.apply_edit.is_some());
2930
let workspace_execute_command =
3031
value.workspace.as_ref().is_some_and(|workspace| workspace.execute_command.is_some());
32+
let workspace_configuration = value
33+
.workspace
34+
.as_ref()
35+
.is_some_and(|workspace| workspace.configuration.is_some_and(|config| config));
3136

32-
Self { code_action_provider, workspace_apply_edit, workspace_execute_command }
37+
Self {
38+
code_action_provider,
39+
workspace_apply_edit,
40+
workspace_execute_command,
41+
workspace_configuration,
42+
}
3343
}
3444
}
3545

crates/oxc_language_server/src/main.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const OXC_CONFIG_FILE: &str = ".oxlintrc.json";
4545
struct Backend {
4646
client: Client,
4747
root_uri: OnceCell<Option<Uri>>,
48+
capabilities: OnceCell<Capabilities>,
4849
server_linter: RwLock<ServerLinter>,
4950
diagnostics_report_map: ConcurrentHashMap<String, Vec<DiagnosticReport>>,
5051
options: Mutex<Options>,
@@ -102,21 +103,37 @@ impl LanguageServer for Backend {
102103
*self.options.lock().await = value;
103104
}
104105

106+
let capabilities = Capabilities::from(params.capabilities);
107+
self.capabilities.set(capabilities.clone()).map_err(|err| {
108+
let message = match err {
109+
SetError::AlreadyInitializedError(_) => {
110+
"capabilities are already initialized".into()
111+
}
112+
SetError::InitializingError(_) => "initializing error".into(),
113+
};
114+
115+
Error { code: ErrorCode::ParseError, message, data: None }
116+
})?;
117+
105118
self.init_nested_configs().await;
106119
let oxlintrc = self.init_linter_config().await;
107120
self.init_ignore_glob(oxlintrc).await;
108121
Ok(InitializeResult {
109122
server_info: Some(ServerInfo { name: "oxc".into(), version: None }),
110123
offset_encoding: None,
111-
capabilities: Capabilities::from(params.capabilities).into(),
124+
capabilities: capabilities.into(),
112125
})
113126
}
114127

115128
async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
116129
let changed_options =
117130
if let Ok(options) = serde_json::from_value::<Options>(params.settings) {
118131
options
119-
} else {
132+
} else if self
133+
.capabilities
134+
.get()
135+
.is_some_and(|capabilities| capabilities.workspace_configuration)
136+
{
120137
// Fallback if some client didn't took changed configuration in params of `workspace/configuration`
121138
let Some(options) = self
122139
.client
@@ -133,6 +150,10 @@ impl LanguageServer for Backend {
133150
return;
134151
};
135152
options
153+
} else {
154+
// the client did not give os changes to the configuration and he is not able to respond to `workspace/configuration`
155+
// so we fallback to the current configuration
156+
self.options.lock().await.clone()
136157
};
137158

138159
let current_option = &self.options.lock().await.clone();
@@ -565,6 +586,7 @@ async fn main() {
565586
let (service, socket) = LspService::build(|client| Backend {
566587
client,
567588
root_uri: OnceCell::new(),
589+
capabilities: OnceCell::new(),
568590
server_linter: RwLock::new(server_linter),
569591
diagnostics_report_map,
570592
options: Mutex::new(Options::default()),

0 commit comments

Comments
 (0)