1use collections::HashMap;
  2use std::{ops::Range, sync::LazyLock};
  3use tree_sitter::{Query, QueryMatch};
  4
  5use crate::MigrationPatterns;
  6use crate::patterns::{
  7    KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN, KEYMAP_ACTION_ARRAY_PATTERN,
  8    KEYMAP_ACTION_STRING_PATTERN, KEYMAP_CONTEXT_PATTERN,
  9};
 10
 11pub const KEYMAP_PATTERNS: MigrationPatterns = &[
 12    (
 13        KEYMAP_ACTION_ARRAY_PATTERN,
 14        replace_array_with_single_string,
 15    ),
 16    (
 17        KEYMAP_ACTION_ARRAY_ARGUMENT_AS_OBJECT_PATTERN,
 18        replace_action_argument_object_with_single_value,
 19    ),
 20    (KEYMAP_ACTION_STRING_PATTERN, replace_string_action),
 21    (KEYMAP_CONTEXT_PATTERN, rename_context_key),
 22];
 23
 24fn replace_array_with_single_string(
 25    contents: &str,
 26    mat: &QueryMatch,
 27    query: &Query,
 28) -> Option<(Range<usize>, String)> {
 29    let array_ix = query.capture_index_for_name("array")?;
 30    let action_name_ix = query.capture_index_for_name("action_name")?;
 31    let argument_ix = query.capture_index_for_name("argument")?;
 32
 33    let action_name = contents.get(
 34        mat.nodes_for_capture_index(action_name_ix)
 35            .next()?
 36            .byte_range(),
 37    )?;
 38    let argument = contents.get(
 39        mat.nodes_for_capture_index(argument_ix)
 40            .next()?
 41            .byte_range(),
 42    )?;
 43
 44    let replacement = TRANSFORM_ARRAY.get(&(action_name, argument))?;
 45    let replacement_as_string = format!("\"{replacement}\"");
 46    let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
 47
 48    Some((range_to_replace, replacement_as_string))
 49}
 50
 51static TRANSFORM_ARRAY: LazyLock<HashMap<(&str, &str), &str>> = LazyLock::new(|| {
 52    HashMap::from_iter([
 53        // activate
 54        (
 55            ("workspace::ActivatePaneInDirection", "Up"),
 56            "workspace::ActivatePaneUp",
 57        ),
 58        (
 59            ("workspace::ActivatePaneInDirection", "Down"),
 60            "workspace::ActivatePaneDown",
 61        ),
 62        (
 63            ("workspace::ActivatePaneInDirection", "Left"),
 64            "workspace::ActivatePaneLeft",
 65        ),
 66        (
 67            ("workspace::ActivatePaneInDirection", "Right"),
 68            "workspace::ActivatePaneRight",
 69        ),
 70        // swap
 71        (
 72            ("workspace::SwapPaneInDirection", "Up"),
 73            "workspace::SwapPaneUp",
 74        ),
 75        (
 76            ("workspace::SwapPaneInDirection", "Down"),
 77            "workspace::SwapPaneDown",
 78        ),
 79        (
 80            ("workspace::SwapPaneInDirection", "Left"),
 81            "workspace::SwapPaneLeft",
 82        ),
 83        (
 84            ("workspace::SwapPaneInDirection", "Right"),
 85            "workspace::SwapPaneRight",
 86        ),
 87        // menu
 88        (
 89            ("app_menu::NavigateApplicationMenuInDirection", "Left"),
 90            "app_menu::ActivateMenuLeft",
 91        ),
 92        (
 93            ("app_menu::NavigateApplicationMenuInDirection", "Right"),
 94            "app_menu::ActivateMenuRight",
 95        ),
 96        // vim push
 97        (("vim::PushOperator", "Change"), "vim::PushChange"),
 98        (("vim::PushOperator", "Delete"), "vim::PushDelete"),
 99        (("vim::PushOperator", "Yank"), "vim::PushYank"),
100        (("vim::PushOperator", "Replace"), "vim::PushReplace"),
101        (
102            ("vim::PushOperator", "DeleteSurrounds"),
103            "vim::PushDeleteSurrounds",
104        ),
105        (("vim::PushOperator", "Mark"), "vim::PushMark"),
106        (("vim::PushOperator", "Indent"), "vim::PushIndent"),
107        (("vim::PushOperator", "Outdent"), "vim::PushOutdent"),
108        (("vim::PushOperator", "AutoIndent"), "vim::PushAutoIndent"),
109        (("vim::PushOperator", "Rewrap"), "vim::PushRewrap"),
110        (
111            ("vim::PushOperator", "ShellCommand"),
112            "vim::PushShellCommand",
113        ),
114        (("vim::PushOperator", "Lowercase"), "vim::PushLowercase"),
115        (("vim::PushOperator", "Uppercase"), "vim::PushUppercase"),
116        (
117            ("vim::PushOperator", "OppositeCase"),
118            "vim::PushOppositeCase",
119        ),
120        (("vim::PushOperator", "Register"), "vim::PushRegister"),
121        (
122            ("vim::PushOperator", "RecordRegister"),
123            "vim::PushRecordRegister",
124        ),
125        (
126            ("vim::PushOperator", "ReplayRegister"),
127            "vim::PushReplayRegister",
128        ),
129        (
130            ("vim::PushOperator", "ReplaceWithRegister"),
131            "vim::PushReplaceWithRegister",
132        ),
133        (
134            ("vim::PushOperator", "ToggleComments"),
135            "vim::PushToggleComments",
136        ),
137        // vim switch
138        (("vim::SwitchMode", "Normal"), "vim::SwitchToNormalMode"),
139        (("vim::SwitchMode", "Insert"), "vim::SwitchToInsertMode"),
140        (("vim::SwitchMode", "Replace"), "vim::SwitchToReplaceMode"),
141        (("vim::SwitchMode", "Visual"), "vim::SwitchToVisualMode"),
142        (
143            ("vim::SwitchMode", "VisualLine"),
144            "vim::SwitchToVisualLineMode",
145        ),
146        (
147            ("vim::SwitchMode", "VisualBlock"),
148            "vim::SwitchToVisualBlockMode",
149        ),
150        (
151            ("vim::SwitchMode", "HelixNormal"),
152            "vim::SwitchToHelixNormalMode",
153        ),
154        // vim resize
155        (("vim::ResizePane", "Widen"), "vim::ResizePaneRight"),
156        (("vim::ResizePane", "Narrow"), "vim::ResizePaneLeft"),
157        (("vim::ResizePane", "Shorten"), "vim::ResizePaneDown"),
158        (("vim::ResizePane", "Lengthen"), "vim::ResizePaneUp"),
159        // fold at level
160        (("editor::FoldAtLevel", "1"), "editor::FoldAtLevel1"),
161        (("editor::FoldAtLevel", "2"), "editor::FoldAtLevel2"),
162        (("editor::FoldAtLevel", "3"), "editor::FoldAtLevel3"),
163        (("editor::FoldAtLevel", "4"), "editor::FoldAtLevel4"),
164        (("editor::FoldAtLevel", "5"), "editor::FoldAtLevel5"),
165        (("editor::FoldAtLevel", "6"), "editor::FoldAtLevel6"),
166        (("editor::FoldAtLevel", "7"), "editor::FoldAtLevel7"),
167        (("editor::FoldAtLevel", "8"), "editor::FoldAtLevel8"),
168        (("editor::FoldAtLevel", "9"), "editor::FoldAtLevel9"),
169    ])
170});
171
172/// [ "editor::FoldAtLevel", { "level": 1 } ] -> [ "editor::FoldAtLevel", 1 ]
173fn replace_action_argument_object_with_single_value(
174    contents: &str,
175    mat: &QueryMatch,
176    query: &Query,
177) -> Option<(Range<usize>, String)> {
178    let array_ix = query.capture_index_for_name("array")?;
179    let action_name_ix = query.capture_index_for_name("action_name")?;
180    let argument_key_ix = query.capture_index_for_name("argument_key")?;
181    let argument_value_ix = query.capture_index_for_name("argument_value")?;
182
183    let action_name = contents.get(
184        mat.nodes_for_capture_index(action_name_ix)
185            .next()?
186            .byte_range(),
187    )?;
188    let argument_key = contents.get(
189        mat.nodes_for_capture_index(argument_key_ix)
190            .next()?
191            .byte_range(),
192    )?;
193    let argument_value = contents.get(
194        mat.nodes_for_capture_index(argument_value_ix)
195            .next()?
196            .byte_range(),
197    )?;
198
199    let new_action_name = UNWRAP_OBJECTS.get(&action_name)?.get(&argument_key)?;
200
201    let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
202    let replacement = format!("[\"{}\", {}]", new_action_name, argument_value);
203    Some((range_to_replace, replacement))
204}
205
206/// "ctrl-k ctrl-1": [ "editor::PushOperator", { "Object": {} } ] -> [ "editor::vim::PushObject", {} ]
207static UNWRAP_OBJECTS: LazyLock<HashMap<&str, HashMap<&str, &str>>> = LazyLock::new(|| {
208    HashMap::from_iter([
209        (
210            "editor::FoldAtLevel",
211            HashMap::from_iter([("level", "editor::FoldAtLevel")]),
212        ),
213        (
214            "vim::PushOperator",
215            HashMap::from_iter([
216                ("Object", "vim::PushObject"),
217                ("FindForward", "vim::PushFindForward"),
218                ("FindBackward", "vim::PushFindBackward"),
219                ("Sneak", "vim::PushSneak"),
220                ("SneakBackward", "vim::PushSneakBackward"),
221                ("AddSurrounds", "vim::PushAddSurrounds"),
222                ("ChangeSurrounds", "vim::PushChangeSurrounds"),
223                ("Jump", "vim::PushJump"),
224                ("Digraph", "vim::PushDigraph"),
225                ("Literal", "vim::PushLiteral"),
226            ]),
227        ),
228    ])
229});
230
231fn replace_string_action(
232    contents: &str,
233    mat: &QueryMatch,
234    query: &Query,
235) -> Option<(Range<usize>, String)> {
236    let action_name_ix = query.capture_index_for_name("action_name")?;
237    let action_name_node = mat.nodes_for_capture_index(action_name_ix).next()?;
238    let action_name_range = action_name_node.byte_range();
239    let action_name = contents.get(action_name_range.clone())?;
240
241    if let Some(new_action_name) = STRING_REPLACE.get(&action_name) {
242        return Some((action_name_range, new_action_name.to_string()));
243    }
244
245    None
246}
247
248/// "ctrl-k ctrl-1": "inline_completion::ToggleMenu" -> "edit_prediction::ToggleMenu"
249static STRING_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
250    HashMap::from_iter([
251        (
252            "inline_completion::ToggleMenu",
253            "edit_prediction::ToggleMenu",
254        ),
255        ("editor::NextInlineCompletion", "editor::NextEditPrediction"),
256        (
257            "editor::PreviousInlineCompletion",
258            "editor::PreviousEditPrediction",
259        ),
260        (
261            "editor::AcceptPartialInlineCompletion",
262            "editor::AcceptPartialEditPrediction",
263        ),
264        ("editor::ShowInlineCompletion", "editor::ShowEditPrediction"),
265        (
266            "editor::AcceptInlineCompletion",
267            "editor::AcceptEditPrediction",
268        ),
269        (
270            "editor::ToggleInlineCompletions",
271            "editor::ToggleEditPrediction",
272        ),
273    ])
274});
275
276fn rename_context_key(
277    contents: &str,
278    mat: &QueryMatch,
279    query: &Query,
280) -> Option<(Range<usize>, String)> {
281    let context_predicate_ix = query.capture_index_for_name("context_predicate")?;
282    let context_predicate_range = mat
283        .nodes_for_capture_index(context_predicate_ix)
284        .next()?
285        .byte_range();
286    let old_predicate = contents.get(context_predicate_range.clone())?.to_string();
287    let mut new_predicate = old_predicate.to_string();
288    for (old_key, new_key) in CONTEXT_REPLACE.iter() {
289        new_predicate = new_predicate.replace(old_key, new_key);
290    }
291    if new_predicate != old_predicate {
292        Some((context_predicate_range, new_predicate))
293    } else {
294        None
295    }
296}
297
298/// "context": "Editor && inline_completion && !showing_completions" -> "Editor && edit_prediction && !showing_completions"
299pub static CONTEXT_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
300    HashMap::from_iter([
301        ("inline_completion", "edit_prediction"),
302        (
303            "inline_completion_requires_modifier",
304            "edit_prediction_requires_modifier",
305        ),
306    ])
307});