settings: Improve performance for internal editorconfig resolution (#48243)
Smit Barmase
created 2 months ago
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
Change summary
crates/project/tests/integration/project_tests.rs | 48 +++++++++++++++++
crates/settings/src/editorconfig_store.rs | 42 ++++++++------
2 files changed, 71 insertions(+), 19 deletions(-)
Detailed changes
@@ -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);
@@ -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 !for_path.starts_with(directory_with_config) {
- continue;
- }
- let parsed_editorconfig = parsed_editorconfig?;
- if parsed_editorconfig.is_root {
- properties = EditorconfigProperties::new();
+ 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;
+ }
+ }
}
- 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()?;
+ }
}
}