logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree with a 3-file patch applied (no upstream history). Patch (crates/gpui_linux/src/linux/wayland/): - serial.rs: add SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; add latest_serial_of() - window.rs: activate() uses keyboard-enter serial (Mutter focus gate) Mutter honors window activation only when the token carries the keyboard- focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial. See docs/tray-window-focus-wayland.md in logiguard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
366
crates/migrator/src/migrations.rs
Normal file
366
crates/migrator/src/migrations.rs
Normal file
@@ -0,0 +1,366 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
use settings_content::{PlatformOverrides, ReleaseChannelOverrides};
|
||||
|
||||
/// Applies a migration callback to the root settings object as well as all
|
||||
/// nested platform, release-channel, and profile override objects.
|
||||
pub(crate) fn migrate_settings(
|
||||
value: &mut Value,
|
||||
migrate_one: &mut dyn FnMut(&mut serde_json::Map<String, Value>) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
let Some(root_object) = value.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
migrate_one(root_object)?;
|
||||
|
||||
let override_keys = ReleaseChannelOverrides::OVERRIDE_KEYS
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(PlatformOverrides::OVERRIDE_KEYS.iter().copied());
|
||||
|
||||
for key in override_keys {
|
||||
if let Some(sub_object) = root_object.get_mut(key) {
|
||||
if let Some(sub_map) = sub_object.as_object_mut() {
|
||||
migrate_one(sub_map)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(profiles) = root_object.get_mut("profiles") {
|
||||
if let Some(profiles_object) = profiles.as_object_mut() {
|
||||
for profile_value in profiles_object.values_mut() {
|
||||
if let Some(profile_map) = profile_value.as_object_mut() {
|
||||
if let Some(inner) = profile_map
|
||||
.get_mut("settings")
|
||||
.and_then(|v| v.as_object_mut())
|
||||
{
|
||||
migrate_one(inner)?;
|
||||
} else {
|
||||
migrate_one(profile_map)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Applies a migration callback to a value and its `languages` children,
|
||||
/// at the root level as well as all nested platform, release-channel, and
|
||||
/// profile override objects.
|
||||
pub(crate) fn migrate_language_setting(
|
||||
value: &mut Value,
|
||||
migrate_fn: fn(&mut Value, path: &[&str]) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
fn apply_to_value_and_languages(
|
||||
value: &mut Value,
|
||||
prefix: &[&str],
|
||||
migrate_fn: fn(&mut Value, path: &[&str]) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
migrate_fn(value, prefix)?;
|
||||
let languages = value
|
||||
.as_object_mut()
|
||||
.and_then(|obj| obj.get_mut("languages"))
|
||||
.and_then(|languages| languages.as_object_mut());
|
||||
if let Some(languages) = languages {
|
||||
for (language_name, language) in languages.iter_mut() {
|
||||
let mut path: Vec<&str> = prefix.to_vec();
|
||||
path.push("languages");
|
||||
path.push(language_name);
|
||||
migrate_fn(language, &path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if !value.is_object() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
apply_to_value_and_languages(value, &[], migrate_fn)?;
|
||||
|
||||
let Some(root_object) = value.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let override_keys = ReleaseChannelOverrides::OVERRIDE_KEYS
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(PlatformOverrides::OVERRIDE_KEYS.iter().copied());
|
||||
|
||||
for key in override_keys {
|
||||
if let Some(sub_value) = root_object.get_mut(key) {
|
||||
apply_to_value_and_languages(sub_value, &[key], migrate_fn)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(profiles) = root_object.get_mut("profiles") {
|
||||
if let Some(profiles_object) = profiles.as_object_mut() {
|
||||
let profile_names: Vec<String> = profiles_object.keys().cloned().collect();
|
||||
for profile_name in &profile_names {
|
||||
if let Some(profile_value) = profiles_object.get_mut(profile_name.as_str()) {
|
||||
if let Some(settings_value) = profile_value
|
||||
.as_object_mut()
|
||||
.and_then(|m| m.get_mut("settings"))
|
||||
{
|
||||
apply_to_value_and_languages(
|
||||
settings_value,
|
||||
&["profiles", profile_name],
|
||||
migrate_fn,
|
||||
)?;
|
||||
} else {
|
||||
apply_to_value_and_languages(
|
||||
profile_value,
|
||||
&["profiles", profile_name],
|
||||
migrate_fn,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_01_02 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_01_29 {
|
||||
mod keymap;
|
||||
mod settings;
|
||||
|
||||
pub(crate) use keymap::KEYMAP_PATTERNS;
|
||||
pub(crate) use settings::{SETTINGS_PATTERNS, replace_edit_prediction_provider_setting};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_03_29 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_04_15 {
|
||||
mod keymap;
|
||||
mod settings;
|
||||
|
||||
pub(crate) use keymap::KEYMAP_PATTERNS;
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_04_21 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_04_23 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_05_05 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_05_08 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_06_16 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_06_25 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_06_27 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_07_08 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_10_01 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::flatten_code_actions_formatters;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_10_02 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::remove_formatters_on_save;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_10_03 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_10_16 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::restore_code_actions_on_format;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_10_17 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::make_file_finder_include_ignored_an_enum;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_10_21 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::make_relative_line_numbers_an_enum;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_11_12 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_11_20 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_11_25 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::remove_context_server_source;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_12_01 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_12_08 {
|
||||
mod keymap;
|
||||
|
||||
pub(crate) use keymap::KEYMAP_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_12_15 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_01_27 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::make_auto_indent_an_enum;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2026_02_02 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::move_edit_prediction_provider_to_edit_predictions;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2026_02_03 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::migrate_experimental_sweep_mercury;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2026_02_04 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::migrate_tool_permission_defaults;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2026_02_25 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::migrate_builtin_agent_servers_to_registry;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2026_03_16 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2026_03_23 {
|
||||
mod keymap;
|
||||
|
||||
pub(crate) use keymap::KEYMAP_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2026_03_30 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::make_play_sound_when_agent_done_an_enum;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2026_04_01 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::restructure_profiles_with_settings_key;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2026_04_10 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::rename_web_search_to_search_web;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2026_04_17 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::promote_show_branch_icon_true_to_show_branch_status_icon;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2026_05_04 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
62
crates/migrator/src/migrations/m_2025_01_02/settings.rs
Normal file
62
crates/migrator/src/migrations/m_2025_01_02/settings.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use collections::HashMap;
|
||||
use std::{ops::Range, sync::LazyLock};
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_NESTED_KEY_VALUE_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
|
||||
SETTINGS_NESTED_KEY_VALUE_PATTERN,
|
||||
replace_deprecated_settings_values,
|
||||
)];
|
||||
|
||||
fn replace_deprecated_settings_values(
|
||||
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)?;
|
||||
|
||||
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)?;
|
||||
|
||||
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())?;
|
||||
|
||||
UPDATED_SETTINGS
|
||||
.get(&(parent_object_name, setting_name))
|
||||
.and_then(|new_values| {
|
||||
new_values
|
||||
.iter()
|
||||
.find_map(|(old_value, new_value)| {
|
||||
(*old_value == setting_value).then(|| new_value.to_string())
|
||||
})
|
||||
.map(|new_value| (setting_value_range, new_value))
|
||||
})
|
||||
}
|
||||
|
||||
static UPDATED_SETTINGS: LazyLock<HashMap<(&str, &str), Vec<(&str, &str)>>> = LazyLock::new(|| {
|
||||
HashMap::from_iter([
|
||||
(
|
||||
("chat_panel", "button"),
|
||||
vec![("true", "\"always\""), ("false", "\"never\"")],
|
||||
),
|
||||
(
|
||||
("scrollbar", "diagnostics"),
|
||||
vec![("true", "\"all\""), ("false", "\"none\"")],
|
||||
),
|
||||
])
|
||||
});
|
||||
27
crates/migrator/src/migrations/m_2025_01_27/settings.rs
Normal file
27
crates/migrator/src/migrations/m_2025_01_27/settings.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_language_setting;
|
||||
|
||||
pub fn make_auto_indent_an_enum(value: &mut Value) -> Result<()> {
|
||||
migrate_language_setting(value, migrate_auto_indent)
|
||||
}
|
||||
|
||||
fn migrate_auto_indent(value: &mut Value, _path: &[&str]) -> Result<()> {
|
||||
let Some(auto_indent) = value
|
||||
.as_object_mut()
|
||||
.and_then(|obj| obj.get_mut("auto_indent"))
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
*auto_indent = match auto_indent {
|
||||
Value::Bool(true) => Value::String("syntax_aware".to_string()),
|
||||
Value::Bool(false) => Value::String("none".to_string()),
|
||||
Value::String(s) if s == "syntax_aware" || s == "preserve_indent" || s == "none" => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => anyhow::bail!("Expected auto_indent to be a boolean or valid enum value"),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
307
crates/migrator/src/migrations/m_2025_01_29/keymap.rs
Normal file
307
crates/migrator/src/migrations/m_2025_01_29/keymap.rs
Normal file
@@ -0,0 +1,307 @@
|
||||
use collections::HashMap;
|
||||
use std::{ops::Range, sync::LazyLock};
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::{
|
||||
KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN, KEYMAP_ACTION_ARRAY_PATTERN,
|
||||
KEYMAP_ACTION_STRING_PATTERN, KEYMAP_CONTEXT_PATTERN,
|
||||
};
|
||||
|
||||
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"),
|
||||
// fold at level
|
||||
(("editor::FoldAtLevel", "1"), "editor::FoldAtLevel1"),
|
||||
(("editor::FoldAtLevel", "2"), "editor::FoldAtLevel2"),
|
||||
(("editor::FoldAtLevel", "3"), "editor::FoldAtLevel3"),
|
||||
(("editor::FoldAtLevel", "4"), "editor::FoldAtLevel4"),
|
||||
(("editor::FoldAtLevel", "5"), "editor::FoldAtLevel5"),
|
||||
(("editor::FoldAtLevel", "6"), "editor::FoldAtLevel6"),
|
||||
(("editor::FoldAtLevel", "7"), "editor::FoldAtLevel7"),
|
||||
(("editor::FoldAtLevel", "8"), "editor::FoldAtLevel8"),
|
||||
(("editor::FoldAtLevel", "9"), "editor::FoldAtLevel9"),
|
||||
])
|
||||
});
|
||||
|
||||
/// [ "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))
|
||||
} 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::MigrationPatterns;
|
||||
use crate::patterns::{
|
||||
SETTINGS_LANGUAGES_PATTERN, SETTINGS_NESTED_KEY_VALUE_PATTERN, SETTINGS_ROOT_KEY_VALUE_PATTERN,
|
||||
};
|
||||
|
||||
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)?;
|
||||
|
||||
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::MigrationPatterns;
|
||||
use crate::patterns::KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN;
|
||||
|
||||
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::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_NESTED_KEY_VALUE_PATTERN;
|
||||
|
||||
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)?;
|
||||
|
||||
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)?;
|
||||
|
||||
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)?;
|
||||
|
||||
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::MigrationPatterns;
|
||||
use crate::patterns::KEYMAP_ACTION_STRING_PATTERN;
|
||||
|
||||
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::MigrationPatterns;
|
||||
use crate::patterns::KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN;
|
||||
|
||||
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"]));
|
||||
65
crates/migrator/src/migrations/m_2025_03_29/settings.rs
Normal file
65
crates/migrator/src/migrations/m_2025_03_29/settings.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_ROOT_KEY_VALUE_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[
|
||||
(SETTINGS_ROOT_KEY_VALUE_PATTERN, replace_setting_name),
|
||||
(SETTINGS_ROOT_KEY_VALUE_PATTERN, replace_setting_value),
|
||||
];
|
||||
|
||||
fn replace_setting_value(
|
||||
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)?;
|
||||
|
||||
if setting_name != "hide_mouse_while_typing" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value_capture_ix = query.capture_index_for_name("value")?;
|
||||
let value_range = mat
|
||||
.nodes_for_capture_index(value_capture_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let value = contents.get(value_range.clone())?;
|
||||
|
||||
let new_value = if value.trim() == "true" {
|
||||
"\"on_typing_and_movement\""
|
||||
} else if value.trim() == "false" {
|
||||
"\"never\""
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some((value_range, new_value.to_string()))
|
||||
}
|
||||
|
||||
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 = if setting_name == "hide_mouse_while_typing" {
|
||||
"hide_mouse"
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some((setting_name_range, new_setting_name.to_string()))
|
||||
}
|
||||
31
crates/migrator/src/migrations/m_2025_04_15/keymap.rs
Normal file
31
crates/migrator/src/migrations/m_2025_04_15/keymap.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use collections::HashMap;
|
||||
use std::{ops::Range, sync::LazyLock};
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::KEYMAP_ACTION_STRING_PATTERN;
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// "space": "outline_panel::Open" -> "outline_panel::OpenSelectedEntry"
|
||||
static STRING_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
|
||||
HashMap::from_iter([("outline_panel::Open", "outline_panel::OpenSelectedEntry")])
|
||||
});
|
||||
29
crates/migrator/src/migrations/m_2025_04_15/settings.rs
Normal file
29
crates/migrator/src/migrations/m_2025_04_15/settings.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_ASSISTANT_TOOLS_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
|
||||
SETTINGS_ASSISTANT_TOOLS_PATTERN,
|
||||
replace_bash_with_terminal_in_profiles,
|
||||
)];
|
||||
|
||||
fn replace_bash_with_terminal_in_profiles(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let tool_name_capture_ix = query.capture_index_for_name("tool_name")?;
|
||||
let tool_name_range = mat
|
||||
.nodes_for_capture_index(tool_name_capture_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let tool_name = contents.get(tool_name_range.clone())?;
|
||||
|
||||
if tool_name != "bash" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((tool_name_range, "terminal".to_string()))
|
||||
}
|
||||
25
crates/migrator/src/migrations/m_2025_04_21/settings.rs
Normal file
25
crates/migrator/src/migrations/m_2025_04_21/settings.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_ASSISTANT_TOOLS_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns =
|
||||
&[(SETTINGS_ASSISTANT_TOOLS_PATTERN, rename_tools)];
|
||||
|
||||
fn rename_tools(contents: &str, mat: &QueryMatch, query: &Query) -> Option<(Range<usize>, String)> {
|
||||
let tool_name_capture_ix = query.capture_index_for_name("tool_name")?;
|
||||
let tool_name_range = mat
|
||||
.nodes_for_capture_index(tool_name_capture_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let tool_name = contents.get(tool_name_range.clone())?;
|
||||
|
||||
let new_name = match tool_name {
|
||||
"find_replace_file" => "edit_file",
|
||||
"regex_search" => "grep",
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some((tool_name_range, new_name.to_string()))
|
||||
}
|
||||
27
crates/migrator/src/migrations/m_2025_04_23/settings.rs
Normal file
27
crates/migrator/src/migrations/m_2025_04_23/settings.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_ASSISTANT_TOOLS_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns =
|
||||
&[(SETTINGS_ASSISTANT_TOOLS_PATTERN, rename_path_search_tool)];
|
||||
|
||||
fn rename_path_search_tool(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let tool_name_capture_ix = query.capture_index_for_name("tool_name")?;
|
||||
let tool_name_range = mat
|
||||
.nodes_for_capture_index(tool_name_capture_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let tool_name = contents.get(tool_name_range.clone())?;
|
||||
|
||||
if tool_name == "path_search" {
|
||||
return Some((tool_name_range, "find_path".to_string()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
41
crates/migrator/src/migrations/m_2025_05_05/settings.rs
Normal file
41
crates/migrator/src/migrations/m_2025_05_05/settings.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::{
|
||||
MigrationPatterns, patterns::SETTINGS_ASSISTANT_PATTERN,
|
||||
patterns::SETTINGS_EDIT_PREDICTIONS_ASSISTANT_PATTERN,
|
||||
};
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[
|
||||
(SETTINGS_ASSISTANT_PATTERN, rename_assistant),
|
||||
(
|
||||
SETTINGS_EDIT_PREDICTIONS_ASSISTANT_PATTERN,
|
||||
rename_edit_prediction_assistant,
|
||||
),
|
||||
];
|
||||
|
||||
fn rename_assistant(
|
||||
_contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let key_capture_ix = query.capture_index_for_name("key")?;
|
||||
let key_range = mat
|
||||
.nodes_for_capture_index(key_capture_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
Some((key_range, "agent".to_string()))
|
||||
}
|
||||
|
||||
fn rename_edit_prediction_assistant(
|
||||
_contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let key_capture_ix = query.capture_index_for_name("enabled_in_assistant")?;
|
||||
let key_range = mat
|
||||
.nodes_for_capture_index(key_capture_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
Some((key_range, "enabled_in_text_threads".to_string()))
|
||||
}
|
||||
26
crates/migrator/src/migrations/m_2025_05_08/settings.rs
Normal file
26
crates/migrator/src/migrations/m_2025_05_08/settings.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::{MigrationPatterns, patterns::SETTINGS_DUPLICATED_AGENT_PATTERN};
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns =
|
||||
&[(SETTINGS_DUPLICATED_AGENT_PATTERN, comment_duplicated_agent)];
|
||||
|
||||
fn comment_duplicated_agent(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let pair_ix = query.capture_index_for_name("pair1")?;
|
||||
let mut range = mat.nodes_for_capture_index(pair_ix).next()?.byte_range();
|
||||
|
||||
// Include the comma into the commented region
|
||||
let rtext = &contents[range.end..];
|
||||
if let Some(comma_index) = rtext.find(',') {
|
||||
range.end += comma_index + 1;
|
||||
}
|
||||
|
||||
let value = contents[range.clone()].to_string();
|
||||
let commented_value = format!("/* Duplicated key auto-commented: {value} */");
|
||||
Some((range, commented_value))
|
||||
}
|
||||
92
crates/migrator/src/migrations/m_2025_06_16/settings.rs
Normal file
92
crates/migrator/src/migrations/m_2025_06_16/settings.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
|
||||
SETTINGS_CONTEXT_SERVER_PATTERN,
|
||||
migrate_context_server_settings,
|
||||
)];
|
||||
|
||||
const SETTINGS_CONTEXT_SERVER_PATTERN: &str = r#"(document
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @context-servers)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @server-name)
|
||||
value: (object) @server-settings
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @context-servers "context_servers")
|
||||
)"#;
|
||||
|
||||
fn migrate_context_server_settings(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let server_settings_index = query.capture_index_for_name("server-settings")?;
|
||||
let server_settings = mat.nodes_for_capture_index(server_settings_index).next()?;
|
||||
|
||||
let mut has_command = false;
|
||||
let mut has_settings = false;
|
||||
let mut has_url = false;
|
||||
let mut other_keys = 0;
|
||||
let mut column = None;
|
||||
|
||||
// Parse the server settings to check what keys it contains
|
||||
let mut cursor = server_settings.walk();
|
||||
for child in server_settings.children(&mut cursor) {
|
||||
if child.kind() == "pair"
|
||||
&& let Some(key_node) = child.child_by_field_name("key")
|
||||
{
|
||||
if let (None, Some(quote_content)) = (column, key_node.child(0)) {
|
||||
column = Some(quote_content.start_position().column);
|
||||
}
|
||||
if let Some(string_content) = key_node.child(1) {
|
||||
let key = &contents[string_content.byte_range()];
|
||||
match key {
|
||||
// If it already has a source key, don't modify it
|
||||
"source" => return None,
|
||||
"command" => has_command = true,
|
||||
"settings" => has_settings = true,
|
||||
"url" => has_url = true,
|
||||
_ => other_keys += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let source_type = if has_command { "custom" } else { "extension" };
|
||||
|
||||
// Insert the source key at the beginning of the object
|
||||
let start = server_settings.start_byte() + 1;
|
||||
let indent = " ".repeat(column.unwrap_or(12));
|
||||
|
||||
if !has_command && !has_settings && !has_url {
|
||||
return Some((
|
||||
start..start,
|
||||
format!(
|
||||
r#"
|
||||
{indent}"source": "{}",
|
||||
{indent}"settings": {{}}{}
|
||||
"#,
|
||||
source_type,
|
||||
if other_keys > 0 { "," } else { "" }
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Some((
|
||||
start..start,
|
||||
format!(
|
||||
r#"
|
||||
{indent}"source": "{}","#,
|
||||
source_type
|
||||
),
|
||||
))
|
||||
}
|
||||
133
crates/migrator/src/migrations/m_2025_06_25/settings.rs
Normal file
133
crates/migrator/src/migrations/m_2025_06_25/settings.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[
|
||||
(SETTINGS_VERSION_PATTERN, remove_version_fields),
|
||||
(
|
||||
SETTINGS_NESTED_VERSION_PATTERN,
|
||||
remove_nested_version_fields,
|
||||
),
|
||||
];
|
||||
|
||||
const SETTINGS_VERSION_PATTERN: &str = r#"(document
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @key)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @version_key)
|
||||
value: (_) @version_value
|
||||
) @version_pair
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @key "agent")
|
||||
(#eq? @version_key "version")
|
||||
)"#;
|
||||
|
||||
const SETTINGS_NESTED_VERSION_PATTERN: &str = r#"(document
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @language_models)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @provider)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @version_key)
|
||||
value: (_) @version_value
|
||||
) @version_pair
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @language_models "language_models")
|
||||
(#match? @provider "^(anthropic|openai)$")
|
||||
(#eq? @version_key "version")
|
||||
)"#;
|
||||
|
||||
fn remove_version_fields(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let version_pair_ix = query.capture_index_for_name("version_pair")?;
|
||||
let version_pair_node = mat.nodes_for_capture_index(version_pair_ix).next()?;
|
||||
|
||||
remove_pair_with_whitespace(contents, version_pair_node)
|
||||
}
|
||||
|
||||
fn remove_nested_version_fields(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let version_pair_ix = query.capture_index_for_name("version_pair")?;
|
||||
let version_pair_node = mat.nodes_for_capture_index(version_pair_ix).next()?;
|
||||
|
||||
remove_pair_with_whitespace(contents, version_pair_node)
|
||||
}
|
||||
|
||||
fn remove_pair_with_whitespace(
|
||||
contents: &str,
|
||||
pair_node: tree_sitter::Node,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let mut range_to_remove = pair_node.byte_range();
|
||||
|
||||
// Check if there's a comma after this pair
|
||||
if let Some(next_sibling) = pair_node.next_sibling() {
|
||||
if next_sibling.kind() == "," {
|
||||
range_to_remove.end = next_sibling.end_byte();
|
||||
}
|
||||
} else {
|
||||
// If no next sibling, check if there's a comma before
|
||||
if let Some(prev_sibling) = pair_node.prev_sibling()
|
||||
&& prev_sibling.kind() == ","
|
||||
{
|
||||
range_to_remove.start = prev_sibling.start_byte();
|
||||
}
|
||||
}
|
||||
|
||||
// Include any leading whitespace/newline, including comments
|
||||
let text_before = &contents[..range_to_remove.start];
|
||||
if let Some(last_newline) = text_before.rfind('\n') {
|
||||
let whitespace_start = last_newline + 1;
|
||||
let potential_whitespace = &contents[whitespace_start..range_to_remove.start];
|
||||
|
||||
// Check if it's only whitespace or comments
|
||||
let mut is_whitespace_or_comment = true;
|
||||
let mut in_comment = false;
|
||||
let mut chars = potential_whitespace.chars().peekable();
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
if in_comment {
|
||||
if ch == '\n' {
|
||||
in_comment = false;
|
||||
}
|
||||
} else if ch == '/' && chars.peek() == Some(&'/') {
|
||||
in_comment = true;
|
||||
chars.next(); // Skip the second '/'
|
||||
} else if !ch.is_whitespace() {
|
||||
is_whitespace_or_comment = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if is_whitespace_or_comment {
|
||||
range_to_remove.start = whitespace_start;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check if we need to include trailing whitespace up to the next line
|
||||
let text_after = &contents[range_to_remove.end..];
|
||||
if let Some(newline_pos) = text_after.find('\n')
|
||||
&& text_after[..newline_pos].chars().all(|c| c.is_whitespace())
|
||||
{
|
||||
range_to_remove.end += newline_pos + 1;
|
||||
}
|
||||
|
||||
Some((range_to_remove, String::new()))
|
||||
}
|
||||
132
crates/migrator/src/migrations/m_2025_06_27/settings.rs
Normal file
132
crates/migrator/src/migrations/m_2025_06_27/settings.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
|
||||
SETTINGS_CONTEXT_SERVER_PATTERN,
|
||||
flatten_context_server_command,
|
||||
)];
|
||||
|
||||
const SETTINGS_CONTEXT_SERVER_PATTERN: &str = r#"(document
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @context-servers)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @server-name)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @source-key)
|
||||
value: (string (string_content) @source-value)
|
||||
)
|
||||
(pair
|
||||
key: (string (string_content) @command-key)
|
||||
value: (object) @command-object
|
||||
) @command-pair
|
||||
) @server-settings
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @context-servers "context_servers")
|
||||
(#eq? @source-key "source")
|
||||
(#eq? @source-value "custom")
|
||||
(#eq? @command-key "command")
|
||||
)"#;
|
||||
|
||||
fn flatten_context_server_command(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let command_pair_index = query.capture_index_for_name("command-pair")?;
|
||||
let command_pair = mat.nodes_for_capture_index(command_pair_index).next()?;
|
||||
|
||||
let command_object_index = query.capture_index_for_name("command-object")?;
|
||||
let command_object = mat.nodes_for_capture_index(command_object_index).next()?;
|
||||
|
||||
let server_settings_index = query.capture_index_for_name("server-settings")?;
|
||||
let _server_settings = mat.nodes_for_capture_index(server_settings_index).next()?;
|
||||
|
||||
// Parse the command object to extract path, args, and env
|
||||
let mut path_value = None;
|
||||
let mut args_value = None;
|
||||
let mut env_value = None;
|
||||
|
||||
let mut cursor = command_object.walk();
|
||||
for child in command_object.children(&mut cursor) {
|
||||
if child.kind() == "pair"
|
||||
&& let Some(key_node) = child.child_by_field_name("key")
|
||||
&& let Some(string_content) = key_node.child(1)
|
||||
{
|
||||
let key = &contents[string_content.byte_range()];
|
||||
if let Some(value_node) = child.child_by_field_name("value") {
|
||||
let value_range = value_node.byte_range();
|
||||
match key {
|
||||
"path" => path_value = Some(&contents[value_range]),
|
||||
"args" => args_value = Some(&contents[value_range]),
|
||||
"env" => env_value = Some(&contents[value_range]),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let path = path_value?;
|
||||
|
||||
// Get the proper indentation from the command pair
|
||||
let command_pair_start = command_pair.start_byte();
|
||||
let line_start = contents[..command_pair_start]
|
||||
.rfind('\n')
|
||||
.map(|pos| pos + 1)
|
||||
.unwrap_or(0);
|
||||
let indent = &contents[line_start..command_pair_start];
|
||||
|
||||
// Build the replacement string
|
||||
let mut replacement = format!("\"command\": {}", path);
|
||||
|
||||
// Add args if present - need to reduce indentation
|
||||
if let Some(args) = args_value {
|
||||
replacement.push_str(",\n");
|
||||
replacement.push_str(indent);
|
||||
replacement.push_str("\"args\": ");
|
||||
let reduced_args = reduce_indentation(args, 4);
|
||||
replacement.push_str(&reduced_args);
|
||||
}
|
||||
|
||||
// Add env if present - need to reduce indentation
|
||||
if let Some(env) = env_value {
|
||||
replacement.push_str(",\n");
|
||||
replacement.push_str(indent);
|
||||
replacement.push_str("\"env\": ");
|
||||
replacement.push_str(&reduce_indentation(env, 4));
|
||||
}
|
||||
|
||||
let range_to_replace = command_pair.byte_range();
|
||||
Some((range_to_replace, replacement))
|
||||
}
|
||||
|
||||
fn reduce_indentation(text: &str, spaces: usize) -> String {
|
||||
let lines: Vec<&str> = text.lines().collect();
|
||||
let mut result = String::new();
|
||||
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
if i > 0 {
|
||||
result.push('\n');
|
||||
}
|
||||
|
||||
// Count leading spaces
|
||||
let leading_spaces = line.chars().take_while(|&c| c == ' ').count();
|
||||
|
||||
if leading_spaces >= spaces {
|
||||
// Reduce indentation
|
||||
result.push_str(&line[spaces..]);
|
||||
} else {
|
||||
// Keep line as is if it doesn't have enough indentation
|
||||
result.push_str(line);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
37
crates/migrator/src/migrations/m_2025_07_08/settings.rs
Normal file
37
crates/migrator/src/migrations/m_2025_07_08/settings.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_ROOT_KEY_VALUE_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
|
||||
SETTINGS_ROOT_KEY_VALUE_PATTERN,
|
||||
migrate_drag_and_drop_selection,
|
||||
)];
|
||||
|
||||
fn migrate_drag_and_drop_selection(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let name_ix = query.capture_index_for_name("name")?;
|
||||
let name_range = mat.nodes_for_capture_index(name_ix).next()?.byte_range();
|
||||
let name = contents.get(name_range)?;
|
||||
|
||||
if name != "drag_and_drop_selection" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value_ix = query.capture_index_for_name("value")?;
|
||||
let value_node = mat.nodes_for_capture_index(value_ix).next()?;
|
||||
let value_range = value_node.byte_range();
|
||||
let value = contents.get(value_range.clone())?;
|
||||
|
||||
match value {
|
||||
"true" | "false" => {
|
||||
let replacement = format!("{{\n \"enabled\": {}\n }}", value);
|
||||
Some((value_range, replacement))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
74
crates/migrator/src/migrations/m_2025_10_01/settings.rs
Normal file
74
crates/migrator/src/migrations/m_2025_10_01/settings.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use crate::migrations::migrate_language_setting;
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
pub fn flatten_code_actions_formatters(value: &mut Value) -> Result<()> {
|
||||
migrate_language_setting(value, |value, _path| {
|
||||
let Some(obj) = value.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
for key in ["formatter", "format_on_save"] {
|
||||
let Some(formatter) = obj.get_mut(key) else {
|
||||
continue;
|
||||
};
|
||||
let new_formatter = match formatter {
|
||||
Value::Array(arr) => {
|
||||
let mut new_arr = Vec::new();
|
||||
let mut found_code_actions = false;
|
||||
for item in arr {
|
||||
let Some(obj) = item.as_object() else {
|
||||
new_arr.push(item.clone());
|
||||
continue;
|
||||
};
|
||||
let code_actions_obj = obj
|
||||
.get("code_actions")
|
||||
.and_then(|code_actions| code_actions.as_object());
|
||||
let Some(code_actions) = code_actions_obj else {
|
||||
new_arr.push(item.clone());
|
||||
continue;
|
||||
};
|
||||
found_code_actions = true;
|
||||
for (name, enabled) in code_actions {
|
||||
if !enabled.as_bool().unwrap_or(true) {
|
||||
continue;
|
||||
}
|
||||
new_arr.push(serde_json::json!({
|
||||
"code_action": name
|
||||
}));
|
||||
}
|
||||
}
|
||||
if !found_code_actions {
|
||||
continue;
|
||||
}
|
||||
Value::Array(new_arr)
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
let mut new_arr = Vec::new();
|
||||
let code_actions_obj = obj
|
||||
.get("code_actions")
|
||||
.and_then(|code_actions| code_actions.as_object());
|
||||
let Some(code_actions) = code_actions_obj else {
|
||||
continue;
|
||||
};
|
||||
for (name, enabled) in code_actions {
|
||||
if !enabled.as_bool().unwrap_or(true) {
|
||||
continue;
|
||||
}
|
||||
new_arr.push(serde_json::json!({
|
||||
"code_action": name
|
||||
}));
|
||||
}
|
||||
if new_arr.len() == 1 {
|
||||
new_arr.pop().unwrap()
|
||||
} else {
|
||||
Value::Array(new_arr)
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
obj.insert(key.to_string(), new_formatter);
|
||||
}
|
||||
return Ok(());
|
||||
})
|
||||
}
|
||||
41
crates/migrator/src/migrations/m_2025_10_02/settings.rs
Normal file
41
crates/migrator/src/migrations/m_2025_10_02/settings.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_language_setting;
|
||||
|
||||
pub fn remove_formatters_on_save(value: &mut Value) -> Result<()> {
|
||||
migrate_language_setting(value, remove_formatters_on_save_inner)
|
||||
}
|
||||
|
||||
fn remove_formatters_on_save_inner(value: &mut Value, path: &[&str]) -> Result<()> {
|
||||
let Some(obj) = value.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(format_on_save) = obj.get("format_on_save").cloned() else {
|
||||
return Ok(());
|
||||
};
|
||||
let is_format_on_save_set_to_formatter = format_on_save
|
||||
.as_str()
|
||||
.map_or(true, |s| s != "on" && s != "off");
|
||||
if !is_format_on_save_set_to_formatter {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn fmt_path(path: &[&str], key: &str) -> String {
|
||||
let mut path = path.to_vec();
|
||||
path.push(key);
|
||||
path.join(".")
|
||||
}
|
||||
|
||||
anyhow::ensure!(
|
||||
obj.get("formatter").is_none(),
|
||||
r#"Setting formatters in both "format_on_save" and "formatter" is deprecated. Please migrate the formatters from {} into {}"#,
|
||||
fmt_path(path, "format_on_save"),
|
||||
fmt_path(path, "formatter")
|
||||
);
|
||||
|
||||
obj.insert("format_on_save".to_string(), serde_json::json!("on"));
|
||||
obj.insert("formatter".to_string(), format_on_save);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
30
crates/migrator/src/migrations/m_2025_10_03/settings.rs
Normal file
30
crates/migrator/src/migrations/m_2025_10_03/settings.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_ROOT_KEY_VALUE_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns =
|
||||
&[(SETTINGS_ROOT_KEY_VALUE_PATTERN, rename_agent_font_size)];
|
||||
|
||||
/// Renames the setting `agent_font_size` to `agent_ui_font_size`
|
||||
fn rename_agent_font_size(
|
||||
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())?;
|
||||
|
||||
if setting_name != "agent_font_size" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((setting_name_range, "agent_ui_font_size".to_string()))
|
||||
}
|
||||
71
crates/migrator/src/migrations/m_2025_10_16/settings.rs
Normal file
71
crates/migrator/src/migrations/m_2025_10_16/settings.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_language_setting;
|
||||
|
||||
pub fn restore_code_actions_on_format(value: &mut Value) -> Result<()> {
|
||||
migrate_language_setting(value, restore_code_actions_on_format_inner)
|
||||
}
|
||||
|
||||
fn restore_code_actions_on_format_inner(value: &mut Value, path: &[&str]) -> Result<()> {
|
||||
let Some(obj) = value.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
let code_actions_on_format = obj
|
||||
.get("code_actions_on_format")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Value::Object(Default::default()));
|
||||
|
||||
fn fmt_path(path: &[&str], key: &str) -> String {
|
||||
let mut path = path.to_vec();
|
||||
path.push(key);
|
||||
path.join(".")
|
||||
}
|
||||
|
||||
let Some(mut code_actions_map) = code_actions_on_format.as_object().cloned() else {
|
||||
anyhow::bail!(
|
||||
r#"The `code_actions_on_format` is in an invalid state and cannot be migrated at {}. Please ensure the code_actions_on_format setting is a Map<String, bool>"#,
|
||||
fmt_path(path, "code_actions_on_format"),
|
||||
);
|
||||
};
|
||||
|
||||
let Some(formatter) = obj.get("formatter") else {
|
||||
return Ok(());
|
||||
};
|
||||
let formatter_array = if let Some(array) = formatter.as_array() {
|
||||
array.clone()
|
||||
} else {
|
||||
vec![formatter.clone()]
|
||||
};
|
||||
if formatter_array.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut code_action_formatters = Vec::new();
|
||||
for formatter in formatter_array {
|
||||
let Some(code_action) = formatter.get("code_action") else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(code_action_name) = code_action.as_str() else {
|
||||
anyhow::bail!(
|
||||
r#"The `code_action` is in an invalid state and cannot be migrated at {}. Please ensure the code_action setting is a String"#,
|
||||
fmt_path(path, "formatter"),
|
||||
);
|
||||
};
|
||||
code_action_formatters.push(code_action_name.to_string());
|
||||
}
|
||||
|
||||
code_actions_map.extend(
|
||||
code_action_formatters
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|code_action| (code_action, Value::Bool(true))),
|
||||
);
|
||||
|
||||
obj.insert("formatter".to_string(), Value::Array(vec![]));
|
||||
obj.insert(
|
||||
"code_actions_on_format".into(),
|
||||
Value::Object(code_actions_map),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
30
crates/migrator/src/migrations/m_2025_10_17/settings.rs
Normal file
30
crates/migrator/src/migrations/m_2025_10_17/settings.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_settings;
|
||||
|
||||
pub fn make_file_finder_include_ignored_an_enum(value: &mut Value) -> Result<()> {
|
||||
migrate_settings(value, &mut migrate_one)
|
||||
}
|
||||
|
||||
fn migrate_one(obj: &mut serde_json::Map<String, Value>) -> Result<()> {
|
||||
let Some(file_finder) = obj.get_mut("file_finder") else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let Some(file_finder_obj) = file_finder.as_object_mut() else {
|
||||
anyhow::bail!("Expected file_finder to be an object");
|
||||
};
|
||||
|
||||
let Some(include_ignored) = file_finder_obj.get_mut("include_ignored") else {
|
||||
return Ok(());
|
||||
};
|
||||
*include_ignored = match include_ignored {
|
||||
Value::Bool(true) => Value::String("all".to_string()),
|
||||
Value::Bool(false) => Value::String("indexed".to_string()),
|
||||
Value::Null => Value::String("smart".to_string()),
|
||||
Value::String(s) if s == "all" || s == "indexed" || s == "smart" => return Ok(()),
|
||||
_ => anyhow::bail!("Expected include_ignored to be a boolean or null"),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
22
crates/migrator/src/migrations/m_2025_10_21/settings.rs
Normal file
22
crates/migrator/src/migrations/m_2025_10_21/settings.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_settings;
|
||||
|
||||
pub fn make_relative_line_numbers_an_enum(value: &mut Value) -> Result<()> {
|
||||
migrate_settings(value, &mut migrate_one)
|
||||
}
|
||||
|
||||
fn migrate_one(obj: &mut serde_json::Map<String, Value>) -> Result<()> {
|
||||
let Some(relative_line_numbers) = obj.get_mut("relative_line_numbers") else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
*relative_line_numbers = match relative_line_numbers {
|
||||
Value::Bool(true) => Value::String("enabled".to_string()),
|
||||
Value::Bool(false) => Value::String("disabled".to_string()),
|
||||
Value::String(s) if s == "enabled" || s == "disabled" || s == "wrapped" => return Ok(()),
|
||||
_ => anyhow::bail!("Expected relative_line_numbers to be a boolean"),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
84
crates/migrator/src/migrations/m_2025_11_12/settings.rs
Normal file
84
crates/migrator/src/migrations/m_2025_11_12/settings.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_NESTED_KEY_VALUE_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[
|
||||
(
|
||||
SETTINGS_NESTED_KEY_VALUE_PATTERN,
|
||||
rename_open_file_on_paste_setting,
|
||||
),
|
||||
(
|
||||
SETTINGS_NESTED_KEY_VALUE_PATTERN,
|
||||
replace_open_file_on_paste_setting_value,
|
||||
),
|
||||
];
|
||||
|
||||
fn rename_open_file_on_paste_setting(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
if !is_project_panel_open_file_on_paste(contents, mat, query) {
|
||||
return None;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Some((setting_name_range, "auto_open".to_string()))
|
||||
}
|
||||
|
||||
fn replace_open_file_on_paste_setting_value(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
if !is_project_panel_open_file_on_paste(contents, mat, query) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value_ix = query.capture_index_for_name("setting_value")?;
|
||||
let value_node = mat.nodes_for_capture_index(value_ix).next()?;
|
||||
let value_range = value_node.byte_range();
|
||||
let value_text = contents.get(value_range.clone())?.trim();
|
||||
|
||||
let normalized_value = match value_text {
|
||||
"true" => "true",
|
||||
"false" => "false",
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some((
|
||||
value_range,
|
||||
format!("{{ \"on_paste\": {normalized_value} }}"),
|
||||
))
|
||||
}
|
||||
|
||||
fn is_project_panel_open_file_on_paste(contents: &str, mat: &QueryMatch, query: &Query) -> bool {
|
||||
let parent_key_ix = match query.capture_index_for_name("parent_key") {
|
||||
Some(ix) => ix,
|
||||
None => return false,
|
||||
};
|
||||
let parent_range = match mat.nodes_for_capture_index(parent_key_ix).next() {
|
||||
Some(node) => node.byte_range(),
|
||||
None => return false,
|
||||
};
|
||||
if contents.get(parent_range) != Some("project_panel") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let setting_name_ix = match query.capture_index_for_name("setting_name") {
|
||||
Some(ix) => ix,
|
||||
None => return false,
|
||||
};
|
||||
let setting_name_range = match mat.nodes_for_capture_index(setting_name_ix).next() {
|
||||
Some(node) => node.byte_range(),
|
||||
None => return false,
|
||||
};
|
||||
contents.get(setting_name_range) == Some("open_file_on_paste")
|
||||
}
|
||||
76
crates/migrator/src/migrations/m_2025_11_20/settings.rs
Normal file
76
crates/migrator/src/migrations/m_2025_11_20/settings.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
|
||||
SETTINGS_AGENT_SERVERS_CUSTOM_PATTERN,
|
||||
migrate_custom_agent_settings,
|
||||
)];
|
||||
|
||||
const SETTINGS_AGENT_SERVERS_CUSTOM_PATTERN: &str = r#"(document
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @agent-servers)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @server-name)
|
||||
value: (object) @server-settings
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @agent-servers "agent_servers")
|
||||
)"#;
|
||||
|
||||
fn migrate_custom_agent_settings(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let server_name_index = query.capture_index_for_name("server-name")?;
|
||||
let server_name = mat.nodes_for_capture_index(server_name_index).next()?;
|
||||
let server_name_text = &contents[server_name.byte_range()];
|
||||
|
||||
if matches!(server_name_text, "gemini" | "claude" | "codex") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let server_settings_index = query.capture_index_for_name("server-settings")?;
|
||||
let server_settings = mat.nodes_for_capture_index(server_settings_index).next()?;
|
||||
|
||||
let mut column = None;
|
||||
|
||||
// Parse the server settings to check what keys it contains
|
||||
let mut cursor = server_settings.walk();
|
||||
for child in server_settings.children(&mut cursor) {
|
||||
if child.kind() == "pair" {
|
||||
if let Some(key_node) = child.child_by_field_name("key") {
|
||||
if let (None, Some(quote_content)) = (column, key_node.child(0)) {
|
||||
column = Some(quote_content.start_position().column);
|
||||
}
|
||||
if let Some(string_content) = key_node.child(1) {
|
||||
let key = &contents[string_content.byte_range()];
|
||||
match key {
|
||||
// If it already has a type key, don't modify it
|
||||
"type" => return None,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the type key at the beginning of the object
|
||||
let start = server_settings.start_byte() + 1;
|
||||
let indent = " ".repeat(column.unwrap_or(12));
|
||||
|
||||
Some((
|
||||
start..start,
|
||||
format!(
|
||||
r#"
|
||||
{indent}"type": "custom","#
|
||||
),
|
||||
))
|
||||
}
|
||||
21
crates/migrator/src/migrations/m_2025_11_25/settings.rs
Normal file
21
crates/migrator/src/migrations/m_2025_11_25/settings.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_settings;
|
||||
|
||||
pub fn remove_context_server_source(value: &mut Value) -> Result<()> {
|
||||
migrate_settings(value, &mut migrate_one)
|
||||
}
|
||||
|
||||
fn migrate_one(obj: &mut serde_json::Map<String, Value>) -> Result<()> {
|
||||
if let Some(context_servers) = obj.get_mut("context_servers") {
|
||||
if let Some(servers) = context_servers.as_object_mut() {
|
||||
for (_, server) in servers.iter_mut() {
|
||||
if let Some(server_obj) = server.as_object_mut() {
|
||||
server_obj.remove("source");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
55
crates/migrator/src/migrations/m_2025_12_01/settings.rs
Normal file
55
crates/migrator/src/migrations/m_2025_12_01/settings.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_NESTED_KEY_VALUE_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
|
||||
SETTINGS_NESTED_KEY_VALUE_PATTERN,
|
||||
rename_enable_preview_from_code_navigation_setting,
|
||||
)];
|
||||
|
||||
fn rename_enable_preview_from_code_navigation_setting(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
if !is_enable_preview_from_code_navigation(contents, mat, query) {
|
||||
return None;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Some((
|
||||
setting_name_range,
|
||||
"enable_keep_preview_on_code_navigation".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
fn is_enable_preview_from_code_navigation(contents: &str, mat: &QueryMatch, query: &Query) -> bool {
|
||||
let parent_key_ix = match query.capture_index_for_name("parent_key") {
|
||||
Some(ix) => ix,
|
||||
None => return false,
|
||||
};
|
||||
let parent_range = match mat.nodes_for_capture_index(parent_key_ix).next() {
|
||||
Some(node) => node.byte_range(),
|
||||
None => return false,
|
||||
};
|
||||
if contents.get(parent_range) != Some("preview_tabs") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let setting_name_ix = match query.capture_index_for_name("setting_name") {
|
||||
Some(ix) => ix,
|
||||
None => return false,
|
||||
};
|
||||
let setting_name_range = match mat.nodes_for_capture_index(setting_name_ix).next() {
|
||||
Some(node) => node.byte_range(),
|
||||
None => return false,
|
||||
};
|
||||
contents.get(setting_name_range) == Some("enable_preview_from_code_navigation")
|
||||
}
|
||||
33
crates/migrator/src/migrations/m_2025_12_08/keymap.rs
Normal file
33
crates/migrator/src/migrations/m_2025_12_08/keymap.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use collections::HashMap;
|
||||
use std::{ops::Range, sync::LazyLock};
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::KEYMAP_ACTION_STRING_PATTERN;
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
static STRING_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
|
||||
HashMap::from_iter([(
|
||||
"editor::AcceptPartialEditPrediction",
|
||||
"editor::AcceptNextWordEditPrediction",
|
||||
)])
|
||||
});
|
||||
52
crates/migrator/src/migrations/m_2025_12_15/settings.rs
Normal file
52
crates/migrator/src/migrations/m_2025_12_15/settings.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_NESTED_KEY_VALUE_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
|
||||
SETTINGS_NESTED_KEY_VALUE_PATTERN,
|
||||
rename_restore_on_startup_values,
|
||||
)];
|
||||
|
||||
fn rename_restore_on_startup_values(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
if !is_restore_on_startup_setting(contents, mat, query) {
|
||||
return None;
|
||||
}
|
||||
|
||||
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())?;
|
||||
|
||||
// The value includes quotes, so we check for the quoted string
|
||||
let new_value = match setting_value.trim() {
|
||||
"\"none\"" => "\"empty_tab\"",
|
||||
"\"welcome\"" => "\"launchpad\"",
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some((setting_value_range, new_value.to_string()))
|
||||
}
|
||||
|
||||
fn is_restore_on_startup_setting(contents: &str, mat: &QueryMatch, query: &Query) -> bool {
|
||||
// Check that the parent key is "workspace" (since restore_on_startup is under workspace settings)
|
||||
// Actually, restore_on_startup can be at the root level too, so we need to handle both cases
|
||||
// The SETTINGS_NESTED_KEY_VALUE_PATTERN captures parent_key and setting_name
|
||||
|
||||
let setting_name_ix = match query.capture_index_for_name("setting_name") {
|
||||
Some(ix) => ix,
|
||||
None => return false,
|
||||
};
|
||||
let setting_name_range = match mat.nodes_for_capture_index(setting_name_ix).next() {
|
||||
Some(node) => node.byte_range(),
|
||||
None => return false,
|
||||
};
|
||||
contents.get(setting_name_range) == Some("restore_on_startup")
|
||||
}
|
||||
42
crates/migrator/src/migrations/m_2026_02_02/settings.rs
Normal file
42
crates/migrator/src/migrations/m_2026_02_02/settings.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_settings;
|
||||
|
||||
pub fn move_edit_prediction_provider_to_edit_predictions(value: &mut Value) -> Result<()> {
|
||||
migrate_settings(value, &mut migrate_one)
|
||||
}
|
||||
|
||||
fn migrate_one(obj: &mut serde_json::Map<String, Value>) -> Result<()> {
|
||||
let Some(features) = obj.get_mut("features") else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let Some(features_obj) = features.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let Some(provider) = features_obj.remove("edit_prediction_provider") else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let features_is_empty = features_obj.is_empty();
|
||||
|
||||
if features_is_empty {
|
||||
obj.remove("features");
|
||||
}
|
||||
|
||||
let edit_predictions = obj
|
||||
.entry("edit_predictions")
|
||||
.or_insert_with(|| Value::Object(Default::default()));
|
||||
|
||||
let Some(edit_predictions_obj) = edit_predictions.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if !edit_predictions_obj.contains_key("provider") {
|
||||
edit_predictions_obj.insert("provider".to_string(), provider);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
54
crates/migrator/src/migrations/m_2026_02_03/settings.rs
Normal file
54
crates/migrator/src/migrations/m_2026_02_03/settings.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_settings;
|
||||
|
||||
pub fn migrate_experimental_sweep_mercury(value: &mut Value) -> Result<()> {
|
||||
migrate_settings(value, &mut |obj| {
|
||||
migrate_one(obj);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn migrate_one(obj: &mut serde_json::Map<String, Value>) {
|
||||
if let Some(edit_predictions) = obj.get_mut("edit_predictions") {
|
||||
if let Some(edit_predictions_obj) = edit_predictions.as_object_mut() {
|
||||
migrate_provider_field(edit_predictions_obj, "provider");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(features) = obj.get_mut("features") {
|
||||
if let Some(features_obj) = features.as_object_mut() {
|
||||
migrate_provider_field(features_obj, "edit_prediction_provider");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn migrate_provider_field(obj: &mut serde_json::Map<String, Value>, field_name: &str) {
|
||||
let Some(provider) = obj.get(field_name) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(provider_obj) = provider.as_object() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(experimental_name) = provider_obj.get("experimental") else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(name) = experimental_name.as_str() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let provider_name = match name {
|
||||
"sweep" | "mercury" => name,
|
||||
"zeta2" => "zed",
|
||||
_ => return,
|
||||
};
|
||||
|
||||
obj.insert(
|
||||
field_name.to_string(),
|
||||
Value::String(provider_name.to_string()),
|
||||
);
|
||||
}
|
||||
124
crates/migrator/src/migrations/m_2026_02_04/settings.rs
Normal file
124
crates/migrator/src/migrations/m_2026_02_04/settings.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use anyhow::{Result, bail};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_settings;
|
||||
|
||||
const AGENT_KEY: &str = "agent";
|
||||
const ALWAYS_ALLOW_TOOL_ACTIONS: &str = "always_allow_tool_actions";
|
||||
const DEFAULT_KEY: &str = "default";
|
||||
const DEFAULT_MODE_KEY: &str = "default_mode";
|
||||
const PROFILES_KEY: &str = "profiles";
|
||||
const TOOL_PERMISSIONS_KEY: &str = "tool_permissions";
|
||||
const TOOLS_KEY: &str = "tools";
|
||||
|
||||
pub fn migrate_tool_permission_defaults(value: &mut Value) -> Result<()> {
|
||||
migrate_settings(value, &mut migrate_one)
|
||||
}
|
||||
|
||||
fn migrate_one(obj: &mut serde_json::Map<String, Value>) -> Result<()> {
|
||||
if let Some(agent) = obj.get_mut(AGENT_KEY) {
|
||||
migrate_agent_with_profiles(agent)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn migrate_agent_with_profiles(agent: &mut Value) -> Result<()> {
|
||||
migrate_agent_tool_permissions(agent)?;
|
||||
|
||||
if let Some(agent_object) = agent.as_object_mut() {
|
||||
if let Some(profiles) = agent_object.get_mut(PROFILES_KEY) {
|
||||
if let Some(profiles_object) = profiles.as_object_mut() {
|
||||
for (_profile_name, profile) in profiles_object.iter_mut() {
|
||||
migrate_agent_tool_permissions(profile)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn migrate_agent_tool_permissions(agent: &mut Value) -> Result<()> {
|
||||
let Some(agent_object) = agent.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let should_migrate_always_allow = match agent_object.get(ALWAYS_ALLOW_TOOL_ACTIONS) {
|
||||
Some(Value::Bool(true)) => {
|
||||
agent_object.remove(ALWAYS_ALLOW_TOOL_ACTIONS);
|
||||
true
|
||||
}
|
||||
Some(Value::Bool(false)) | Some(Value::Null) | None => {
|
||||
agent_object.remove(ALWAYS_ALLOW_TOOL_ACTIONS);
|
||||
false
|
||||
}
|
||||
Some(_) => {
|
||||
// Non-boolean value — leave it in place so the schema validator
|
||||
// can report it, rather than silently dropping user data.
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if should_migrate_always_allow {
|
||||
if matches!(
|
||||
agent_object.get(TOOL_PERMISSIONS_KEY),
|
||||
None | Some(Value::Null)
|
||||
) {
|
||||
agent_object.insert(
|
||||
TOOL_PERMISSIONS_KEY.to_string(),
|
||||
Value::Object(Default::default()),
|
||||
);
|
||||
}
|
||||
|
||||
let Some(Value::Object(tool_permissions_object)) =
|
||||
agent_object.get_mut(TOOL_PERMISSIONS_KEY)
|
||||
else {
|
||||
bail!(
|
||||
"agent.tool_permissions should be an object or null when migrating \
|
||||
always_allow_tool_actions"
|
||||
);
|
||||
};
|
||||
|
||||
if !tool_permissions_object.contains_key(DEFAULT_KEY)
|
||||
&& !tool_permissions_object.contains_key(DEFAULT_MODE_KEY)
|
||||
{
|
||||
tool_permissions_object
|
||||
.insert(DEFAULT_KEY.to_string(), Value::String("allow".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tool_permissions) = agent_object.get_mut(TOOL_PERMISSIONS_KEY) {
|
||||
migrate_default_mode_to_default(tool_permissions)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn migrate_default_mode_to_default(tool_permissions: &mut Value) -> Result<()> {
|
||||
let Some(tool_permissions_object) = tool_permissions.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let Some(default_mode) = tool_permissions_object.remove(DEFAULT_MODE_KEY) {
|
||||
if !tool_permissions_object.contains_key(DEFAULT_KEY) {
|
||||
tool_permissions_object.insert(DEFAULT_KEY.to_string(), default_mode);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tools) = tool_permissions_object.get_mut(TOOLS_KEY) {
|
||||
if let Some(tools_object) = tools.as_object_mut() {
|
||||
for (_tool_name, tool_rules) in tools_object.iter_mut() {
|
||||
if let Some(tool_rules_object) = tool_rules.as_object_mut() {
|
||||
if let Some(default_mode) = tool_rules_object.remove(DEFAULT_MODE_KEY) {
|
||||
if !tool_rules_object.contains_key(DEFAULT_KEY) {
|
||||
tool_rules_object.insert(DEFAULT_KEY.to_string(), default_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
161
crates/migrator/src/migrations/m_2026_02_25/settings.rs
Normal file
161
crates/migrator/src/migrations/m_2026_02_25/settings.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_settings;
|
||||
|
||||
const AGENT_SERVERS_KEY: &str = "agent_servers";
|
||||
|
||||
struct BuiltinMapping {
|
||||
old_key: &'static str,
|
||||
registry_key: &'static str,
|
||||
}
|
||||
|
||||
const BUILTIN_MAPPINGS: &[BuiltinMapping] = &[
|
||||
BuiltinMapping {
|
||||
old_key: "gemini",
|
||||
registry_key: "gemini",
|
||||
},
|
||||
BuiltinMapping {
|
||||
old_key: "claude",
|
||||
registry_key: "claude-acp",
|
||||
},
|
||||
BuiltinMapping {
|
||||
old_key: "codex",
|
||||
registry_key: "codex-acp",
|
||||
},
|
||||
];
|
||||
|
||||
const REGISTRY_COMPATIBLE_FIELDS: &[&str] = &[
|
||||
"env",
|
||||
"default_mode",
|
||||
"default_model",
|
||||
"favorite_models",
|
||||
"default_config_options",
|
||||
"favorite_config_option_values",
|
||||
];
|
||||
|
||||
pub fn migrate_builtin_agent_servers_to_registry(value: &mut Value) -> Result<()> {
|
||||
migrate_settings(value, &mut migrate_one)
|
||||
}
|
||||
|
||||
fn migrate_one(obj: &mut serde_json::Map<String, Value>) -> Result<()> {
|
||||
let Some(agent_servers) = obj.get_mut(AGENT_SERVERS_KEY) else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(servers_map) = agent_servers.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
for mapping in BUILTIN_MAPPINGS {
|
||||
migrate_builtin_entry(servers_map, mapping);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn migrate_builtin_entry(
|
||||
servers_map: &mut serde_json::Map<String, Value>,
|
||||
mapping: &BuiltinMapping,
|
||||
) {
|
||||
// Check if the old key exists and needs migration before taking ownership.
|
||||
let needs_migration = servers_map
|
||||
.get(mapping.old_key)
|
||||
.and_then(|v| v.as_object())
|
||||
.is_some_and(|obj| !obj.contains_key("type"));
|
||||
|
||||
if !needs_migration {
|
||||
return;
|
||||
}
|
||||
|
||||
// When the registry key differs from the old key and the target already
|
||||
// exists, just remove the stale old entry to avoid overwriting user data.
|
||||
if mapping.old_key != mapping.registry_key && servers_map.contains_key(mapping.registry_key) {
|
||||
servers_map.remove(mapping.old_key);
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(old_entry) = servers_map.remove(mapping.old_key) else {
|
||||
return;
|
||||
};
|
||||
let Some(old_obj) = old_entry.as_object() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let has_command = old_obj.contains_key("command");
|
||||
let ignore_system_version = old_obj
|
||||
.get("ignore_system_version")
|
||||
.and_then(|v| v.as_bool());
|
||||
|
||||
// A custom entry is needed when the user configured a custom binary
|
||||
// or explicitly opted into using the system version via
|
||||
// `ignore_system_version: false` (only meaningful for gemini).
|
||||
let needs_custom = has_command
|
||||
|| (mapping.old_key == "gemini" && matches!(ignore_system_version, Some(false)));
|
||||
|
||||
if needs_custom {
|
||||
let local_key = format!("{}-custom", mapping.registry_key);
|
||||
|
||||
// Don't overwrite an existing `-custom` entry.
|
||||
if servers_map.contains_key(&local_key) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut custom_obj = serde_json::Map::new();
|
||||
custom_obj.insert("type".to_string(), Value::String("custom".to_string()));
|
||||
|
||||
if has_command {
|
||||
if let Some(command) = old_obj.get("command") {
|
||||
custom_obj.insert("command".to_string(), command.clone());
|
||||
}
|
||||
if let Some(args) = old_obj.get("args") {
|
||||
if !args.as_array().is_some_and(|a| a.is_empty()) {
|
||||
custom_obj.insert("args".to_string(), args.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ignore_system_version: false — the user wants the binary from $PATH
|
||||
custom_obj.insert(
|
||||
"command".to_string(),
|
||||
Value::String(mapping.old_key.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
// Carry over all compatible fields to the custom entry.
|
||||
for &field in REGISTRY_COMPATIBLE_FIELDS {
|
||||
if let Some(value) = old_obj.get(field) {
|
||||
match value {
|
||||
Value::Array(arr) if arr.is_empty() => continue,
|
||||
Value::Object(map) if map.is_empty() => continue,
|
||||
Value::Null => continue,
|
||||
_ => {
|
||||
custom_obj.insert(field.to_string(), value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
servers_map.insert(local_key, Value::Object(custom_obj));
|
||||
} else {
|
||||
// Build a registry entry with compatible fields only.
|
||||
let mut registry_obj = serde_json::Map::new();
|
||||
registry_obj.insert("type".to_string(), Value::String("registry".to_string()));
|
||||
|
||||
for &field in REGISTRY_COMPATIBLE_FIELDS {
|
||||
if let Some(value) = old_obj.get(field) {
|
||||
match value {
|
||||
Value::Array(arr) if arr.is_empty() => continue,
|
||||
Value::Object(map) if map.is_empty() => continue,
|
||||
Value::Null => continue,
|
||||
_ => {
|
||||
registry_obj.insert(field.to_string(), value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
servers_map.insert(
|
||||
mapping.registry_key.to_string(),
|
||||
Value::Object(registry_obj),
|
||||
);
|
||||
}
|
||||
}
|
||||
50
crates/migrator/src/migrations/m_2026_03_16/settings.rs
Normal file
50
crates/migrator/src/migrations/m_2026_03_16/settings.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_NESTED_KEY_VALUE_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns =
|
||||
&[(SETTINGS_NESTED_KEY_VALUE_PATTERN, rename_heex_settings)];
|
||||
|
||||
fn rename_heex_settings(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
if !is_heex_settings(contents, mat, query) {
|
||||
return None;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Some((setting_name_range, "HEEx".to_string()))
|
||||
}
|
||||
|
||||
fn is_heex_settings(contents: &str, mat: &QueryMatch, query: &Query) -> bool {
|
||||
let parent_key_ix = match query.capture_index_for_name("parent_key") {
|
||||
Some(ix) => ix,
|
||||
None => return false,
|
||||
};
|
||||
let parent_range = match mat.nodes_for_capture_index(parent_key_ix).next() {
|
||||
Some(node) => node.byte_range(),
|
||||
None => return false,
|
||||
};
|
||||
if contents.get(parent_range) != Some("languages") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let setting_name_ix = match query.capture_index_for_name("setting_name") {
|
||||
Some(ix) => ix,
|
||||
None => return false,
|
||||
};
|
||||
let setting_name_range = match mat.nodes_for_capture_index(setting_name_ix).next() {
|
||||
Some(node) => node.byte_range(),
|
||||
None => return false,
|
||||
};
|
||||
contents.get(setting_name_range) == Some("HEEX")
|
||||
}
|
||||
47
crates/migrator/src/migrations/m_2026_03_23/keymap.rs
Normal file
47
crates/migrator/src/migrations/m_2026_03_23/keymap.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
|
||||
pub const KEYMAP_PATTERNS: MigrationPatterns =
|
||||
&[(crate::patterns::KEYMAP_CONTEXT_PATTERN, rename_context_key)];
|
||||
|
||||
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.clone();
|
||||
|
||||
const REPLACEMENTS: &[(&str, &str)] = &[
|
||||
(
|
||||
"edit_prediction_conflict && !showing_completions",
|
||||
"(edit_prediction && in_leading_whitespace)",
|
||||
),
|
||||
(
|
||||
"edit_prediction_conflict && showing_completions",
|
||||
"(edit_prediction && showing_completions)",
|
||||
),
|
||||
(
|
||||
"edit_prediction_conflict",
|
||||
"(edit_prediction && (showing_completions || in_leading_whitespace))",
|
||||
),
|
||||
];
|
||||
|
||||
for (old, new) in REPLACEMENTS {
|
||||
new_predicate = new_predicate.replace(old, new);
|
||||
}
|
||||
|
||||
if new_predicate != old_predicate {
|
||||
Some((context_predicate_range, new_predicate))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
29
crates/migrator/src/migrations/m_2026_03_30/settings.rs
Normal file
29
crates/migrator/src/migrations/m_2026_03_30/settings.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_settings;
|
||||
|
||||
pub fn make_play_sound_when_agent_done_an_enum(value: &mut Value) -> Result<()> {
|
||||
migrate_settings(value, &mut migrate_one)
|
||||
}
|
||||
|
||||
fn migrate_one(obj: &mut serde_json::Map<String, Value>) -> Result<()> {
|
||||
let Some(play_sound) = obj
|
||||
.get_mut("agent")
|
||||
.and_then(|agent| agent.as_object_mut())
|
||||
.and_then(|agent| agent.get_mut("play_sound_when_agent_done"))
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
*play_sound = match play_sound {
|
||||
Value::Bool(true) => Value::String("always".to_string()),
|
||||
Value::Bool(false) => Value::String("never".to_string()),
|
||||
Value::String(s) if s == "never" || s == "when_hidden" || s == "always" => return Ok(()),
|
||||
_ => {
|
||||
anyhow::bail!("Expected play_sound_when_agent_done to be a boolean or valid enum value")
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
29
crates/migrator/src/migrations/m_2026_04_01/settings.rs
Normal file
29
crates/migrator/src/migrations/m_2026_04_01/settings.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
pub fn restructure_profiles_with_settings_key(value: &mut Value) -> Result<()> {
|
||||
let Some(root_object) = value.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let Some(profiles) = root_object.get_mut("profiles") else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let Some(profiles_map) = profiles.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
for profile_value in profiles_map.values_mut() {
|
||||
if profile_value
|
||||
.as_object()
|
||||
.is_some_and(|m| m.contains_key("settings") || m.contains_key("base"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
*profile_value = serde_json::json!({ "settings": profile_value });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
50
crates/migrator/src/migrations/m_2026_04_10/settings.rs
Normal file
50
crates/migrator/src/migrations/m_2026_04_10/settings.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_settings;
|
||||
|
||||
const AGENT_KEY: &str = "agent";
|
||||
const PROFILES_KEY: &str = "profiles";
|
||||
const TOOL_PERMISSIONS_KEY: &str = "tool_permissions";
|
||||
const TOOLS_KEY: &str = "tools";
|
||||
const OLD_TOOL_NAME: &str = "web_search";
|
||||
const NEW_TOOL_NAME: &str = "search_web";
|
||||
|
||||
pub fn rename_web_search_to_search_web(value: &mut Value) -> Result<()> {
|
||||
migrate_settings(value, &mut migrate_agent_value)
|
||||
}
|
||||
|
||||
fn migrate_agent_value(object: &mut serde_json::Map<String, Value>) -> Result<()> {
|
||||
let Some(agent) = object.get_mut(AGENT_KEY).and_then(|v| v.as_object_mut()) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let Some(tools) = agent
|
||||
.get_mut(TOOL_PERMISSIONS_KEY)
|
||||
.and_then(|v| v.as_object_mut())
|
||||
.and_then(|tp| tp.get_mut(TOOLS_KEY))
|
||||
.and_then(|v| v.as_object_mut())
|
||||
{
|
||||
rename_key(tools);
|
||||
}
|
||||
|
||||
if let Some(profiles) = agent.get_mut(PROFILES_KEY).and_then(|v| v.as_object_mut()) {
|
||||
for (_profile_name, profile) in profiles.iter_mut() {
|
||||
if let Some(tools) = profile
|
||||
.as_object_mut()
|
||||
.and_then(|p| p.get_mut(TOOLS_KEY))
|
||||
.and_then(|v| v.as_object_mut())
|
||||
{
|
||||
rename_key(tools);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rename_key(tools: &mut serde_json::Map<String, Value>) {
|
||||
if let Some(value) = tools.remove(OLD_TOOL_NAME) {
|
||||
tools.insert(NEW_TOOL_NAME.to_string(), value);
|
||||
}
|
||||
}
|
||||
47
crates/migrator/src/migrations/m_2026_04_17/settings.rs
Normal file
47
crates/migrator/src/migrations/m_2026_04_17/settings.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::migrations::migrate_settings;
|
||||
|
||||
const SETTINGS_KEY: &str = "settings";
|
||||
const TITLE_BAR_KEY: &str = "title_bar";
|
||||
const OLD_KEY: &str = "show_branch_icon";
|
||||
const NEW_KEY: &str = "show_branch_status_icon";
|
||||
|
||||
pub fn promote_show_branch_icon_true_to_show_branch_status_icon(value: &mut Value) -> Result<()> {
|
||||
migrate_settings(value, &mut migrate_one)
|
||||
}
|
||||
|
||||
fn migrate_one(object: &mut serde_json::Map<String, Value>) -> Result<()> {
|
||||
migrate_title_bar_value(object);
|
||||
|
||||
if let Some(settings) = object
|
||||
.get_mut(SETTINGS_KEY)
|
||||
.and_then(|value| value.as_object_mut())
|
||||
{
|
||||
migrate_title_bar_value(settings);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn migrate_title_bar_value(object: &mut serde_json::Map<String, Value>) {
|
||||
let Some(title_bar) = object
|
||||
.get_mut(TITLE_BAR_KEY)
|
||||
.and_then(|value| value.as_object_mut())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(old_value) = title_bar.remove(OLD_KEY) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if title_bar.contains_key(NEW_KEY) {
|
||||
return;
|
||||
}
|
||||
|
||||
if old_value == Value::Bool(true) {
|
||||
title_bar.insert(NEW_KEY.to_string(), Value::Bool(true));
|
||||
}
|
||||
}
|
||||
38
crates/migrator/src/migrations/m_2026_05_04/settings.rs
Normal file
38
crates/migrator/src/migrations/m_2026_05_04/settings.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_ROOT_KEY_VALUE_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns =
|
||||
&[(SETTINGS_ROOT_KEY_VALUE_PATTERN, replace_setting_value)];
|
||||
|
||||
fn replace_setting_value(
|
||||
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)?;
|
||||
|
||||
if setting_name != "hide_mouse" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value_capture_ix = query.capture_index_for_name("value")?;
|
||||
let value_range = mat
|
||||
.nodes_for_capture_index(value_capture_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let value = contents.get(value_range.clone())?;
|
||||
|
||||
if value.trim() != "\"on_typing_and_movement\"" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((value_range, "\"on_typing_and_action\"".to_string()))
|
||||
}
|
||||
5425
crates/migrator/src/migrator.rs
Normal file
5425
crates/migrator/src/migrator.rs
Normal file
File diff suppressed because it is too large
Load Diff
13
crates/migrator/src/patterns.rs
Normal file
13
crates/migrator/src/patterns.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
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_ASSISTANT_PATTERN, SETTINGS_ASSISTANT_TOOLS_PATTERN,
|
||||
SETTINGS_DUPLICATED_AGENT_PATTERN, SETTINGS_EDIT_PREDICTIONS_ASSISTANT_PATTERN,
|
||||
SETTINGS_LANGUAGES_PATTERN, SETTINGS_NESTED_KEY_VALUE_PATTERN, SETTINGS_ROOT_KEY_VALUE_PATTERN,
|
||||
};
|
||||
77
crates/migrator/src/patterns/keymap.rs
Normal file
77
crates/migrator/src/patterns/keymap.rs
Normal file
@@ -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")
|
||||
)"#;
|
||||
110
crates/migrator/src/patterns/settings.rs
Normal file
110
crates/migrator/src/patterns/settings.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
pub const SETTINGS_ROOT_KEY_VALUE_PATTERN: &str = r#"(document
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @name)
|
||||
value: (_) @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")
|
||||
)"#;
|
||||
|
||||
pub const SETTINGS_ASSISTANT_TOOLS_PATTERN: &str = r#"(document
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @assistant)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @profiles)
|
||||
value: (object
|
||||
(pair
|
||||
key: (_)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @tools_key)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @tool_name)
|
||||
value: (_) @tool_value
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @assistant "assistant")
|
||||
(#eq? @profiles "profiles")
|
||||
(#eq? @tools_key "tools")
|
||||
)"#;
|
||||
|
||||
pub const SETTINGS_ASSISTANT_PATTERN: &str = r#"(document
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @key)
|
||||
)
|
||||
)
|
||||
(#eq? @key "assistant")
|
||||
)"#;
|
||||
|
||||
pub const SETTINGS_EDIT_PREDICTIONS_ASSISTANT_PATTERN: &str = r#"(document
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @edit_predictions)
|
||||
value: (object
|
||||
(pair key: (string (string_content) @enabled_in_assistant))
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @edit_predictions "edit_predictions")
|
||||
(#eq? @enabled_in_assistant "enabled_in_assistant")
|
||||
)"#;
|
||||
|
||||
pub const SETTINGS_DUPLICATED_AGENT_PATTERN: &str = r#"(document
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @agent1)
|
||||
value: (_)
|
||||
) @pair1
|
||||
(pair
|
||||
key: (string (string_content) @agent2)
|
||||
value: (_)
|
||||
)
|
||||
)
|
||||
(#eq? @agent1 "agent")
|
||||
(#eq? @agent2 "agent")
|
||||
)"#;
|
||||
Reference in New Issue
Block a user