Migrate keymap and settings + edit predictions rename (#23834)

- [x] snake case keymap properties
- [x] flatten actions
- [x] keymap migration + notfication
- [x] settings migration + notification
- [x] inline completions -> edit predictions 

### future: 
- keymap notification doesn't show up on start up, only on keymap save.
this is existing bug in zed, will be addressed in seperate PR.

Release Notes:

- Added a notification for deprecated settings and keymaps, allowing you
to migrate them with a single click. A backup of your existing keymap
and settings will be created in your home directory.
- Modified some keymap actions and settings for consistency.

---------

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
smit 2025-02-07 21:17:07 +05:30 committed by GitHub
parent a1544f47ad
commit 00c2a30059
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 2106 additions and 617 deletions

View file

@ -20,6 +20,7 @@ use command_palette_hooks::CommandPaletteFilter;
use editor::ProposedChangesEditorToolbar;
use editor::{scroll::Autoscroll, Editor, MultiBuffer};
use feature_flags::{FeatureFlagAppExt, FeatureFlagViewExt, GitUiFeatureFlag};
use fs::Fs;
use futures::{channel::mpsc, select_biased, StreamExt};
use gpui::{
actions, point, px, Action, App, AppContext as _, AsyncApp, Context, DismissEvent, Element,
@ -1144,18 +1145,34 @@ pub fn handle_keymap_file_changes(
cx.update(|cx| {
let load_result = KeymapFile::load(&user_keymap_content, cx);
match load_result {
KeymapFileLoadResult::Success { key_bindings } => {
KeymapFileLoadResult::Success {
key_bindings,
keymap_file,
} => {
reload_keymaps(cx, key_bindings);
dismiss_app_notification(&notification_id, cx);
show_keymap_migration_notification_if_needed(
keymap_file,
notification_id.clone(),
cx,
);
}
KeymapFileLoadResult::SomeFailedToLoad {
key_bindings,
keymap_file,
error_message,
} => {
if !key_bindings.is_empty() {
reload_keymaps(cx, key_bindings);
}
show_keymap_file_load_error(notification_id.clone(), error_message, cx)
dismiss_app_notification(&notification_id, cx);
if !show_keymap_migration_notification_if_needed(
keymap_file,
notification_id.clone(),
cx,
) {
show_keymap_file_load_error(notification_id.clone(), error_message, cx);
}
}
KeymapFileLoadResult::JsonParseFailure { error } => {
show_keymap_file_json_error(notification_id.clone(), &error, cx)
@ -1187,6 +1204,61 @@ fn show_keymap_file_json_error(
});
}
fn show_keymap_migration_notification_if_needed(
keymap_file: KeymapFile,
notification_id: NotificationId,
cx: &mut App,
) -> bool {
if !KeymapFile::should_migrate_keymap(keymap_file) {
return false;
}
show_app_notification(notification_id, cx, move |cx| {
cx.new(move |_cx| {
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.";
let button_text = "Backup and Migrate Keymap";
MessageNotification::new_from_builder(move |_, _| {
gpui::div().text_xs().child(message).into_any()
})
.primary_message(button_text)
.primary_on_click(move |_, cx| {
let fs = <dyn Fs>::global(cx);
cx.spawn(move |weak_notification, mut cx| async move {
KeymapFile::migrate_keymap(fs).await.ok();
weak_notification.update(&mut cx, |_, cx| {
cx.emit(DismissEvent);
}).ok();
}).detach();
})
})
});
return true;
}
fn show_settings_migration_notification_if_needed(
notification_id: NotificationId,
settings: serde_json::Value,
cx: &mut App,
) {
if !SettingsStore::should_migrate_settings(&settings) {
return;
}
show_app_notification(notification_id, cx, move |cx| {
cx.new(move |_cx| {
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.";
let button_text = "Backup and Migrate Settings";
MessageNotification::new_from_builder(move |_, _| {
gpui::div().text_xs().child(message).into_any()
})
.primary_message(button_text)
.primary_on_click(move |_, cx| {
let fs = <dyn Fs>::global(cx);
cx.update_global(|store: &mut SettingsStore, _| store.migrate_settings(fs));
cx.emit(DismissEvent);
})
})
});
}
fn show_keymap_file_load_error(
notification_id: NotificationId,
markdown_error_message: MarkdownString,
@ -1259,12 +1331,12 @@ pub fn load_default_keymap(cx: &mut App) {
}
}
pub fn handle_settings_changed(error: Option<anyhow::Error>, cx: &mut App) {
pub fn handle_settings_changed(result: Result<serde_json::Value, anyhow::Error>, cx: &mut App) {
struct SettingsParseErrorNotification;
let id = NotificationId::unique::<SettingsParseErrorNotification>();
match error {
Some(error) => {
match result {
Err(error) => {
if let Some(InvalidSettingsError::LocalSettings { .. }) =
error.downcast_ref::<InvalidSettingsError>()
{
@ -1283,7 +1355,10 @@ pub fn handle_settings_changed(error: Option<anyhow::Error>, cx: &mut App) {
})
});
}
None => dismiss_app_notification(&id, cx),
Ok(settings) => {
dismiss_app_notification(&id, cx);
show_settings_migration_notification_if_needed(id, settings, cx);
}
}
}
@ -3925,24 +4000,28 @@ mod tests {
"vim::FindCommand"
| "vim::Literal"
| "vim::ResizePane"
| "vim::SwitchMode"
| "vim::PushOperator"
| "vim::PushObject"
| "vim::PushFindForward"
| "vim::PushFindBackward"
| "vim::PushSneak"
| "vim::PushSneakBackward"
| "vim::PushChangeSurrounds"
| "vim::PushJump"
| "vim::PushDigraph"
| "vim::PushLiteral"
| "vim::Number"
| "vim::SelectRegister"
| "terminal::SendText"
| "terminal::SendKeystroke"
| "app_menu::OpenApplicationMenu"
| "app_menu::NavigateApplicationMenuInDirection"
| "picker::ConfirmInput"
| "editor::HandleInput"
| "editor::FoldAtLevel"
| "pane::ActivateItem"
| "workspace::ActivatePane"
| "workspace::ActivatePaneInDirection"
| "workspace::MoveItemToPane"
| "workspace::MoveItemToPaneInDirection"
| "workspace::OpenTerminal"
| "workspace::SwapPaneInDirection"
| "workspace::SendKeystrokes"
| "zed::OpenBrowser"
| "zed::OpenZedUrl" => {}

View file

@ -4,7 +4,7 @@ use copilot::{Copilot, CopilotCompletionProvider};
use editor::{Editor, EditorMode};
use feature_flags::{FeatureFlagAppExt, PredictEditsFeatureFlag};
use gpui::{AnyWindowHandle, App, AppContext, Context, Entity, WeakEntity};
use language::language_settings::{all_language_settings, InlineCompletionProvider};
use language::language_settings::{all_language_settings, EditPredictionProvider};
use settings::SettingsStore;
use std::{cell::RefCell, rc::Rc, sync::Arc};
use supermaven::{Supermaven, SupermavenCompletionProvider};
@ -41,8 +41,8 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
editors
.borrow_mut()
.insert(editor_handle, window.window_handle());
let provider = all_language_settings(None, cx).inline_completions.provider;
assign_inline_completion_provider(
let provider = all_language_settings(None, cx).edit_predictions.provider;
assign_edit_prediction_provider(
editor,
provider,
&client,
@ -54,11 +54,11 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
})
.detach();
let mut provider = all_language_settings(None, cx).inline_completions.provider;
let mut provider = all_language_settings(None, cx).edit_predictions.provider;
for (editor, window) in editors.borrow().iter() {
_ = window.update(cx, |_window, window, cx| {
_ = editor.update(cx, |editor, cx| {
assign_inline_completion_provider(
assign_edit_prediction_provider(
editor,
provider,
&client,
@ -79,8 +79,8 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
let client = client.clone();
let user_store = user_store.clone();
move |active, cx| {
let provider = all_language_settings(None, cx).inline_completions.provider;
assign_inline_completion_providers(&editors, provider, &client, user_store.clone(), cx);
let provider = all_language_settings(None, cx).edit_predictions.provider;
assign_edit_prediction_providers(&editors, provider, &client, user_store.clone(), cx);
if active && !cx.is_action_available(&zeta::ClearHistory) {
cx.on_action(clear_zeta_edit_history);
}
@ -93,7 +93,7 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
let client = client.clone();
let user_store = user_store.clone();
move |cx| {
let new_provider = all_language_settings(None, cx).inline_completions.provider;
let new_provider = all_language_settings(None, cx).edit_predictions.provider;
if new_provider != provider {
let tos_accepted = user_store
@ -109,7 +109,7 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
);
provider = new_provider;
assign_inline_completion_providers(
assign_edit_prediction_providers(
&editors,
provider,
&client,
@ -119,7 +119,7 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
if !tos_accepted {
match provider {
InlineCompletionProvider::Zed => {
EditPredictionProvider::Zed => {
let Some(window) = cx.active_window() else {
return;
};
@ -133,9 +133,9 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
})
.ok();
}
InlineCompletionProvider::None
| InlineCompletionProvider::Copilot
| InlineCompletionProvider::Supermaven => {}
EditPredictionProvider::None
| EditPredictionProvider::Copilot
| EditPredictionProvider::Supermaven => {}
}
}
}
@ -150,9 +150,9 @@ fn clear_zeta_edit_history(_: &zeta::ClearHistory, cx: &mut App) {
}
}
fn assign_inline_completion_providers(
fn assign_edit_prediction_providers(
editors: &Rc<RefCell<HashMap<WeakEntity<Editor>, AnyWindowHandle>>>,
provider: InlineCompletionProvider,
provider: EditPredictionProvider,
client: &Arc<Client>,
user_store: Entity<UserStore>,
cx: &mut App,
@ -160,7 +160,7 @@ fn assign_inline_completion_providers(
for (editor, window) in editors.borrow().iter() {
_ = window.update(cx, |_window, window, cx| {
_ = editor.update(cx, |editor, cx| {
assign_inline_completion_provider(
assign_edit_prediction_provider(
editor,
provider,
&client,
@ -187,7 +187,7 @@ fn register_backward_compatible_actions(editor: &mut Editor, cx: &mut Context<Ed
editor
.register_action(cx.listener(
|editor, _: &copilot::NextSuggestion, window: &mut Window, cx: &mut Context<Editor>| {
editor.next_inline_completion(&Default::default(), window, cx);
editor.next_edit_prediction(&Default::default(), window, cx);
},
))
.detach();
@ -197,7 +197,7 @@ fn register_backward_compatible_actions(editor: &mut Editor, cx: &mut Context<Ed
_: &copilot::PreviousSuggestion,
window: &mut Window,
cx: &mut Context<Editor>| {
editor.previous_inline_completion(&Default::default(), window, cx);
editor.previous_edit_prediction(&Default::default(), window, cx);
},
))
.detach();
@ -213,9 +213,9 @@ fn register_backward_compatible_actions(editor: &mut Editor, cx: &mut Context<Ed
.detach();
}
fn assign_inline_completion_provider(
fn assign_edit_prediction_provider(
editor: &mut Editor,
provider: InlineCompletionProvider,
provider: EditPredictionProvider,
client: &Arc<Client>,
user_store: Entity<UserStore>,
window: &mut Window,
@ -225,8 +225,8 @@ fn assign_inline_completion_provider(
let singleton_buffer = editor.buffer().read(cx).as_singleton();
match provider {
InlineCompletionProvider::None => {}
InlineCompletionProvider::Copilot => {
EditPredictionProvider::None => {}
EditPredictionProvider::Copilot => {
if let Some(copilot) = Copilot::global(cx) {
if let Some(buffer) = singleton_buffer {
if buffer.read(cx).file().is_some() {
@ -236,16 +236,16 @@ fn assign_inline_completion_provider(
}
}
let provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
editor.set_inline_completion_provider(Some(provider), window, cx);
editor.set_edit_prediction_provider(Some(provider), window, cx);
}
}
InlineCompletionProvider::Supermaven => {
EditPredictionProvider::Supermaven => {
if let Some(supermaven) = Supermaven::global(cx) {
let provider = cx.new(|_| SupermavenCompletionProvider::new(supermaven));
editor.set_inline_completion_provider(Some(provider), window, cx);
editor.set_edit_prediction_provider(Some(provider), window, cx);
}
}
InlineCompletionProvider::Zed => {
EditPredictionProvider::Zed => {
if cx.has_flag::<PredictEditsFeatureFlag>()
|| (cfg!(debug_assertions) && client.status().borrow().is_connected())
{
@ -280,7 +280,7 @@ fn assign_inline_completion_provider(
let provider =
cx.new(|_| zeta::ZetaInlineCompletionProvider::new(zeta, data_collection));
editor.set_inline_completion_provider(Some(provider), window, cx);
editor.set_edit_prediction_provider(Some(provider), window, cx);
}
}
}

View file

@ -301,14 +301,14 @@ impl Render for QuickActionBar {
.toggleable(IconPosition::Start, inline_completion_enabled && show_inline_completions)
.disabled(!inline_completion_enabled)
.action(Some(
editor::actions::ToggleInlineCompletions.boxed_clone(),
editor::actions::ToggleEditPrediction.boxed_clone(),
)).handler({
let editor = editor.clone();
move |window, cx| {
editor
.update(cx, |editor, cx| {
editor.toggle_inline_completions(
&editor::actions::ToggleInlineCompletions,
&editor::actions::ToggleEditPrediction,
window,
cx,
);