settings ui: Update file headers when adding or removing projects (#40968)

Anthony Eid created

This PR gets the `SettingsWindow` struct to subscribe to all
`Entity<Project>` events and any future project entities that are
created. When a project emits an event that signals a worktree has been
added or removed, the settings window refetches all settings files it
can find.

This fixes a bug where the settings ui would notice some project
settings that were created or opened after the `SettingsWindow` has been
initialized.

I also renamed `LOCAL` file mask to `PROJECT` to be inline with the
`SettingsFile` naming convention.

Release Notes:

- settings ui: Fix bug where project setting files wouldn't be detected
if they were created or opened after while an active settings window is
open

Change summary

crates/settings_ui/src/page_data.rs   | 146 +++++++++++-----------
crates/settings_ui/src/settings_ui.rs | 187 +++++++++++++++++-----------
crates/workspace/src/workspace.rs     |   2 
3 files changed, 186 insertions(+), 149 deletions(-)

Detailed changes

crates/settings_ui/src/page_data.rs 🔗

@@ -5,7 +5,7 @@ use strum::IntoDiscriminant as _;
 use ui::{IntoElement, SharedString};
 
 use crate::{
-    DynamicItem, LOCAL, SettingField, SettingItem, SettingsFieldMetadata, SettingsPage,
+    DynamicItem, PROJECT, SettingField, SettingItem, SettingsFieldMetadata, SettingsPage,
     SettingsPageItem, SubPageLink, USER, all_language_names, sub_page_stack,
 };
 
@@ -26,7 +26,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
             items: vec![
                 SettingsPageItem::SectionHeader("General Settings"),
                 SettingsPageItem::SettingItem(SettingItem {
-                    files: LOCAL,
+                    files: PROJECT,
                     title: "Project Name",
                     description: "The displayed name of this project. If left empty, the root directory name will be displayed.",
                     field: Box::new(
@@ -1022,7 +1022,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                         },
                     }),
                     metadata: None,
-                    files: USER | LOCAL,
+                    files: USER | PROJECT,
                 }),
                 // todo(settings_ui): This needs a custom component
                 SettingsPageItem::SettingItem(SettingItem {
@@ -1046,7 +1046,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                         .unimplemented(),
                     ),
                     metadata: None,
-                    files: USER | LOCAL,
+                    files: USER | PROJECT,
                 }),
             ],
         },
@@ -2117,7 +2117,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                             .unimplemented(),
                         ),
                         metadata: None,
-                        files: USER | LOCAL,
+                        files: USER | PROJECT,
                     }),
                 ]);
 
