From 641c58d04ced013e1cdd20bc97edae84bc9c4bf1 Mon Sep 17 00:00:00 2001 From: Shaz Ravenswood Date: Sat, 7 Feb 2026 07:35:18 +0800 Subject: [PATCH] lsp: Update root_path for compatibility with language servers (#48587) This PR updates the deprecated `rootPath` field in the LSP `InitializeParams` for backwards compatibility with language servers that still rely on this field. ### Issue Some language servers (notably the Salesforce Apex Language Server) only read from the deprecated `rootPath` field in the LSP initialize request and do not use `rootUri` or `workspaceFolders`. When Zed sends `root_path: None`, these language servers fail to initialize because they cannot determine the workspace root. Example error from the Apex Language Server: ``` NullPointerException at apex.jorje.lsp.impl.db.nddb.NdApexIndex.getToolsStoragePath(NdApexIndex.java:723) ``` The Apex LSP's initialize handler does: ```java serverSetup.setRootPath(params.getRootPath()); ``` VSCode's LanguageClient sends both `rootPath` and `rootUri` for backwards compatibility: https://github.com/microsoft/vscode-languageserver-node/blob/main/client/src/common/client.ts#L1434 ### Fix Derive `rootPath` from the existing `root_uri` field when building the initialize params. The LSP spec states that if both `rootPath` and `rootUri` are provided, `rootUri` wins, so this change should be backwards compatible and won't affect language servers that properly use `rootUri`(1). --- (1) [LSP Specification - InitializeParams](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initializeParams) - notes that `rootPath` is deprecated in favor of `workspaceFolders`, but for backwards compatibility it should still be provided when possible. Release Notes: - Improved compatibility with legacy language servers --- crates/lsp/src/lsp.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index e211eb1d444e33b98eb5e274a998ee4914d80f6d..e2ad995b0f983b9a67d4ae5b03af851412e76337 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -738,7 +738,12 @@ impl LanguageServer { #[allow(deprecated)] InitializeParams { process_id: Some(std::process::id()), - root_path: None, + root_path: Some( + self.root_uri + .to_file_path() + .map(|path| path.to_string_lossy().into_owned()) + .unwrap_or_else(|_| self.root_uri.path().to_string()), + ), root_uri: Some(self.root_uri.clone()), initialization_options: None, capabilities: ClientCapabilities { @@ -2104,4 +2109,38 @@ mod tests { "{\"jsonrpc\":\"\",\"id\":0,\"error\":null}" ); } + + #[gpui::test] + async fn test_initialize_params_has_root_path_and_root_uri(cx: &mut TestAppContext) { + cx.update(|cx| { + release_channel::init(semver::Version::new(0, 0, 0), cx); + }); + let (server, _fake) = FakeLanguageServer::new( + LanguageServerId(0), + LanguageServerBinary { + path: "path/to/language-server".into(), + arguments: vec![], + env: None, + }, + "test-lsp".to_string(), + Default::default(), + &mut cx.to_async(), + ); + + let params = cx.update(|cx| server.default_initialize_params(false, false, cx)); + + #[allow(deprecated)] + let root_uri = params.root_uri.expect("root_uri should be set"); + #[allow(deprecated)] + let root_path = params.root_path.expect("root_path should be set"); + + let expected_path = root_uri + .to_file_path() + .expect("root_uri should be a valid file path"); + assert_eq!( + root_path, + expected_path.to_string_lossy(), + "root_path should be derived from root_uri" + ); + } }