digraph.rs

  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}