diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f98d12f7aca5cd0fe95f195f0f9920c607970cd7..c2a9816a10d09b8b83abfef6ddabd34a1b90de2c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -5294,6 +5294,51 @@ impl Project { pub fn path_style(&self, cx: &App) -> PathStyle { self.worktree_store.read(cx).path_style() } + + pub fn contains_local_settings_file( + &self, + worktree_id: WorktreeId, + rel_path: &RelPath, + cx: &App, + ) -> bool { + self.worktree_for_id(worktree_id, cx) + .map_or(false, |worktree| { + worktree.read(cx).entry_for_path(rel_path).is_some() + }) + } + + pub fn update_local_settings_file( + &self, + worktree_id: WorktreeId, + rel_path: Arc, + cx: &mut App, + update: impl 'static + Send + FnOnce(&mut settings::SettingsContent, &App), + ) { + let Some(worktree) = self.worktree_for_id(worktree_id, cx) else { + // todo(settings_ui) error? + return; + }; + cx.spawn(async move |cx| { + let file = worktree + .update(cx, |worktree, cx| worktree.load_file(&rel_path, cx))? + .await + .context("Failed to load settings file")?; + + let new_text = cx.read_global::(|store, cx| { + store.new_text_for_update(file.text, move |settings| update(settings, cx)) + })?; + worktree + .update(cx, |worktree, cx| { + let line_ending = text::LineEnding::detect(&new_text); + worktree.write_file(rel_path.clone(), new_text.into(), line_ending, cx) + })? + .await + .context("Failed to write settings file")?; + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } } pub struct PathMatchCandidateSet { diff --git a/crates/settings_ui/Cargo.toml b/crates/settings_ui/Cargo.toml index 75508164382cf848e213efc5accb94e5faf392d0..d331954972c91f9a86ae150276bf9d9559bc8417 100644 --- a/crates/settings_ui/Cargo.toml +++ b/crates/settings_ui/Cargo.toml @@ -24,6 +24,7 @@ fs.workspace = true fuzzy.workspace = true gpui.workspace = true menu.workspace = true +paths.workspace = true project.workspace = true serde.workspace = true settings.workspace = true diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index d1edc7f424e3d590551a8566d5fcc17643538272..a62f8ef7bf2a59cb1cb0b9bc9e047a313ada85a5 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -1,5 +1,6 @@ //! # settings_ui mod components; +use anyhow::Result; use editor::{Editor, EditorEvent}; use feature_flags::{FeatureFlag, FeatureFlagAppExt as _}; use fuzzy::StringMatchCandidate; @@ -25,7 +26,7 @@ use ui::{ ContextMenu, Divider, DropdownMenu, DropdownStyle, Switch, SwitchColor, TreeViewItem, prelude::*, }; -use util::{paths::PathStyle, rel_path::RelPath}; +use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath}; use crate::components::SettingsEditor; @@ -3308,6 +3309,55 @@ impl Render for SettingsWindow { } } +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() + } + 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| { + project.contains_local_settings_file(worktree_id, &rel_path, cx) + }) + }); + let Some(project) = project else { + anyhow::bail!( + "Could not find worktree containing settings file: {}", + &rel_path.display(PathStyle::local()) + ); + }; + project.update(cx, |project, cx| { + project.update_local_settings_file(worktree_id, rel_path, cx, update); + }); + return Ok(()); + } + SettingsUiFile::User => { + // todo(settings_ui) error? + SettingsStore::global(cx).update_settings_file(::global(cx), update); + Ok(()) + } + SettingsUiFile::Server(_) => unimplemented!(), + } +} + fn render_text_field + Into + AsRef + Clone>( field: SettingField, file: SettingsUiFile, @@ -3326,12 +3376,13 @@ fn render_text_field + Into + AsRef + Clone>( metadata.and_then(|metadata| metadata.placeholder), |editor, placeholder| editor.with_placeholder(placeholder), ) - .on_confirm(move |new_text, cx: &mut App| { - cx.update_global(move |store: &mut SettingsStore, cx| { - store.update_settings_file(::global(cx), move |settings, _cx| { + .on_confirm({ + move |new_text, cx| { + update_settings_file(file.clone(), cx, move |settings, _cx| { *(field.pick_mut)(settings) = new_text.map(Into::into); - }); - }); + }) + .log_err(); // todo(settings_ui) don't log err + } }) .into_any_element() } @@ -3354,12 +3405,10 @@ fn render_toggle_button + From + Copy>( .on_click({ move |state, _window, cx| { let state = *state == ui::ToggleState::Selected; - let field = field; - cx.update_global(move |store: &mut SettingsStore, cx| { - store.update_settings_file(::global(cx), move |settings, _cx| { - *(field.pick_mut)(settings) = Some(state.into()); - }); - }); + update_settings_file(file.clone(), cx, move |settings, _cx| { + *(field.pick_mut)(settings) = Some(state.into()); + }) + .log_err(); // todo(settings_ui) don't log err } }) .color(SwitchColor::Accent) @@ -3373,7 +3422,7 @@ fn render_dropdown( cx: &mut App, ) -> AnyElement where - T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + 'static, + T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static, { let variants = || -> &'static [T] { ::VARIANTS }; let labels = || -> &'static [&'static str] { ::VARIANTS }; @@ -3388,11 +3437,8 @@ where "dropdown", current_value_label, ContextMenu::build(window, cx, move |mut menu, _, _| { - for (value, label) in variants() - .into_iter() - .copied() - .zip(labels().into_iter().copied()) - { + for (&value, &label) in std::iter::zip(variants(), labels()) { + let file = file.clone(); menu = menu.toggleable_entry( label, value == current_value, @@ -3402,14 +3448,10 @@ where if value == current_value { return; } - cx.update_global(move |store: &mut SettingsStore, cx| { - store.update_settings_file( - ::global(cx), - move |settings, _cx| { - *(field.pick_mut)(settings) = Some(value); - }, - ); - }); + update_settings_file(file.clone(), cx, move |settings, _cx| { + *(field.pick_mut)(settings) = Some(value); + }) + .log_err(); // todo(settings_ui) don't log err }, ); }