Compare commits
7 Commits
main
...
remote-pro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d46eca9d3 | ||
|
|
f92403a6dd | ||
|
|
7adb4fb73d | ||
|
|
cedca47a82 | ||
|
|
ef3065abe7 | ||
|
|
8bfa78d34c | ||
|
|
115431d917 |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -12477,7 +12477,6 @@ dependencies = [
|
||||
"dap",
|
||||
"dap_adapters",
|
||||
"db",
|
||||
"encoding_rs",
|
||||
"extension",
|
||||
"fancy-regex",
|
||||
"fs",
|
||||
|
||||
@@ -40,7 +40,6 @@ clock.workspace = true
|
||||
collections.workspace = true
|
||||
context_server.workspace = true
|
||||
dap.workspace = true
|
||||
encoding_rs.workspace = true
|
||||
extension.workspace = true
|
||||
fancy-regex.workspace = true
|
||||
fs.workspace = true
|
||||
|
||||
@@ -65,7 +65,7 @@ use debugger::{
|
||||
dap_store::{DapStore, DapStoreEvent},
|
||||
session::Session,
|
||||
};
|
||||
use encoding_rs;
|
||||
|
||||
pub use environment::ProjectEnvironment;
|
||||
#[cfg(test)]
|
||||
use futures::future::join_all;
|
||||
@@ -5444,48 +5444,6 @@ impl Project {
|
||||
worktree.read(cx).entry_for_path(rel_path).is_some()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_local_settings_file(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
rel_path: Arc<RelPath>,
|
||||
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 has_bom = file.has_bom;
|
||||
|
||||
let new_text = cx.read_global::<SettingsStore, _>(|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,
|
||||
encoding_rs::UTF_8,
|
||||
has_bom,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.context("Failed to write settings file")?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PathMatchCandidateSet {
|
||||
|
||||
@@ -27,6 +27,7 @@ fs.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
heck.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
paths.workspace = true
|
||||
|
||||
@@ -17,7 +17,7 @@ where
|
||||
labels: &'static [&'static str],
|
||||
should_do_title_case: bool,
|
||||
tab_index: Option<isize>,
|
||||
on_change: Rc<dyn Fn(T, &mut App) + 'static>,
|
||||
on_change: Rc<dyn Fn(T, &mut ui::Window, &mut App) + 'static>,
|
||||
}
|
||||
|
||||
impl<T> EnumVariantDropdown<T>
|
||||
@@ -29,7 +29,7 @@ where
|
||||
current_value: T,
|
||||
variants: &'static [T],
|
||||
labels: &'static [&'static str],
|
||||
on_change: impl Fn(T, &mut App) + 'static,
|
||||
on_change: impl Fn(T, &mut ui::Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
@@ -78,8 +78,8 @@ where
|
||||
value == current_value,
|
||||
IconPosition::End,
|
||||
None,
|
||||
move |_, cx| {
|
||||
on_change(value, cx);
|
||||
move |window, cx| {
|
||||
on_change(value, window, cx);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@ pub struct FontPickerDelegate {
|
||||
filtered_fonts: Vec<StringMatch>,
|
||||
selected_index: usize,
|
||||
current_font: SharedString,
|
||||
on_font_changed: Arc<dyn Fn(SharedString, &mut App) + 'static>,
|
||||
on_font_changed: Arc<dyn Fn(SharedString, &mut Window, &mut App) + 'static>,
|
||||
}
|
||||
|
||||
impl FontPickerDelegate {
|
||||
fn new(
|
||||
current_font: SharedString,
|
||||
on_font_changed: impl Fn(SharedString, &mut App) + 'static,
|
||||
on_font_changed: impl Fn(SharedString, &mut Window, &mut App) + 'static,
|
||||
cx: &mut Context<FontPicker>,
|
||||
) -> Self {
|
||||
let font_family_cache = FontFamilyCache::global(cx);
|
||||
@@ -132,10 +132,10 @@ impl PickerDelegate for FontPickerDelegate {
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<FontPicker>) {
|
||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<FontPicker>) {
|
||||
if let Some(font_match) = self.filtered_fonts.get(self.selected_index) {
|
||||
let font = font_match.string.clone();
|
||||
(self.on_font_changed)(font.into(), cx);
|
||||
(self.on_font_changed)(font.into(), window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ impl PickerDelegate for FontPickerDelegate {
|
||||
|
||||
pub fn font_picker(
|
||||
current_font: SharedString,
|
||||
on_font_changed: impl Fn(SharedString, &mut App) + 'static,
|
||||
on_font_changed: impl Fn(SharedString, &mut Window, &mut App) + 'static,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<FontPicker>,
|
||||
) -> FontPicker {
|
||||
|
||||
@@ -13,13 +13,13 @@ pub struct IconThemePickerDelegate {
|
||||
filtered_themes: Vec<StringMatch>,
|
||||
selected_index: usize,
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: Arc<dyn Fn(SharedString, &mut App) + 'static>,
|
||||
on_theme_changed: Arc<dyn Fn(SharedString, &mut Window, &mut App) + 'static>,
|
||||
}
|
||||
|
||||
impl IconThemePickerDelegate {
|
||||
fn new(
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: impl Fn(SharedString, &mut App) + 'static,
|
||||
on_theme_changed: impl Fn(SharedString, &mut Window, &mut App) + 'static,
|
||||
cx: &mut Context<IconThemePicker>,
|
||||
) -> Self {
|
||||
let theme_registry = ThemeRegistry::global(cx);
|
||||
@@ -32,15 +32,15 @@ impl IconThemePickerDelegate {
|
||||
|
||||
let selected_index = icon_themes
|
||||
.iter()
|
||||
.position(|icon_themes| *icon_themes == current_theme)
|
||||
.position(|icon_theme| *icon_theme == current_theme)
|
||||
.unwrap_or(0);
|
||||
|
||||
let filtered_themes = icon_themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, icon_themes)| StringMatch {
|
||||
.map(|(index, theme)| StringMatch {
|
||||
candidate_id: index,
|
||||
string: icon_themes.to_string(),
|
||||
string: theme.to_string(),
|
||||
positions: Vec::new(),
|
||||
score: 0.0,
|
||||
})
|
||||
@@ -67,13 +67,18 @@ impl PickerDelegate for IconThemePickerDelegate {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<IconThemePicker>) {
|
||||
self.selected_index = ix.min(self.filtered_themes.len().saturating_sub(1));
|
||||
fn set_selected_index(
|
||||
&mut self,
|
||||
index: usize,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<IconThemePicker>,
|
||||
) {
|
||||
self.selected_index = index.min(self.filtered_themes.len().saturating_sub(1));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
"Search icon theme…".into()
|
||||
"Search icon themes…".into()
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
@@ -89,9 +94,9 @@ impl PickerDelegate for IconThemePickerDelegate {
|
||||
icon_themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, icon_theme)| StringMatch {
|
||||
.map(|(index, theme)| StringMatch {
|
||||
candidate_id: index,
|
||||
string: icon_theme.to_string(),
|
||||
string: theme.to_string(),
|
||||
positions: Vec::new(),
|
||||
score: 0.0,
|
||||
})
|
||||
@@ -100,16 +105,16 @@ impl PickerDelegate for IconThemePickerDelegate {
|
||||
let _candidates: Vec<StringMatchCandidate> = icon_themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(id, icon_theme)| StringMatchCandidate::new(id, icon_theme.as_ref()))
|
||||
.map(|(id, theme)| StringMatchCandidate::new(id, theme.as_ref()))
|
||||
.collect();
|
||||
|
||||
icon_themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, icon_theme)| icon_theme.to_lowercase().contains(&query.to_lowercase()))
|
||||
.map(|(index, icon_theme)| StringMatch {
|
||||
.filter(|(_, theme)| theme.to_lowercase().contains(&query.to_lowercase()))
|
||||
.map(|(index, theme)| StringMatch {
|
||||
candidate_id: index,
|
||||
string: icon_theme.to_string(),
|
||||
string: theme.to_string(),
|
||||
positions: Vec::new(),
|
||||
score: 0.0,
|
||||
})
|
||||
@@ -119,7 +124,7 @@ impl PickerDelegate for IconThemePickerDelegate {
|
||||
let selected_index = if query.is_empty() {
|
||||
icon_themes
|
||||
.iter()
|
||||
.position(|icon_theme| *icon_theme == current_theme)
|
||||
.position(|theme| *theme == current_theme)
|
||||
.unwrap_or(0)
|
||||
} else {
|
||||
matches
|
||||
@@ -138,12 +143,12 @@ impl PickerDelegate for IconThemePickerDelegate {
|
||||
fn confirm(
|
||||
&mut self,
|
||||
_secondary: bool,
|
||||
_window: &mut Window,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<IconThemePicker>,
|
||||
) {
|
||||
if let Some(theme_match) = self.filtered_themes.get(self.selected_index) {
|
||||
let theme = theme_match.string.clone();
|
||||
(self.on_theme_changed)(theme.into(), cx);
|
||||
(self.on_theme_changed)(theme.into(), window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,15 +161,15 @@ impl PickerDelegate for IconThemePickerDelegate {
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
index: usize,
|
||||
selected: bool,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<IconThemePicker>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let theme_match = self.filtered_themes.get(ix)?;
|
||||
let theme_match = self.filtered_themes.get(index)?;
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
ListItem::new(index)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
@@ -176,7 +181,7 @@ impl PickerDelegate for IconThemePickerDelegate {
|
||||
|
||||
pub fn icon_theme_picker(
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: impl Fn(SharedString, &mut App) + 'static,
|
||||
on_theme_changed: impl Fn(SharedString, &mut Window, &mut App) + 'static,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<IconThemePicker>,
|
||||
) -> IconThemePicker {
|
||||
|
||||
@@ -9,7 +9,7 @@ use ui::{
|
||||
pub struct SettingsInputField {
|
||||
initial_text: Option<String>,
|
||||
placeholder: Option<&'static str>,
|
||||
confirm: Option<Box<dyn Fn(Option<String>, &mut App)>>,
|
||||
confirm: Option<Box<dyn Fn(Option<String>, &mut Window, &mut App)>>,
|
||||
tab_index: Option<isize>,
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@ impl SettingsInputField {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_confirm(mut self, confirm: impl Fn(Option<String>, &mut App) + 'static) -> Self {
|
||||
pub fn on_confirm(
|
||||
mut self,
|
||||
confirm: impl Fn(Option<String>, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.confirm = Some(Box::new(confirm));
|
||||
self
|
||||
}
|
||||
@@ -83,13 +86,13 @@ impl RenderOnce for SettingsInputField {
|
||||
.child(editor)
|
||||
.when_some(self.confirm, |this, confirm| {
|
||||
this.on_action::<menu::Confirm>({
|
||||
move |_, _, cx| {
|
||||
move |_, window, cx| {
|
||||
let Some(editor) = weak_editor.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let new_value = editor.read_with(cx, |editor, cx| editor.text(cx));
|
||||
let new_value = (!new_value.is_empty()).then_some(new_value);
|
||||
confirm(new_value, cx);
|
||||
confirm(new_value, window, cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,13 +13,13 @@ pub struct ThemePickerDelegate {
|
||||
filtered_themes: Vec<StringMatch>,
|
||||
selected_index: usize,
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: Arc<dyn Fn(SharedString, &mut App) + 'static>,
|
||||
on_theme_changed: Arc<dyn Fn(SharedString, &mut Window, &mut App) + 'static>,
|
||||
}
|
||||
|
||||
impl ThemePickerDelegate {
|
||||
fn new(
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: impl Fn(SharedString, &mut App) + 'static,
|
||||
on_theme_changed: impl Fn(SharedString, &mut Window, &mut App) + 'static,
|
||||
cx: &mut Context<ThemePicker>,
|
||||
) -> Self {
|
||||
let theme_registry = ThemeRegistry::global(cx);
|
||||
@@ -130,10 +130,10 @@ impl PickerDelegate for ThemePickerDelegate {
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<ThemePicker>) {
|
||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<ThemePicker>) {
|
||||
if let Some(theme_match) = self.filtered_themes.get(self.selected_index) {
|
||||
let theme = theme_match.string.clone();
|
||||
(self.on_theme_changed)(theme.into(), cx);
|
||||
(self.on_theme_changed)(theme.into(), window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ impl PickerDelegate for ThemePickerDelegate {
|
||||
|
||||
pub fn theme_picker(
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: impl Fn(SharedString, &mut App) + 'static,
|
||||
on_theme_changed: impl Fn(SharedString, &mut Window, &mut App) + 'static,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<ThemePicker>,
|
||||
) -> ThemePicker {
|
||||
|
||||
@@ -217,7 +217,7 @@ fn render_api_key_provider(
|
||||
SettingsInputField::new()
|
||||
.tab_index(0)
|
||||
.with_placeholder("xxxxxxxxxxxxxxxxxxxx")
|
||||
.on_confirm(move |api_key, cx| {
|
||||
.on_confirm(move |api_key, _window, cx| {
|
||||
write_key(api_key.filter(|key| !key.is_empty()), cx);
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -2,7 +2,7 @@ mod components;
|
||||
mod page_data;
|
||||
mod pages;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context as _, Result};
|
||||
use editor::{Editor, EditorEvent};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{
|
||||
@@ -11,7 +11,9 @@ use gpui::{
|
||||
Subscription, Task, TitlebarOptions, UniformListScrollHandle, Window, WindowBounds,
|
||||
WindowHandle, WindowOptions, actions, div, list, point, prelude::*, px, uniform_list,
|
||||
};
|
||||
use project::{Project, WorktreeId};
|
||||
|
||||
use language::Buffer;
|
||||
use project::{Project, ProjectPath, WorktreeId};
|
||||
use release_channel::ReleaseChannel;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
@@ -154,7 +156,7 @@ trait AnySettingField {
|
||||
current_file: &SettingsUiFile,
|
||||
file_set_in: &settings::SettingsFile,
|
||||
cx: &App,
|
||||
) -> Option<Box<dyn Fn(&mut App)>>;
|
||||
) -> Option<Box<dyn Fn(&mut Window, &mut App)>>;
|
||||
|
||||
fn json_path(&self) -> Option<&'static str>;
|
||||
}
|
||||
@@ -184,7 +186,7 @@ impl<T: PartialEq + Clone + Send + Sync + 'static> AnySettingField for SettingFi
|
||||
current_file: &SettingsUiFile,
|
||||
file_set_in: &settings::SettingsFile,
|
||||
cx: &App,
|
||||
) -> Option<Box<dyn Fn(&mut App)>> {
|
||||
) -> Option<Box<dyn Fn(&mut Window, &mut App)>> {
|
||||
if file_set_in == &settings::SettingsFile::Default {
|
||||
return None;
|
||||
}
|
||||
@@ -203,7 +205,7 @@ impl<T: PartialEq + Clone + Send + Sync + 'static> AnySettingField for SettingFi
|
||||
}
|
||||
let current_file = current_file.clone();
|
||||
|
||||
return Some(Box::new(move |cx| {
|
||||
return Some(Box::new(move |window, cx| {
|
||||
let store = SettingsStore::global(cx);
|
||||
let default_value = (this.pick)(store.raw_default_settings());
|
||||
let is_set_somewhere_other_than_default = store
|
||||
@@ -215,9 +217,15 @@ impl<T: PartialEq + Clone + Send + Sync + 'static> AnySettingField for SettingFi
|
||||
} else {
|
||||
None
|
||||
};
|
||||
update_settings_file(current_file.clone(), None, cx, move |settings, _| {
|
||||
(this.write)(settings, value_to_set);
|
||||
})
|
||||
update_settings_file(
|
||||
current_file.clone(),
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
move |settings, _| {
|
||||
(this.write)(settings, value_to_set);
|
||||
},
|
||||
)
|
||||
// todo(settings_ui): Don't log err
|
||||
.log_err();
|
||||
}));
|
||||
@@ -575,7 +583,6 @@ pub fn open_settings_editor(
|
||||
}
|
||||
|
||||
// We have to defer this to get the workspace off the stack.
|
||||
|
||||
let path = path.map(ToOwned::to_owned);
|
||||
cx.defer(move |cx| {
|
||||
let current_rem_size: f32 = theme::ThemeSettings::get_global(cx).ui_font_size(cx).into();
|
||||
@@ -671,6 +678,8 @@ pub struct SettingsWindow {
|
||||
pages: Vec<SettingsPage>,
|
||||
search_bar: Entity<Editor>,
|
||||
search_task: Option<Task<()>>,
|
||||
/// Cached settings file buffers to avoid repeated disk I/O on each settings change
|
||||
project_setting_file_buffers: HashMap<ProjectPath, Entity<Buffer>>,
|
||||
/// Index into navbar_entries
|
||||
navbar_entry: usize,
|
||||
navbar_entries: Vec<NavBarEntry>,
|
||||
@@ -1069,8 +1078,8 @@ fn render_settings_item(
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Reset to Default"))
|
||||
.on_click({
|
||||
move |_, _, cx| {
|
||||
reset_to_default(cx);
|
||||
move |_, window, cx| {
|
||||
reset_to_default(window, cx);
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -1499,6 +1508,7 @@ impl SettingsWindow {
|
||||
files: vec![],
|
||||
|
||||
current_file: current_file,
|
||||
project_setting_file_buffers: HashMap::default(),
|
||||
pages: vec![],
|
||||
navbar_entries: vec![],
|
||||
navbar_entry: 0,
|
||||
@@ -2026,14 +2036,9 @@ impl SettingsWindow {
|
||||
}
|
||||
|
||||
if let Some(worktree_id) = settings_ui_file.worktree_id() {
|
||||
let directory_name = all_projects(cx)
|
||||
let directory_name = all_projects(self.original_window.as_ref(), 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())
|
||||
});
|
||||
.map(|worktree| worktree.read(cx).root_name());
|
||||
|
||||
let Some(directory_name) = directory_name else {
|
||||
log::error!(
|
||||
@@ -2043,7 +2048,8 @@ impl SettingsWindow {
|
||||
continue;
|
||||
};
|
||||
|
||||
self.worktree_root_dirs.insert(worktree_id, directory_name);
|
||||
self.worktree_root_dirs
|
||||
.insert(worktree_id, directory_name.as_unix_str().to_string());
|
||||
}
|
||||
|
||||
let focus_handle = prev_files
|
||||
@@ -2059,7 +2065,7 @@ impl SettingsWindow {
|
||||
|
||||
let mut missing_worktrees = Vec::new();
|
||||
|
||||
for worktree in all_projects(cx)
|
||||
for worktree in all_projects(self.original_window.as_ref(), cx)
|
||||
.flat_map(|project| project.read(cx).visible_worktrees(cx))
|
||||
.filter(|tree| !self.worktree_root_dirs.contains_key(&tree.read(cx).id()))
|
||||
{
|
||||
@@ -3525,7 +3531,10 @@ impl Render for SettingsWindow {
|
||||
}
|
||||
}
|
||||
|
||||
fn all_projects(cx: &App) -> impl Iterator<Item = Entity<project::Project>> {
|
||||
fn all_projects(
|
||||
window: Option<&WindowHandle<Workspace>>,
|
||||
cx: &App,
|
||||
) -> impl Iterator<Item = Entity<project::Project>> {
|
||||
workspace::AppState::global(cx)
|
||||
.upgrade()
|
||||
.map(|app_state| {
|
||||
@@ -3535,6 +3544,9 @@ fn all_projects(cx: &App) -> impl Iterator<Item = Entity<project::Project>> {
|
||||
.workspaces()
|
||||
.iter()
|
||||
.filter_map(|workspace| Some(workspace.read(cx).ok()?.project().clone()))
|
||||
.chain(
|
||||
window.and_then(|workspace| Some(workspace.read(cx).ok()?.project().clone())),
|
||||
)
|
||||
})
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -3543,6 +3555,7 @@ fn all_projects(cx: &App) -> impl Iterator<Item = Entity<project::Project>> {
|
||||
fn update_settings_file(
|
||||
file: SettingsUiFile,
|
||||
file_name: Option<&'static str>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
|
||||
) -> Result<()> {
|
||||
@@ -3551,41 +3564,11 @@ fn update_settings_file(
|
||||
match file {
|
||||
SettingsUiFile::Project((worktree_id, rel_path)) => {
|
||||
let rel_path = rel_path.join(paths::local_settings_file_relative_path());
|
||||
let Some((worktree, project)) = all_projects(cx).find_map(|project| {
|
||||
project
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.zip(Some(project))
|
||||
}) else {
|
||||
anyhow::bail!("Could not find project with worktree id: {}", worktree_id);
|
||||
let Some(settings_window) = window.root::<SettingsWindow>().flatten() else {
|
||||
anyhow::bail!("No settings window found");
|
||||
};
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
let task = if project.contains_local_settings_file(worktree_id, &rel_path, cx) {
|
||||
None
|
||||
} else {
|
||||
Some(worktree.update(cx, |worktree, cx| {
|
||||
worktree.create_entry(rel_path.clone(), false, None, cx)
|
||||
}))
|
||||
};
|
||||
|
||||
cx.spawn(async move |project, cx| {
|
||||
if let Some(task) = task
|
||||
&& task.await.is_err()
|
||||
{
|
||||
return;
|
||||
};
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.update_local_settings_file(worktree_id, rel_path, cx, update);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
return Ok(());
|
||||
update_project_setting_file(worktree_id, rel_path, update, settings_window, cx)
|
||||
}
|
||||
SettingsUiFile::User => {
|
||||
// todo(settings_ui) error?
|
||||
@@ -3596,6 +3579,86 @@ fn update_settings_file(
|
||||
}
|
||||
}
|
||||
|
||||
fn update_project_setting_file(
|
||||
worktree_id: WorktreeId,
|
||||
rel_path: Arc<RelPath>,
|
||||
update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
|
||||
settings_window: Entity<SettingsWindow>,
|
||||
cx: &mut App,
|
||||
) -> Result<()> {
|
||||
let Some((worktree, project)) =
|
||||
all_projects(settings_window.read(cx).original_window.as_ref(), cx).find_map(|project| {
|
||||
project
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.zip(Some(project))
|
||||
})
|
||||
else {
|
||||
anyhow::bail!("Could not find project with worktree id: {}", worktree_id);
|
||||
};
|
||||
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: rel_path.clone(),
|
||||
};
|
||||
|
||||
let needs_creation = !project
|
||||
.read(cx)
|
||||
.contains_local_settings_file(worktree_id, &rel_path, cx);
|
||||
|
||||
let create_task = needs_creation.then(|| {
|
||||
worktree.update(cx, |worktree, cx| {
|
||||
worktree.create_entry(rel_path.clone(), false, None, cx)
|
||||
})
|
||||
});
|
||||
let buffer_store = project.read(cx).buffer_store().clone();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
if let Some(create_task) = create_task {
|
||||
create_task.await?;
|
||||
}
|
||||
let cached_buffer = settings_window.read_with(cx, |settings_window, _| {
|
||||
settings_window
|
||||
.project_setting_file_buffers
|
||||
.get(&project_path)
|
||||
.cloned()
|
||||
})?;
|
||||
let buffer = if let Some(cached_buffer) = cached_buffer {
|
||||
cached_buffer
|
||||
} else {
|
||||
let buffer = buffer_store
|
||||
.update(cx, |store, cx| store.open_buffer(project_path.clone(), cx))?
|
||||
.await
|
||||
.context("Failed to open settings file")?;
|
||||
|
||||
settings_window.update(cx, |this, _cx| {
|
||||
this.project_setting_file_buffers
|
||||
.insert(project_path, buffer.clone());
|
||||
})?;
|
||||
|
||||
buffer
|
||||
};
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let current_text = buffer.text();
|
||||
let new_text = cx
|
||||
.global::<SettingsStore>()
|
||||
.new_text_for_update(current_text, |settings| update(settings, cx));
|
||||
buffer.edit([(0..buffer.len(), new_text)], None, cx);
|
||||
})?;
|
||||
|
||||
buffer_store
|
||||
.update(cx, |store, cx| store.save_buffer(buffer, cx))?
|
||||
.await
|
||||
.context("Failed to save settings file")?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
|
||||
fn render_text_field<T: From<String> + Into<String> + AsRef<str> + Clone>(
|
||||
field: SettingField<T>,
|
||||
file: SettingsUiFile,
|
||||
@@ -3617,10 +3680,16 @@ fn render_text_field<T: From<String> + Into<String> + AsRef<str> + Clone>(
|
||||
|editor, placeholder| editor.with_placeholder(placeholder),
|
||||
)
|
||||
.on_confirm({
|
||||
move |new_text, cx| {
|
||||
update_settings_file(file.clone(), field.json_path, cx, move |settings, _cx| {
|
||||
(field.write)(settings, new_text.map(Into::into));
|
||||
})
|
||||
move |new_text, window, cx| {
|
||||
update_settings_file(
|
||||
file.clone(),
|
||||
field.json_path,
|
||||
window,
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
(field.write)(settings, new_text.map(Into::into));
|
||||
},
|
||||
)
|
||||
.log_err(); // todo(settings_ui) don't log err
|
||||
}
|
||||
})
|
||||
@@ -3645,11 +3714,11 @@ fn render_toggle_button<B: Into<bool> + From<bool> + Copy>(
|
||||
Switch::new("toggle_button", toggle_state)
|
||||
.tab_index(0_isize)
|
||||
.on_click({
|
||||
move |state, _window, cx| {
|
||||
move |state, window, cx| {
|
||||
telemetry::event!("Settings Change", setting = field.json_path, type = file.setting_type());
|
||||
|
||||
let state = *state == ui::ToggleState::Selected;
|
||||
update_settings_file(file.clone(), field.json_path, cx, move |settings, _cx| {
|
||||
update_settings_file(file.clone(), field.json_path, window, cx, move |settings, _cx| {
|
||||
(field.write)(settings, Some(state.into()));
|
||||
})
|
||||
.log_err(); // todo(settings_ui) don't log err
|
||||
@@ -3669,11 +3738,17 @@ fn render_number_field<T: NumberFieldType + Send + Sync>(
|
||||
let value = value.copied().unwrap_or_else(T::min_value);
|
||||
NumberField::new("numeric_stepper", value, window, cx)
|
||||
.on_change({
|
||||
move |value, _window, cx| {
|
||||
move |value, window, cx| {
|
||||
let value = *value;
|
||||
update_settings_file(file.clone(), field.json_path, cx, move |settings, _cx| {
|
||||
(field.write)(settings, Some(value));
|
||||
})
|
||||
update_settings_file(
|
||||
file.clone(),
|
||||
field.json_path,
|
||||
window,
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
(field.write)(settings, Some(value));
|
||||
},
|
||||
)
|
||||
.log_err(); // todo(settings_ui) don't log err
|
||||
}
|
||||
})
|
||||
@@ -3701,13 +3776,19 @@ where
|
||||
let current_value = current_value.copied().unwrap_or(variants()[0]);
|
||||
|
||||
EnumVariantDropdown::new("dropdown", current_value, variants(), labels(), {
|
||||
move |value, cx| {
|
||||
move |value, window, cx| {
|
||||
if value == current_value {
|
||||
return;
|
||||
}
|
||||
update_settings_file(file.clone(), field.json_path, cx, move |settings, _cx| {
|
||||
(field.write)(settings, Some(value));
|
||||
})
|
||||
update_settings_file(
|
||||
file.clone(),
|
||||
field.json_path,
|
||||
window,
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
(field.write)(settings, Some(value));
|
||||
},
|
||||
)
|
||||
.log_err(); // todo(settings_ui) don't log err
|
||||
}
|
||||
})
|
||||
@@ -3752,10 +3833,11 @@ fn render_font_picker(
|
||||
Some(cx.new(move |cx| {
|
||||
font_picker(
|
||||
current_value.clone().into(),
|
||||
move |font_name, cx| {
|
||||
move |font_name, window, cx| {
|
||||
update_settings_file(
|
||||
file.clone(),
|
||||
field.json_path,
|
||||
window,
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
(field.write)(settings, Some(font_name.into()));
|
||||
@@ -3801,10 +3883,11 @@ fn render_theme_picker(
|
||||
let current_value = current_value.clone();
|
||||
theme_picker(
|
||||
current_value,
|
||||
move |theme_name, cx| {
|
||||
move |theme_name, window, cx| {
|
||||
update_settings_file(
|
||||
file.clone(),
|
||||
field.json_path,
|
||||
window,
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
(field.write)(
|
||||
@@ -3853,10 +3936,11 @@ fn render_icon_theme_picker(
|
||||
let current_value = current_value.clone();
|
||||
icon_theme_picker(
|
||||
current_value,
|
||||
move |theme_name, cx| {
|
||||
move |theme_name, window, cx| {
|
||||
update_settings_file(
|
||||
file.clone(),
|
||||
field.json_path,
|
||||
window,
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
(field.write)(
|
||||
@@ -3970,6 +4054,7 @@ pub mod test {
|
||||
worktree_root_dirs: HashMap::default(),
|
||||
files: Vec::default(),
|
||||
current_file: crate::SettingsUiFile::User,
|
||||
project_setting_file_buffers: HashMap::default(),
|
||||
pages,
|
||||
search_bar: cx.new(|cx| Editor::single_line(window, cx)),
|
||||
navbar_entry: selected_idx.expect("Must have a selected navbar entry"),
|
||||
|
||||
Reference in New Issue
Block a user