1use std::sync::Arc;
2
3use collections::HashMap;
4use gpui::AppContext;
5use settings::Settings;
6use std::sync::LazyLock;
7use ui::ViewContext;
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
37impl Vim {
38 pub fn insert_digraph(
39 &mut self,
40 first_char: char,
41 second_char: char,
42 cx: &mut ViewContext<Self>,
43 ) {
44 let text = lookup_digraph(first_char, second_char, cx);
45
46 self.pop_operator(cx);
47 if self.editor_input_enabled() {
48 self.update_editor(cx, |_, editor, cx| editor.insert(&text, cx));
49 } else {
50 self.input_ignored(text, cx);
51 }
52 }
53}
54
55#[cfg(test)]
56mod test {
57 use collections::HashMap;
58 use settings::SettingsStore;
59
60 use crate::{
61 state::Mode,
62 test::{NeovimBackedTestContext, VimTestContext},
63 VimSettings,
64 };
65
66 #[gpui::test]
67 async fn test_digraph_insert_mode(cx: &mut gpui::TestAppContext) {
68 let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;
69
70 cx.set_shared_state("Hellˇo").await;
71 cx.simulate_shared_keystrokes("a ctrl-k o : escape").await;
72 cx.shared_state().await.assert_eq("Helloˇö");
73
74 cx.set_shared_state("Hellˇo").await;
75 cx.simulate_shared_keystrokes("a ctrl-k : o escape").await;
76 cx.shared_state().await.assert_eq("Helloˇö");
77
78 cx.set_shared_state("Hellˇo").await;
79 cx.simulate_shared_keystrokes("i ctrl-k o : escape").await;
80 cx.shared_state().await.assert_eq("Hellˇöo");
81 }
82
83 #[gpui::test]
84 async fn test_digraph_insert_multicursor(cx: &mut gpui::TestAppContext) {
85 let mut cx: VimTestContext = VimTestContext::new(cx, true).await;
86
87 cx.set_state("Hellˇo wˇorld", Mode::Normal);
88 cx.simulate_keystrokes("a ctrl-k o : escape");
89 cx.assert_state("Helloˇö woˇörld", Mode::Normal);
90 }
91
92 #[gpui::test]
93 async fn test_digraph_replace(cx: &mut gpui::TestAppContext) {
94 let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;
95
96 cx.set_shared_state("Hellˇo").await;
97 cx.simulate_shared_keystrokes("r ctrl-k o :").await;
98 cx.shared_state().await.assert_eq("Hellˇö");
99 }
100
101 #[gpui::test]
102 async fn test_digraph_find(cx: &mut gpui::TestAppContext) {
103 let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;
104
105 cx.set_shared_state("ˇHellö world").await;
106 cx.simulate_shared_keystrokes("f ctrl-k o :").await;
107 cx.shared_state().await.assert_eq("Hellˇö world");
108
109 cx.set_shared_state("ˇHellö world").await;
110 cx.simulate_shared_keystrokes("t ctrl-k o :").await;
111 cx.shared_state().await.assert_eq("Helˇlö world");
112 }
113
114 #[gpui::test]
115 async fn test_digraph_replace_mode(cx: &mut gpui::TestAppContext) {
116 let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;
117
118 cx.set_shared_state("ˇHello").await;
119 cx.simulate_shared_keystrokes(
120 "shift-r ctrl-k a ' ctrl-k e ` ctrl-k i : ctrl-k o ~ ctrl-k u - escape",
121 )
122 .await;
123 cx.shared_state().await.assert_eq("áèïõˇū");
124 }
125
126 #[gpui::test]
127 async fn test_digraph_custom(cx: &mut gpui::TestAppContext) {
128 let mut cx: VimTestContext = VimTestContext::new(cx, true).await;
129
130 cx.update_global(|store: &mut SettingsStore, cx| {
131 store.update_user_settings::<VimSettings>(cx, |s| {
132 let mut custom_digraphs = HashMap::default();
133 custom_digraphs.insert("|-".into(), "⊢".into());
134 custom_digraphs.insert(":)".into(), "👨💻".into());
135 s.custom_digraphs = Some(custom_digraphs);
136 });
137 });
138
139 cx.set_state("ˇ", Mode::Normal);
140 cx.simulate_keystrokes("a ctrl-k | - escape");
141 cx.assert_state("ˇ⊢", Mode::Normal);
142
143 // Test support for multi-codepoint mappings
144 cx.set_state("ˇ", Mode::Normal);
145 cx.simulate_keystrokes("a ctrl-k : ) escape");
146 cx.assert_state("ˇ👨💻", Mode::Normal);
147 }
148
149 #[gpui::test]
150 async fn test_digraph_keymap_conflict(cx: &mut gpui::TestAppContext) {
151 let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;
152
153 cx.set_shared_state("Hellˇo").await;
154 cx.simulate_shared_keystrokes("a ctrl-k s , escape").await;
155 cx.shared_state().await.assert_eq("Helloˇş");
156 }
157}