settings: Improve performance for internal editorconfig resolution (#48243)

Replaces O(N) iteration over all internal configs with O(D × log N)
direct ancestor lookups, where D is path depth and N is total config
count.

Release Notes:

- N/A
This commit is contained in:
Smit Barmase authored and GitHub committed 2026-02-03 11:45:45 +05:30
1 parent 3c4368a076
commit 5ff32226cf
2 files changed
+71 -19

No files matched your search

@@ -349,6 +349,54 @@ async fn test_external_editorconfig_support(cx: &mut gpui::TestAppContext) {
});
}
#[gpui::test]
async fn test_internal_editorconfig_root_stops_traversal(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/worktree"),
json!({
".editorconfig": "[*]\nindent_size = 99\n",
"src": {
".editorconfig": "root = true\n[*]\nindent_size = 2\n",
"file.rs": "fn main() {}",
}
}),
)
.await;
let project = Project::test(fs, [path!("/worktree").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
cx.executor().run_until_parked();
cx.update(|cx| {
let tree = worktree.read(cx);
let file_entry = tree
.entry_for_path(rel_path("src/file.rs"))
.unwrap()
.clone();
let file = File::for_entry(file_entry, worktree.clone());
let file_language = project
.read(cx)
.languages()
.load_language_for_file_path(file.path.as_std_path());
let file_language = cx
.foreground_executor()
.block_on(file_language)
.expect("Failed to get file language");
let file = file as _;
let settings = language_settings(Some(file_language.name()), Some(&file), cx).into_owned();
assert_eq!(Some(settings.tab_size), NonZeroU32::new(2));
});
}
#[gpui::test]
async fn test_external_editorconfig_root_stops_traversal(cx: &mut gpui::TestAppContext) {
init_test(cx);
+23 -19
View File
@@ -323,12 +323,13 @@ impl EditorconfigStore {
) -> Option<EditorconfigProperties> {
let mut properties = EditorconfigProperties::new();
let state = self.worktree_state.get(&for_worktree);
let empty_path: Arc<RelPath> = RelPath::empty().into();
let internal_root_config_is_root = state
.and_then(|state| state.internal_configs.get(&empty_path))
.and_then(|state| state.internal_configs.get(RelPath::empty()))
.and_then(|data| data.1.as_ref())
.is_some_and(|ec| ec.is_root);
let std_path = for_path.as_std_path();
if !internal_root_config_is_root {
for (_, _, parsed_editorconfig) in self.external_configs(for_worktree) {
if let Some(parsed_editorconfig) = parsed_editorconfig {
@@ -336,29 +337,32 @@ impl EditorconfigStore {
properties = EditorconfigProperties::new();
}
for section in &parsed_editorconfig.sections {
section
.apply_to(&mut properties, for_path.as_std_path())
.log_err()?;
section.apply_to(&mut properties, std_path).log_err()?;
}
}
}
}
for (directory_with_config, _, parsed_editorconfig) in self.internal_configs(for_worktree) {
if directory_with_config > for_path {
break;
if let Some(state) = state {
let mut internal_configs: SmallVec<[&Editorconfig; 8]> = SmallVec::new();
for ancestor in for_path.ancestors() {
if let Some((_, parsed)) = state.internal_configs.get(ancestor) {
let config = parsed.as_ref()?;
internal_configs.push(config);
if config.is_root {
break;
}
}
}
if !for_path.starts_with(directory_with_config) {
continue;
}
let parsed_editorconfig = parsed_editorconfig?;
if parsed_editorconfig.is_root {
properties = EditorconfigProperties::new();
}
for section in &parsed_editorconfig.sections {
section
.apply_to(&mut properties, for_path.as_std_path())
.log_err()?;
for config in internal_configs.into_iter().rev() {
if config.is_root {
properties = EditorconfigProperties::new();
}
for section in &config.sections {
section.apply_to(&mut properties, std_path).log_err()?;
}
}
}