5425 lines · 150.0 KB · Rust
RawDownload
//! ## When to create a migration and why?//! A migration is necessary when keymap actions or settings are renamed or transformed (e.g., from an array to a string, a string to an array, a boolean to an enum, etc.).//!//! This ensures that users with outdated settings are automatically updated to use the corresponding new settings internally.//! It also provides a quick way to migrate their existing settings to the latest state using button in UI.//!//! ## How to create a migration?//! Migrations use Tree-sitter to query commonly used patterns, such as actions with a string or actions with an array where the second argument is an object, etc.//! Once queried, *you can filter out the modified items* and write the replacement logic.//!//! You *must not* modify previous migrations; always create new ones instead.//! This is important because if a user is in an intermediate state, they can smoothly transition to the latest state.//! Modifying existing migrations means they will only work for users upgrading from version x-1 to x, but not from x-2 to x, and so on, where x is the latest version.//!//! You only need to write replacement logic for x-1 to x because you can be certain that, internally, every user will be at x-1, regardless of their on disk state. use anyhow::{Context as _, Result};use settings_json::{infer_json_indent_size, parse_json_with_comments, update_value_in_json_text};use std::{cmp::Reverse, ops::Range, sync::LazyLock};use streaming_iterator::StreamingIterator;use tree_sitter::{Query, QueryMatch}; use patterns::SETTINGS_NESTED_KEY_VALUE_PATTERN; mod migrations;mod patterns; fn migrate(text: &str, patterns: MigrationPatterns, query: &Query) -> Result<Option<String>> {    let mut parser = tree_sitter::Parser::new();    parser.set_language(&tree_sitter_json::LANGUAGE.into())?;    let syntax_tree = parser        .parse(text, None)        .context("failed to parse settings")?;     let mut cursor = tree_sitter::QueryCursor::new();    let mut matches = cursor.matches(query, syntax_tree.root_node(), text.as_bytes());     let mut edits = vec![];    while let Some(mat) = matches.next() {        if let Some((_, callback)) = patterns.get(mat.pattern_index) {            edits.extend(callback(text, mat, query));        }    }     edits.sort_by_key(|(range, _)| (range.start, Reverse(range.end)));    edits.dedup_by(|(range_b, _), (range_a, _)| {        range_a.contains(&range_b.start) || range_a.contains(&range_b.end)    });     if edits.is_empty() {        Ok(None)    } else {        let mut new_text = text.to_string();        for (range, replacement) in edits.iter().rev() {            new_text.replace_range(range.clone(), replacement);        }        if new_text == text {            log::error!(                "Edits computed for configuration migration do not cause a change: {:?}",                edits            );            Ok(None)        } else {            Ok(Some(new_text))        }    }} /// Runs the provided migrations on the given text./// Will automatically return `Ok(None)` if there's no content to migrate.fn run_migrations(text: &str, migrations: &[MigrationType]) -> Result<Option<String>> {    if text.is_empty() {        return Ok(None);    }     let mut current_text = text.to_string();    let mut result: Option<String> = None;    let json_indent_size = infer_json_indent_size(&current_text);    for migration in migrations.iter() {        let migrated_text = match migration {            MigrationType::TreeSitter(patterns, query) => migrate(&current_text, patterns, query)?,            MigrationType::Json(callback) => {                if current_text.trim().is_empty() {                    return Ok(None);                }                let old_content: serde_json_lenient::Value =                    parse_json_with_comments(&current_text)?;                let old_value = serde_json::to_value(&old_content).unwrap();                let mut new_value = old_value.clone();                callback(&mut new_value)?;                if new_value != old_value {                    let mut current = current_text.clone();                    let mut edits = vec![];                    update_value_in_json_text(                        &mut current,                        &mut vec![],                        json_indent_size,                        &old_value,                        &new_value,                        &mut edits,                    );                    let mut migrated_text = current_text.clone();                    for (range, replacement) in edits.into_iter() {                        migrated_text.replace_range(range, &replacement);                    }                    Some(migrated_text)                } else {                    None                }            }        };        if let Some(migrated_text) = migrated_text {            current_text = migrated_text.clone();            result = Some(migrated_text);        }    }    Ok(result.filter(|new_text| text != new_text))} pub fn migrate_keymap(text: &str) -> Result<Option<String>> {    let migrations: &[MigrationType] = &[        MigrationType::TreeSitter(            migrations::m_2025_01_29::KEYMAP_PATTERNS,            &KEYMAP_QUERY_2025_01_29,        ),        MigrationType::TreeSitter(            migrations::m_2025_01_30::KEYMAP_PATTERNS,            &KEYMAP_QUERY_2025_01_30,        ),        MigrationType::TreeSitter(            migrations::m_2025_03_03::KEYMAP_PATTERNS,            &KEYMAP_QUERY_2025_03_03,        ),        MigrationType::TreeSitter(            migrations::m_2025_03_06::KEYMAP_PATTERNS,            &KEYMAP_QUERY_2025_03_06,        ),        MigrationType::TreeSitter(            migrations::m_2025_04_15::KEYMAP_PATTERNS,            &KEYMAP_QUERY_2025_04_15,        ),        MigrationType::TreeSitter(            migrations::m_2025_12_08::KEYMAP_PATTERNS,            &KEYMAP_QUERY_2025_12_08,        ),        MigrationType::TreeSitter(            migrations::m_2026_03_23::KEYMAP_PATTERNS,            &KEYMAP_QUERY_2026_03_23,        ),    ];    run_migrations(text, migrations)} enum MigrationType<'a> {    TreeSitter(MigrationPatterns, &'a Query),    Json(fn(&mut serde_json::Value) -> Result<()>),} pub fn migrate_settings(text: &str) -> Result<Option<String>> {    let migrations: &[MigrationType] = &[        MigrationType::TreeSitter(            migrations::m_2025_01_02::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_01_02,        ),        MigrationType::TreeSitter(            migrations::m_2025_01_29::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_01_29,        ),        MigrationType::TreeSitter(            migrations::m_2025_01_30::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_01_30,        ),        MigrationType::TreeSitter(            migrations::m_2025_03_29::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_03_29,        ),        MigrationType::TreeSitter(            migrations::m_2025_04_15::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_04_15,        ),        MigrationType::TreeSitter(            migrations::m_2025_04_21::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_04_21,        ),        MigrationType::TreeSitter(            migrations::m_2025_04_23::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_04_23,        ),        MigrationType::TreeSitter(            migrations::m_2025_05_05::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_05_05,        ),        MigrationType::TreeSitter(            migrations::m_2025_05_08::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_05_08,        ),        MigrationType::TreeSitter(            migrations::m_2025_06_16::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_06_16,        ),        MigrationType::TreeSitter(            migrations::m_2025_06_25::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_06_25,        ),        MigrationType::TreeSitter(            migrations::m_2025_06_27::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_06_27,        ),        MigrationType::TreeSitter(            migrations::m_2025_07_08::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_07_08,        ),        MigrationType::Json(migrations::m_2025_10_01::flatten_code_actions_formatters),        MigrationType::Json(migrations::m_2025_10_02::remove_formatters_on_save),        MigrationType::TreeSitter(            migrations::m_2025_10_03::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_10_03,        ),        MigrationType::Json(migrations::m_2025_10_16::restore_code_actions_on_format),        MigrationType::Json(migrations::m_2025_10_17::make_file_finder_include_ignored_an_enum),        MigrationType::Json(migrations::m_2025_10_21::make_relative_line_numbers_an_enum),        MigrationType::TreeSitter(            migrations::m_2025_11_12::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_11_12,        ),        MigrationType::TreeSitter(            migrations::m_2025_12_01::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_12_01,        ),        MigrationType::TreeSitter(            migrations::m_2025_11_20::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_11_20,        ),        MigrationType::Json(migrations::m_2025_11_25::remove_context_server_source),        MigrationType::TreeSitter(            migrations::m_2025_12_15::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2025_12_15,        ),        MigrationType::Json(migrations::m_2025_01_27::make_auto_indent_an_enum),        MigrationType::Json(            migrations::m_2026_02_02::move_edit_prediction_provider_to_edit_predictions,        ),        MigrationType::Json(migrations::m_2026_02_03::migrate_experimental_sweep_mercury),        MigrationType::Json(migrations::m_2026_02_04::migrate_tool_permission_defaults),        MigrationType::Json(migrations::m_2026_02_25::migrate_builtin_agent_servers_to_registry),        MigrationType::TreeSitter(            migrations::m_2026_03_16::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2026_03_16,        ),        MigrationType::Json(migrations::m_2026_03_30::make_play_sound_when_agent_done_an_enum),        MigrationType::Json(migrations::m_2026_04_01::restructure_profiles_with_settings_key),        MigrationType::Json(migrations::m_2026_04_10::rename_web_search_to_search_web),        MigrationType::Json(            migrations::m_2026_04_17::promote_show_branch_icon_true_to_show_branch_status_icon,        ),        MigrationType::TreeSitter(            migrations::m_2026_05_04::SETTINGS_PATTERNS,            &SETTINGS_QUERY_2026_05_04,        ),    ];    run_migrations(text, migrations)} pub fn migrate_edit_prediction_provider_settings(text: &str) -> Result<Option<String>> {    migrate(        text,        &[(            SETTINGS_NESTED_KEY_VALUE_PATTERN,            migrations::m_2025_01_29::replace_edit_prediction_provider_setting,        )],        &EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY,    )} pub type MigrationPatterns = &'static [(    &'static str,    fn(&str, &QueryMatch, &Query) -> Option<(Range<usize>, String)>,)]; macro_rules! define_query {    ($var_name:ident, $patterns_path:path) => {        static $var_name: LazyLock<Query> = LazyLock::new(|| {            Query::new(                &tree_sitter_json::LANGUAGE.into(),                &$patterns_path                    .iter()                    .map(|pattern| pattern.0)                    .collect::<String>(),            )            .unwrap()        });    };} // keymapdefine_query!(    KEYMAP_QUERY_2025_01_29,    migrations::m_2025_01_29::KEYMAP_PATTERNS);define_query!(    KEYMAP_QUERY_2025_01_30,    migrations::m_2025_01_30::KEYMAP_PATTERNS);define_query!(    KEYMAP_QUERY_2025_03_03,    migrations::m_2025_03_03::KEYMAP_PATTERNS);define_query!(    KEYMAP_QUERY_2025_03_06,    migrations::m_2025_03_06::KEYMAP_PATTERNS);define_query!(    KEYMAP_QUERY_2025_04_15,    migrations::m_2025_04_15::KEYMAP_PATTERNS); // settingsdefine_query!(    SETTINGS_QUERY_2025_01_02,    migrations::m_2025_01_02::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_01_29,    migrations::m_2025_01_29::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_01_30,    migrations::m_2025_01_30::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_03_29,    migrations::m_2025_03_29::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_04_15,    migrations::m_2025_04_15::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_04_21,    migrations::m_2025_04_21::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_04_23,    migrations::m_2025_04_23::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_05_05,    migrations::m_2025_05_05::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_05_08,    migrations::m_2025_05_08::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_06_16,    migrations::m_2025_06_16::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_06_25,    migrations::m_2025_06_25::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_06_27,    migrations::m_2025_06_27::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_07_08,    migrations::m_2025_07_08::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_10_03,    migrations::m_2025_10_03::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_11_12,    migrations::m_2025_11_12::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_12_01,    migrations::m_2025_12_01::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2025_11_20,    migrations::m_2025_11_20::SETTINGS_PATTERNS);define_query!(    KEYMAP_QUERY_2025_12_08,    migrations::m_2025_12_08::KEYMAP_PATTERNS);define_query!(    SETTINGS_QUERY_2025_12_15,    migrations::m_2025_12_15::SETTINGS_PATTERNS);define_query!(    SETTINGS_QUERY_2026_03_16,    migrations::m_2026_03_16::SETTINGS_PATTERNS);define_query!(    KEYMAP_QUERY_2026_03_23,    migrations::m_2026_03_23::KEYMAP_PATTERNS);define_query!(    SETTINGS_QUERY_2026_05_04,    migrations::m_2026_05_04::SETTINGS_PATTERNS); // custom querystatic EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {    Query::new(        &tree_sitter_json::LANGUAGE.into(),        SETTINGS_NESTED_KEY_VALUE_PATTERN,    )    .unwrap()}); #[cfg(test)]mod tests {    use super::*;    use unindent::Unindent as _;     #[track_caller]    fn assert_migrated_correctly(migrated: Option<String>, expected: Option<&str>) {        match (&migrated, &expected) {            (Some(migrated), Some(expected)) => {                pretty_assertions::assert_str_eq!(expected, migrated);            }            _ => {                pretty_assertions::assert_eq!(migrated.as_deref(), expected);            }        }    }     #[track_caller]    fn assert_migrate_keymap(input: &str, output: Option<&str>) {        let migrated = migrate_keymap(input).unwrap();        pretty_assertions::assert_eq!(migrated.as_deref(), output);    }     #[track_caller]    fn assert_migrate_settings(input: &str, output: Option<&str>) {        let migrated = migrate_settings(input).unwrap();        assert_migrated_correctly(migrated.clone(), output);         // expect that rerunning the migration does not result in another migration        if let Some(migrated) = migrated {            let rerun = migrate_settings(&migrated).unwrap();            assert_migrated_correctly(rerun, None);        }    }     #[track_caller]    fn assert_migrate_with_migrations(        migrations: &[MigrationType],        input: &str,        output: Option<&str>,    ) {        let migrated = run_migrations(input, migrations).unwrap();        assert_migrated_correctly(migrated.clone(), output);         // expect that rerunning the migration does not result in another migration        if let Some(migrated) = migrated {            let rerun = run_migrations(&migrated, migrations).unwrap();            assert_migrated_correctly(rerun, None);        }    }     #[test]    fn test_empty_content() {        assert_migrate_settings("", None)    }     #[test]    fn test_replace_array_with_single_string() {        assert_migrate_keymap(            r#"            [                {                    "bindings": {                        "cmd-1": ["workspace::ActivatePaneInDirection", "Up"]                    }                }            ]            "#,            Some(                r#"            [                {                    "bindings": {                        "cmd-1": "workspace::ActivatePaneUp"                    }                }            ]            "#,            ),        )    }     #[test]    fn test_replace_action_argument_object_with_single_value() {        assert_migrate_keymap(

Lines 1–500 of 5425.