Settings/keymap backup path next to files + update notification messages (#24517)

Before:


![image](https://github.com/user-attachments/assets/5b7d8677-b0db-4a66-ac30-e4751ba4182d)

After:


![image](https://github.com/user-attachments/assets/94743bc2-2902-43a3-8d6e-e0e0e6e469ec)

Release Notes:

- N/A
This commit is contained in:
Michael Sloan 2025-02-09 16:51:37 -07:00 committed by GitHub
parent cf74d653bd
commit 1a133ab9d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 116 additions and 62 deletions

View file

@ -145,12 +145,24 @@ pub fn settings_file() -> &'static PathBuf {
SETTINGS_FILE.get_or_init(|| config_dir().join("settings.json")) SETTINGS_FILE.get_or_init(|| config_dir().join("settings.json"))
} }
/// Returns the path to the `settings_backup.json` file.
pub fn settings_backup_file() -> &'static PathBuf {
static SETTINGS_FILE: OnceLock<PathBuf> = OnceLock::new();
SETTINGS_FILE.get_or_init(|| config_dir().join("settings_backup.json"))
}
/// Returns the path to the `keymap.json` file. /// Returns the path to the `keymap.json` file.
pub fn keymap_file() -> &'static PathBuf { pub fn keymap_file() -> &'static PathBuf {
static KEYMAP_FILE: OnceLock<PathBuf> = OnceLock::new(); static KEYMAP_FILE: OnceLock<PathBuf> = OnceLock::new();
KEYMAP_FILE.get_or_init(|| config_dir().join("keymap.json")) KEYMAP_FILE.get_or_init(|| config_dir().join("keymap.json"))
} }
/// Returns the path to the `keymap_backup.json` file.
pub fn keymap_backup_file() -> &'static PathBuf {
static KEYMAP_FILE: OnceLock<PathBuf> = OnceLock::new();
KEYMAP_FILE.get_or_init(|| config_dir().join("keymap_backup.json"))
}
/// Returns the path to the `tasks.json` file. /// Returns the path to the `tasks.json` file.
pub fn tasks_file() -> &'static PathBuf { pub fn tasks_file() -> &'static PathBuf {
static TASKS_FILE: OnceLock<PathBuf> = OnceLock::new(); static TASKS_FILE: OnceLock<PathBuf> = OnceLock::new();

View file

@ -588,24 +588,24 @@ impl KeymapFile {
let Some(new_text) = migrate_keymap(&old_text) else { let Some(new_text) = migrate_keymap(&old_text) else {
return Ok(()); return Ok(());
}; };
let initial_path = paths::keymap_file().as_path(); let keymap_path = paths::keymap_file().as_path();
if fs.is_file(initial_path).await { if fs.is_file(keymap_path).await {
let backup_path = paths::home_dir().join(".zed_keymap_backup"); fs.atomic_write(paths::keymap_backup_file().to_path_buf(), old_text)
fs.atomic_write(backup_path, old_text)
.await .await
.with_context(|| { .with_context(|| {
"Failed to create settings backup in home directory".to_string() "Failed to create settings backup in home directory".to_string()
})?; })?;
let resolved_path = fs.canonicalize(initial_path).await.with_context(|| { let resolved_path = fs
format!("Failed to canonicalize keymap path {:?}", initial_path) .canonicalize(keymap_path)
})?; .await
.with_context(|| format!("Failed to canonicalize keymap path {:?}", keymap_path))?;
fs.atomic_write(resolved_path.clone(), new_text) fs.atomic_write(resolved_path.clone(), new_text)
.await .await
.with_context(|| format!("Failed to write keymap to file {:?}", resolved_path))?; .with_context(|| format!("Failed to write keymap to file {:?}", resolved_path))?;
} else { } else {
fs.atomic_write(initial_path.to_path_buf(), new_text) fs.atomic_write(keymap_path.to_path_buf(), new_text)
.await .await
.with_context(|| format!("Failed to write keymap to file {:?}", initial_path))?; .with_context(|| format!("Failed to write keymap to file {:?}", keymap_path))?;
} }
Ok(()) Ok(())

View file

@ -415,11 +415,11 @@ impl SettingsStore {
let new_text = cx.read_global(|store: &SettingsStore, cx| { let new_text = cx.read_global(|store: &SettingsStore, cx| {
store.new_text_for_update::<T>(old_text, |content| update(content, cx)) store.new_text_for_update::<T>(old_text, |content| update(content, cx))
})?; })?;
let initial_path = paths::settings_file().as_path(); let settings_path = paths::settings_file().as_path();
if fs.is_file(initial_path).await { if fs.is_file(settings_path).await {
let resolved_path = let resolved_path =
fs.canonicalize(initial_path).await.with_context(|| { fs.canonicalize(settings_path).await.with_context(|| {
format!("Failed to canonicalize settings path {:?}", initial_path) format!("Failed to canonicalize settings path {:?}", settings_path)
})?; })?;
fs.atomic_write(resolved_path.clone(), new_text) fs.atomic_write(resolved_path.clone(), new_text)
@ -428,10 +428,10 @@ impl SettingsStore {
format!("Failed to write settings to file {:?}", resolved_path) format!("Failed to write settings to file {:?}", resolved_path)
})?; })?;
} else { } else {
fs.atomic_write(initial_path.to_path_buf(), new_text) fs.atomic_write(settings_path.to_path_buf(), new_text)
.await .await
.with_context(|| { .with_context(|| {
format!("Failed to write settings to file {:?}", initial_path) format!("Failed to write settings to file {:?}", settings_path)
})?; })?;
} }
@ -1011,17 +1011,16 @@ impl SettingsStore {
let Some(new_text) = migrate_settings(&old_text) else { let Some(new_text) = migrate_settings(&old_text) else {
return anyhow::Ok(()); return anyhow::Ok(());
}; };
let initial_path = paths::settings_file().as_path(); let settings_path = paths::settings_file().as_path();
if fs.is_file(initial_path).await { if fs.is_file(settings_path).await {
let backup_path = paths::home_dir().join(".zed_settings_backup"); fs.atomic_write(paths::settings_backup_file().to_path_buf(), old_text)
fs.atomic_write(backup_path, old_text)
.await .await
.with_context(|| { .with_context(|| {
"Failed to create settings backup in home directory".to_string() "Failed to create settings backup in home directory".to_string()
})?; })?;
let resolved_path = let resolved_path =
fs.canonicalize(initial_path).await.with_context(|| { fs.canonicalize(settings_path).await.with_context(|| {
format!("Failed to canonicalize settings path {:?}", initial_path) format!("Failed to canonicalize settings path {:?}", settings_path)
})?; })?;
fs.atomic_write(resolved_path.clone(), new_text) fs.atomic_write(resolved_path.clone(), new_text)
.await .await
@ -1029,10 +1028,10 @@ impl SettingsStore {
format!("Failed to write settings to file {:?}", resolved_path) format!("Failed to write settings to file {:?}", resolved_path)
})?; })?;
} else { } else {
fs.atomic_write(initial_path.to_path_buf(), new_text) fs.atomic_write(settings_path.to_path_buf(), new_text)
.await .await
.with_context(|| { .with_context(|| {
format!("Failed to write settings to file {:?}", initial_path) format!("Failed to write settings to file {:?}", settings_path)
})?; })?;
} }
anyhow::Ok(()) anyhow::Ok(())

