1use std::sync::Arc;
2
3use collections::HashMap;
4use gpui::AppContext;
5use lazy_static::lazy_static;
6use settings::Settings;
7use ui::WindowContext;
8
9use crate::{Vim, VimSettings};
10
11mod default;
12
13lazy_static! {
14 static ref DEFAULT_DIGRAPHS_MAP: HashMap<String, Arc<str>> = {
15 let mut map = HashMap::default();
16 for &(a, b, c) in default::DEFAULT_DIGRAPHS {
17 let key = format!("{a}{b}");
18 let value = char::from_u32(c).unwrap().to_string().into();
19 map.insert(key, value);
20 }
21 map
22 };
23}
24
25fn lookup_digraph(a: char, b: char, cx: &AppContext) -> Arc<str> {
26 let custom_digraphs = &VimSettings::get_global(cx).custom_digraphs;
27 let input = format!("{a}{b}");
28 let reversed = format!("{b}{a}");
29
30 custom_digraphs
31 .get(&input)
32 .or_else(|| DEFAULT_DIGRAPHS_MAP.get(&input))
33 .or_else(|| custom_digraphs.get(&reversed))
34 .or_else(|| DEFAULT_DIGRAPHS_MAP.get(&reversed))
35 .cloned()
36 .unwrap_or_else(|| b.to_string().into())
37}
38
39pub fn insert_digraph(first_char: char, second_char: char, cx: &mut WindowContext) {
40 let text = lookup_digraph(first_char, second_char, &cx);
41
42 Vim::update(cx, |vim, cx| vim.pop_operator(cx));
43 if Vim::read(cx).state().editor_input_enabled() {
44 Vim::update(cx, |vim, cx| {
45 vim.update_active_editor(cx, |_, editor, cx| editor.insert(&text, cx));
46 });
47 } else {
48 Vim::active_editor_input_ignored(text, cx);
49 }
50}
51
52#[cfg(test)]
53mod test {
54 use collections::HashMap;
55 use settings::SettingsStore;
56
57 use crate::{
58 state::Mode,
59 test::{NeovimBackedTestContext, VimTestContext},
60 VimSettings,
61 };
62
63 #[gpui::test]
64 async fn test_digraph_insert_mode(cx: &mut gpui::TestAppContext) {
65 let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;
66
67 cx.set_shared_state("Hellˇo").await;
68 cx.simulate_shared_keystrokes("a ctrl-k o : escape").await;
69 cx.shared_state().await.assert_eq("Helloˇö");
70
71 cx.set_shared_state("Hellˇo").await;
72 cx.simulate_shared_keystrokes("a ctrl-k : o escape").await;
73 cx.shared_state().await.assert_eq("Helloˇö");
74
75 cx.set_shared_state("Hellˇo").await;
76 cx.simulate_shared_keystrokes("i ctrl-k o : escape").await;
77 cx.shared_state().await.assert_eq("Hellˇöo");
78 }
79
80 #[gpui::test]
81 async fn test_digraph_insert_multicursor(cx: &mut gpui::TestAppContext) {
82 let mut cx: VimTestContext = VimTestContext::new(cx, true).await;
83
84 cx.set_state("Hellˇo wˇorld", Mode::Normal);
85 cx.simulate_keystrokes("a ctrl-k o : escape");
86 cx.assert_state("Helloˇö woˇörld", Mode::Normal);
87 }
88
89 #[gpui::test]
90 async fn test_digraph_replace(cx: &mut gpui::TestAppContext) {
91 let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;
92
93 cx.set_shared_state("Hellˇo").await;
94 cx.simulate_shared_keystrokes("r ctrl-k o :").await;
95 cx.shared_state().await.assert_eq("Hellˇö");
96 }
97
98 #[gpui::test]
99 async fn test_digraph_find(cx: &mut gpui::TestAppContext) {
100 let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;
101
102 cx.set_shared_state("ˇHellö world").await;
103 cx.simulate_shared_keystrokes("f ctrl-k o :").await;
104 cx.shared_state().await.assert_eq("Hellˇö world");
105
106 cx.set_shared_state("ˇHellö world").await;
107 cx.simulate_shared_keystrokes("t ctrl-k o :").await;
108 cx.shared_state().await.assert_eq("Helˇlö world");
109 }
110
111 #[gpui::test]
112 async fn test_digraph_replace_mode(cx: &mut gpui::TestAppContext) {
113 let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;
114
115 cx.set_shared_state("ˇHello").await;
116 cx.simulate_shared_keystrokes(
117 "shift-r ctrl-k a ' ctrl-k e ` ctrl-k i : ctrl-k o ~ ctrl-k u - escape",
118 )
119 .await;
120 cx.shared_state().await.assert_eq("áèïõˇū");
121 }
122
123 #[gpui::test]
124 async fn test_digraph_custom(cx: &mut gpui::TestAppContext) {
125 let mut cx: VimTestContext = VimTestContext::new(cx, true).await;
126
127 cx.update_global(|store: &mut SettingsStore, cx| {
128 store.update_user_settings::<VimSettings>(cx, |s| {
129 let mut custom_digraphs = HashMap::default();
130 custom_digraphs.insert("|-".into(), "⊢".into());
131 custom_digraphs.insert(":)".into(), "👨💻".into());
132 s.custom_digraphs = Some(custom_digraphs);
133 });
134 });
135
136 cx.set_state("ˇ", Mode::Normal);
137 cx.simulate_keystrokes("a ctrl-k | - escape");
138 cx.assert_state("ˇ⊢", Mode::Normal);
139
140 // Test support for multi-codepoint mappings
141 cx.set_state("ˇ", Mode::Normal);
142 cx.simulate_keystrokes("a ctrl-k : ) escape");
143 cx.assert_state("ˇ👨💻", Mode::Normal);
144 }
145
146 #[gpui::test]
147 async fn test_digraph_keymap_conflict(cx: &mut gpui::TestAppContext) {
148 let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;
149
150 cx.set_shared_state("Hellˇo").await;
151 cx.simulate_shared_keystrokes("a ctrl-k s , escape").await;
152 cx.shared_state().await.assert_eq("Helloˇş");
153 }
154}