diff --git a/crates/migrator/src/migrations.rs b/crates/migrator/src/migrations.rs new file mode 100644 index 0000000000..2603919b8c --- /dev/null +++ b/crates/migrator/src/migrations.rs @@ -0,0 +1,27 @@ +pub(crate) mod m_2025_01_29 { + mod keymap; + mod settings; + + pub(crate) use keymap::KEYMAP_PATTERNS; + pub(crate) use settings::{replace_edit_prediction_provider_setting, SETTINGS_PATTERNS}; +} + +pub(crate) mod m_2025_01_30 { + mod keymap; + mod settings; + + pub(crate) use keymap::KEYMAP_PATTERNS; + pub(crate) use settings::SETTINGS_PATTERNS; +} + +pub(crate) mod m_2025_03_03 { + mod keymap; + + pub(crate) use keymap::KEYMAP_PATTERNS; +} + +pub(crate) mod m_2025_03_06 { + mod keymap; + + pub(crate) use keymap::KEYMAP_PATTERNS; +} diff --git a/crates/migrator/src/migrations/m_2025_01_29/keymap.rs b/crates/migrator/src/migrations/m_2025_01_29/keymap.rs new file mode 100644 index 0000000000..2be465470d --- /dev/null +++ b/crates/migrator/src/migrations/m_2025_01_29/keymap.rs @@ -0,0 +1,297 @@ +use collections::HashMap; +use std::{ops::Range, sync::LazyLock}; +use tree_sitter::{Query, QueryMatch}; + +use crate::patterns::{ + KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN, KEYMAP_ACTION_ARRAY_PATTERN, + KEYMAP_ACTION_STRING_PATTERN, KEYMAP_CONTEXT_PATTERN, +}; +use crate::MigrationPatterns; + +pub const KEYMAP_PATTERNS: MigrationPatterns = &[ + ( + KEYMAP_ACTION_ARRAY_PATTERN, + replace_array_with_single_string, + ), + ( + KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN, + replace_action_argument_object_with_single_value, + ), + (KEYMAP_ACTION_STRING_PATTERN, replace_string_action), + (KEYMAP_CONTEXT_PATTERN, rename_context_key), +]; + +fn replace_array_with_single_string( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let array_ix = query.capture_index_for_name("array")?; + let action_name_ix = query.capture_index_for_name("action_name")?; + let argument_ix = query.capture_index_for_name("argument")?; + + let action_name = contents.get( + mat.nodes_for_capture_index(action_name_ix) + .next()? + .byte_range(), + )?; + let argument = contents.get( + mat.nodes_for_capture_index(argument_ix) + .next()? + .byte_range(), + )?; + + let replacement = TRANSFORM_ARRAY.get(&(action_name, argument))?; + let replacement_as_string = format!("\"{replacement}\""); + let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range(); + + Some((range_to_replace, replacement_as_string)) +} + +static TRANSFORM_ARRAY: LazyLock> = LazyLock::new(|| { + HashMap::from_iter([ + // activate + ( + ("workspace::ActivatePaneInDirection", "Up"), + "workspace::ActivatePaneUp", + ), + ( + ("workspace::ActivatePaneInDirection", "Down"), + "workspace::ActivatePaneDown", + ), + ( + ("workspace::ActivatePaneInDirection", "Left"), + "workspace::ActivatePaneLeft", + ), + ( + ("workspace::ActivatePaneInDirection", "Right"), + "workspace::ActivatePaneRight", + ), + // swap + ( + ("workspace::SwapPaneInDirection", "Up"), + "workspace::SwapPaneUp", + ), + ( + ("workspace::SwapPaneInDirection", "Down"), + "workspace::SwapPaneDown", + ), + ( + ("workspace::SwapPaneInDirection", "Left"), + "workspace::SwapPaneLeft", + ), + ( + ("workspace::SwapPaneInDirection", "Right"), + "workspace::SwapPaneRight", + ), + // menu + ( + ("app_menu::NavigateApplicationMenuInDirection", "Left"), + "app_menu::ActivateMenuLeft", + ), + ( + ("app_menu::NavigateApplicationMenuInDirection", "Right"), + "app_menu::ActivateMenuRight", + ), + // vim push + (("vim::PushOperator", "Change"), "vim::PushChange"), + (("vim::PushOperator", "Delete"), "vim::PushDelete"), + (("vim::PushOperator", "Yank"), "vim::PushYank"), + (("vim::PushOperator", "Replace"), "vim::PushReplace"), + ( + ("vim::PushOperator", "DeleteSurrounds"), + "vim::PushDeleteSurrounds", + ), + (("vim::PushOperator", "Mark"), "vim::PushMark"), + (("vim::PushOperator", "Indent"), "vim::PushIndent"), + (("vim::PushOperator", "Outdent"), "vim::PushOutdent"), + (("vim::PushOperator", "AutoIndent"), "vim::PushAutoIndent"), + (("vim::PushOperator", "Rewrap"), "vim::PushRewrap"), + ( + ("vim::PushOperator", "ShellCommand"), + "vim::PushShellCommand", + ), + (("vim::PushOperator", "Lowercase"), "vim::PushLowercase"), + (("vim::PushOperator", "Uppercase"), "vim::PushUppercase"), + ( + ("vim::PushOperator", "OppositeCase"), + "vim::PushOppositeCase", + ), + (("vim::PushOperator", "Register"), "vim::PushRegister"), + ( + ("vim::PushOperator", "RecordRegister"), + "vim::PushRecordRegister", + ), + ( + ("vim::PushOperator", "ReplayRegister"), + "vim::PushReplayRegister", + ), + ( + ("vim::PushOperator", "ReplaceWithRegister"), + "vim::PushReplaceWithRegister", + ), + ( + ("vim::PushOperator", "ToggleComments"), + "vim::PushToggleComments", + ), + // vim switch + (("vim::SwitchMode", "Normal"), "vim::SwitchToNormalMode"), + (("vim::SwitchMode", "Insert"), "vim::SwitchToInsertMode"), + (("vim::SwitchMode", "Replace"), "vim::SwitchToReplaceMode"), + (("vim::SwitchMode", "Visual"), "vim::SwitchToVisualMode"), + ( + ("vim::SwitchMode", "VisualLine"), + "vim::SwitchToVisualLineMode", + ), + ( + ("vim::SwitchMode", "VisualBlock"), + "vim::SwitchToVisualBlockMode", + ), + ( + ("vim::SwitchMode", "HelixNormal"), + "vim::SwitchToHelixNormalMode", + ), + // vim resize + (("vim::ResizePane", "Widen"), "vim::ResizePaneRight"), + (("vim::ResizePane", "Narrow"), "vim::ResizePaneLeft"), + (("vim::ResizePane", "Shorten"), "vim::ResizePaneDown"), + (("vim::ResizePane", "Lengthen"), "vim::ResizePaneUp"), + ]) +}); + +/// [ "editor::FoldAtLevel", { "level": 1 } ] -> [ "editor::FoldAtLevel", 1 ] +fn replace_action_argument_object_with_single_value( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let array_ix = query.capture_index_for_name("array")?; + let action_name_ix = query.capture_index_for_name("action_name")?; + let argument_key_ix = query.capture_index_for_name("argument_key")?; + let argument_value_ix = query.capture_index_for_name("argument_value")?; + + let action_name = contents.get( + mat.nodes_for_capture_index(action_name_ix) + .next()? + .byte_range(), + )?; + let argument_key = contents.get( + mat.nodes_for_capture_index(argument_key_ix) + .next()? + .byte_range(), + )?; + let argument_value = contents.get( + mat.nodes_for_capture_index(argument_value_ix) + .next()? + .byte_range(), + )?; + + let new_action_name = UNWRAP_OBJECTS.get(&action_name)?.get(&argument_key)?; + + let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range(); + let replacement = format!("[\"{}\", {}]", new_action_name, argument_value); + Some((range_to_replace, replacement)) +} + +/// "ctrl-k ctrl-1": [ "editor::PushOperator", { "Object": {} } ] -> [ "editor::vim::PushObject", {} ] +static UNWRAP_OBJECTS: LazyLock>> = LazyLock::new(|| { + HashMap::from_iter([ + ( + "editor::FoldAtLevel", + HashMap::from_iter([("level", "editor::FoldAtLevel")]), + ), + ( + "vim::PushOperator", + HashMap::from_iter([ + ("Object", "vim::PushObject"), + ("FindForward", "vim::PushFindForward"), + ("FindBackward", "vim::PushFindBackward"), + ("Sneak", "vim::PushSneak"), + ("SneakBackward", "vim::PushSneakBackward"), + ("AddSurrounds", "vim::PushAddSurrounds"), + ("ChangeSurrounds", "vim::PushChangeSurrounds"), + ("Jump", "vim::PushJump"), + ("Digraph", "vim::PushDigraph"), + ("Literal", "vim::PushLiteral"), + ]), + ), + ]) +}); + +fn replace_string_action( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let action_name_ix = query.capture_index_for_name("action_name")?; + let action_name_node = mat.nodes_for_capture_index(action_name_ix).next()?; + let action_name_range = action_name_node.byte_range(); + let action_name = contents.get(action_name_range.clone())?; + + if let Some(new_action_name) = STRING_REPLACE.get(&action_name) { + return Some((action_name_range, new_action_name.to_string())); + } + + None +} + +/// "ctrl-k ctrl-1": "inline_completion::ToggleMenu" -> "edit_prediction::ToggleMenu" +static STRING_REPLACE: LazyLock> = LazyLock::new(|| { + HashMap::from_iter([ + ( + "inline_completion::ToggleMenu", + "edit_prediction::ToggleMenu", + ), + ("editor::NextInlineCompletion", "editor::NextEditPrediction"), + ( + "editor::PreviousInlineCompletion", + "editor::PreviousEditPrediction", + ), + ( + "editor::AcceptPartialInlineCompletion", + "editor::AcceptPartialEditPrediction", + ), + ("editor::ShowInlineCompletion", "editor::ShowEditPrediction"), + ( + "editor::AcceptInlineCompletion", + "editor::AcceptEditPrediction", + ), + ( + "editor::ToggleInlineCompletions", + "editor::ToggleEditPrediction", + ), + ]) +}); + +fn rename_context_key( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let context_predicate_ix = query.capture_index_for_name("context_predicate")?; + let context_predicate_range = mat + .nodes_for_capture_index(context_predicate_ix) + .next()? + .byte_range(); + let old_predicate = contents.get(context_predicate_range.clone())?.to_string(); + let mut new_predicate = old_predicate.to_string(); + for (old_key, new_key) in CONTEXT_REPLACE.iter() { + new_predicate = new_predicate.replace(old_key, new_key); + } + if new_predicate != old_predicate { + Some((context_predicate_range, new_predicate.to_string())) + } else { + None + } +} + +/// "context": "Editor && inline_completion && !showing_completions" -> "Editor && edit_prediction && !showing_completions" +pub static CONTEXT_REPLACE: LazyLock> = LazyLock::new(|| { + HashMap::from_iter([ + ("inline_completion", "edit_prediction"), + ( + "inline_completion_requires_modifier", + "edit_prediction_requires_modifier", + ), + ]) +}); diff --git a/crates/migrator/src/migrations/m_2025_01_29/settings.rs b/crates/migrator/src/migrations/m_2025_01_29/settings.rs new file mode 100644 index 0000000000..ba11779c35 --- /dev/null +++ b/crates/migrator/src/migrations/m_2025_01_29/settings.rs @@ -0,0 +1,101 @@ +use collections::HashMap; +use std::{ops::Range, sync::LazyLock}; +use tree_sitter::{Query, QueryMatch}; + +use crate::patterns::{ + SETTINGS_LANGUAGES_PATTERN, SETTINGS_NESTED_KEY_VALUE_PATTERN, SETTINGS_ROOT_KEY_VALUE_PATTERN, +}; +use crate::MigrationPatterns; + +pub const SETTINGS_PATTERNS: MigrationPatterns = &[ + (SETTINGS_ROOT_KEY_VALUE_PATTERN, replace_setting_name), + ( + SETTINGS_NESTED_KEY_VALUE_PATTERN, + replace_edit_prediction_provider_setting, + ), + (SETTINGS_LANGUAGES_PATTERN, replace_setting_in_languages), +]; + +fn replace_setting_name( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let setting_capture_ix = query.capture_index_for_name("name")?; + let setting_name_range = mat + .nodes_for_capture_index(setting_capture_ix) + .next()? + .byte_range(); + let setting_name = contents.get(setting_name_range.clone())?; + let new_setting_name = SETTINGS_STRING_REPLACE.get(&setting_name)?; + Some((setting_name_range, new_setting_name.to_string())) +} + +pub static SETTINGS_STRING_REPLACE: LazyLock> = + LazyLock::new(|| { + HashMap::from_iter([ + ( + "show_inline_completions_in_menu", + "show_edit_predictions_in_menu", + ), + ("show_inline_completions", "show_edit_predictions"), + ( + "inline_completions_disabled_in", + "edit_predictions_disabled_in", + ), + ("inline_completions", "edit_predictions"), + ]) + }); + +pub fn replace_edit_prediction_provider_setting( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let parent_object_capture_ix = query.capture_index_for_name("parent_key")?; + let parent_object_range = mat + .nodes_for_capture_index(parent_object_capture_ix) + .next()? + .byte_range(); + let parent_object_name = contents.get(parent_object_range.clone())?; + + let setting_name_ix = query.capture_index_for_name("setting_name")?; + let setting_range = mat + .nodes_for_capture_index(setting_name_ix) + .next()? + .byte_range(); + let setting_name = contents.get(setting_range.clone())?; + + if parent_object_name == "features" && setting_name == "inline_completion_provider" { + return Some((setting_range, "edit_prediction_provider".into())); + } + + None +} + +fn replace_setting_in_languages( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let setting_capture_ix = query.capture_index_for_name("setting_name")?; + let setting_name_range = mat + .nodes_for_capture_index(setting_capture_ix) + .next()? + .byte_range(); + let setting_name = contents.get(setting_name_range.clone())?; + let new_setting_name = LANGUAGE_SETTINGS_REPLACE.get(&setting_name)?; + + Some((setting_name_range, new_setting_name.to_string())) +} + +static LANGUAGE_SETTINGS_REPLACE: LazyLock> = + LazyLock::new(|| { + HashMap::from_iter([ + ("show_inline_completions", "show_edit_predictions"), + ( + "inline_completions_disabled_in", + "edit_predictions_disabled_in", + ), + ]) + }); diff --git a/crates/migrator/src/migrations/m_2025_01_30/keymap.rs b/crates/migrator/src/migrations/m_2025_01_30/keymap.rs new file mode 100644 index 0000000000..e660b99ab1 --- /dev/null +++ b/crates/migrator/src/migrations/m_2025_01_30/keymap.rs @@ -0,0 +1,82 @@ +use collections::HashMap; +use convert_case::{Case, Casing}; +use std::{ops::Range, sync::LazyLock}; +use tree_sitter::{Query, QueryMatch}; + +use crate::patterns::KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN; +use crate::MigrationPatterns; + +pub const KEYMAP_PATTERNS: MigrationPatterns = &[( + KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN, + action_argument_snake_case, +)]; + +fn to_snake_case(text: &str) -> String { + text.to_case(Case::Snake) +} + +fn action_argument_snake_case( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let array_ix = query.capture_index_for_name("array")?; + let action_name_ix = query.capture_index_for_name("action_name")?; + let argument_key_ix = query.capture_index_for_name("argument_key")?; + let argument_value_ix = query.capture_index_for_name("argument_value")?; + let action_name = contents.get( + mat.nodes_for_capture_index(action_name_ix) + .next()? + .byte_range(), + )?; + + let replacement_key = ACTION_ARGUMENT_SNAKE_CASE_REPLACE.get(action_name)?; + let argument_key = contents.get( + mat.nodes_for_capture_index(argument_key_ix) + .next()? + .byte_range(), + )?; + + if argument_key != *replacement_key { + return None; + } + + let argument_value_node = mat.nodes_for_capture_index(argument_value_ix).next()?; + let argument_value = contents.get(argument_value_node.byte_range())?; + + let new_key = to_snake_case(argument_key); + let new_value = if argument_value_node.kind() == "string" { + format!("\"{}\"", to_snake_case(argument_value.trim_matches('"'))) + } else { + argument_value.to_string() + }; + + let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range(); + let replacement = format!( + "[\"{}\", {{ \"{}\": {} }}]", + action_name, new_key, new_value + ); + + Some((range_to_replace, replacement)) +} + +static ACTION_ARGUMENT_SNAKE_CASE_REPLACE: LazyLock> = LazyLock::new(|| { + HashMap::from_iter([ + ("vim::NextWordStart", "ignorePunctuation"), + ("vim::NextWordEnd", "ignorePunctuation"), + ("vim::PreviousWordStart", "ignorePunctuation"), + ("vim::PreviousWordEnd", "ignorePunctuation"), + ("vim::MoveToNext", "partialWord"), + ("vim::MoveToPrev", "partialWord"), + ("vim::Down", "displayLines"), + ("vim::Up", "displayLines"), + ("vim::EndOfLine", "displayLines"), + ("vim::StartOfLine", "displayLines"), + ("vim::FirstNonWhitespace", "displayLines"), + ("pane::CloseActiveItem", "saveIntent"), + ("vim::Paste", "preserveClipboard"), + ("vim::Word", "ignorePunctuation"), + ("vim::Subword", "ignorePunctuation"), + ("vim::IndentObj", "includeBelow"), + ]) +}); diff --git a/crates/migrator/src/migrations/m_2025_01_30/settings.rs b/crates/migrator/src/migrations/m_2025_01_30/settings.rs new file mode 100644 index 0000000000..bc65764151 --- /dev/null +++ b/crates/migrator/src/migrations/m_2025_01_30/settings.rs @@ -0,0 +1,83 @@ +use std::ops::Range; +use tree_sitter::{Query, QueryMatch}; + +use crate::patterns::SETTINGS_NESTED_KEY_VALUE_PATTERN; +use crate::MigrationPatterns; + +pub const SETTINGS_PATTERNS: MigrationPatterns = &[ + ( + SETTINGS_NESTED_KEY_VALUE_PATTERN, + replace_tab_close_button_setting_key, + ), + ( + SETTINGS_NESTED_KEY_VALUE_PATTERN, + replace_tab_close_button_setting_value, + ), +]; + +fn replace_tab_close_button_setting_key( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let parent_object_capture_ix = query.capture_index_for_name("parent_key")?; + let parent_object_range = mat + .nodes_for_capture_index(parent_object_capture_ix) + .next()? + .byte_range(); + let parent_object_name = contents.get(parent_object_range.clone())?; + + let setting_name_ix = query.capture_index_for_name("setting_name")?; + let setting_range = mat + .nodes_for_capture_index(setting_name_ix) + .next()? + .byte_range(); + let setting_name = contents.get(setting_range.clone())?; + + if parent_object_name == "tabs" && setting_name == "always_show_close_button" { + return Some((setting_range, "show_close_button".into())); + } + + None +} + +fn replace_tab_close_button_setting_value( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let parent_object_capture_ix = query.capture_index_for_name("parent_key")?; + let parent_object_range = mat + .nodes_for_capture_index(parent_object_capture_ix) + .next()? + .byte_range(); + let parent_object_name = contents.get(parent_object_range.clone())?; + + let setting_name_ix = query.capture_index_for_name("setting_name")?; + let setting_name_range = mat + .nodes_for_capture_index(setting_name_ix) + .next()? + .byte_range(); + let setting_name = contents.get(setting_name_range.clone())?; + + let setting_value_ix = query.capture_index_for_name("setting_value")?; + let setting_value_range = mat + .nodes_for_capture_index(setting_value_ix) + .next()? + .byte_range(); + let setting_value = contents.get(setting_value_range.clone())?; + + if parent_object_name == "tabs" && setting_name == "always_show_close_button" { + match setting_value { + "true" => { + return Some((setting_value_range, "\"always\"".to_string())); + } + "false" => { + return Some((setting_value_range, "\"hover\"".to_string())); + } + _ => {} + } + } + + None +} diff --git a/crates/migrator/src/migrations/m_2025_03_03/keymap.rs b/crates/migrator/src/migrations/m_2025_03_03/keymap.rs new file mode 100644 index 0000000000..0ed7c9e88a --- /dev/null +++ b/crates/migrator/src/migrations/m_2025_03_03/keymap.rs @@ -0,0 +1,75 @@ +use collections::HashMap; +use std::{ops::Range, sync::LazyLock}; +use tree_sitter::{Query, QueryMatch}; + +use crate::patterns::KEYMAP_ACTION_STRING_PATTERN; +use crate::MigrationPatterns; + +pub const KEYMAP_PATTERNS: MigrationPatterns = + &[(KEYMAP_ACTION_STRING_PATTERN, replace_string_action)]; + +fn replace_string_action( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let action_name_ix = query.capture_index_for_name("action_name")?; + let action_name_node = mat.nodes_for_capture_index(action_name_ix).next()?; + let action_name_range = action_name_node.byte_range(); + let action_name = contents.get(action_name_range.clone())?; + + if let Some(new_action_name) = STRING_REPLACE.get(&action_name) { + return Some((action_name_range, new_action_name.to_string())); + } + + if let Some((new_action_name, options)) = STRING_TO_ARRAY_REPLACE.get(action_name) { + let full_string_range = action_name_node.parent()?.byte_range(); + let mut options_parts = Vec::new(); + for (key, value) in options.iter() { + options_parts.push(format!("\"{}\": {}", key, value)); + } + let options_str = options_parts.join(", "); + let replacement = format!("[\"{}\", {{ {} }}]", new_action_name, options_str); + return Some((full_string_range, replacement)); + } + + None +} + +static STRING_REPLACE: LazyLock> = LazyLock::new(|| { + HashMap::from_iter([ + ( + "editor::GoToPrevDiagnostic", + "editor::GoToPreviousDiagnostic", + ), + ("editor::ContextMenuPrev", "editor::ContextMenuPrevious"), + ("search::SelectPrevMatch", "search::SelectPreviousMatch"), + ("file_finder::SelectPrev", "file_finder::SelectPrevious"), + ("menu::SelectPrev", "menu::SelectPrevious"), + ("editor::TabPrev", "editor::Backtab"), + ("pane::ActivatePrevItem", "pane::ActivatePreviousItem"), + ("vim::MoveToPrev", "vim::MoveToPrevious"), + ("vim::MoveToPrevMatch", "vim::MoveToPreviousMatch"), + ]) +}); + +/// "editor::GoToPrevHunk" -> ["editor::GoToPreviousHunk", { "center_cursor": true }] +static STRING_TO_ARRAY_REPLACE: LazyLock)>> = + LazyLock::new(|| { + HashMap::from_iter([ + ( + "editor::GoToHunk", + ( + "editor::GoToHunk", + HashMap::from_iter([("center_cursor", true)]), + ), + ), + ( + "editor::GoToPrevHunk", + ( + "editor::GoToPreviousHunk", + HashMap::from_iter([("center_cursor", true)]), + ), + ), + ]) + }); diff --git a/crates/migrator/src/migrations/m_2025_03_06/keymap.rs b/crates/migrator/src/migrations/m_2025_03_06/keymap.rs new file mode 100644 index 0000000000..6975cdafa2 --- /dev/null +++ b/crates/migrator/src/migrations/m_2025_03_06/keymap.rs @@ -0,0 +1,38 @@ +use collections::HashSet; +use std::{ops::Range, sync::LazyLock}; +use tree_sitter::{Query, QueryMatch}; + +use crate::patterns::KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN; +use crate::MigrationPatterns; + +pub const KEYMAP_PATTERNS: MigrationPatterns = &[( + KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN, + replace_array_with_single_string, +)]; + +fn replace_array_with_single_string( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let array_ix = query.capture_index_for_name("array")?; + let action_name_ix = query.capture_index_for_name("action_name")?; + + let action_name = contents.get( + mat.nodes_for_capture_index(action_name_ix) + .next()? + .byte_range(), + )?; + + if TRANSFORM_ARRAY.contains(&action_name) { + let replacement_as_string = format!("\"{action_name}\""); + let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range(); + return Some((range_to_replace, replacement_as_string)); + } + + None +} + +/// ["editor::GoToPreviousHunk", { "center_cursor": true }] -> "editor::GoToPreviousHunk" +static TRANSFORM_ARRAY: LazyLock> = + LazyLock::new(|| HashSet::from_iter(["editor::GoToHunk", "editor::GoToPreviousHunk"])); diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs index bc950c08a9..e1d5ef0199 100644 --- a/crates/migrator/src/migrator.rs +++ b/crates/migrator/src/migrator.rs @@ -1,10 +1,29 @@ +//! ## When to create a migration and why? +//! A migration is necessary when keymap actions or settings are renamed or transformed (e.g., from an array to a string, a string to an array, a boolean to an enum, etc.). +//! +//! This ensures that users with outdated settings are automatically updated to use the corresponding new settings internally. +//! It also provides a quick way to migrate their existing settings to the latest state using button in UI. +//! +//! ## How to create a migration? +//! Migrations use Tree-sitter to query commonly used patterns, such as actions with a string or actions with an array where the second argument is an object, etc. +//! Once queried, *you can filter out the modified items* and write the replacement logic. +//! +//! You *must not* modify previous migrations; always create new ones instead. +//! This is important because if a user is in an intermediate state, they can smoothly transition to the latest state. +//! Modifying existing migrations means they will only work for users upgrading from version x-1 to x, but not from x-2 to x, and so on, where x is the latest version. +//! +//! You only need to write replacement logic for x-1 to x because you can be certain that, internally, every user will be at x-1, regardless of their on disk state. + use anyhow::{Context, Result}; -use collections::HashMap; -use convert_case::{Case, Casing}; use std::{cmp::Reverse, ops::Range, sync::LazyLock}; use streaming_iterator::StreamingIterator; use tree_sitter::{Query, QueryMatch}; +use patterns::SETTINGS_NESTED_KEY_VALUE_PATTERN; + +mod migrations; +mod patterns; + fn migrate(text: &str, patterns: MigrationPatterns, query: &Query) -> Result> { let mut parser = tree_sitter::Parser::new(); parser.set_language(&tree_sitter_json::LANGUAGE.into())?; @@ -46,26 +65,55 @@ fn migrate(text: &str, patterns: MigrationPatterns, query: &Query) -> Result Result> { + let mut current_text = text.to_string(); + let mut result: Option = None; + for (patterns, query) in migrations.iter() { + if let Some(migrated_text) = migrate(¤t_text, patterns, query)? { + current_text = migrated_text.clone(); + result = Some(migrated_text); + } + } + Ok(result) +} + pub fn migrate_keymap(text: &str) -> Result> { - let transformed_text = migrate( - text, - KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS, - &KEYMAP_MIGRATION_TRANSFORMATION_QUERY, - )?; - let replacement_text = migrate( - &transformed_text.as_ref().unwrap_or(&text.to_string()), - KEYMAP_MIGRATION_REPLACEMENT_PATTERNS, - &KEYMAP_MIGRATION_REPLACEMENT_QUERY, - )?; - Ok(replacement_text.or(transformed_text)) + let migrations: &[(MigrationPatterns, &Query)] = &[ + ( + migrations::m_2025_01_29::KEYMAP_PATTERNS, + &KEYMAP_QUERY_2025_01_29, + ), + ( + migrations::m_2025_01_30::KEYMAP_PATTERNS, + &KEYMAP_QUERY_2025_01_30, + ), + ( + migrations::m_2025_03_03::KEYMAP_PATTERNS, + &KEYMAP_QUERY_2025_03_03, + ), + ( + migrations::m_2025_03_06::KEYMAP_PATTERNS, + &KEYMAP_QUERY_2025_03_06, + ), + ]; + run_migrations(text, migrations) } pub fn migrate_settings(text: &str) -> Result> { - migrate( - &text, - SETTINGS_MIGRATION_PATTERNS, - &SETTINGS_MIGRATION_QUERY, - ) + let migrations: &[(MigrationPatterns, &Query)] = &[ + ( + migrations::m_2025_01_29::SETTINGS_PATTERNS, + &SETTINGS_QUERY_2025_01_29, + ), + ( + migrations::m_2025_01_30::SETTINGS_PATTERNS, + &SETTINGS_QUERY_2025_01_30, + ), + ]; + run_migrations(text, migrations) } pub fn migrate_edit_prediction_provider_settings(text: &str) -> Result> { @@ -73,578 +121,61 @@ pub fn migrate_edit_prediction_provider_settings(text: &str) -> Result Option<(Range, String)>, )]; -const KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS: MigrationPatterns = &[ - (ACTION_ARRAY_PATTERN, replace_array_with_single_string), - ( - ACTION_ARGUMENT_OBJECT_PATTERN, - replace_action_argument_object_with_single_value, - ), - (ACTION_STRING_PATTERN, replace_string_action), - (CONTEXT_PREDICATE_PATTERN, rename_context_key), -]; - -static KEYMAP_MIGRATION_TRANSFORMATION_QUERY: LazyLock = LazyLock::new(|| { - Query::new( - &tree_sitter_json::LANGUAGE.into(), - &KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS - .iter() - .map(|pattern| pattern.0) - .collect::(), - ) - .unwrap() -}); - -const ACTION_ARRAY_PATTERN: &str = r#"(document - (array - (object - (pair - key: (string (string_content) @name) - value: ( - (object - (pair - key: (string) - value: ((array - . (string (string_content) @action_name) - . (string (string_content) @argument) - .)) @array - ) - ) - ) +macro_rules! define_query { + ($var_name:ident, $patterns_path:path) => { + static $var_name: LazyLock = LazyLock::new(|| { + Query::new( + &tree_sitter_json::LANGUAGE.into(), + &$patterns_path + .iter() + .map(|pattern| pattern.0) + .collect::(), ) - ) - ) - (#eq? @name "bindings") -)"#; - -fn replace_array_with_single_string( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let array_ix = query.capture_index_for_name("array")?; - let action_name_ix = query.capture_index_for_name("action_name")?; - let argument_ix = query.capture_index_for_name("argument")?; - - let action_name = contents.get( - mat.nodes_for_capture_index(action_name_ix) - .next()? - .byte_range(), - )?; - let argument = contents.get( - mat.nodes_for_capture_index(argument_ix) - .next()? - .byte_range(), - )?; - - let replacement = TRANSFORM_ARRAY.get(&(action_name, argument))?; - let replacement_as_string = format!("\"{replacement}\""); - let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range(); - - Some((range_to_replace, replacement_as_string)) -} - -static TRANSFORM_ARRAY: LazyLock> = LazyLock::new(|| { - HashMap::from_iter([ - // activate - ( - ("workspace::ActivatePaneInDirection", "Up"), - "workspace::ActivatePaneUp", - ), - ( - ("workspace::ActivatePaneInDirection", "Down"), - "workspace::ActivatePaneDown", - ), - ( - ("workspace::ActivatePaneInDirection", "Left"), - "workspace::ActivatePaneLeft", - ), - ( - ("workspace::ActivatePaneInDirection", "Right"), - "workspace::ActivatePaneRight", - ), - // swap - ( - ("workspace::SwapPaneInDirection", "Up"), - "workspace::SwapPaneUp", - ), - ( - ("workspace::SwapPaneInDirection", "Down"), - "workspace::SwapPaneDown", - ), - ( - ("workspace::SwapPaneInDirection", "Left"), - "workspace::SwapPaneLeft", - ), - ( - ("workspace::SwapPaneInDirection", "Right"), - "workspace::SwapPaneRight", - ), - // menu - ( - ("app_menu::NavigateApplicationMenuInDirection", "Left"), - "app_menu::ActivateMenuLeft", - ), - ( - ("app_menu::NavigateApplicationMenuInDirection", "Right"), - "app_menu::ActivateMenuRight", - ), - // vim push - (("vim::PushOperator", "Change"), "vim::PushChange"), - (("vim::PushOperator", "Delete"), "vim::PushDelete"), - (("vim::PushOperator", "Yank"), "vim::PushYank"), - (("vim::PushOperator", "Replace"), "vim::PushReplace"), - ( - ("vim::PushOperator", "DeleteSurrounds"), - "vim::PushDeleteSurrounds", - ), - (("vim::PushOperator", "Mark"), "vim::PushMark"), - (("vim::PushOperator", "Indent"), "vim::PushIndent"), - (("vim::PushOperator", "Outdent"), "vim::PushOutdent"), - (("vim::PushOperator", "AutoIndent"), "vim::PushAutoIndent"), - (("vim::PushOperator", "Rewrap"), "vim::PushRewrap"), - ( - ("vim::PushOperator", "ShellCommand"), - "vim::PushShellCommand", - ), - (("vim::PushOperator", "Lowercase"), "vim::PushLowercase"), - (("vim::PushOperator", "Uppercase"), "vim::PushUppercase"), - ( - ("vim::PushOperator", "OppositeCase"), - "vim::PushOppositeCase", - ), - (("vim::PushOperator", "Register"), "vim::PushRegister"), - ( - ("vim::PushOperator", "RecordRegister"), - "vim::PushRecordRegister", - ), - ( - ("vim::PushOperator", "ReplayRegister"), - "vim::PushReplayRegister", - ), - ( - ("vim::PushOperator", "ReplaceWithRegister"), - "vim::PushReplaceWithRegister", - ), - ( - ("vim::PushOperator", "ToggleComments"), - "vim::PushToggleComments", - ), - // vim switch - (("vim::SwitchMode", "Normal"), "vim::SwitchToNormalMode"), - (("vim::SwitchMode", "Insert"), "vim::SwitchToInsertMode"), - (("vim::SwitchMode", "Replace"), "vim::SwitchToReplaceMode"), - (("vim::SwitchMode", "Visual"), "vim::SwitchToVisualMode"), - ( - ("vim::SwitchMode", "VisualLine"), - "vim::SwitchToVisualLineMode", - ), - ( - ("vim::SwitchMode", "VisualBlock"), - "vim::SwitchToVisualBlockMode", - ), - ( - ("vim::SwitchMode", "HelixNormal"), - "vim::SwitchToHelixNormalMode", - ), - // vim resize - (("vim::ResizePane", "Widen"), "vim::ResizePaneRight"), - (("vim::ResizePane", "Narrow"), "vim::ResizePaneLeft"), - (("vim::ResizePane", "Shorten"), "vim::ResizePaneDown"), - (("vim::ResizePane", "Lengthen"), "vim::ResizePaneUp"), - ]) -}); - -const ACTION_ARGUMENT_OBJECT_PATTERN: &str = r#"(document - (array - (object - (pair - key: (string (string_content) @name) - value: ( - (object - (pair - key: (string) - value: ((array - . (string (string_content) @action_name) - . (object - (pair - key: (string (string_content) @action_key) - value: (_) @argument)) - . ) @array - )) - ) - ) - ) - ) - ) - (#eq? @name "bindings") -)"#; - -/// [ "editor::FoldAtLevel", { "level": 1 } ] -> [ "editor::FoldAtLevel", 1 ] -fn replace_action_argument_object_with_single_value( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let array_ix = query.capture_index_for_name("array")?; - let action_name_ix = query.capture_index_for_name("action_name")?; - let action_key_ix = query.capture_index_for_name("action_key")?; - let argument_ix = query.capture_index_for_name("argument")?; - - let action_name = contents.get( - mat.nodes_for_capture_index(action_name_ix) - .next()? - .byte_range(), - )?; - let action_key = contents.get( - mat.nodes_for_capture_index(action_key_ix) - .next()? - .byte_range(), - )?; - let argument = contents.get( - mat.nodes_for_capture_index(argument_ix) - .next()? - .byte_range(), - )?; - - let new_action_name = UNWRAP_OBJECTS.get(&action_name)?.get(&action_key)?; - - let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range(); - let replacement = format!("[\"{}\", {}]", new_action_name, argument); - Some((range_to_replace, replacement)) -} - -/// "ctrl-k ctrl-1": [ "editor::PushOperator", { "Object": {} } ] -> [ "editor::vim::PushObject", {} ] -static UNWRAP_OBJECTS: LazyLock>> = LazyLock::new(|| { - HashMap::from_iter([ - ( - "editor::FoldAtLevel", - HashMap::from_iter([("level", "editor::FoldAtLevel")]), - ), - ( - "vim::PushOperator", - HashMap::from_iter([ - ("Object", "vim::PushObject"), - ("FindForward", "vim::PushFindForward"), - ("FindBackward", "vim::PushFindBackward"), - ("Sneak", "vim::PushSneak"), - ("SneakBackward", "vim::PushSneakBackward"), - ("AddSurrounds", "vim::PushAddSurrounds"), - ("ChangeSurrounds", "vim::PushChangeSurrounds"), - ("Jump", "vim::PushJump"), - ("Digraph", "vim::PushDigraph"), - ("Literal", "vim::PushLiteral"), - ]), - ), - ]) -}); - -const KEYMAP_MIGRATION_REPLACEMENT_PATTERNS: MigrationPatterns = &[( - ACTION_ARGUMENT_SNAKE_CASE_PATTERN, - action_argument_snake_case, -)]; - -static KEYMAP_MIGRATION_REPLACEMENT_QUERY: LazyLock = LazyLock::new(|| { - Query::new( - &tree_sitter_json::LANGUAGE.into(), - &KEYMAP_MIGRATION_REPLACEMENT_PATTERNS - .iter() - .map(|pattern| pattern.0) - .collect::(), - ) - .unwrap() -}); - -const ACTION_STRING_PATTERN: &str = r#"(document - (array - (object - (pair - key: (string (string_content) @name) - value: ( - (object - (pair - key: (string) - value: (string (string_content) @action_name) - ) - ) - ) - ) - ) - ) - (#eq? @name "bindings") -)"#; - -fn replace_string_action( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let action_name_ix = query.capture_index_for_name("action_name")?; - let action_name_node = mat.nodes_for_capture_index(action_name_ix).next()?; - let action_name_range = action_name_node.byte_range(); - let action_name = contents.get(action_name_range.clone())?; - - if let Some(new_action_name) = STRING_REPLACE.get(&action_name) { - return Some((action_name_range, new_action_name.to_string())); - } - - if let Some((new_action_name, options)) = STRING_TO_ARRAY_REPLACE.get(action_name) { - let full_string_range = action_name_node.parent()?.byte_range(); - let mut options_parts = Vec::new(); - for (key, value) in options.iter() { - options_parts.push(format!("\"{}\": {}", key, value)); - } - let options_str = options_parts.join(", "); - let replacement = format!("[\"{}\", {{ {} }}]", new_action_name, options_str); - return Some((full_string_range, replacement)); - } - - None -} - -/// "ctrl-k ctrl-1": "inline_completion::ToggleMenu" -> "edit_prediction::ToggleMenu" -static STRING_REPLACE: LazyLock> = LazyLock::new(|| { - HashMap::from_iter([ - ( - "inline_completion::ToggleMenu", - "edit_prediction::ToggleMenu", - ), - ("editor::NextInlineCompletion", "editor::NextEditPrediction"), - ( - "editor::PreviousInlineCompletion", - "editor::PreviousEditPrediction", - ), - ( - "editor::AcceptPartialInlineCompletion", - "editor::AcceptPartialEditPrediction", - ), - ("editor::ShowInlineCompletion", "editor::ShowEditPrediction"), - ( - "editor::AcceptInlineCompletion", - "editor::AcceptEditPrediction", - ), - ( - "editor::ToggleInlineCompletions", - "editor::ToggleEditPrediction", - ), - ( - "editor::GoToPrevDiagnostic", - "editor::GoToPreviousDiagnostic", - ), - ("editor::ContextMenuPrev", "editor::ContextMenuPrevious"), - ("search::SelectPrevMatch", "search::SelectPreviousMatch"), - ("file_finder::SelectPrev", "file_finder::SelectPrevious"), - ("menu::SelectPrev", "menu::SelectPrevious"), - ("editor::TabPrev", "editor::Backtab"), - ("pane::ActivatePrevItem", "pane::ActivatePreviousItem"), - ("vim::MoveToPrev", "vim::MoveToPrevious"), - ("vim::MoveToPrevMatch", "vim::MoveToPreviousMatch"), - ]) -}); - -/// "editor::GoToPrevHunk" -> ["editor::GoToPreviousHunk", { "center_cursor": true }] -static STRING_TO_ARRAY_REPLACE: LazyLock)>> = - LazyLock::new(|| { - HashMap::from_iter([ - ( - "editor::GoToHunk", - ( - "editor::GoToHunk", - HashMap::from_iter([("center_cursor", true)]), - ), - ), - ( - "editor::GoToPrevHunk", - ( - "editor::GoToPreviousHunk", - HashMap::from_iter([("center_cursor", true)]), - ), - ), - ]) - }); - -const CONTEXT_PREDICATE_PATTERN: &str = r#"(document - (array - (object - (pair - key: (string (string_content) @name) - value: (string (string_content) @context_predicate) - ) - ) - ) - (#eq? @name "context") -)"#; - -fn rename_context_key( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let context_predicate_ix = query.capture_index_for_name("context_predicate")?; - let context_predicate_range = mat - .nodes_for_capture_index(context_predicate_ix) - .next()? - .byte_range(); - let old_predicate = contents.get(context_predicate_range.clone())?.to_string(); - let mut new_predicate = old_predicate.to_string(); - for (old_key, new_key) in CONTEXT_REPLACE.iter() { - new_predicate = new_predicate.replace(old_key, new_key); - } - if new_predicate != old_predicate { - Some((context_predicate_range, new_predicate.to_string())) - } else { - None - } -} - -/// "context": "Editor && inline_completion && !showing_completions" -> "Editor && edit_prediction && !showing_completions" -pub static CONTEXT_REPLACE: LazyLock> = LazyLock::new(|| { - HashMap::from_iter([ - ("inline_completion", "edit_prediction"), - ( - "inline_completion_requires_modifier", - "edit_prediction_requires_modifier", - ), - ]) -}); - -const ACTION_ARGUMENT_SNAKE_CASE_PATTERN: &str = r#"(document - (array - (object - (pair - key: (string (string_content) @name) - value: ( - (object - (pair - key: (string) - value: ((array - . (string (string_content) @action_name) - . (object - (pair - key: (string (string_content) @argument_key) - value: (_) @argument_value)) - . ) @array - )) - ) - ) - ) - ) - ) - (#eq? @name "bindings") -)"#; - -fn to_snake_case(text: &str) -> String { - text.to_case(Case::Snake) -} - -fn action_argument_snake_case( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let array_ix = query.capture_index_for_name("array")?; - let action_name_ix = query.capture_index_for_name("action_name")?; - let argument_key_ix = query.capture_index_for_name("argument_key")?; - let argument_value_ix = query.capture_index_for_name("argument_value")?; - let action_name = contents.get( - mat.nodes_for_capture_index(action_name_ix) - .next()? - .byte_range(), - )?; - - let replacement_key = ACTION_ARGUMENT_SNAKE_CASE_REPLACE.get(action_name)?; - let argument_key = contents.get( - mat.nodes_for_capture_index(argument_key_ix) - .next()? - .byte_range(), - )?; - - if argument_key != *replacement_key { - return None; - } - - let argument_value_node = mat.nodes_for_capture_index(argument_value_ix).next()?; - let argument_value = contents.get(argument_value_node.byte_range())?; - - let new_key = to_snake_case(argument_key); - let new_value = if argument_value_node.kind() == "string" { - format!("\"{}\"", to_snake_case(argument_value.trim_matches('"'))) - } else { - argument_value.to_string() + .unwrap() + }); }; - - let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range(); - let replacement = format!( - "[\"{}\", {{ \"{}\": {} }}]", - action_name, new_key, new_value - ); - - Some((range_to_replace, replacement)) } -pub static ACTION_ARGUMENT_SNAKE_CASE_REPLACE: LazyLock> = - LazyLock::new(|| { - HashMap::from_iter([ - ("vim::NextWordStart", "ignorePunctuation"), - ("vim::NextWordEnd", "ignorePunctuation"), - ("vim::PreviousWordStart", "ignorePunctuation"), - ("vim::PreviousWordEnd", "ignorePunctuation"), - ("vim::MoveToNext", "partialWord"), - ("vim::MoveToPrev", "partialWord"), - ("vim::Down", "displayLines"), - ("vim::Up", "displayLines"), - ("vim::EndOfLine", "displayLines"), - ("vim::StartOfLine", "displayLines"), - ("vim::FirstNonWhitespace", "displayLines"), - ("pane::CloseActiveItem", "saveIntent"), - ("vim::Paste", "preserveClipboard"), - ("vim::Word", "ignorePunctuation"), - ("vim::Subword", "ignorePunctuation"), - ("vim::IndentObj", "includeBelow"), - ]) - }); +// keymap +define_query!( + KEYMAP_QUERY_2025_01_29, + migrations::m_2025_01_29::KEYMAP_PATTERNS +); +define_query!( + KEYMAP_QUERY_2025_01_30, + migrations::m_2025_01_30::KEYMAP_PATTERNS +); +define_query!( + KEYMAP_QUERY_2025_03_03, + migrations::m_2025_03_03::KEYMAP_PATTERNS +); +define_query!( + KEYMAP_QUERY_2025_03_06, + migrations::m_2025_03_06::KEYMAP_PATTERNS +); -const SETTINGS_MIGRATION_PATTERNS: MigrationPatterns = &[ - (SETTINGS_STRING_REPLACE_QUERY, replace_setting_name), - ( - SETTINGS_NESTED_KEY_VALUE_PATTERN, - replace_edit_prediction_provider_setting, - ), - ( - SETTINGS_NESTED_KEY_VALUE_PATTERN, - replace_tab_close_button_setting_key, - ), - ( - SETTINGS_NESTED_KEY_VALUE_PATTERN, - replace_tab_close_button_setting_value, - ), - ( - SETTINGS_REPLACE_IN_LANGUAGES_QUERY, - replace_setting_in_languages, - ), -]; - -static SETTINGS_MIGRATION_QUERY: LazyLock = LazyLock::new(|| { - Query::new( - &tree_sitter_json::LANGUAGE.into(), - &SETTINGS_MIGRATION_PATTERNS - .iter() - .map(|pattern| pattern.0) - .collect::(), - ) - .unwrap() -}); +// settings +define_query!( + SETTINGS_QUERY_2025_01_29, + migrations::m_2025_01_29::SETTINGS_PATTERNS +); +define_query!( + SETTINGS_QUERY_2025_01_30, + migrations::m_2025_01_30::SETTINGS_PATTERNS +); +// custom query static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock = LazyLock::new(|| { Query::new( &tree_sitter_json::LANGUAGE.into(), @@ -653,199 +184,6 @@ static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock = LazyLock::new .unwrap() }); -const SETTINGS_STRING_REPLACE_QUERY: &str = r#"(document - (object - (pair - key: (string (string_content) @name) - value: (_) - ) - ) -)"#; - -fn replace_setting_name( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let setting_capture_ix = query.capture_index_for_name("name")?; - let setting_name_range = mat - .nodes_for_capture_index(setting_capture_ix) - .next()? - .byte_range(); - let setting_name = contents.get(setting_name_range.clone())?; - let new_setting_name = SETTINGS_STRING_REPLACE.get(&setting_name)?; - Some((setting_name_range, new_setting_name.to_string())) -} - -pub static SETTINGS_STRING_REPLACE: LazyLock> = - LazyLock::new(|| { - HashMap::from_iter([ - ( - "show_inline_completions_in_menu", - "show_edit_predictions_in_menu", - ), - ("show_inline_completions", "show_edit_predictions"), - ( - "inline_completions_disabled_in", - "edit_predictions_disabled_in", - ), - ("inline_completions", "edit_predictions"), - ]) - }); - -const SETTINGS_NESTED_KEY_VALUE_PATTERN: &str = r#" -(object - (pair - key: (string (string_content) @parent_key) - value: (object - (pair - key: (string (string_content) @setting_name) - value: (_) @setting_value - ) - ) - ) -) -"#; - -fn replace_edit_prediction_provider_setting( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let parent_object_capture_ix = query.capture_index_for_name("parent_key")?; - let parent_object_range = mat - .nodes_for_capture_index(parent_object_capture_ix) - .next()? - .byte_range(); - let parent_object_name = contents.get(parent_object_range.clone())?; - - let setting_name_ix = query.capture_index_for_name("setting_name")?; - let setting_range = mat - .nodes_for_capture_index(setting_name_ix) - .next()? - .byte_range(); - let setting_name = contents.get(setting_range.clone())?; - - if parent_object_name == "features" && setting_name == "inline_completion_provider" { - return Some((setting_range, "edit_prediction_provider".into())); - } - - None -} - -fn replace_tab_close_button_setting_key( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let parent_object_capture_ix = query.capture_index_for_name("parent_key")?; - let parent_object_range = mat - .nodes_for_capture_index(parent_object_capture_ix) - .next()? - .byte_range(); - let parent_object_name = contents.get(parent_object_range.clone())?; - - let setting_name_ix = query.capture_index_for_name("setting_name")?; - let setting_range = mat - .nodes_for_capture_index(setting_name_ix) - .next()? - .byte_range(); - let setting_name = contents.get(setting_range.clone())?; - - if parent_object_name == "tabs" && setting_name == "always_show_close_button" { - return Some((setting_range, "show_close_button".into())); - } - - None -} - -fn replace_tab_close_button_setting_value( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let parent_object_capture_ix = query.capture_index_for_name("parent_key")?; - let parent_object_range = mat - .nodes_for_capture_index(parent_object_capture_ix) - .next()? - .byte_range(); - let parent_object_name = contents.get(parent_object_range.clone())?; - - let setting_name_ix = query.capture_index_for_name("setting_name")?; - let setting_name_range = mat - .nodes_for_capture_index(setting_name_ix) - .next()? - .byte_range(); - let setting_name = contents.get(setting_name_range.clone())?; - - let setting_value_ix = query.capture_index_for_name("setting_value")?; - let setting_value_range = mat - .nodes_for_capture_index(setting_value_ix) - .next()? - .byte_range(); - let setting_value = contents.get(setting_value_range.clone())?; - - if parent_object_name == "tabs" && setting_name == "always_show_close_button" { - match setting_value { - "true" => { - return Some((setting_value_range, "\"always\"".to_string())); - } - "false" => { - return Some((setting_value_range, "\"hover\"".to_string())); - } - _ => {} - } - } - - None -} - -const SETTINGS_REPLACE_IN_LANGUAGES_QUERY: &str = r#" -(object - (pair - key: (string (string_content) @languages) - value: (object - (pair - key: (string) - value: (object - (pair - key: (string (string_content) @setting_name) - value: (_) @value - ) - ) - )) - ) -) -(#eq? @languages "languages") -"#; - -fn replace_setting_in_languages( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let setting_capture_ix = query.capture_index_for_name("setting_name")?; - let setting_name_range = mat - .nodes_for_capture_index(setting_capture_ix) - .next()? - .byte_range(); - let setting_name = contents.get(setting_name_range.clone())?; - let new_setting_name = LANGUAGE_SETTINGS_REPLACE.get(&setting_name)?; - - Some((setting_name_range, new_setting_name.to_string())) -} - -static LANGUAGE_SETTINGS_REPLACE: LazyLock> = - LazyLock::new(|| { - HashMap::from_iter([ - ("show_inline_completions", "show_edit_predictions"), - ( - "inline_completions_disabled_in", - "edit_predictions_disabled_in", - ), - ]) - }); - #[cfg(test)] mod tests { use super::*; @@ -987,14 +325,17 @@ mod tests { } #[test] - fn test_string_to_array_replace() { + fn test_incremental_migrations() { + // Here string transforms to array internally. Then, that array transforms back to string. assert_migrate_keymap( r#" [ { "bindings": { - "ctrl-q": "editor::GoToHunk", - "ctrl-w": "editor::GoToPrevHunk" + "ctrl-q": "editor::GoToHunk", // should remain same + "ctrl-w": "editor::GoToPrevHunk", // should rename + "ctrl-q": ["editor::GoToHunk", { "center_cursor": true }], // should transform + "ctrl-w": ["editor::GoToPreviousHunk", { "center_cursor": true }] // should transform } } ] @@ -1004,8 +345,10 @@ mod tests { [ { "bindings": { - "ctrl-q": ["editor::GoToHunk", { "center_cursor": true }], - "ctrl-w": ["editor::GoToPreviousHunk", { "center_cursor": true }] + "ctrl-q": "editor::GoToHunk", // should remain same + "ctrl-w": "editor::GoToPreviousHunk", // should rename + "ctrl-q": "editor::GoToHunk", // should transform + "ctrl-w": "editor::GoToPreviousHunk" // should transform } } ] diff --git a/crates/migrator/src/patterns.rs b/crates/migrator/src/patterns.rs new file mode 100644 index 0000000000..b793ac51b9 --- /dev/null +++ b/crates/migrator/src/patterns.rs @@ -0,0 +1,11 @@ +mod keymap; +mod settings; + +pub(crate) use keymap::{ + KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN, KEYMAP_ACTION_ARRAY_PATTERN, + KEYMAP_ACTION_STRING_PATTERN, KEYMAP_CONTEXT_PATTERN, +}; + +pub(crate) use settings::{ + SETTINGS_LANGUAGES_PATTERN, SETTINGS_NESTED_KEY_VALUE_PATTERN, SETTINGS_ROOT_KEY_VALUE_PATTERN, +}; diff --git a/crates/migrator/src/patterns/keymap.rs b/crates/migrator/src/patterns/keymap.rs new file mode 100644 index 0000000000..439c10fd4a --- /dev/null +++ b/crates/migrator/src/patterns/keymap.rs @@ -0,0 +1,77 @@ +pub const KEYMAP_ACTION_ARRAY_PATTERN: &str = r#"(document + (array + (object + (pair + key: (string (string_content) @name) + value: ( + (object + (pair + key: (string) + value: ((array + . (string (string_content) @action_name) + . (string (string_content) @argument) + .)) @array + ) + ) + ) + ) + ) + ) + (#eq? @name "bindings") +)"#; + +pub const KEYMAP_ACTION_STRING_PATTERN: &str = r#"(document + (array + (object + (pair + key: (string (string_content) @name) + value: ( + (object + (pair + key: (string) + value: (string (string_content) @action_name) + ) + ) + ) + ) + ) + ) + (#eq? @name "bindings") +)"#; + +pub const KEYMAP_CONTEXT_PATTERN: &str = r#"(document + (array + (object + (pair + key: (string (string_content) @name) + value: (string (string_content) @context_predicate) + ) + ) + ) + (#eq? @name "context") +)"#; + +pub const KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN: &str = r#"(document + (array + (object + (pair + key: (string (string_content) @name) + value: ( + (object + (pair + key: (string) + value: ((array + . (string (string_content) @action_name) + . (object + (pair + key: (string (string_content) @argument_key) + value: (_) @argument_value)) + . ) @array + )) + ) + ) + ) + ) + ) + (#eq? @name "bindings") +)"#; diff --git a/crates/migrator/src/patterns/settings.rs b/crates/migrator/src/patterns/settings.rs new file mode 100644 index 0000000000..107b781699 --- /dev/null +++ b/crates/migrator/src/patterns/settings.rs @@ -0,0 +1,41 @@ +pub const SETTINGS_ROOT_KEY_VALUE_PATTERN: &str = r#"(document + (object + (pair + key: (string (string_content) @name) + value: (_) + ) + ) +)"#; + +pub const SETTINGS_NESTED_KEY_VALUE_PATTERN: &str = r#"(document + (object + (pair + key: (string (string_content) @parent_key) + value: (object + (pair + key: (string (string_content) @setting_name) + value: (_) @setting_value + ) + ) + ) + ) +)"#; + +pub const SETTINGS_LANGUAGES_PATTERN: &str = r#"(document + (object + (pair + key: (string (string_content) @languages) + value: (object + (pair + key: (string) + value: (object + (pair + key: (string (string_content) @setting_name) + value: (_) @value + ) + ) + )) + ) + ) + (#eq? @languages "languages") +)"#;