migrator: Add versioned migrations (#26215)
There is a drawback to how we currently write our migrations: For example: 1. Suppose we change one of our actions from a string to an array and rename it, then roll out the preview build: Before: `"ctrl-x": "editor::GoToPrevHunk"` Latest: `"ctrl-x": ["editor::GoToPreviousHunk", { "center_cursor": true }]` To handle this, we wrote migration `A` to convert the string to an array. 2. Now, suppose we decide to change it back to a string: - User who hasn't migrated yet on Preview: `"ctrl-x": "editor::GoToPrevHunk"` - User who has migrated on Preview: `"ctrl-x": ["editor::GoToPreviousHunk", { "center_cursor": true }]` - Latest: `"ctrl-x": "editor::GoToPreviousHunk"` To handle this, we would need to remove migration `A` and add two more migrations: - **Migration B**: `"ctrl-x": "editor::GoToPrevHunk"` -> `"ctrl-x": "editor::GoToPreviousHunk"` - **Migration C**: `"ctrl-x": ["editor::GoToPreviousHunk", { "center_cursor": true }]` -> `"ctrl-x": "editor::GoToPreviousHunk"` Nice. But over time, this keeps increasing, making it impossible to track outdated versions and handle all cases. Missing a case means users stuck on `"ctrl-x": "editor::GoToPrevHunk"` will remain there and won't be automatically migrated to the latest state. --- To fix this, we introduce versioned migrations. Instead of removing migration `A`, we simply write a new migration that takes the user to the latest version—i.e., in this case, migration `C`. - A user who hasn't migrated before will go through both migrations `A` and `C` in order. - A user who has already migrated will only go through `C`, since `A` wouldn't change anything for them. With incremental migrations, we only need to write migrations on top of the latest state (big win!), as know internally they all would be on latest state. You *must not* modify previous migrations. Always create new ones instead. This also serves as base for only prompting user to migrate, when feature reaches stable. That way, preview and stable keymap and settings are in sync. cc: @mgsloan @ConradIrwin @probably-neb Release Notes: - N/A
This commit is contained in:
parent
6fd9708eee
commit
219d36f589
11 changed files with 948 additions and 773 deletions
297
crates/migrator/src/migrations/m_2025_01_29/keymap.rs
Normal file
297
crates/migrator/src/migrations/m_2025_01_29/keymap.rs
Normal file
|
@ -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<usize>, 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<HashMap<(&str, &str), &str>> = 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<usize>, 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<HashMap<&str, HashMap<&str, &str>>> = 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<usize>, 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<HashMap<&str, &str>> = 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<usize>, 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<HashMap<&str, &str>> = LazyLock::new(|| {
|
||||
HashMap::from_iter([
|
||||
("inline_completion", "edit_prediction"),
|
||||
(
|
||||
"inline_completion_requires_modifier",
|
||||
"edit_prediction_requires_modifier",
|
||||
),
|
||||
])
|
||||
});
|
101
crates/migrator/src/migrations/m_2025_01_29/settings.rs
Normal file
101
crates/migrator/src/migrations/m_2025_01_29/settings.rs
Normal file
|
@ -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<usize>, 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<HashMap<&'static str, &'static str>> =
|
||||
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<usize>, 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<usize>, 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<HashMap<&'static str, &'static str>> =
|
||||
LazyLock::new(|| {
|
||||
HashMap::from_iter([
|
||||
("show_inline_completions", "show_edit_predictions"),
|
||||
(
|
||||
"inline_completions_disabled_in",
|
||||
"edit_predictions_disabled_in",
|
||||
),
|
||||
])
|
||||
});
|
82
crates/migrator/src/migrations/m_2025_01_30/keymap.rs
Normal file
82
crates/migrator/src/migrations/m_2025_01_30/keymap.rs
Normal file
|
@ -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<usize>, 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<HashMap<&str, &str>> = 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"),
|
||||
])
|
||||
});
|
83
crates/migrator/src/migrations/m_2025_01_30/settings.rs
Normal file
83
crates/migrator/src/migrations/m_2025_01_30/settings.rs
Normal file
|
@ -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<usize>, 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<usize>, 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
|
||||
}
|
75
crates/migrator/src/migrations/m_2025_03_03/keymap.rs
Normal file
75
crates/migrator/src/migrations/m_2025_03_03/keymap.rs
Normal file
|
@ -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<usize>, 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<HashMap<&str, &str>> = 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<HashMap<&str, (&str, HashMap<&str, bool>)>> =
|
||||
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)]),
|
||||
),
|
||||
),
|
||||
])
|
||||
});
|
38
crates/migrator/src/migrations/m_2025_03_06/keymap.rs
Normal file
38
crates/migrator/src/migrations/m_2025_03_06/keymap.rs
Normal file
|
@ -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<usize>, 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<HashSet<&str>> =
|
||||
LazyLock::new(|| HashSet::from_iter(["editor::GoToHunk", "editor::GoToPreviousHunk"]));
|
Loading…
Add table
Add a link
Reference in a new issue