Add edit JSON button (#39732)

Mikayla Maki created

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

Cargo.lock                                     |   1 
crates/gpui/src/window.rs                      |   8 
crates/settings/src/settings_store.rs          |  71 ++-
crates/settings_ui/Cargo.toml                  |   5 
crates/settings_ui/examples/.zed/settings.json |   1 
crates/settings_ui/examples/ui.rs              | 113 ------
crates/settings_ui/src/settings_ui.rs          | 358 +++++++++++++++----
7 files changed, 332 insertions(+), 225 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -14372,6 +14372,7 @@ dependencies = [
  "gpui",
  "heck 0.5.0",
  "language",
+ "log",
  "menu",
  "node_runtime",
  "paths",

crates/gpui/src/window.rs 🔗

@@ -4633,6 +4633,14 @@ pub struct WindowHandle<V> {
     state_type: PhantomData<V>,
 }
 
+impl<V> Debug for WindowHandle<V> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("WindowHandle")
+            .field("any_handle", &self.any_handle.id.as_u64())
+            .finish()
+    }
+}
+
 impl<V: 'static + Render> WindowHandle<V> {
     /// Creates a new handle from a window ID.
     /// This does not check if the root type of the window is `V`.

crates/settings/src/settings_store.rs 🔗

@@ -162,8 +162,8 @@ pub enum SettingsFile {
     User,
     Server,
     Default,
-    /// Local also represents project settings in ssh projects as well as local projects
-    Local((WorktreeId, Arc<RelPath>)),
+    /// Represents project settings in ssh projects as well as local projects
+    Project((WorktreeId, Arc<RelPath>)),
 }
 
 #[derive(Clone)]
@@ -469,7 +469,7 @@ impl SettingsStore {
                 // rev because these are sorted by path, so highest precedence is last
                 .rev()
                 .cloned()
-                .map(SettingsFile::Local),
+                .map(SettingsFile::Project),
         );
 
         if self.server_settings.is_some() {
@@ -496,7 +496,7 @@ impl SettingsStore {
                 .map(|settings| settings.content.as_ref()),
             SettingsFile::Default => Some(self.default_settings.as_ref()),
             SettingsFile::Server => self.server_settings.as_deref(),
-            SettingsFile::Local(ref key) => self.local_settings.get(key),
+            SettingsFile::Project(ref key) => self.local_settings.get(key),
         }
     }
 
@@ -515,8 +515,8 @@ impl SettingsStore {
                 continue;
             }
 
