keymap.rs

 1use collections::HashMap;
 2use convert_case::{Case, Casing};
 3use std::{ops::Range, sync::LazyLock};
 4use tree_sitter::{Query, QueryMatch};
 5
 6use crate::MigrationPatterns;
 7use crate::patterns::KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN;
 8
 9pub const KEYMAP_PATTERNS: MigrationPatterns = &[(
10    KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN,
11    action_argument_snake_case,
12)];
13
14fn to_snake_case(text: &str) -> String {
15    text.to_case(Case::Snake)
16}
17
18fn action_argument_snake_case(
19    contents: &str,
20    mat: &QueryMatch,
21    query: &Query,
22) -> Option<(Range<usize>, String)> {
23    let array_ix = query.capture_index_for_name("array")?;
24    let action_name_ix = query.capture_index_for_name("action_name")?;
25    let argument_key_ix = query.capture_index_for_name("argument_key")?;
26    let argument_value_ix = query.capture_index_for_name("argument_value")?;
27    let action_name = contents.get(
28        mat.nodes_for_capture_index(action_name_ix)
29            .next()?
30            .byte_range(),
31    )?;
32
33    let replacement_key = ACTION_ARGUMENT_SNAKE_CASE_REPLACE.get(action_name)?;
34    let argument_key = contents.get(
35        mat.nodes_for_capture_index(argument_key_ix)
36            .next()?
37            .byte_range(),
38    )?;
39
40    if argument_key != *replacement_key {
41        return None;
42    }
43
44    let argument_value_node = mat.nodes_for_capture_index(argument_value_ix).next()?;
45    let argument_value = contents.get(argument_value_node.byte_range())?;
46
47    let new_key = to_snake_case(argument_key);
48    let new_value = if argument_value_node.kind() == "string" {
49        format!("\"{}\"", to_snake_case(argument_value.trim_matches('"')))
50    } else {
51        argument_value.to_string()
52    };
53
54    let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
55    let replacement = format!(
56        "[\"{}\", {{ \"{}\": {} }}]",
57        action_name, new_key, new_value
58    );
59
60    Some((range_to_replace, replacement))
61}
62
63static ACTION_ARGUMENT_SNAKE_CASE_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
64    HashMap::from_iter([
65        ("vim::NextWordStart", "ignorePunctuation"),
66        ("vim::NextWordEnd", "ignorePunctuation"),
67        ("vim::PreviousWordStart", "ignorePunctuation"),
68        ("vim::PreviousWordEnd", "ignorePunctuation"),
69        ("vim::MoveToNext", "partialWord"),
70        ("vim::MoveToPrev", "partialWord"),
71        ("vim::Down", "displayLines"),
72        ("vim::Up", "displayLines"),
73        ("vim::EndOfLine", "displayLines"),
74        ("vim::StartOfLine", "displayLines"),
75        ("vim::FirstNonWhitespace", "displayLines"),
76        ("pane::CloseActiveItem", "saveIntent"),
77        ("vim::Paste", "preserveClipboard"),
78        ("vim::Word", "ignorePunctuation"),
79        ("vim::Subword", "ignorePunctuation"),
80        ("vim::IndentObj", "includeBelow"),
81    ])
82});