Fix subdirectory `.editorconfig` files being ignored in certain directory structures (#48203)

ᴀᴍᴛᴏᴀᴇʀ created

Closes #48187

The bug occurred when iterating internal_configs (a BTreeMap sorted by
path): the code would `break` on the first non-matching path, causing
configs with lexicographically later paths to be skipped.

For example, when querying "d/d.rs" with configs ["", "b", "d"],
iteration would break at "b" (since "d/d.rs" doesn't start with "b"),
preventing "d"'s config from being applied.

This PR replaces `break` with `continue` to skip non-ancestors, and adds
a minor early-exit optimization when `config_path > for_path` since
later paths can't be ancestors.

Release Notes:

- Fixed subdirectory `.editorconfig` files being ignored in certain
directory structures

Change summary

crates/project/tests/integration/project_tests.rs | 11 ++++++++++-
crates/settings/src/editorconfig_store.rs         |  6 ++++--
2 files changed, 14 insertions(+), 3 deletions(-)

Detailed changes

crates/project/tests/integration/project_tests.rs 🔗

@@ -215,6 +215,13 @@ async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) {
             "b.rs": "fn b() {\n    B\n}",
         },
         "c.js": "def c\n  C\nend",
+        "d": {
+            ".editorconfig": r#"
+            [*.rs]
+                indent_size = 1
+            "#,
+            "d.rs": "fn d() {\n    D\n}",
+        },
         "README.json": "tabs are better\n",
     }));
 
@@ -252,6 +259,7 @@ async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) {
         let settings_a = settings_for("a.rs");
         let settings_b = settings_for("b/b.rs");
         let settings_c = settings_for("c.js");
+        let settings_d = settings_for("d/d.rs");
         let settings_readme = settings_for("README.json");
 
         // .editorconfig overrides .zed/settings
@@ -261,8 +269,9 @@ async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) {
         assert_eq!(settings_a.remove_trailing_whitespace_on_save, true);
         assert_eq!(settings_a.preferred_line_length, 120);
 
-        // .editorconfig in b/ overrides .editorconfig in root
+        // .editorconfig in subdirectory overrides .editorconfig in root
         assert_eq!(Some(settings_b.tab_size), NonZeroU32::new(2));
+        assert_eq!(Some(settings_d.tab_size), NonZeroU32::new(1));
 
         // "indent_size" is not set, so "tab_width" is used
         assert_eq!(Some(settings_c.tab_size), NonZeroU32::new(10));

crates/settings/src/editorconfig_store.rs 🔗

@@ -345,9 +345,11 @@ impl EditorconfigStore {
         }
 
         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) {
-                properties.use_fallbacks();
-                return Some(properties);
+                continue;
             }
             let parsed_editorconfig = parsed_editorconfig?;
             if parsed_editorconfig.is_root {