diff --git a/src/config.rs b/src/config.rs index c20509d..f77c19e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,6 +23,9 @@ pub struct Config { /// if not empty, only trigger completion with special keys #[serde(default = "default_trigger_characters")] pub trigger_characters: Vec, + /// if set, completion request with this string will trigger「方案選單」 + #[serde(default = "default_schema_trigger_character")] + pub schema_trigger_character: String, } /// settings that can be tweaked during running @@ -34,6 +37,8 @@ pub struct Settings { pub max_candidates: Option, /// if not empty, only trigger completion with special keys pub trigger_characters: Option>, + /// if set, completion request with this string will trigger「方案選單」 + pub schema_trigger_character: Option, } impl Default for Config { @@ -45,6 +50,7 @@ impl Default for Config { log_dir: default_log_dir(), max_candidates: default_max_candidates(), trigger_characters: default_trigger_characters(), + schema_trigger_character: default_schema_trigger_character(), } } } @@ -58,7 +64,7 @@ fn default_max_candidates() -> usize { } fn default_trigger_characters() -> Vec { - vec![] + Vec::default() } fn default_shared_data_dir() -> PathBuf { @@ -75,6 +81,10 @@ fn default_log_dir() -> PathBuf { proj_dirs.cache_dir().to_path_buf() } +fn default_schema_trigger_character() -> String { + String::default() +} + #[test] fn test_default_config() { let config: Config = Default::default(); @@ -84,4 +94,8 @@ fn test_default_config() { assert_eq!(config.log_dir, default_log_dir()); assert_eq!(config.max_candidates, default_max_candidates()); assert_eq!(config.trigger_characters, default_trigger_characters()); + assert_eq!( + config.schema_trigger_character, + default_schema_trigger_character() + ); } diff --git a/src/consts.rs b/src/consts.rs index bb86f4e..4a43d51 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -19,4 +19,6 @@ pub static RAW_RE: Lazy = Lazy::new(|| Regex::new(RAW_PTN).unwrap()); pub static AUTO_TRIGGER_RE: Lazy = Lazy::new(|| Regex::new(AUTO_TRIGGER_PTN).unwrap()); // keycodes -pub const K_BACKSPACE: i32 = 0xff08; +// note: run `xmodmap -pk` in shell +pub const KEY_BACKSPACE: i32 = 0xff08; +pub const KEY_F4: i32 = 0xffc1; diff --git a/src/input.rs b/src/input.rs index be41ae1..282f45e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,3 +1,4 @@ +use crate::consts::KEY_F4; use crate::rime::Rime; use crate::utils::{diff, DiffResult}; use ouroboros::self_referencing; @@ -78,13 +79,32 @@ impl InputState { } } - pub fn handle_new_input(&self, new_offset: usize, new_input: &Input) -> InputResult { + fn handle_schema(session_id: usize) -> InputResult { + let rime = Rime::global(); + rime.process_key(session_id, KEY_F4); + let raw_input = rime.get_raw_input(session_id); + InputResult { + session_id, + raw_input, + } + } + + pub fn handle_new_input( + &self, + new_offset: usize, + new_input: &Input, + schema_trigger: &str, + ) -> InputResult { let rime = Rime::global(); let session_id = rime.find_session(self.session_id); // new typing if self.offset != new_offset || self.session_id != session_id { rime.clear_composition(session_id); - return Self::handle_new_typing(session_id, new_input); + if !schema_trigger.is_empty() && new_input.borrow_pinyin() == &schema_trigger { + return Self::handle_schema(session_id); + } else { + return Self::handle_new_typing(session_id, new_input); + } } // continue last typing // handle pinyin @@ -94,7 +114,11 @@ impl InputState { DiffResult::Delete(suffix) => rime.delete_keys(session_id, suffix.len()), DiffResult::New => { rime.clear_composition(session_id); - rime.process_str(session_id, new_input.borrow_pinyin()); + if !schema_trigger.is_empty() && new_input.borrow_pinyin() == &schema_trigger { + rime.process_key(session_id, KEY_F4); + } else { + rime.process_str(session_id, new_input.borrow_pinyin()); + } } _ => (), } diff --git a/src/lsp.rs b/src/lsp.rs index 930d909..9dabd1c 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -94,6 +94,9 @@ impl Backend { self.compile_regex(&v).await; config.trigger_characters = v; } + if let Some(v) = settings.schema_trigger_character { + config.schema_trigger_character = v; + } } async fn create_work_done_progress(&self, token: NumberOrString) -> Result { @@ -137,17 +140,16 @@ impl Backend { } async fn get_completions(&self, uri: Url, position: Position) -> Option> { - let max_candidates = self.config.read().await.max_candidates; - let is_trigger_set = !self.config.read().await.trigger_characters.is_empty(); - let rime = Rime::global(); - // get new input let rope = self.documents.get(&uri.to_string())?; - let line_pos = Position::new(position.line, 0); - let line_begin = utils::position_to_offset(&rope, line_pos)?; + let line_begin = { + let line_pos = Position::new(position.line, 0); + utils::position_to_offset(&rope, line_pos)? + }; let curr_char = utils::position_to_offset(&rope, position)?; let new_input = { let re = self.regex.read().await; + let is_trigger_set = !self.config.read().await.trigger_characters.is_empty(); (curr_char <= rope.len_chars()).then(|| { let slice = rope.slice(line_begin..curr_char).as_str()?; if utils::need_to_check_trigger(is_trigger_set, slice) { @@ -165,11 +167,15 @@ impl Backend { session_id, raw_input, } = match (*last_state).as_ref() { - Some(state) => state.handle_new_input(new_offset, &new_input), + Some(state) => { + let schema_trigger = &self.config.read().await.schema_trigger_character; + state.handle_new_input(new_offset, &new_input, schema_trigger) + } None => InputState::handle_first_state(&new_input), }; // get candidates from current session + let rime = Rime::global(); let RimeResponse { submitted, candidates, @@ -190,8 +196,10 @@ impl Backend { // candidates to completions let range = Range::new(utils::offset_to_position(&rope, real_offset)?, position); let filter_text = new_input.borrow_raw_text().to_string(); - let order_to_sort_text = utils::build_order_to_sort_text(max_candidates); - + let order_to_sort_text = { + let max_candidates = self.config.read().await.max_candidates; + utils::build_order_to_sort_text(max_candidates) + }; let candidate_to_completion_item = move |c: Candidate| -> CompletionItem { CompletionItem { label: format!("{}. {}{}", c.order, &submitted, &c.text), @@ -206,11 +214,12 @@ impl Backend { ..Default::default() } }; + // update input state *last_state = Some(InputState::new(new_input, session_id, new_offset)); // return completions - let cand_iter = candidates.into_iter(); - Some(cand_iter.map(candidate_to_completion_item).collect()) + let item_iter = candidates.into_iter().map(candidate_to_completion_item); + Some(item_iter.collect()) } } diff --git a/src/rime.rs b/src/rime.rs index 777e333..7d9ae5d 100644 --- a/src/rime.rs +++ b/src/rime.rs @@ -1,4 +1,4 @@ -use crate::consts::{K_BACKSPACE, RAW_RE}; +use crate::consts::{KEY_BACKSPACE, RAW_RE}; use librime_sys as librime; use once_cell::sync::OnceCell; use std::ffi::{CStr, CString, NulError}; @@ -258,7 +258,7 @@ impl Rime { pub fn delete_keys(&self, session_id: usize, len: usize) { for _ in 0..len { - self.process_key(session_id, K_BACKSPACE); + self.process_key(session_id, KEY_BACKSPACE); } }