diff --git a/doc/nvim.md b/doc/nvim.md index c15b220..fcb9b9e 100644 --- a/doc/nvim.md +++ b/doc/nvim.md @@ -17,6 +17,8 @@ local start_rime = function() max_candidates = 10, -- [v0.2.0 后不再有用] 与 rime 的候选数量配置最好保持一致 trigger_characters = {}, -- 为空表示全局开启 schema_trigger_character = "&" -- [since v0.2.0] 当输入此字符串时请求补全会触发 “方案选单” + always_incomplete = false -- [since v0.2.3] true 强制补全永远刷新整个列表,而不是使用过滤 + max_tokens = 0 -- [since v0.2.3] 大于 0 表示会在删除到这个字符个数的时候,重建所有候选词,而不使用删除字符操作 }, }); vim.lsp.buf_attach_client(0, client_id) @@ -237,7 +239,7 @@ return M ## 空格键补全 -为了取得与外部输入法更加相似的体验,可以通过配置 cmp 实现用空格键补全,参考配置如下: +为了取得与外部输入法更加相似的体验,可以通过配置 cmp 实现用空格键补全并用回车键直接输入,参考配置如下: ```lua cmp.setup { @@ -248,6 +250,9 @@ cmp.setup { -- ... [''] = cmp.mapping(function(fallback) local entry = cmp.get_selected_entry() + if entry == nil then + entry = cmp.core.view:get_first_entry() + end if entry and entry.source.name == "nvim_lsp" and entry.source.source.client.name == "rime_ls" then cmp.confirm({ @@ -258,6 +263,25 @@ cmp.setup { fallback() end end, {'i', 's'}), + [''] = cmp.mapping(function(fallback) + local entry = cmp.get_selected_entry() + if entry == nil then + entry = cmp.core.view:get_first_entry() + end + if entry and entry.source.name == 'nvim_lsp' + and entry.source.source.client.name == 'rime_ls' then + cmp.abort() + else + if entry ~= nil then + cmp.confirm({ + behavior = cmp.ConfirmBehavior.Replace, + select = true + }) + else + fallback() + end + end + end, {'i', 's'}), -- 其他内容 -- ... } @@ -272,3 +296,23 @@ cmp.setup { 将运行命令修改为 `cmd = vim.lsp.rpc.connect('', )` +## 五笔或者双形用户 + +```lua +require('lspconfig').rime_ls.setup { + init_options = { + enabled = vim.g.rime_enabled, + shared_data_dir = "/usr/share/rime-data", + user_data_dir = "~/.local/share/rime-ls", + log_dir = "~/.local/share/rime-ls", + max_candidates = 9, + trigger_characters = {}, + schema_trigger_character = "&" -- [since v0.2.0] 当输入此字符串时请求补全会触发 “方案选单” + max_tokens = 4, -- 强制在删除到4字的时候重建一次候选词,避免用退格造成的空列表的问题 + always_incomplete = true, -- 将 incomplete 永远设为 true,防止任何时候的过滤代替候选词重建 + }, + on_attach = rime_on_attach, + capabilities = capabilities, +} +``` + diff --git a/src/config.rs b/src/config.rs index 434ff38..7169ad8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -26,6 +26,12 @@ pub struct Config { /// if set, completion request with this string will trigger「方案選單」 #[serde(default = "default_schema_trigger_character")] pub schema_trigger_character: String, + /// if set, when a delete action arrives the number of max tokens, emit a force new_typing + #[serde(default = "default_max_tokens")] + pub max_tokens: usize, + /// if CompletionItem is always incomplete + #[serde(default = "default_always_incomplete")] + pub always_incomplete: bool, } /// settings that can be tweaked during running @@ -66,6 +72,8 @@ impl Default for Config { max_candidates: default_max_candidates(), trigger_characters: default_trigger_characters(), schema_trigger_character: default_schema_trigger_character(), + max_tokens: default_max_tokens(), + always_incomplete: default_always_incomplete(), } } } @@ -74,10 +82,18 @@ fn default_enabled() -> bool { true } +fn default_always_incomplete() -> bool { + false +} + fn default_max_candidates() -> usize { 10 } +fn default_max_tokens() -> usize { + 0 +} + fn default_trigger_characters() -> Vec { Vec::default() } @@ -113,6 +129,8 @@ fn test_default_config() { config.schema_trigger_character, default_schema_trigger_character() ); + assert_eq!(config.always_incomplete, default_always_incomplete()); + assert_eq!(config.max_tokens, default_max_tokens()); } #[test] diff --git a/src/input.rs b/src/input.rs index 33df39d..60dcbe6 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use ouroboros::self_referencing; use regex::Regex; @@ -15,6 +17,16 @@ pub struct Input { pub select: &'this str, } +impl Display for Input { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "pinyin: {}, select: {}", + self.borrow_pinyin(), + self.borrow_select() + )) + } +} + impl Input { /// if matches, take ownership of &str, and self-reference it. pub fn from_str(re: &Regex, text: &str) -> Option { @@ -101,6 +113,7 @@ impl InputState { new_offset: usize, new_input: &Input, schema_trigger: &str, + max_tokens: usize, ) -> InputResult { let rime = Rime::global(); let session_id = rime.find_session(self.session_id); @@ -118,7 +131,14 @@ impl InputState { let diff_pinyin = diff(self.input.borrow_pinyin(), new_input.borrow_pinyin()); match diff_pinyin { DiffResult::Add(suffix) => rime.process_str(session_id, suffix), - DiffResult::Delete(suffix) => rime.delete_keys(session_id, suffix.len()), + DiffResult::Delete(suffix) => { + // if current pinyin len == max_tokens, force new typing + if max_tokens > 0 && max_tokens == new_input.borrow_pinyin().len() { + rime.clear_composition(session_id); + return Self::handle_new_typing(session_id, new_input); + } + rime.delete_keys(session_id, suffix.len()) + } DiffResult::New => { rime.clear_composition(session_id); if !schema_trigger.is_empty() && new_input.borrow_pinyin() == &schema_trigger { diff --git a/src/lsp.rs b/src/lsp.rs index 8561bc7..93f8ee8 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -163,7 +163,12 @@ impl Backend { } = match (*last_state).as_ref() { Some(state) => { let schema_trigger = &self.config.read().await.schema_trigger_character; - state.handle_new_input(new_offset, &new_input, schema_trigger) + state.handle_new_input( + new_offset, + &new_input, + schema_trigger, + self.config.read().await.max_tokens, + ) } None => InputState::handle_first_state(&new_input), }; @@ -235,7 +240,7 @@ impl Backend { .enumerate() .map(|(i, c)| candidate_to_completion_item(i, c)); Some(CompletionList { - is_incomplete, + is_incomplete: (self.config.read().await.always_incomplete || is_incomplete), items: item_iter.collect(), }) }