digraph.rs

  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}