digraph.rs

  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}