diff --git a/Cargo.lock b/Cargo.lock index ada56e87e59dca20aaa3bb240dcdb4756d13270a..cbf1d3b4896dac1e992641695f712a0a146ba745 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14372,6 +14372,7 @@ dependencies = [ "gpui", "heck 0.5.0", "language", + "log", "menu", "node_runtime", "paths", diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 62020cc178d8a555ec973568baecdcd73c313bbc..855759279b7c1af177bd445950960b4ee8f8bf2d 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -4633,6 +4633,14 @@ pub struct WindowHandle { state_type: PhantomData, } +impl Debug for WindowHandle { + 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 WindowHandle { /// Creates a new handle from a window ID. /// This does not check if the root type of the window is `V`. diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 2a94ecb8cdbb77cbd55b0ab5a2bc1474052f2b6f..0f3457bc3463dfb58dc020dfd0398e0efdfd5e21 100644 --- a/crates/settings/src/settings_store.rs +++ b/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)), + /// Represents project settings in ssh projects as well as local projects + Project((WorktreeId, Arc)), } #[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, ) -> (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![]); } } diff --git a/crates/settings_ui/Cargo.toml b/crates/settings_ui/Cargo.toml index 0b456d0cb11d62f227414609c6017199d284754a..0f8aebb3a4ae37de1ed61ed5f5827e0575216c30 100644 --- a/crates/settings_ui/Cargo.toml +++ b/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" diff --git a/crates/settings_ui/examples/.zed/settings.json b/crates/settings_ui/examples/.zed/settings.json deleted file mode 100644 index 0967ef424bce6791893e9a57bb952f80fd536e93..0000000000000000000000000000000000000000 --- a/crates/settings_ui/examples/.zed/settings.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/crates/settings_ui/examples/ui.rs b/crates/settings_ui/examples/ui.rs deleted file mode 100644 index 992f1e39009be01b773c6e3bbf32098858db43d4..0000000000000000000000000000000000000000 --- a/crates/settings_ui/examples/ui.rs +++ /dev/null @@ -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| { - ::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); - }); -} diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 0307b5f27d9ce2f2976f649b193f83048f593d0c..002962c1e937610d8aad1094fb22fe28494212b6 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/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::() + .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> { +pub fn open_settings_editor( + _workspace: &mut Workspace, + workspace_handle: WindowHandle, + cx: &mut App, +) { let existing_window = cx .windows() .into_iter() @@ -443,29 +454,35 @@ pub fn open_settings_editor(cx: &mut App) -> anyhow::Result std::sync::RwLockWriteGuard<'static, Vec> { } pub struct SettingsWindow { + original_window: Option>, files: Vec<(SettingsUiFile, FocusHandle)>, + worktree_root_dirs: HashMap, current_file: SettingsUiFile, pages: Vec, search_bar: Entity, @@ -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, ) -> 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)), // 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)), // 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 { 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 { 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 { + pub fn new( + original_window: Option>, + window: &mut Window, + cx: &mut Context, + ) -> 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) { + self.worktree_root_dirs.clear(); let prev_files = self.files.clone(); let settings_store = cx.global::(); 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, @@ -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 { + 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) { + 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> = 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> { + 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> { - 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 { - 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,