@@ -2314,7 +2314,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                 items.extend(all_language_names(cx).into_iter().map(|language_name| {
                     SettingsPageItem::SubPageLink(SubPageLink {
                         title: language_name,
-                        files: USER | LOCAL,
+                        files: USER | PROJECT,
                         render: Arc::new(|this, window, cx| {
                             this.render_sub_page_items(
                                 language_settings_data()
@@ -4432,7 +4432,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                 SettingsPageItem::SectionHeader("Environment"),
                 SettingsPageItem::DynamicItem(DynamicItem {
                     discriminant: SettingItem {
-                        files: USER | LOCAL,
+                        files: USER | PROJECT,
                         title: "Shell",
                         description: "What shell to use when opening a terminal.",
                         field: Box::new(SettingField {
@@ -4496,7 +4496,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                             settings::ShellDiscriminants::System => vec![],
                             settings::ShellDiscriminants::Program => vec![
                                 SettingItem {
-                                    files: USER | LOCAL,
+                                    files: USER | PROJECT,
                                     title: "Program",
                                     description: "The shell program to use.",
                                     field: Box::new(SettingField {
@@ -4526,7 +4526,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                             ],
                             settings::ShellDiscriminants::WithArguments => vec![
                                 SettingItem {
-                                    files: USER | LOCAL,
+                                    files: USER | PROJECT,
                                     title: "Program",
                                     description: "The shell program to run.",
                                     field: Box::new(SettingField {
@@ -4554,7 +4554,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                                     metadata: None,
                                 },
                                 SettingItem {
-                                    files: USER | LOCAL,
+                                    files: USER | PROJECT,
                                     title: "Arguments",
                                     description: "The arguments to pass to the shell program.",
                                     field: Box::new(
@@ -4585,7 +4585,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                                     metadata: None,
                                 },
                                 SettingItem {
-                                    files: USER | LOCAL,
+                                    files: USER | PROJECT,
                                     title: "Title Override",
                                     description: "An optional string to override the title of the terminal tab.",
                                     field: Box::new(SettingField {
@@ -4615,7 +4615,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                 }),
                 SettingsPageItem::DynamicItem(DynamicItem {
                     discriminant: SettingItem {
-                        files: USER | LOCAL,
+                        files: USER | PROJECT,
                         title: "Working Directory",
                         description: "What working directory to use when launching the terminal.",
                         field: Box::new(SettingField {
@@ -4672,7 +4672,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                             settings::WorkingDirectoryDiscriminants::AlwaysHome => vec![],
                             settings::WorkingDirectoryDiscriminants::Always => vec![
                                 SettingItem {
-                                    files: USER | LOCAL,
+                                    files: USER | PROJECT,
                                     title: "Directory",
                                     description: "The directory path to use (will be shell expanded).",
                                     field: Box::new(SettingField {
@@ -4721,7 +4721,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                         .unimplemented(),
                     ),
                     metadata: None,
-                    files: USER | LOCAL,
+                    files: USER | PROJECT,
                 }),
                 SettingsPageItem::SettingItem(SettingItem {
                     title: "Detect Virtual Environment",
@@ -4748,7 +4748,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                         .unimplemented(),
                     ),
                     metadata: None,
-                    files: USER | LOCAL,
+                    files: USER | PROJECT,
                 }),
                 SettingsPageItem::SectionHeader("Font"),
                 SettingsPageItem::SettingItem(SettingItem {
@@ -5813,7 +5813,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Hard Tabs",
@@ -5832,7 +5832,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Auto Indent",
@@ -5851,7 +5851,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Auto Indent On Paste",
@@ -5870,7 +5870,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SectionHeader("Wrapping"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -5890,7 +5890,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Show Wrap Guides",
@@ -5909,7 +5909,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Preferred Line Length",
@@ -5928,7 +5928,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Wrap Guides",
@@ -5950,7 +5950,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Allow Rewrap",
@@ -5969,7 +5969,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SectionHeader("Indent Guides"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -5992,7 +5992,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Line Width",
@@ -6014,7 +6014,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Active Line Width",
@@ -6039,7 +6039,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Coloring",
@@ -6061,7 +6061,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Background Coloring",
@@ -6086,7 +6086,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SectionHeader("Formatting"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -6109,7 +6109,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Remove Trailing Whitespace On Save",
@@ -6128,7 +6128,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Ensure Final Newline On Save",
@@ -6147,7 +6147,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Formatter",
@@ -6169,7 +6169,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Use On Type Format",
@@ -6188,7 +6188,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Code Actions On Format",
@@ -6210,7 +6210,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SectionHeader("Autoclose"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -6230,7 +6230,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Use Auto Surround",
@@ -6249,7 +6249,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Always Treat Brackets As Autoclosed",
@@ -6268,7 +6268,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Jsx Tag Auto Close",
@@ -6288,7 +6288,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SectionHeader("Edit Predictions"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -6308,7 +6308,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Edit Predictions Disabled In",
@@ -6330,7 +6330,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SectionHeader("Whitespace"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -6350,7 +6350,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Space Whitespace Indicator",
@@ -6372,7 +6372,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Tab Whitespace Indicator",
@@ -6394,7 +6394,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SectionHeader("Completions"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -6414,7 +6414,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Show Completion Documentation",
@@ -6433,7 +6433,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Words",
@@ -6452,7 +6452,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Words Min Length",
@@ -6474,7 +6474,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SectionHeader("Inlay Hints"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -6494,7 +6494,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Show Value Hints",
@@ -6516,7 +6516,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Show Type Hints",
@@ -6535,7 +6535,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Show Parameter Hints",
@@ -6557,7 +6557,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Show Other Hints",
@@ -6579,7 +6579,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Show Background",
@@ -6598,7 +6598,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Edit Debounce Ms",
@@ -6620,7 +6620,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Scroll Debounce Ms",
@@ -6642,7 +6642,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Toggle On Modifiers Press",
@@ -6671,7 +6671,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
     ];
     if current_language().is_none() {
@@ -6709,7 +6709,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Variables",
@@ -6732,7 +6732,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Prefer LSP",
@@ -6752,7 +6752,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SectionHeader("Miscellaneous"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -6774,7 +6774,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Middle Click Paste",
@@ -6805,7 +6805,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
     ]);
 
@@ -6886,7 +6886,7 @@ fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Language Servers",
@@ -6908,7 +6908,7 @@ fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Linked Edits",
@@ -6927,7 +6927,7 @@ fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Go To Definition Fallback",
@@ -6960,7 +6960,7 @@ fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Fetch Timeout (milliseconds)",
@@ -6982,7 +6982,7 @@ fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Insert Mode",
@@ -7001,7 +7001,7 @@ fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SectionHeader("Debuggers"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -7024,7 +7024,7 @@ fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SectionHeader("Prettier"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -7044,7 +7044,7 @@ fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Parser",
@@ -7063,7 +7063,7 @@ fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
                 },
             }),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Plugins",
@@ -7085,7 +7085,7 @@ fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Options",
@@ -7107,7 +7107,7 @@ fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
                 .unimplemented(),
             ),
             metadata: None,
-            files: USER | LOCAL,
+            files: USER | PROJECT,
         }),
     ]
 }

crates/settings_ui/src/settings_ui.rs 🔗

@@ -12,7 +12,7 @@ use gpui::{
     point, prelude::*, px, uniform_list,
 };
 use heck::ToTitleCase as _;
-use project::WorktreeId;
+use project::{Project, WorktreeId};
 use schemars::JsonSchema;
 use serde::Deserialize;
 use settings::{Settings, SettingsContent, SettingsStore};
@@ -33,7 +33,7 @@ use ui::{
 };
 use ui_input::{NumberField, NumberFieldType};
 use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath};
-use workspace::{OpenOptions, OpenVisible, Workspace, client_side_decorations};
+use workspace::{AppState, OpenOptions, OpenVisible, Workspace, client_side_decorations};
 use zed_actions::{OpenSettings, OpenSettingsAt};
 
 use crate::components::{SettingsInputField, font_picker, icon_theme_picker, theme_picker};
@@ -559,7 +559,6 @@ pub fn open_settings_editor(
         existing_window
             .update(cx, |settings_window, window, cx| {
                 settings_window.original_window = Some(workspace_handle);
-                settings_window.observe_last_window_close(cx);
                 window.activate_window();
                 if let Some(path) = path {
                     open_path(path, settings_window, window, cx);
@@ -643,7 +642,6 @@ pub struct SettingsWindow {
     title_bar: Option<Entity<PlatformTitleBar>>,
     original_window: Option<WindowHandle<Workspace>>,
     files: Vec<(SettingsUiFile, FocusHandle)>,
-    drop_down_file: Option<usize>,
     worktree_root_dirs: HashMap<WorktreeId, String>,
     current_file: SettingsUiFile,
     pages: Vec<SettingsPage>,
@@ -1016,7 +1014,7 @@ impl std::fmt::Debug for FileMask {
         if self.contains(USER) {
             items.push("USER");
         }
-        if self.contains(LOCAL) {
+        if self.contains(PROJECT) {
             items.push("LOCAL");
         }
         if self.contains(SERVER) {
@@ -1028,7 +1026,7 @@ impl std::fmt::Debug for FileMask {
 }
 
 const USER: FileMask = FileMask(1 << 0);
-const LOCAL: FileMask = FileMask(1 << 2);
+const PROJECT: FileMask = FileMask(1 << 2);
 const SERVER: FileMask = FileMask(1 << 3);
 
 impl std::ops::BitAnd for FileMask {
@@ -1138,14 +1136,14 @@ impl SettingsUiFile {
     fn mask(&self) -> FileMask {
         match self {
             SettingsUiFile::User => USER,
-            SettingsUiFile::Project(_) => LOCAL,
+            SettingsUiFile::Project(_) => PROJECT,
             SettingsUiFile::Server(_) => SERVER,
         }
     }
 }
 
 impl SettingsWindow {
-    pub fn new(
+    fn new(
         original_window: Option<WindowHandle<Workspace>>,
         window: &mut Window,
         cx: &mut Context<Self>,
@@ -1182,6 +1180,60 @@ impl SettingsWindow {
         })
         .detach();
 
+        cx.on_window_closed(|cx| {
+            if let Some(existing_window) = cx
+                .windows()
+                .into_iter()
+                .find_map(|window| window.downcast::<SettingsWindow>())
+                && cx.windows().len() == 1
+            {
+                cx.update_window(*existing_window, |_, window, _| {
+                    window.remove_window();
+                })
+                .ok();
+            }
+        })
+        .detach();
+
+        if let Some(app_state) = AppState::global(cx).upgrade() {
+            for project in app_state
+                .workspace_store
+                .read(cx)
+                .workspaces()
+                .iter()
+                .filter_map(|space| {
+                    space
+                        .read(cx)
+                        .ok()
+                        .map(|workspace| workspace.project().clone())
+                })
+                .collect::<Vec<_>>()
+            {
+                cx.subscribe_in(&project, window, Self::handle_project_event)
+                    .detach();
+            }
+        } else {
+            log::error!("App state doesn't exist when creating a new settings window");
+        }
+
+        let this_weak = cx.weak_entity();
+        cx.observe_new::<Project>({
+            move |_, window, cx| {
+                let project = cx.entity();
+                let Some(window) = window else {
+                    return;
+                };
+
+                this_weak
+                    .update(cx, |_, cx| {
+                        cx.subscribe_in(&project, window, Self::handle_project_event)
+                            .detach();
+                    })
+                    .ok();
+            }
+        })
+        .detach();
+
         let title_bar = if !cfg!(target_os = "macos") {
             Some(cx.new(|cx| PlatformTitleBar::new("settings-title-bar", cx)))
         } else {
@@ -1198,7 +1250,7 @@ impl SettingsWindow {
 
             worktree_root_dirs: HashMap::default(),
             files: vec![],
-            drop_down_file: None,
+
             current_file: current_file,
             pages: vec![],
             navbar_entries: vec![],
@@ -1232,8 +1284,6 @@ impl SettingsWindow {
             list_state,
         };
 
-        this.observe_last_window_close(cx);
-
         this.fetch_files(window, cx);
         this.build_ui(window, cx);
         this.build_search_index();
@@ -1245,21 +1295,21 @@ impl SettingsWindow {
         this
     }
 
-    fn observe_last_window_close(&mut self, cx: &mut App) {
-        cx.on_window_closed(|cx| {
-            if let Some(existing_window) = cx
-                .windows()
-                .into_iter()
-                .find_map(|window| window.downcast::<SettingsWindow>())
-                && cx.windows().len() == 1
-            {
-                cx.update_window(*existing_window, |_, window, _| {
-                    window.remove_window();
-                })
-                .ok();
+    fn handle_project_event(
+        &mut self,
+        _: &Entity<Project>,
+        event: &project::Event,
+        window: &mut Window,
+        cx: &mut Context<SettingsWindow>,
+    ) {
+        match event {
+            project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded(_) => {
+                cx.defer_in(window, |this, window, cx| {
+                    this.fetch_files(window, cx);
+                });
             }
-        })
-        .detach();
+            _ => {}
+        }
     }
 
     fn toggle_navbar_entry(&mut self, nav_entry_index: usize) {
@@ -1702,7 +1752,7 @@ impl SettingsWindow {
             .iter()
             .any(|(file, _)| file == &self.current_file);
         if !current_file_still_exists {
-            self.change_file(0, false, window, cx);
+            self.change_file(0, window, cx);
         }
     }
 
@@ -1732,21 +1782,12 @@ impl SettingsWindow {
         self.open_navbar_entry_page(first_navbar_entry_index);
     }
 
-    fn change_file(
-        &mut self,
-        ix: usize,
-        drop_down_file: bool,
-        window: &mut Window,
-        cx: &mut Context<SettingsWindow>,
-    ) {
+    fn change_file(&mut self, ix: usize, window: &mut Window, cx: &mut Context<SettingsWindow>) {
         if ix >= self.files.len() {
             self.current_file = SettingsUiFile::User;
             self.build_ui(window, cx);
             return;
         }
-        if drop_down_file {
-            self.drop_down_file = Some(ix);
-        }
 
         if self.files[ix].0 == self.current_file {
             return;
@@ -1770,7 +1811,7 @@ impl SettingsWindow {
         window: &mut Window,
         cx: &mut Context<SettingsWindow>,
     ) -> impl IntoElement {
-        const OVERFLOW_LIMIT: usize = 1;
+        static OVERFLOW_LIMIT: usize = 1;
 
         let file_button =
             |ix, file: &SettingsUiFile, focus_handle, cx: &mut Context<SettingsWindow>| {
@@ -1785,7 +1826,7 @@ impl SettingsWindow {
                 .on_click(cx.listener({
                     let focus_handle = focus_handle.clone();
                     move |this, _: &gpui::ClickEvent, window, cx| {
-                        this.change_file(ix, false, window, cx);
+                        this.change_file(ix, window, cx);
                         focus_handle.focus(window);
                     }
                 }))
@@ -1810,25 +1851,24 @@ impl SettingsWindow {
                         ),
                     )
                     .when(self.files.len() > OVERFLOW_LIMIT, |div| {
-                        div.children(
-                            self.files
-                                .iter()
-                                .enumerate()
-                                .skip(OVERFLOW_LIMIT)
-                                .find(|(_, (file, _))| file == &self.current_file)
-                                .map(|(ix, (file, focus_handle))| {
-                                    file_button(ix, file, focus_handle, cx)
-                                })
-                                .or_else(|| {
-                                    let ix = self.drop_down_file.unwrap_or(OVERFLOW_LIMIT);
-                                    self.files.get(ix).map(|(file, focus_handle)| {
-                                        file_button(ix, file, focus_handle, cx)
-                                    })
-                                }),
-                        )
-                        .when(
-                            self.files.len() > OVERFLOW_LIMIT + 1,
-                            |div| {
+                        let selected_file_ix = self
+                            .files
+                            .iter()
+                            .enumerate()
+                            .skip(OVERFLOW_LIMIT)
+                            .find_map(|(ix, (file, _))| {
+                                if file == &self.current_file {
+                                    Some(ix)
+                                } else {
+                                    None
+                                }
+                            })
+                            .unwrap_or(OVERFLOW_LIMIT);
+
+                        let (file, focus_handle) = &self.files[selected_file_ix];
+
+                        div.child(file_button(selected_file_ix, file, focus_handle, cx))
+                            .when(self.files.len() > OVERFLOW_LIMIT + 1, |div| {
                                 div.child(
                                     DropdownMenu::new(
                                         "more-files",
@@ -1840,18 +1880,19 @@ impl SettingsWindow {
                                                 .enumerate()
                                                 .skip(OVERFLOW_LIMIT + 1)
                                             {
-                                                let (display_name, focus_handle) = if self
-                                                    .drop_down_file
-                                                    .is_some_and(|drop_down_ix| drop_down_ix == ix)
-                                                {
-                                                    ix = OVERFLOW_LIMIT;
-                                                    (
-                                                        self.display_name(&self.files[ix].0),
-                                                        self.files[ix].1.clone(),
-                                                    )
-                                                } else {
-                                                    (self.display_name(&file), focus_handle.clone())
-                                                };
+                                                let (display_name, focus_handle) =
+                                                    if selected_file_ix == ix {
+                                                        ix = OVERFLOW_LIMIT;
+                                                        (
+                                                            self.display_name(&self.files[ix].0),
+                                                            self.files[ix].1.clone(),
+                                                        )
+                                                    } else {
+                                                        (
+                                                            self.display_name(&file),
+                                                            focus_handle.clone(),
+                                                        )
+                                                    };
 
                                                 menu = menu.entry(
                                                     display_name
@@ -1861,9 +1902,7 @@ impl SettingsWindow {
                                                         let this = this.clone();
                                                         move |window, cx| {
                                                             this.update(cx, |this, cx| {
-                                                                this.change_file(
-                                                                    ix, true, window, cx,
-                                                                );
+                                                                this.change_file(ix, window, cx);
                                                             });
                                                             focus_handle.focus(window);
                                                         }
@@ -1884,8 +1923,7 @@ impl SettingsWindow {
                                     })
                                     .tab_index(0),
                                 )
-                            },
-                        )
+                            })
                     }),
             )
             .child(
@@ -3399,7 +3437,6 @@ pub mod test {
             worktree_root_dirs: HashMap::default(),
             files: Vec::default(),
             current_file: crate::SettingsUiFile::User,
-            drop_down_file: None,
             pages,
             search_bar: cx.new(|cx| Editor::single_line(window, cx)),
             navbar_entry: selected_idx.expect("Must have a selected navbar entry"),

crates/workspace/src/workspace.rs 🔗

@@ -1458,7 +1458,7 @@ impl Workspace {
             }),
             cx.on_release(move |this, cx| {
                 this.app_state.workspace_store.update(cx, move |store, _| {
-                    store.workspaces.remove(&window_handle.clone());
+                    store.workspaces.remove(&window_handle);
                 })
             }),
         ];