View file

@ -448,6 +448,14 @@ pub mod simple_message_notification {
self self
} }
pub fn primary_on_click_arc<F>(mut self, on_click: Arc<F>) -> Self
where
F: 'static + Fn(&mut Window, &mut Context<Self>),
{
self.primary_on_click = Some(on_click);
self
}
pub fn secondary_message<S>(mut self, message: S) -> Self pub fn secondary_message<S>(mut self, message: S) -> Self
where where
S: Into<SharedString>, S: Into<SharedString>,
@ -474,6 +482,14 @@ pub mod simple_message_notification {
self self
} }
pub fn secondary_on_click_arc<F>(mut self, on_click: Arc<F>) -> Self
where
F: 'static + Fn(&mut Window, &mut Context<Self>),
{
self.secondary_on_click = Some(on_click);
self
}
pub fn more_info_message<S>(mut self, message: S) -> Self pub fn more_info_message<S>(mut self, message: S) -> Self
where where
S: Into<SharedString>, S: Into<SharedString>,

View file

@ -1217,25 +1217,29 @@ fn show_keymap_migration_notification_if_needed(
if !KeymapFile::should_migrate_keymap(keymap_file) { if !KeymapFile::should_migrate_keymap(keymap_file) {
return false; return false;
} }
show_app_notification(notification_id, cx, move |cx| { let message = MarkdownString(format!(
cx.new(move |_cx| { "Keymap migration needed, as the format for some actions has changed. \
let message = "A newer version of Zed has simplified several keymaps. Your existing keymaps may be deprecated. You can migrate them by clicking below. A backup will be created in your home directory."; You can migrate your keymap by clicking below. A backup will be created at {}.",
let button_text = "Backup and Migrate Keymap"; MarkdownString::inline_code(&paths::keymap_backup_file().to_string_lossy())
MessageNotification::new_from_builder(move |_, _| { ));
gpui::div().text_xs().child(message).into_any() show_markdown_app_notification(
}) notification_id,
.primary_message(button_text) message,
.primary_on_click(move |_, cx| { "Backup and Migrate Keymap".into(),
let fs = <dyn Fs>::global(cx); move |_, cx| {
cx.spawn(move |weak_notification, mut cx| async move { let fs = <dyn Fs>::global(cx);
KeymapFile::migrate_keymap(fs).await.ok(); cx.spawn(move |weak_notification, mut cx| async move {
weak_notification.update(&mut cx, |_, cx| { KeymapFile::migrate_keymap(fs).await.ok();
weak_notification
.update(&mut cx, |_, cx| {
cx.emit(DismissEvent); cx.emit(DismissEvent);
}).ok(); })
}).detach(); .ok();
}) })
}) .detach();
}); },
cx,
);
return true; return true;
} }
@ -1247,33 +1251,55 @@ fn show_settings_migration_notification_if_needed(
if !SettingsStore::should_migrate_settings(&settings) { if !SettingsStore::should_migrate_settings(&settings) {
return; return;
} }
show_app_notification(notification_id, cx, move |cx| { let message = MarkdownString(format!(
cx.new(move |_cx| { "Settings migration needed, as the format for some settings has changed. \
let message = "A newer version of Zed has updated some settings. Your existing settings may be deprecated. You can migrate them by clicking below. A backup will be created in your home directory."; You can migrate your settings by clicking below. A backup will be created at {}.",
let button_text = "Backup and Migrate Settings"; MarkdownString::inline_code(&paths::settings_backup_file().to_string_lossy())
MessageNotification::new_from_builder(move |_, _| { ));
gpui::div().text_xs().child(message).into_any() show_markdown_app_notification(
}) notification_id,
.primary_message(button_text) message,
.primary_on_click(move |_, cx| { "Backup and Migrate Settings".into(),
let fs = <dyn Fs>::global(cx); move |_, cx| {
cx.update_global(|store: &mut SettingsStore, _| store.migrate_settings(fs)); let fs = <dyn Fs>::global(cx);
cx.emit(DismissEvent); cx.update_global(|store: &mut SettingsStore, _| store.migrate_settings(fs));
}) cx.emit(DismissEvent);
}) },
}); cx,
);
} }
fn show_keymap_file_load_error( fn show_keymap_file_load_error(
notification_id: NotificationId, notification_id: NotificationId,
markdown_error_message: MarkdownString, error_message: MarkdownString,
cx: &mut App, cx: &mut App,
) { ) {
show_markdown_app_notification(
notification_id.clone(),
error_message,
"Open Keymap File".into(),
|window, cx| {
window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx);
cx.emit(DismissEvent);
},
cx,
)
}
fn show_markdown_app_notification<F>(
notification_id: NotificationId,
message: MarkdownString,
primary_button_message: SharedString,
primary_button_on_click: F,
cx: &mut App,
) where
F: 'static + Send + Sync + Fn(&mut Window, &mut Context<MessageNotification>),
{
let parsed_markdown = cx.background_executor().spawn(async move { let parsed_markdown = cx.background_executor().spawn(async move {
let file_location_directory = None; let file_location_directory = None;
let language_registry = None; let language_registry = None;
markdown_preview::markdown_parser::parse_markdown( markdown_preview::markdown_parser::parse_markdown(
&markdown_error_message.0, &message.0,
file_location_directory, file_location_directory,
language_registry, language_registry,
) )
@ -1282,10 +1308,14 @@ fn show_keymap_file_load_error(
cx.spawn(move |cx| async move { cx.spawn(move |cx| async move {
let parsed_markdown = Arc::new(parsed_markdown.await); let parsed_markdown = Arc::new(parsed_markdown.await);
let primary_button_message = primary_button_message.clone();
let primary_button_on_click = Arc::new(primary_button_on_click);
cx.update(|cx| { cx.update(|cx| {
show_app_notification(notification_id, cx, move |cx| { show_app_notification(notification_id, cx, move |cx| {
let workspace_handle = cx.entity().downgrade(); let workspace_handle = cx.entity().downgrade();
let parsed_markdown = parsed_markdown.clone(); let parsed_markdown = parsed_markdown.clone();
let primary_button_message = primary_button_message.clone();
let primary_button_on_click = primary_button_on_click.clone();
cx.new(move |_cx| { cx.new(move |_cx| {
MessageNotification::new_from_builder(move |window, cx| { MessageNotification::new_from_builder(move |window, cx| {
gpui::div() gpui::div()
@ -1298,11 +1328,8 @@ fn show_keymap_file_load_error(
)) ))
.into_any() .into_any()
}) })
.primary_message("Open Keymap File") .primary_message(primary_button_message)
.primary_on_click(|window, cx| { .primary_on_click_arc(primary_button_on_click)
window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx);
cx.emit(DismissEvent);
})
}) })
}) })
}) })