settings_ui: Make open project settings action open settings UI (#42669)

This PR makes the `OpenProjectSettings` action open the settings UI in
project settings mode for the first visible worktree, instead of opening
the file. It also adds a `OpenProjectSettingsFile` action that maintains
the old behavior.

Finally, this PR partially fixes a bug where the settings UI won't load
project settings when the settings window is loaded before opening a
project/workspace. This happens because the global `app_state` isn't
correct in the `Subscription` that refreshes the available setting files
to open. The bug is still present in some cases, but it's out of scope
for this PR.

Release Notes:

- settings ui: Project Settings action now opens settings UI instead of
a file
This commit is contained in:
Anthony Eid
2025-11-13 20:06:09 -05:00
committed by GitHub
parent 6f99eeffa8
commit 3da4d3aac3
4 changed files with 107 additions and 29 deletions
+95 -24
View File
@@ -15,7 +15,7 @@ use project::{Project, WorktreeId};
use release_channel::ReleaseChannel;
use schemars::JsonSchema;
use serde::Deserialize;
use settings::{Settings, SettingsContent, SettingsStore};
use settings::{Settings, SettingsContent, SettingsStore, initial_project_settings_content};
use std::{
any::{Any, TypeId, type_name},
cell::RefCell,
@@ -35,7 +35,7 @@ use ui::{
use ui_input::{NumberField, NumberFieldType};
use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath};
use workspace::{AppState, OpenOptions, OpenVisible, Workspace, client_side_decorations};
use zed_actions::{OpenSettings, OpenSettingsAt};
use zed_actions::{OpenProjectSettings, OpenSettings, OpenSettingsAt};
use crate::components::{
EnumVariantDropdown, SettingsInputField, font_picker, icon_theme_picker, theme_picker,
@@ -379,26 +379,30 @@ pub fn init(cx: &mut App) {
init_renderers(cx);
cx.observe_new(|workspace: &mut workspace::Workspace, _, _| {
workspace.register_action(
|workspace, OpenSettingsAt { path }: &OpenSettingsAt, window, cx| {
workspace
.register_action(
|workspace, OpenSettingsAt { path }: &OpenSettingsAt, window, cx| {
let window_handle = window
.window_handle()
.downcast::<Workspace>()
.expect("Workspaces are root Windows");
open_settings_editor(workspace, Some(&path), false, window_handle, cx);
},
)
.register_action(|workspace, _: &OpenSettings, window, cx| {
let window_handle = window
.window_handle()
.downcast::<Workspace>()
.expect("Workspaces are root Windows");
open_settings_editor(workspace, Some(&path), window_handle, cx);
},
);
})
.detach();
cx.observe_new(|workspace: &mut workspace::Workspace, _, _| {
workspace.register_action(|workspace, _: &OpenSettings, window, cx| {
let window_handle = window
.window_handle()
.downcast::<Workspace>()
.expect("Workspaces are root Windows");
open_settings_editor(workspace, None, window_handle, cx);
});
open_settings_editor(workspace, None, false, window_handle, cx);
})
.register_action(|workspace, _: &OpenProjectSettings, window, cx| {
let window_handle = window
.window_handle()
.downcast::<Workspace>()
.expect("Workspaces are root Windows");
open_settings_editor(workspace, None, true, window_handle, cx);
});
})
.detach();
}
@@ -506,6 +510,7 @@ fn init_renderers(cx: &mut App) {
pub fn open_settings_editor(
_workspace: &mut Workspace,
path: Option<&str>,
open_project_settings: bool,
workspace_handle: WindowHandle<Workspace>,
cx: &mut App,
) {
@@ -514,6 +519,8 @@ pub fn open_settings_editor(
/// Assumes a settings GUI window is already open
fn open_path(
path: &str,
// Note: This option is unsupported right now
_open_project_settings: bool,
settings_window: &mut SettingsWindow,
window: &mut Window,
cx: &mut Context<SettingsWindow>,
@@ -540,7 +547,17 @@ pub fn open_settings_editor(
settings_window.original_window = Some(workspace_handle);
window.activate_window();
if let Some(path) = path {
open_path(path, settings_window, window, cx);
open_path(path, open_project_settings, settings_window, window, cx);
} else if open_project_settings {
if let Some(file_index) = settings_window
.files
.iter()
.position(|(file, _)| file.worktree_id().is_some())
{
settings_window.change_file(file_index, window, cx);
}
cx.notify();
}
})
.ok();
@@ -588,7 +605,17 @@ pub fn open_settings_editor(
cx.new(|cx| SettingsWindow::new(Some(workspace_handle), window, cx));
settings_window.update(cx, |settings_window, cx| {
if let Some(path) = path {
open_path(&path, settings_window, window, cx);
open_path(&path, open_project_settings, settings_window, window, cx);
} else if open_project_settings {
if let Some(file_index) = settings_window
.files
.iter()
.position(|(file, _)| file.worktree_id().is_some())
{
settings_window.change_file(file_index, window, cx);
}
settings_window.fetch_files(window, cx);
}
});
@@ -1159,7 +1186,7 @@ fn all_language_names(cx: &App) -> Vec<SharedString> {
}
#[allow(unused)]
#[derive(Clone, PartialEq)]
#[derive(Clone, PartialEq, Debug)]
enum SettingsUiFile {
User, // Uses all settings.
Project((WorktreeId, Arc<RelPath>)), // Has a special name, and special set of settings
@@ -1283,15 +1310,34 @@ impl SettingsWindow {
})
.collect::<Vec<_>>()
{
cx.observe_release_in(&project, window, |this, _, window, cx| {
this.fetch_files(window, cx)
})
.detach();
cx.subscribe_in(&project, window, Self::handle_project_event)
.detach();
}
for workspace in app_state
.workspace_store
.read(cx)
.workspaces()
.iter()
.filter_map(|space| space.entity(cx).ok())
{
cx.observe_release_in(&workspace, window, |this, _, window, cx| {
this.fetch_files(window, cx)
})
.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>({
let this_weak = this_weak.clone();
move |_, window, cx| {
let project = cx.entity();
let Some(window) = window else {
@@ -1299,7 +1345,13 @@ impl SettingsWindow {
};
this_weak
.update(cx, |_, cx| {
.update(cx, |this, cx| {
this.fetch_files(window, cx);
cx.observe_release_in(&project, window, |_, _, window, cx| {
cx.defer_in(window, |this, window, cx| this.fetch_files(window, cx));
})
.detach();
cx.subscribe_in(&project, window, Self::handle_project_event)
.detach();
})
@@ -1308,6 +1360,24 @@ impl SettingsWindow {
})
.detach();
cx.observe_new::<Workspace>(move |_, window, cx| {
let workspace = cx.entity();
let Some(window) = window else {
return;
};
this_weak
.update(cx, |this, cx| {
this.fetch_files(window, cx);
cx.observe_release_in(&workspace, window, |this, _, window, cx| {
this.fetch_files(window, cx)
})
.detach();
})
.ok();
})
.detach();
let title_bar = if !cfg!(target_os = "macos") {
Some(cx.new(|cx| PlatformTitleBar::new("settings-title-bar", cx)))
} else {
@@ -1818,6 +1888,7 @@ impl SettingsWindow {
cx.notify();
}
#[track_caller]
fn fetch_files(&mut self, window: &mut Window, cx: &mut Context<SettingsWindow>) {
self.worktree_root_dirs.clear();
let prev_files = self.files.clone();
@@ -1870,7 +1941,7 @@ impl SettingsWindow {
let mut missing_worktrees = Vec::new();
for worktree in all_projects(cx)
.flat_map(|project| project.read(cx).worktrees(cx))
.flat_map(|project| project.read(cx).visible_worktrees(cx))
.filter(|tree| !self.worktree_root_dirs.contains_key(&tree.read(cx).id()))
{
let worktree = worktree.read(cx);
@@ -3028,7 +3099,7 @@ impl SettingsWindow {
tree.create_entry(
settings_path.clone(),
false,
Some("{\n\n}".as_bytes().to_vec()),
Some(initial_project_settings_content().as_bytes().to_vec()),
cx,
)
}))
+4 -4
View File
@@ -107,8 +107,8 @@ actions!(
Minimize,
/// Opens the default settings file.
OpenDefaultSettings,
/// Opens project-specific settings.
OpenProjectSettings,
/// Opens project-specific settings file.
OpenProjectSettingsFile,
/// Opens the project tasks configuration.
OpenProjectTasks,
/// Opens the tasks panel.
@@ -1783,7 +1783,7 @@ pub fn open_new_ssh_project_from_project(
fn open_project_settings_file(
workspace: &mut Workspace,
_: &OpenProjectSettings,
_: &OpenProjectSettingsFile,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
@@ -5067,7 +5067,7 @@ mod tests {
.update(cx, |workspace, window, cx| {
// Call the exact function that contains the bug
eprintln!("About to call open_project_settings_file");
open_project_settings_file(workspace, &OpenProjectSettings, window, cx);
open_project_settings_file(workspace, &OpenProjectSettingsFile, window, cx);
})
.unwrap();
+5 -1
View File
@@ -69,7 +69,11 @@ pub fn app_menus(cx: &mut App) -> Vec<Menu> {
items: vec![
MenuItem::action("Open Settings", zed_actions::OpenSettings),
MenuItem::action("Open Settings File", super::OpenSettingsFile),
MenuItem::action("Open Project Settings", super::OpenProjectSettings),
MenuItem::action("Open Project Settings", zed_actions::OpenProjectSettings),
MenuItem::action(
"Open Project Settings File",
super::OpenProjectSettingsFile,
),
MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
MenuItem::separator(),
MenuItem::action("Open Keymap", zed_actions::OpenKeymap),
+3
View File
@@ -43,6 +43,9 @@ actions!(
/// Opens the settings JSON file.
#[action(deprecated_aliases = ["zed_actions::OpenSettings"])]
OpenSettingsFile,
/// Opens project-specific settings.
#[action(deprecated_aliases = ["zed_actions::OpenProjectSettings"])]
OpenProjectSettings,
/// Opens the default keymap file.
OpenDefaultKeymap,
/// Opens the user keymap file.