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