mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
json_schema_store: match symlinked settings/keymap files for autocomplete
When a config file is opened via its symlink path, schema_file_match returned only the path-as-given. The schema selector matches that string against fileMatch globs like "**/*settings.json", but the opened buffer URI is the canonicalized real path, so the schema never bound and autocomplete was disabled. Return both the original path and the canonicalized path (when they differ), so either form binds to the schema. No-op for non-symlinked paths. Closes #54888.
This commit is contained in:
parent
832c17e819
commit
5eaab414fc
3 changed files with 97 additions and 23 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -9268,6 +9268,7 @@ dependencies = [
|
||||||
"settings",
|
"settings",
|
||||||
"snippet_provider",
|
"snippet_provider",
|
||||||
"task",
|
"task",
|
||||||
|
"tempfile",
|
||||||
"theme",
|
"theme",
|
||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ default = []
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
settings = { workspace = true, features = ["test-support"] }
|
settings = { workspace = true, features = ["test-support"] }
|
||||||
|
tempfile.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -449,45 +449,48 @@ pub fn all_schema_file_associations(
|
||||||
.flat_map(|(_, glob_strings)| glob_strings)
|
.flat_map(|(_, glob_strings)| glob_strings)
|
||||||
.cloned();
|
.cloned();
|
||||||
let jsonc_globs = extension_globs.chain(override_globs).collect::<Vec<_>>();
|
let jsonc_globs = extension_globs.chain(override_globs).collect::<Vec<_>>();
|
||||||
|
let settings_file_matches = schema_file_match_entries(paths::settings_file());
|
||||||
|
let keymap_file_matches = schema_file_match_entries(paths::keymap_file());
|
||||||
|
let mut tasks_file_matches = schema_file_match_entries(paths::tasks_file());
|
||||||
|
tasks_file_matches.push(
|
||||||
|
paths::local_tasks_file_relative_path()
|
||||||
|
.as_unix_str()
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
let mut debug_file_matches = schema_file_match_entries(paths::debug_scenarios_file());
|
||||||
|
debug_file_matches.push(
|
||||||
|
paths::local_debug_file_relative_path()
|
||||||
|
.as_unix_str()
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
let snippet_file_matches =
|
||||||
|
schema_file_match_entries(paths::snippets_dir().join("*.json").as_path());
|
||||||
|
|
||||||
let mut file_associations = serde_json::json!([
|
let mut file_associations = serde_json::json!([
|
||||||
{
|
{
|
||||||
"fileMatch": [
|
"fileMatch": settings_file_matches,
|
||||||
schema_file_match(paths::settings_file()),
|
|
||||||
],
|
|
||||||
"url": format!("{SCHEMA_URI_PREFIX}settings"),
|
"url": format!("{SCHEMA_URI_PREFIX}settings"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fileMatch": [
|
"fileMatch": [
|
||||||
paths::local_settings_file_relative_path()],
|
paths::local_settings_file_relative_path()
|
||||||
|
],
|
||||||
"url": format!("{SCHEMA_URI_PREFIX}project_settings"),
|
"url": format!("{SCHEMA_URI_PREFIX}project_settings"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fileMatch": [schema_file_match(paths::keymap_file())],
|
"fileMatch": keymap_file_matches,
|
||||||
"url": format!("{SCHEMA_URI_PREFIX}keymap"),
|
"url": format!("{SCHEMA_URI_PREFIX}keymap"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fileMatch": [
|
"fileMatch": tasks_file_matches,
|
||||||
schema_file_match(paths::tasks_file()),
|
|
||||||
paths::local_tasks_file_relative_path()
|
|
||||||
],
|
|
||||||
"url": format!("{SCHEMA_URI_PREFIX}tasks"),
|
"url": format!("{SCHEMA_URI_PREFIX}tasks"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fileMatch": [
|
"fileMatch": debug_file_matches,
|
||||||
schema_file_match(paths::debug_scenarios_file()),
|
|
||||||
paths::local_debug_file_relative_path()
|
|
||||||
],
|
|
||||||
"url": format!("{SCHEMA_URI_PREFIX}debug_tasks"),
|
"url": format!("{SCHEMA_URI_PREFIX}debug_tasks"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fileMatch": [
|
"fileMatch": snippet_file_matches,
|
||||||
schema_file_match(
|
|
||||||
paths::snippets_dir()
|
|
||||||
.join("*.json")
|
|
||||||
.as_path()
|
|
||||||
)
|
|
||||||
],
|
|
||||||
"url": format!("{SCHEMA_URI_PREFIX}snippets"),
|
"url": format!("{SCHEMA_URI_PREFIX}snippets"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -619,11 +622,80 @@ fn root_schema_from_action_schema(
|
||||||
schema
|
schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build the LSP fileMatch entries for `path`.
|
||||||
|
///
|
||||||
|
/// The JSON LSP matches incoming file URIs against these glob patterns,
|
||||||
|
/// so we register both the symlinked location (if any) and the resolved
|
||||||
|
/// canonical path. Without this, opening `~/.config/zed/settings.json`
|
||||||
|
/// when it is a symlink to a file under a different directory no longer
|
||||||
|
/// binds the settings schema (zed-industries/zed#54888).
|
||||||
|
fn schema_file_match_entries(path: &std::path::Path) -> Vec<String> {
|
||||||
|
let mut out = Vec::with_capacity(2);
|
||||||
|
out.push(stripped_match(path));
|
||||||
|
if let Ok(canonical) = path.canonicalize() {
|
||||||
|
if canonical != path {
|
||||||
|
out.push(stripped_match(&canonical));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn schema_file_match(path: &std::path::Path) -> String {
|
fn stripped_match(path: &std::path::Path) -> String {
|
||||||
path.strip_prefix(path.parent().unwrap().parent().unwrap())
|
let parent = path.parent().and_then(|p| p.parent()).unwrap_or(path);
|
||||||
.unwrap()
|
path.strip_prefix(parent)
|
||||||
|
.unwrap_or(path)
|
||||||
.display()
|
.display()
|
||||||
.to_string()
|
.to_string()
|
||||||
.replace('\\', "/")
|
.replace('\\', "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stripped_match_drops_two_parent_components() {
|
||||||
|
let path = PathBuf::from("/home/user/.config/zed/settings.json");
|
||||||
|
assert_eq!(stripped_match(&path), "zed/settings.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_file_match_entries_returns_single_for_regular_file() {
|
||||||
|
let tmp = tempfile::TempDir::new().unwrap();
|
||||||
|
// Some platforms expose the temp directory through a symlinked prefix.
|
||||||
|
// Canonicalize the root so this test only covers non-symlinked files.
|
||||||
|
let root = tmp.path().canonicalize().unwrap();
|
||||||
|
let zed_dir = root.join("zed");
|
||||||
|
fs::create_dir(&zed_dir).unwrap();
|
||||||
|
let regular = zed_dir.join("settings.json");
|
||||||
|
fs::write(®ular, "{}").unwrap();
|
||||||
|
let entries = schema_file_match_entries(®ular);
|
||||||
|
assert_eq!(entries.len(), 1);
|
||||||
|
assert_eq!(entries[0], "zed/settings.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_file_match_entries_returns_both_for_symlink() {
|
||||||
|
let tmp = tempfile::TempDir::new().unwrap();
|
||||||
|
let zed_dir = tmp.path().join("zed");
|
||||||
|
fs::create_dir(&zed_dir).unwrap();
|
||||||
|
let target = tmp.path().join("settings_target.json");
|
||||||
|
fs::write(&target, "{}").unwrap();
|
||||||
|
let link = zed_dir.join("settings.json");
|
||||||
|
#[cfg(unix)]
|
||||||
|
std::os::unix::fs::symlink(&target, &link).unwrap();
|
||||||
|
#[cfg(windows)]
|
||||||
|
std::os::windows::fs::symlink_file(&target, &link).unwrap();
|
||||||
|
let entries = schema_file_match_entries(&link);
|
||||||
|
assert!(entries.iter().any(|entry| entry == "zed/settings.json"));
|
||||||
|
assert!(
|
||||||
|
entries
|
||||||
|
.iter()
|
||||||
|
.any(|entry| entry.ends_with("settings_target.json"))
|
||||||
|
);
|
||||||
|
assert_eq!(entries.len(), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue