settings_ui: Collect all settings files (#38816)

Closes #ISSUE

Updates the settings editor to collect all known settings files from the
settings store, in order to show them in the UI. Additionally adds a
fake worktree instantiation in the settings UI example binary in order
to have more than one file available when testing.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Ben Kunkle
2025-09-24 21:16:06 +00:00
committed by GitHub
parent ed7bd5a8ed
commit 523c042930
8 changed files with 157 additions and 19 deletions
Generated
+3
View File
@@ -14508,6 +14508,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"assets",
"client",
"command_palette_hooks",
"editor",
"feature_flags",
@@ -14516,9 +14517,11 @@ dependencies = [
"gpui",
"language",
"menu",
"node_runtime",
"paths",
"project",
"serde",
"session",
"settings",
"theme",
"ui",
+1 -1
View File
@@ -43,7 +43,7 @@ impl Session {
}
}
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
pub fn test() -> Self {
Self {
session_id: Uuid::new_v4().to_string(),
+2 -1
View File
@@ -24,7 +24,8 @@ pub use keymap_file::{
pub use settings_file::*;
pub use settings_json::*;
pub use settings_store::{
InvalidSettingsError, LocalSettingsKind, Settings, SettingsKey, SettingsLocation, SettingsStore,
InvalidSettingsError, LocalSettingsKind, Settings, SettingsFile, SettingsKey, SettingsLocation,
SettingsStore,
};
pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource};
+40
View File
@@ -156,6 +156,16 @@ pub struct SettingsStore {
mpsc::UnboundedSender<Box<dyn FnOnce(AsyncApp) -> LocalBoxFuture<'static, Result<()>>>>,
}
#[derive(Clone, PartialEq)]
pub enum SettingsFile {
User,
Global,
Extension,
Server,
Default,
Local((WorktreeId, Arc<Path>)),
}
#[derive(Clone)]
pub struct Editorconfig {
pub is_root: bool,
@@ -479,6 +489,36 @@ impl SettingsStore {
})
})
}
pub fn get_all_files(&self) -> Vec<SettingsFile> {
let mut files = Vec::from_iter(
self.local_settings
.keys()
// rev because these are sorted by path, so highest precedence is last
.rev()
.cloned()
.map(SettingsFile::Local),
);
if self.server_settings.is_some() {
files.push(SettingsFile::Server);
}
// ignoring profiles
// ignoring os profiles
// ignoring release channel profiles
if self.user_settings.is_some() {
files.push(SettingsFile::User);
}
if self.extension_settings.is_some() {
files.push(SettingsFile::Extension);
}
if self.global_settings.is_some() {
files.push(SettingsFile::Global);
}
files.push(SettingsFile::Default);
files
}
}
impl SettingsStore {
+4 -1
View File
@@ -32,12 +32,15 @@ workspace-hack.workspace = true
workspace.workspace = true
[dev-dependencies]
settings = { workspace = true, features = ["test-support"] }
settings.workspace = true
futures.workspace = true
language.workspace = true
assets.workspace = true
paths.workspace = true
zlog.workspace = true
client.workspace = true
session.workspace = true
node_runtime.workspace = true
[[example]]
name = "ui"
@@ -0,0 +1 @@
{}
+68 -3
View File
@@ -1,11 +1,42 @@
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()));
@@ -14,15 +45,49 @@ fn main() {
fs.clone(),
paths::settings_file().clone(),
);
zlog::init();
zlog::init_output_stderr();
app.run(move |cx| {
<dyn fs::Fs>::set_global(fs.clone(), cx);
settings::init(cx);
theme::init(theme::LoadThemes::JustBase, cx);
client::init_settings(cx);
workspace::init_settings(cx);
project::Project::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();
+38 -13
View File
@@ -200,7 +200,7 @@ struct SettingItem {
}
#[allow(unused)]
#[derive(Clone)]
#[derive(Clone, PartialEq)]
enum SettingsFile {
User, // Uses all settings.
Local((WorktreeId, Arc<Path>)), // Has a special name, and special set of settings
@@ -216,11 +216,11 @@ impl SettingsFile {
}
}
fn name(&self) -> String {
fn name(&self) -> SharedString {
match self {
SettingsFile::User => "User".to_string(),
SettingsFile::Local((_, path)) => format!("Local ({})", path.display()),
SettingsFile::Server(file) => format!("Server ({})", file),
SettingsFile::User => SharedString::new_static("User"),
SettingsFile::Local((_, path)) => format!("Local ({})", path.display()).into(),
SettingsFile::Server(file) => format!("Server ({})", file).into(),
}
}
}
@@ -234,22 +234,18 @@ impl SettingsWindow {
editor
});
let mut this = Self {
files: vec![
SettingsFile::User,
SettingsFile::Local((
WorktreeId::from_usize(0),
Arc::from(Path::new("/my-project/")),
)),
],
files: vec![],
current_file: current_file,
pages: vec![],
current_page: 0,
search,
};
cx.observe_global_in::<SettingsStore>(window, move |_, _, cx| {
cx.observe_global_in::<SettingsStore>(window, move |this, _, cx| {
this.fetch_files(cx);
cx.notify();
})
.detach();
this.fetch_files(cx);
this.build_ui();
this
@@ -259,7 +255,36 @@ impl SettingsWindow {
self.pages = self.current_file.pages();
}
fn fetch_files(&mut self, cx: &mut App) {
let settings_store = cx.global::<SettingsStore>();
let mut ui_files = vec![];
let all_files = settings_store.get_all_files();
for file in all_files {
let settings_ui_file = match file {
settings::SettingsFile::User => SettingsFile::User,
settings::SettingsFile::Global => continue,
settings::SettingsFile::Extension => continue,
settings::SettingsFile::Server => SettingsFile::Server("todo: server name"),
settings::SettingsFile::Default => continue,
settings::SettingsFile::Local(location) => SettingsFile::Local(location),
};
ui_files.push(settings_ui_file);
}
ui_files.reverse();
if !ui_files.contains(&self.current_file) {
self.change_file(0);
}
self.files = ui_files;
}
fn change_file(&mut self, ix: usize) {
if ix >= self.files.len() {
self.current_file = SettingsFile::User;
return;
}
if self.files[ix] == self.current_file {
return;
}
self.current_file = self.files[ix].clone();
self.build_ui();
}