Cargo.lock 🔗
@@ -14372,6 +14372,7 @@ dependencies = [
"gpui",
"heck 0.5.0",
"language",
+ "log",
"menu",
"node_runtime",
"paths",
Mikayla Maki created
Closes #ISSUE
Release Notes:
- N/A *or* Added/Fixed/Improved ...
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(-)
@@ -14372,6 +14372,7 @@ dependencies = [
"gpui",
"heck 0.5.0",
"language",
+ "log",
"menu",
"node_runtime",
"paths",
@@ -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`.
@@ -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![]);
}
}
@@ -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"
@@ -1 +0,0 @@
-{}
@@ -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);
- });
-}
@@ -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,