-            if let SettingsFile::Local((wt_id, ref path)) = file
-                && let SettingsFile::Local((target_wt_id, ref target_path)) = target_file
+            if let SettingsFile::Project((wt_id, ref path)) = file
+                && let SettingsFile::Project((target_wt_id, ref target_path)) = target_file
                 && (wt_id != target_wt_id || !target_path.starts_with(path))
             {
                 // if requesting value from a local file, don't return values from local files in different worktrees
@@ -543,7 +543,7 @@ impl SettingsStore {
         target_file: SettingsFile,
         pick: fn(&SettingsContent) -> &Option<T>,
     ) -> (SettingsFile, Option<&T>) {
-        // TODO: Add a metadata field for overriding the "overrides" tag, for contextually different settings
+        // todo(settings_ui): Add a metadata field for overriding the "overrides" tag, for contextually different settings
         //  e.g. disable AI isn't overridden, or a vec that gets extended instead or some such
 
         // todo(settings_ui) cache all files
@@ -556,9 +556,9 @@ impl SettingsStore {
             }
             found_file = true;
 
-            if let SettingsFile::Local((wt_id, ref path)) = file
-                && let SettingsFile::Local((target_wt_id, ref target_path)) = target_file
-                && (wt_id != target_wt_id || !target_path.starts_with(&path))
+            if let SettingsFile::Project((worktree_id, ref path)) = file
+                && let SettingsFile::Project((target_worktree_id, ref target_path)) = target_file
+                && (worktree_id != target_worktree_id || !target_path.starts_with(&path))
             {
                 // if requesting value from a local file, don't return values from local files in different worktrees
                 continue;
@@ -1718,7 +1718,7 @@ mod tests {
         let default_value = get(&store.default_settings).unwrap();
 
         assert_eq!(
-            store.get_value_from_file(SettingsFile::Local(local.clone()), get),
+            store.get_value_from_file(SettingsFile::Project(local.clone()), get),
             (SettingsFile::User, Some(&0))
         );
         assert_eq!(
@@ -1727,7 +1727,7 @@ mod tests {
         );
         store.set_user_settings(r#"{}"#, cx).unwrap();
         assert_eq!(
-            store.get_value_from_file(SettingsFile::Local(local.clone()), get),
+            store.get_value_from_file(SettingsFile::Project(local.clone()), get),
             (SettingsFile::Default, Some(&default_value))
         );
         store
@@ -1740,8 +1740,8 @@ mod tests {
             )
             .unwrap();
         assert_eq!(
-            store.get_value_from_file(SettingsFile::Local(local.clone()), get),
-            (SettingsFile::Local(local), Some(&80))
+            store.get_value_from_file(SettingsFile::Project(local.clone()), get),
+            (SettingsFile::Project(local), Some(&80))
         );
         assert_eq!(
             store.get_value_from_file(SettingsFile::User, get),
@@ -1821,12 +1821,12 @@ mod tests {
 
         // each local child should only inherit from it's parent
         assert_eq!(
-            store.get_value_from_file(SettingsFile::Local(local_2_child), get),
-            (SettingsFile::Local(local_2), Some(&2))
+            store.get_value_from_file(SettingsFile::Project(local_2_child), get),
+            (SettingsFile::Project(local_2), Some(&2))
         );
         assert_eq!(
-            store.get_value_from_file(SettingsFile::Local(local_1_child.clone()), get),
-            (SettingsFile::Local(local_1.clone()), Some(&1))
+            store.get_value_from_file(SettingsFile::Project(local_1_child.clone()), get),
+            (SettingsFile::Project(local_1.clone()), Some(&1))
         );
 
         // adjacent children should be treated as siblings not inherit from each other
@@ -1851,8 +1851,8 @@ mod tests {
             .unwrap();
 
         assert_eq!(
-            store.get_value_from_file(SettingsFile::Local(local_1_adjacent_child.clone()), get),
-            (SettingsFile::Local(local_1.clone()), Some(&1))
+            store.get_value_from_file(SettingsFile::Project(local_1_adjacent_child.clone()), get),
+            (SettingsFile::Project(local_1.clone()), Some(&1))
         );
         store
             .set_local_settings(
@@ -1873,8 +1873,8 @@ mod tests {
             )
             .unwrap();
         assert_eq!(
-            store.get_value_from_file(SettingsFile::Local(local_1_child), get),
-            (SettingsFile::Local(local_1), Some(&1))
+            store.get_value_from_file(SettingsFile::Project(local_1_child), get),
+            (SettingsFile::Project(local_1), Some(&1))
         );
     }
 
@@ -1950,9 +1950,9 @@ mod tests {
             overrides,
             vec![
                 SettingsFile::User,
-                SettingsFile::Local(wt0_root.clone()),
-                SettingsFile::Local(wt0_child1.clone()),
-                SettingsFile::Local(wt1_root.clone()),
+                SettingsFile::Project(wt0_root.clone()),
+                SettingsFile::Project(wt0_child1.clone()),
+                SettingsFile::Project(wt1_root.clone()),
             ]
         );
 
@@ -1960,25 +1960,26 @@ mod tests {
         assert_eq!(
             overrides,
             vec![
-                SettingsFile::Local(wt0_root.clone()),
-                SettingsFile::Local(wt0_child1.clone()),
-                SettingsFile::Local(wt1_root.clone()),
+                SettingsFile::Project(wt0_root.clone()),
+                SettingsFile::Project(wt0_child1.clone()),
+                SettingsFile::Project(wt1_root.clone()),
             ]
         );
 
-        let overrides = store.get_overrides_for_field(SettingsFile::Local(wt0_root), get);
+        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_root), get);
         assert_eq!(overrides, vec![]);
 
-        let overrides = store.get_overrides_for_field(SettingsFile::Local(wt0_child1.clone()), get);
+        let overrides =
+            store.get_overrides_for_field(SettingsFile::Project(wt0_child1.clone()), get);
         assert_eq!(overrides, vec![]);
 
-        let overrides = store.get_overrides_for_field(SettingsFile::Local(wt0_child2), get);
+        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_child2), get);
         assert_eq!(overrides, vec![]);
 
-        let overrides = store.get_overrides_for_field(SettingsFile::Local(wt1_root), get);
+        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt1_root), get);
         assert_eq!(overrides, vec![]);
 
-        let overrides = store.get_overrides_for_field(SettingsFile::Local(wt1_subdir), get);
+        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt1_subdir), get);
         assert_eq!(overrides, vec![]);
 
         let wt0_deep_child = (
@@ -1995,10 +1996,10 @@ mod tests {
             )
             .unwrap();
 
-        let overrides = store.get_overrides_for_field(SettingsFile::Local(wt0_deep_child), get);
+        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_deep_child), get);
         assert_eq!(overrides, vec![]);
 
-        let overrides = store.get_overrides_for_field(SettingsFile::Local(wt0_child1), get);
+        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_child1), get);
         assert_eq!(overrides, vec![]);
     }
 }

