languages: Fix local path of JSON and YAML schemas (#44794)

Closes #30938

Release Notes:

- Fixed: Unable to load relative path JSON schema for YAML validation
(#30938)


This patch follows the vscode LSP client logic, see
[`jsonClient.ts`](https://github.com/microsoft/vscode/blob/cee904f80cc6d15f22c850482789e06b1f536e72/extensions/json-language-features/client/src/jsonClient.ts#L768-L770).
The `url` of the JSON schemas settings and the YAML schemas settings
should be resolved to an absolute path in the LSP client when it is
submitted to the server.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Zhongqiu ZhaoandConrad Irwin authored and GitHub committed 2026-02-12 04:50:54 +00:00
1 parent 8a3badeb90
commit fa1e392a90
6 files changed
+74 -8

No files matched your search

+1 -1
View File
@@ -379,7 +379,7 @@ pub trait LspAdapterDelegate: Send + Sync {
fn http_client(&self) -> Arc<dyn HttpClient>;
fn worktree_id(&self) -> WorktreeId;
fn worktree_root_path(&self) -> &Path;
fn resolve_executable_path(&self, path: PathBuf) -> PathBuf;
fn resolve_relative_path(&self, path: PathBuf) -> PathBuf;
fn update_status(&self, language: LanguageServerName, status: BinaryStatus);
fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>>;
async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>;
+35 -1
View File
@@ -296,7 +296,7 @@ impl LspAdapter for JsonLspAdapter {
});
let project_options = cx.update(|cx| {
language_server_settings(delegate.as_ref(), &self.name(), cx)
.and_then(|s| s.settings.clone())
.and_then(|s| worktree_root(delegate, s.settings.clone()))
});
if let Some(override_options) = project_options {
@@ -320,6 +320,40 @@ impl LspAdapter for JsonLspAdapter {
}
}
fn worktree_root(delegate: &Arc<dyn LspAdapterDelegate>, settings: Option<Value>) -> Option<Value> {
let Some(Value::Object(mut settings_map)) = settings else {
return settings;
};
let Some(Value::Object(json_config)) = settings_map.get_mut("json") else {
return Some(Value::Object(settings_map));
};
let Some(Value::Array(schemas)) = json_config.get_mut("schemas") else {
return Some(Value::Object(settings_map));
};
for schema in schemas.iter_mut() {
let Value::Object(schema_map) = schema else {
continue;
};
let Some(Value::String(url)) = schema_map.get_mut("url") else {
continue;
};
if !url.starts_with(".") && !url.starts_with("~") {
continue;
}
*url = delegate
.resolve_relative_path(url.clone().into())
.to_string_lossy()
.into_owned();
}
Some(Value::Object(settings_map))
}
async fn get_cached_server_binary(
container_dir: PathBuf,
node: &NodeRuntime,
+33 -1
View File
@@ -156,7 +156,7 @@ impl LspAdapter for YamlLspAdapter {
let project_options = cx.update(|cx| {
language_server_settings(delegate.as_ref(), &Self::SERVER_NAME, cx)
.and_then(|s| s.settings.clone())
.and_then(|s| worktree_root(delegate, s.settings.clone()))
});
if let Some(override_options) = project_options {
merge_json_value_into(override_options, &mut options);
@@ -165,6 +165,38 @@ impl LspAdapter for YamlLspAdapter {
}
}
fn worktree_root(delegate: &Arc<dyn LspAdapterDelegate>, settings: Option<Value>) -> Option<Value> {
let Some(Value::Object(mut settings_map)) = settings else {
return settings;
};
let Some(Value::Object(yaml_config)) = settings_map.get_mut("yaml") else {
return Some(Value::Object(settings_map));
};
let Some(Value::Object(schemas)) = yaml_config.remove("schemas") else {
return Some(Value::Object(settings_map));
};
let schemas = schemas
.into_iter()
.map(|(url, v)| {
if !url.starts_with(".") && !url.starts_with("~") {
(url, v)
} else {
let resolved_url = delegate
.resolve_relative_path(url.into())
.to_string_lossy()
.into_owned();
(resolved_url, v)
}
})
.collect::<serde_json::Map<String, Value>>();
yaml_config.insert("schemas".into(), Value::Object(schemas));
Some(Value::Object(settings_map))
}
async fn get_cached_server_binary(
container_dir: PathBuf,
node: &NodeRuntime,
+1 -1
View File
@@ -265,7 +265,7 @@ impl DapStore {
DapBinary::Default => None,
DapBinary::Custom(binary) => {
let path = PathBuf::from(binary);
Some(worktree.read(cx).resolve_executable_path(path))
Some(worktree.read(cx).resolve_relative_path(path))
}
});
let user_args = dap_settings.and_then(|s| s.args.clone());
+3 -3
View File
@@ -713,7 +713,7 @@ impl LocalLspStore {
env.extend(settings.env.unwrap_or_default());
Ok(LanguageServerBinary {
path: delegate.resolve_executable_path(path),
path: delegate.resolve_relative_path(path),
env: Some(env),
arguments: settings
.arguments
@@ -14069,8 +14069,8 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
self.worktree.abs_path().as_ref()
}
fn resolve_executable_path(&self, path: PathBuf) -> PathBuf {
self.worktree.resolve_executable_path(path)
fn resolve_relative_path(&self, path: PathBuf) -> PathBuf {
self.worktree.resolve_relative_path(path)
}
async fn shell_env(&self) -> HashMap<String, String> {
+1 -1
View File
@@ -2495,7 +2495,7 @@ impl Snapshot {
///
/// Relative paths that do not exist in the worktree may
/// still be found using the `PATH` environment variable.
pub fn resolve_executable_path(&self, path: PathBuf) -> PathBuf {
pub fn resolve_relative_path(&self, path: PathBuf) -> PathBuf {
if let Some(path_str) = path.to_str() {
if let Some(remaining_path) = path_str.strip_prefix("~/") {
return home_dir().join(remaining_path);