crates/settings_ui/Cargo.toml 🔗

@@ -39,6 +39,7 @@ util.workspace = true
 workspace-hack.workspace = true
 workspace.workspace = true
 zed_actions.workspace = true
+log.workspace = true
 
 [dev-dependencies]
 assets.workspace = true
@@ -52,7 +53,3 @@ session.workspace = true
 settings.workspace = true
 zlog.workspace = true
 pretty_assertions.workspace = true
-
-[[example]]
-name = "ui"
-path = "examples/ui.rs"

crates/settings_ui/examples/ui.rs 🔗

@@ -1,113 +0,0 @@
-use std::sync::Arc;
-
-use futures::StreamExt;
-use gpui::AppContext as _;
-use settings::{DEFAULT_KEYMAP_PATH, KeymapFile, SettingsStore, watch_config_file};
-use settings_ui::open_settings_editor;
-use ui::BorrowAppContext;
-
-fn merge_paths(a: &std::path::Path, b: &std::path::Path) -> std::path::PathBuf {
-    let a_parts: Vec<_> = a.components().collect();
-    let b_parts: Vec<_> = b.components().collect();
-
-    let mut overlap = 0;
-    for i in 0..=a_parts.len().min(b_parts.len()) {
-        if a_parts[a_parts.len() - i..] == b_parts[..i] {
-            overlap = i;
-        }
-    }
-
-    let mut result = std::path::PathBuf::new();
-    for part in &a_parts {
-        result.push(part.as_os_str());
-    }
-    for part in &b_parts[overlap..] {
-        result.push(part.as_os_str());
-    }
-    result
-}
-
-fn main() {
-    zlog::init();
-    zlog::init_output_stderr();
-
-    let [crate_path, file_path] = [env!("CARGO_MANIFEST_DIR"), file!()].map(std::path::Path::new);
-    let example_dir_abs_path = merge_paths(crate_path, file_path)
-        .parent()
-        .unwrap()
-        .to_path_buf();
-
-    let app = gpui::Application::new().with_assets(assets::Assets);
-
-    let fs = Arc::new(fs::RealFs::new(None, app.background_executor()));
-    let mut user_settings_file_rx = watch_config_file(
-        &app.background_executor(),
-        fs.clone(),
-        paths::settings_file().clone(),
-    );
-
-    app.run(move |cx| {
-        <dyn fs::Fs>::set_global(fs.clone(), cx);
-        settings::init(cx);
-        settings_ui::init(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
-        client::init_settings(cx);
-        workspace::init_settings(cx);
-        // production client because fake client requires gpui/test-support
-        // and that causes issues with the real stuff we want to do
-        let client = client::Client::production(cx);
-        let user_store = cx.new(|cx| client::UserStore::new(client.clone(), cx));
-        let languages = Arc::new(language::LanguageRegistry::new(
-            cx.background_executor().clone(),
-        ));
-
-        client::init(&client, cx);
-
-        project::Project::init(&client, cx);
-
-        zlog::info!(
-            "Creating fake worktree in {}",
-            example_dir_abs_path.display(),
-        );
-        let project = project::Project::local(
-            client.clone(),
-            node_runtime::NodeRuntime::unavailable(),
-            user_store,
-            languages,
-            fs.clone(),
-            Some(Default::default()), // WARN: if None is passed here, prepare to be process bombed
-            cx,
-        );
-        let worktree_task = project.update(cx, |project, cx| {
-            project.create_worktree(example_dir_abs_path, true, cx)
-        });
-        cx.spawn(async move |_| {
-            let worktree = worktree_task.await.unwrap();
-            std::mem::forget(worktree);
-        })
-        .detach();
-        std::mem::forget(project);
-
-        language::init(cx);
-        editor::init(cx);
-        menu::init();
-
-        let keybindings =
-            KeymapFile::load_asset_allow_partial_failure(DEFAULT_KEYMAP_PATH, cx).unwrap();
-        cx.bind_keys(keybindings);
-        cx.spawn(async move |cx| {
-            while let Some(content) = user_settings_file_rx.next().await {
-                cx.update(|cx| {
-                    cx.update_global(|store: &mut SettingsStore, cx| {
-                        store.set_user_settings(&content, cx).unwrap()
-                    })
-                })
-                .ok();
-            }
-        })
-        .detach();
-
-        open_settings_editor(cx).unwrap();
-        cx.activate(true);
-    });
-}

crates/settings_ui/src/settings_ui.rs 🔗

@@ -35,6 +35,7 @@ use ui::{
 };
 use ui_input::{NumberField, NumberFieldType};
 use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath};
+use workspace::{OpenOptions, OpenVisible, Workspace};
 use zed_actions::OpenSettingsEditor;
 
 use crate::components::SettingsEditor;
@@ -220,9 +221,15 @@ pub fn init(cx: &mut App) {
                 }
             });
             if has_flag {
-                div.on_action(cx.listener(|_, _: &OpenSettingsEditor, _, cx| {
-                    open_settings_editor(cx).ok();
-                }))
+                div.on_action(
+                    cx.listener(|workspace, _: &OpenSettingsEditor, window, cx| {
+                        let window_handle = window
+                            .window_handle()
+                            .downcast::<Workspace>()
+                            .expect("Workspaces are root Windows");
+                        open_settings_editor(workspace, window_handle, cx);
+                    }),
+                )
             } else {
                 div
             }
@@ -435,7 +442,11 @@ fn init_renderers(cx: &mut App) {
     // });
 }
 
-pub fn open_settings_editor(cx: &mut App) -> anyhow::Result<WindowHandle<SettingsWindow>> {
+pub fn open_settings_editor(
+    _workspace: &mut Workspace,
+    workspace_handle: WindowHandle<Workspace>,
+    cx: &mut App,
+) {
     let existing_window = cx
         .windows()
         .into_iter()
@@ -443,29 +454,35 @@ pub fn open_settings_editor(cx: &mut App) -> anyhow::Result<WindowHandle<Setting
 
     if let Some(existing_window) = existing_window {
         existing_window
-            .update(cx, |_, window, _| {
+            .update(cx, |settings_window, window, _| {
+                settings_window.original_window = Some(workspace_handle);
                 window.activate_window();
             })
             .ok();
-        return Ok(existing_window);
+        return;
     }
 
-    cx.open_window(
-        WindowOptions {
-            titlebar: Some(TitlebarOptions {
-                title: Some("Settings Window".into()),
-                appears_transparent: true,
-                traffic_light_position: Some(point(px(12.0), px(12.0))),
-            }),
-            focus: true,
-            show: true,
-            kind: gpui::WindowKind::Normal,
-            window_background: cx.theme().window_background_appearance(),
-            window_min_size: Some(size(px(800.), px(600.))), // 4:3 Aspect Ratio
-            ..Default::default()
-        },
-        |window, cx| cx.new(|cx| SettingsWindow::new(window, cx)),
-    )
+    // We have to defer this to get the workspace off the stack.
+
+    cx.defer(move |cx| {
+        cx.open_window(
+            WindowOptions {
+                titlebar: Some(TitlebarOptions {
+                    title: Some("Settings Window".into()),
+                    appears_transparent: true,
+                    traffic_light_position: Some(point(px(12.0), px(12.0))),
+                }),
+                focus: true,
+                show: true,
+                kind: gpui::WindowKind::Normal,
+                window_background: cx.theme().window_background_appearance(),
+                window_min_size: Some(size(px(800.), px(600.))), // 4:3 Aspect Ratio
+                ..Default::default()
+            },
+            |window, cx| cx.new(|cx| SettingsWindow::new(Some(workspace_handle), window, cx)),
+        )
+        .log_err();
+    });
 }
 
 /// The current sub page path that is selected.
@@ -489,7 +506,9 @@ fn sub_page_stack_mut() -> std::sync::RwLockWriteGuard<'static, Vec<SubPage>> {
 }
 
 pub struct SettingsWindow {
+    original_window: Option<WindowHandle<Workspace>>,
     files: Vec<(SettingsUiFile, FocusHandle)>,
+    worktree_root_dirs: HashMap<WorktreeId, String>,
     current_file: SettingsUiFile,
     pages: Vec<SettingsPage>,
     search_bar: Entity<Editor>,
@@ -549,12 +568,13 @@ impl std::fmt::Debug for SettingsPageItem {
 impl SettingsPageItem {
     fn render(
         &self,
-        file: SettingsUiFile,
+        settings_window: &SettingsWindow,
         section_header: &'static str,
         is_last: bool,
         window: &mut Window,
         cx: &mut Context<SettingsWindow>,
     ) -> AnyElement {
+        let file = settings_window.current_file.clone();
         match self {
             SettingsPageItem::SectionHeader(header) => v_flex()
                 .w_full()
@@ -602,7 +622,9 @@ impl SettingsPageItem {
                                             this.child(
                                                 Label::new(format!(
                                                     "— set in {}",
-                                                    file_set_in.name()
+                                                    settings_window
+                                                        .display_name(&file_set_in)
+                                                        .expect("File name should exist")
                                                 ))
                                                 .color(Color::Muted)
                                                 .size(LabelSize::Small),
@@ -764,27 +786,24 @@ impl PartialEq for SubPageLink {
 #[allow(unused)]
 #[derive(Clone, PartialEq)]
 enum SettingsUiFile {
-    User,                              // Uses all settings.
-    Local((WorktreeId, Arc<RelPath>)), // Has a special name, and special set of settings
-    Server(&'static str),              // Uses a special name, and the user settings
+    User,                                // Uses all settings.
+    Project((WorktreeId, Arc<RelPath>)), // Has a special name, and special set of settings
+    Server(&'static str),                // Uses a special name, and the user settings
 }
 
 impl SettingsUiFile {
-    fn name(&self) -> SharedString {
+    fn worktree_id(&self) -> Option<WorktreeId> {
         match self {
-            SettingsUiFile::User => SharedString::new_static("User"),
-            // TODO is PathStyle::local() ever not appropriate?
-            SettingsUiFile::Local((_, path)) => {
-                format!("Local ({})", path.display(PathStyle::local())).into()
-            }
-            SettingsUiFile::Server(file) => format!("Server ({})", file).into(),
+            SettingsUiFile::User => None,
+            SettingsUiFile::Project((worktree_id, _)) => Some(*worktree_id),
+            SettingsUiFile::Server(_) => None,
         }
     }
 
     fn from_settings(file: settings::SettingsFile) -> Option<Self> {
         Some(match file {
             settings::SettingsFile::User => SettingsUiFile::User,
-            settings::SettingsFile::Local(location) => SettingsUiFile::Local(location),
+            settings::SettingsFile::Project(location) => SettingsUiFile::Project(location),
             settings::SettingsFile::Server => SettingsUiFile::Server("todo: server name"),
             settings::SettingsFile::Default => return None,
         })
@@ -793,7 +812,7 @@ impl SettingsUiFile {
     fn to_settings(&self) -> settings::SettingsFile {
         match self {
             SettingsUiFile::User => settings::SettingsFile::User,
-            SettingsUiFile::Local(location) => settings::SettingsFile::Local(location.clone()),
+            SettingsUiFile::Project(location) => settings::SettingsFile::Project(location.clone()),
             SettingsUiFile::Server(_) => settings::SettingsFile::Server,
         }
     }
@@ -801,14 +820,18 @@ impl SettingsUiFile {
     fn mask(&self) -> FileMask {
         match self {
             SettingsUiFile::User => USER,
-            SettingsUiFile::Local(_) => LOCAL,
+            SettingsUiFile::Project(_) => LOCAL,
             SettingsUiFile::Server(_) => SERVER,
         }
     }
 }
 
 impl SettingsWindow {
-    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
+    pub fn new(
+        original_window: Option<WindowHandle<Workspace>>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Self {
         let font_family_cache = theme::FontFamilyCache::global(cx);
 
         cx.spawn(async move |this, cx| {
@@ -842,6 +865,8 @@ impl SettingsWindow {
         .detach();
 
         let mut this = Self {
+            original_window,
+            worktree_root_dirs: HashMap::default(),
             files: vec![],
             current_file: current_file,
             pages: vec![],
@@ -1142,6 +1167,7 @@ impl SettingsWindow {
     }
 
     fn fetch_files(&mut self, cx: &mut Context<SettingsWindow>) {
+        self.worktree_root_dirs.clear();
         let prev_files = self.files.clone();
         let settings_store = cx.global::<SettingsStore>();
         let mut ui_files = vec![];
@@ -1150,6 +1176,28 @@ impl SettingsWindow {
             let Some(settings_ui_file) = SettingsUiFile::from_settings(file) else {
                 continue;
             };
+
+            if let Some(worktree_id) = settings_ui_file.worktree_id() {
+                let directory_name = all_projects(cx)
+                    .find_map(|project| project.read(cx).worktree_for_id(worktree_id, cx))
+                    .and_then(|worktree| worktree.read(cx).root_dir())
+                    .and_then(|root_dir| {
+                        root_dir
+                            .file_name()
+                            .map(|os_string| os_string.to_string_lossy().to_string())
+                    });
+
+                let Some(directory_name) = directory_name else {
+                    log::error!(
+                        "No directory name found for settings file at worktree ID: {}",
+                        worktree_id
+                    );
+                    continue;
+                };
+
+                self.worktree_root_dirs.insert(worktree_id, directory_name);
+            }
+
             let focus_handle = prev_files
                 .iter()
                 .find_map(|(prev_file, handle)| {
@@ -1182,7 +1230,7 @@ impl SettingsWindow {
         self.build_ui(cx);
     }
 
-    fn render_files(
+    fn render_files_header(
         &self,
         _window: &mut Window,
         cx: &mut Context<SettingsWindow>,
@@ -1202,22 +1250,79 @@ impl SettingsWindow {
                             .iter()
                             .enumerate()
                             .map(|(ix, (file, focus_handle))| {
-                                Button::new(ix, file.name())
-                                    .toggle_state(file == &self.current_file)
-                                    .selected_style(ButtonStyle::Tinted(ui::TintColor::Accent))
-                                    .track_focus(focus_handle)
-                                    .on_click(cx.listener(
-                                        move |this, evt: &gpui::ClickEvent, window, cx| {
-                                            this.change_file(ix, cx);
-                                            if evt.is_keyboard() {
-                                                this.focus_first_nav_item(window, cx);
-                                            }
-                                        },
-                                    ))
+                                Button::new(
+                                    ix,
+                                    self.display_name(&file)
+                                        .expect("Files should always have a name"),
+                                )
+                                .toggle_state(file == &self.current_file)
+                                .selected_style(ButtonStyle::Tinted(ui::TintColor::Accent))
+                                .track_focus(focus_handle)
+                                .on_click(cx.listener(
+                                    move |this, evt: &gpui::ClickEvent, window, cx| {
+                                        this.change_file(ix, cx);
+                                        if evt.is_keyboard() {
+                                            this.focus_first_nav_item(window, cx);
+                                        }
+                                    },
+                                ))
                             }),
                     ),
             )
-            .child(Button::new("temp", "Edit in settings.json").style(ButtonStyle::Outlined)) // This should be replaced by the actual, functioning button
+            .child(
+                Button::new(
+                    "edit-in-json",
+                    format!("Edit in {}", self.file_location_str()),
+                )
+                .style(ButtonStyle::Outlined)
+                .on_click(cx.listener(|this, _, _, cx| {
+                    this.open_current_settings_file(cx);
+                })),
+            )
+    }
+
+    pub(crate) fn display_name(&self, file: &SettingsUiFile) -> Option<String> {
+        match file {
+            SettingsUiFile::User => Some("User".to_string()),
+            SettingsUiFile::Project((worktree_id, path)) => self
+                .worktree_root_dirs
+                .get(&worktree_id)
+                .map(|directory_name| {
+                    let path_style = PathStyle::local();
+                    if path.is_empty() {
+                        directory_name.clone()
+                    } else {
+                        format!(
+                            "{}{}{}",
+                            directory_name,
+                            path_style.separator(),
+                            path.display(path_style)
+                        )
+                    }
+                }),
+            SettingsUiFile::Server(file) => Some(file.to_string()),
+        }
+    }
+
+    fn file_location_str(&self) -> String {
+        match &self.current_file {
+            SettingsUiFile::User => "settings.json".to_string(),
+            SettingsUiFile::Project((worktree_id, path)) => self
+                .worktree_root_dirs
+                .get(&worktree_id)
+                .map(|directory_name| {
+                    let path_style = PathStyle::local();
+                    let file_path = path.join(paths::local_settings_file_relative_path());
+                    format!(
+                        "{}{}{}",
+                        directory_name,
+                        path_style.separator(),
+                        file_path.display(path_style)
+                    )
+                })
+                .expect("Current file should always be present in root dir map"),
+            SettingsUiFile::Server(file) => file.to_string(),
+        }
     }
 
     fn render_search(&self, _window: &mut Window, cx: &mut App) -> Div {
@@ -1450,7 +1555,7 @@ impl SettingsWindow {
                         section_header = Some(*header);
                     }
                     item.render(
-                        self.current_file.clone(),
+                        self,
                         section_header.expect("All items rendered after a section header"),
                         no_bottom_border || is_last,
                         window,
@@ -1470,7 +1575,8 @@ impl SettingsWindow {
         let page_content;
 
         if sub_page_stack().len() == 0 {
-            page_header = self.render_files(window, cx).into_any_element();
+            page_header = self.render_files_header(window, cx).into_any_element();
+
             page_content = self
                 .render_page_items(self.page_items(), window, cx)
                 .into_any_element();
@@ -1513,6 +1619,113 @@ impl SettingsWindow {
             );
     }
 
+    fn open_current_settings_file(&mut self, cx: &mut Context<Self>) {
+        match &self.current_file {
+            SettingsUiFile::User => {
+                let Some(original_window) = self.original_window else {
+                    return;
+                };
+                original_window
+                    .update(cx, |workspace, window, cx| {
+                        workspace
+                            .with_local_workspace(window, cx, |workspace, window, cx| {
+                                let create_task = workspace.project().update(cx, |project, cx| {
+                                    project.find_or_create_worktree(
+                                        paths::config_dir().as_path(),
+                                        false,
+                                        cx,
+                                    )
+                                });
+                                let open_task = workspace.open_paths(
+                                    vec![paths::settings_file().to_path_buf()],
+                                    OpenOptions {
+                                        visible: Some(OpenVisible::None),
+                                        ..Default::default()
+                                    },
+                                    None,
+                                    window,
+                                    cx,
+                                );
+
+                                cx.spawn_in(window, async move |workspace, cx| {
+                                    create_task.await.ok();
+                                    open_task.await;
+
+                                    workspace.update_in(cx, |_, window, cx| {
+                                        window.activate_window();
+                                        cx.notify();
+                                    })
+                                })
+                                .detach();
+                            })
+                            .detach();
+                    })
+                    .ok();
+            }
+            SettingsUiFile::Project((worktree_id, path)) => {
+                let mut corresponding_workspace: Option<WindowHandle<Workspace>> = None;
+                let settings_path = path.join(paths::local_settings_file_relative_path());
+                let Some(app_state) = workspace::AppState::global(cx).upgrade() else {
+                    return;
+                };
+                for workspace in app_state.workspace_store.read(cx).workspaces() {
+                    let contains_settings_file = workspace
+                        .read_with(cx, |workspace, cx| {
+                            workspace.project().read(cx).contains_local_settings_file(
+                                *worktree_id,
+                                settings_path.as_ref(),
+                                cx,
+                            )
+                        })
+                        .ok();
+                    if Some(true) == contains_settings_file {
+                        corresponding_workspace = Some(*workspace);
+
+                        break;
+                    }
+                }
+
+                let Some(corresponding_workspace) = corresponding_workspace else {
+                    log::error!(
+                        "No corresponding workspace found for settings file {}",
+                        settings_path.as_std_path().display()
+                    );
+
+                    return;
+                };
+
+                // TODO: move zed::open_local_file() APIs to this crate, and
+                // re-implement the "initial_contents" behavior
+                corresponding_workspace
+                    .update(cx, |workspace, window, cx| {
+                        let open_task = workspace.open_path(
+                            (*worktree_id, settings_path.clone()),
+                            None,
+                            true,
+                            window,
+                            cx,
+                        );
+
+                        cx.spawn_in(window, async move |workspace, cx| {
+                            if open_task.await.log_err().is_some() {
+                                workspace
+                                    .update_in(cx, |_, window, cx| {
+                                        window.activate_window();
+                                        cx.notify();
+                                    })
+                                    .ok();
+                            }
+                        })
+                        .detach();
+                    })
+                    .ok();
+            }
+            SettingsUiFile::Server(_) => {
+                return;
+            }
+        };
+    }
+
     fn current_page_index(&self) -> usize {
         self.page_index_from_navbar_index(self.navbar_entry)
     }
@@ -1631,29 +1844,28 @@ impl Render for SettingsWindow {
     }
 }
 
+fn all_projects(cx: &App) -> impl Iterator<Item = Entity<project::Project>> {
+    workspace::AppState::global(cx)
+        .upgrade()
+        .map(|app_state| {
+            app_state
+                .workspace_store
+                .read(cx)
+                .workspaces()
+                .iter()
+                .filter_map(|workspace| Some(workspace.read(cx).ok()?.project().clone()))
+        })
+        .into_iter()
+        .flatten()
+}
+
 fn update_settings_file(
     file: SettingsUiFile,
     cx: &mut App,
     update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
 ) -> Result<()> {
     match file {
-        SettingsUiFile::Local((worktree_id, rel_path)) => {
-            fn all_projects(cx: &App) -> impl Iterator<Item = Entity<project::Project>> {
-                workspace::AppState::global(cx)
-                    .upgrade()
-                    .map(|app_state| {
-                        app_state
-                            .workspace_store
-                            .read(cx)
-                            .workspaces()
-                            .iter()
-                            .filter_map(|workspace| {
-                                Some(workspace.read(cx).ok()?.project().clone())
-                            })
-                    })
-                    .into_iter()
-                    .flatten()
-            }
+        SettingsUiFile::Project((worktree_id, rel_path)) => {
             let rel_path = rel_path.join(paths::local_settings_file_relative_path());
             let project = all_projects(cx).find(|project| {
                 project.read_with(cx, |project, cx| {
@@ -1872,7 +2084,7 @@ mod test {
         }
 
         fn new_builder(window: &mut Window, cx: &mut Context<Self>) -> Self {
-            let mut this = Self::new(window, cx);
+            let mut this = Self::new(None, window, cx);
             this.navbar_entries.clear();
             this.pages.clear();
             this
@@ -2022,6 +2234,8 @@ mod test {
         }
 
         let mut settings_window = SettingsWindow {
+            original_window: None,
+            worktree_root_dirs: HashMap::default(),
             files: Vec::default(),
             current_file: crate::SettingsUiFile::User,
             pages,