diff --git a/README.md b/README.md index 9c5ae30..0241662 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ - `ds-pinyin-lsp.db_path`: `dict.db3` 文件 - `ds-pinyin-lsp.server_path`: `ds-pinyin-lsp` 命令或路经 - `ds-pinyin-lsp.completion_on`: 是否自动启用补全 +- `ds-pinyin-lsp.completion_around_mode`: 是否启用环绕(光标在汉字(包括中文标点符号)开头/中间/结尾)补全模式 +- `ds-pinyin-lsp.completion_trigger_characters`: 触发补全字符,配合 `completion_around_mode` 使用,在启用环绕模式后,可以通过输入触发补全字符启用自动补全 - `ds-pinyin-lsp.show_symbols`: 是否补全中文标点符号 - `ds-pinyin-lsp.show_symbols_only_follow_by_hanzi`: 是否只在中文后面补全中文符号 - `ds-pinyin-lsp.show_symbols_by_n_times`: 是否在输入 `n` 次符号后才显示中文符号补全选项,`0` 表示不开启先选 diff --git a/packages/coc-ds-pinyin/README.md b/packages/coc-ds-pinyin/README.md index dfe9c67..67a1403 100644 --- a/packages/coc-ds-pinyin/README.md +++ b/packages/coc-ds-pinyin/README.md @@ -19,6 +19,8 @@ - `ds-pinyin-lsp.check_on_startup`: Check ds-pinyin-lsp release on start up - `ds-pinyin-lsp.db_path`: db path - `ds-pinyin-lsp.completion_on`: If enable auto completion +- `ds-pinyin-lsp.completion_around_mode`: If enable around mode for autocompletion +- `ds-pinyin-lsp.completion_trigger_characters`: Trigger characters for trigger autocompletion - `ds-pinyin-lsp.show_symbols`: If show Chinese symbols - `ds-pinyin-lsp.show_symbols_only_follow_by_hanzi`: If only show Chinese symbols follow by hanzi - `ds-pinyin-lsp.show_symbols_by_n_times`: If show Chinese symbols by input n times diff --git a/packages/coc-ds-pinyin/package.json b/packages/coc-ds-pinyin/package.json index 482065b..fce1445 100644 --- a/packages/coc-ds-pinyin/package.json +++ b/packages/coc-ds-pinyin/package.json @@ -1,6 +1,6 @@ { "name": "coc-ds-pinyin-lsp", - "version": "0.3.0", + "version": "0.4.0", "description": "pinyin input support for (Neo)vim", "author": "iamcco ", "license": "MIT", @@ -61,6 +61,16 @@ "default": true, "description": "If enable auto completion" }, + "ds-pinyin-lsp.completion_around_mode": { + "type": "boolean", + "default": false, + "description": "If enable around mode for autocompletion" + }, + "ds-pinyin-lsp.completion_trigger_characters": { + "type": "string", + "default": "", + "description": "Trigger characters for trigger autocompletion" + }, "ds-pinyin-lsp.show_symbols": { "type": "boolean", "default": true, diff --git a/packages/coc-ds-pinyin/src/constant.ts b/packages/coc-ds-pinyin/src/constant.ts index 9351b9d..bf16817 100644 --- a/packages/coc-ds-pinyin/src/constant.ts +++ b/packages/coc-ds-pinyin/src/constant.ts @@ -1,3 +1,3 @@ export const extensionName = 'ds-pinyin-lsp'; export const dbName = 'dict.db3'; -export const dbTag = 'v0.3.0'; +export const dbTag = 'v0.4.0'; diff --git a/packages/coc-ds-pinyin/src/ctx.ts b/packages/coc-ds-pinyin/src/ctx.ts index 34e4ae3..a1fe35a 100644 --- a/packages/coc-ds-pinyin/src/ctx.ts +++ b/packages/coc-ds-pinyin/src/ctx.ts @@ -99,6 +99,8 @@ export class Ctx { return { db_path: config.get('db_path') || (existsSync(db_path) ? db_path : ''), completion_on: config.get('completion_on', true), + completion_around_mode: config.get('completion_around_mode', false), + completion_trigger_characters: config.get('completion_trigger_characters', ''), show_symbols: config.get('show_symbols', true), show_symbols_only_follow_by_hanzi: config.get('show_symbols_only_follow_by_hanzi', false), show_symbols_by_n_times: config.get('show_symbols_by_n_times', 0), diff --git a/packages/dict-builder/download.sh b/packages/dict-builder/download.sh new file mode 100755 index 0000000..ae915a3 --- /dev/null +++ b/packages/dict-builder/download.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +curl https://raw.githubusercontent.com/iDvel/rime-ice/main/cn_dicts/8105.dict.yaml -o ./dicts/8105.dict.yaml +curl https://raw.githubusercontent.com/iDvel/rime-ice/main/cn_dicts/base.dict.yaml -o ./dicts/base.dict.yaml +curl https://raw.githubusercontent.com/iDvel/rime-ice/main/cn_dicts/ext.dict.yaml -o ./dicts/ext.dict.yaml +curl https://raw.githubusercontent.com/iDvel/rime-ice/main/cn_dicts/others.dict.yaml -o ./dicts/others.dict.yaml +curl https://raw.githubusercontent.com/iDvel/rime-ice/main/cn_dicts/sogou.dict.yaml -o ./dicts/sogou.dict.yaml +curl https://raw.githubusercontent.com/iDvel/rime-ice/main/cn_dicts/tencent.dict.yaml -o ./dicts/tencent.dict.yaml +curl https://raw.githubusercontent.com/iDvel/rime-ice/main/opencc/others.txt -o ./dicts/others.txt +curl https://raw.githubusercontent.com/iDvel/rime-ice/main/opencc/emoji.txt -o ./dicts/emoji.txt diff --git a/packages/ds-pinyin-lsp/Cargo.toml b/packages/ds-pinyin-lsp/Cargo.toml index 3ef9e9d..a7ba25c 100644 --- a/packages/ds-pinyin-lsp/Cargo.toml +++ b/packages/ds-pinyin-lsp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ds-pinyin-lsp" -version = "0.3.0" +version = "0.4.0" edition = "2021" repository = "https://github.com/iamcco/ds-pinyin-lsp" description = "Pinyin language server for input Chinese." diff --git a/packages/ds-pinyin-lsp/src/lib.rs b/packages/ds-pinyin-lsp/src/lib.rs index bf286d6..a41b614 100644 --- a/packages/ds-pinyin-lsp/src/lib.rs +++ b/packages/ds-pinyin-lsp/src/lib.rs @@ -1,3 +1,4 @@ +pub mod lsp; pub mod sqlite; pub mod types; pub mod utils; diff --git a/packages/ds-pinyin-lsp/src/lsp.rs b/packages/ds-pinyin-lsp/src/lsp.rs new file mode 100644 index 0000000..0ed4a82 --- /dev/null +++ b/packages/ds-pinyin-lsp/src/lsp.rs @@ -0,0 +1,384 @@ +use crate::sqlite::query_dict; +use crate::types::Setting; +use crate::utils::{ + get_current_line, get_pinyin, long_suggests_to_completion_item, query_long_sentence, + suggests_to_completion_item, symbols_to_completion_item, +}; +use dashmap::DashMap; +use lsp_document::{apply_change, IndexedText, TextAdapter}; +use regex::Regex; +use rusqlite::Connection; +use serde_json::Value; +use tokio::sync::{Mutex, MutexGuard}; +use tower_lsp::jsonrpc::Result; +use tower_lsp::lsp_types::*; +use tower_lsp::{Client, LanguageServer}; + +#[derive(Debug)] +pub struct Backend { + pub client: Client, + pub setting: Mutex, + pub conn: Mutex>, + pub documents: DashMap>, + pub symbols: DashMap>, + pub chinese_symbols: String, +} + +#[tower_lsp::async_trait] +impl LanguageServer for Backend { + async fn initialize(&self, params: InitializeParams) -> Result { + if let Some(initialization_options) = params.initialization_options { + self.change_configuration(&initialization_options).await; + } else { + self.error("[ds-pinyin-lsp]: initialization_options is missing, it must include db_path setting!").await; + } + + Ok(InitializeResult { + server_info: None, + capabilities: ServerCapabilities { + text_document_sync: Some(TextDocumentSyncCapability::Kind( + TextDocumentSyncKind::INCREMENTAL, + )), + completion_provider: Some(CompletionOptions { + resolve_provider: Some(false), + trigger_characters: Some( + self.symbols + .iter() + .map(|s| s.key().to_string()) + .collect::>(), + ), + work_done_progress_options: Default::default(), + all_commit_characters: None, + }), + ..ServerCapabilities::default() + }, + }) + } + + async fn shutdown(&self) -> Result<()> { + Ok(()) + } + + async fn did_change_configuration(&self, params: DidChangeConfigurationParams) { + self.change_configuration(¶ms.settings).await; + } + + async fn did_open(&self, params: DidOpenTextDocumentParams) { + self.documents.insert( + params.text_document.uri.to_string(), + IndexedText::new(params.text_document.text), + ); + } + + async fn did_change(&self, params: DidChangeTextDocumentParams) { + let mut document = self + .documents + .entry(params.text_document.uri.to_string()) + .or_insert(IndexedText::new(String::new())); + + let mut content: String; + + for change in params.content_changes { + if let Some(change) = document.lsp_change_to_change(change) { + content = apply_change(&document, change); + *document = IndexedText::new(content); + } + } + } + + async fn did_close(&self, params: DidCloseTextDocumentParams) { + let uri = params.text_document.uri.to_string(); + // remove close document + self.documents.remove(&uri); + self.info(&format!("Close file: {}", &uri)).await; + } + + async fn completion(&self, params: CompletionParams) -> Result> { + // check completion on/off + let setting = self.setting.lock().await; + if !setting.completion_on { + return Ok(Some(CompletionResponse::Array(vec![]))); + } + + let uri = params.text_document_position.text_document.uri.to_string(); + let document = self.documents.get(&uri); + if let None = document { + return Ok(Some(CompletionResponse::Array(vec![]))); + } + + let position = params.text_document_position.position; + let (backward_line, forward_line) = get_current_line( + document.as_ref().unwrap(), // document will never be None here + &position, + ) + .unwrap_or(("", "")); + + if backward_line.is_empty() { + return Ok(Some(CompletionResponse::Array(vec![]))); + } + + let pinyin = get_pinyin(backward_line).unwrap_or(String::new()); + + if pinyin.is_empty() { + if setting.show_symbols { + // check symbol + if let Some(last_char) = backward_line.chars().last() { + if let Some(symbols) = self.symbols.get(&last_char) { + // show_symbols_by_n_times + let times = setting.show_symbols_by_n_times; + if times > 0 + && backward_line.len() as u64 >= times + && Regex::new(&format!( + "{}$", + regex::escape(&String::from(last_char).repeat(times as usize)) + )) + .unwrap() + .is_match(backward_line) + { + return Ok(Some(CompletionResponse::List(CompletionList { + is_incomplete: true, + items: symbols_to_completion_item( + last_char, symbols, position, times, + ), + }))); + } + // show_symbols_only_follow_by_hanzi + if !setting.show_symbols_only_follow_by_hanzi + || (backward_line.len() > 1 + && Regex::new(r"\p{Han}$") + .unwrap() + .is_match(&backward_line[..backward_line.len() - 1])) + { + return Ok(Some(CompletionResponse::List(CompletionList { + is_incomplete: true, + items: symbols_to_completion_item(last_char, symbols, position, 1), + }))); + } + } + } + } + + // return for empty pinyin + return Ok(Some(CompletionResponse::Array(vec![]))); + } + + // 触发模式 + let trigger_completion = !setting.completion_trigger_characters.is_empty() + && Regex::new(&format!( + "{}[a-zA-Z]+$", + regex::escape(&setting.completion_trigger_characters) + )) + .unwrap() + .is_match(backward_line); + + // 环绕模式 + let around_completion = Regex::new(&format!( + r#"(\p{{Han}}|{})((\w|'|"|`)*\s*)*[a-zA-Z]+$"#, + self.chinese_symbols + )) + .unwrap() + .is_match(backward_line) + || Regex::new(&format!( + r#"^((\w|'|"|`)*\s*)*(\p{{Han}}|{})"#, + self.chinese_symbols + )) + .unwrap() + .is_match(forward_line); + + // 开启环绕补全模式,但是: + // - 不符合环绕模式 + // - 不符合触发模式 + if setting.completion_around_mode && !around_completion && !trigger_completion { + return Ok(Some(CompletionResponse::Array(vec![]))); + } + + // pinyin range + let range = Range::new( + Position { + line: position.line, + character: position.character + - (if trigger_completion { + pinyin.len() + setting.completion_trigger_characters.len() + } else { + pinyin.len() + }) as u32, + }, + position, + ); + + if let Some(ref conn) = *self.conn.lock().await { + // dict search match + if let Ok(suggests) = query_dict( + conn, + &pinyin, + setting.max_suggest, + setting.match_as_same_as_input, + ) { + if suggests.len() > 0 { + return Ok(Some(CompletionResponse::List(CompletionList { + is_incomplete: true, + items: suggests_to_completion_item(suggests, range), + }))); + } + } + + // long sentence + if setting.match_long_input { + if let Ok(Some(suggests)) = + query_long_sentence(conn, &pinyin, setting.match_as_same_as_input) + { + if suggests.len() > 0 { + return Ok(Some(CompletionResponse::List(CompletionList { + is_incomplete: true, + items: long_suggests_to_completion_item(suggests, range), + }))); + } + } + } + }; + + // Note: + // hack ghost item for more completion request from client + Ok(Some(CompletionResponse::List(CompletionList { + is_incomplete: true, + items: vec![CompletionItem { + label: String::from("Pinyin Placeholder"), + kind: Some(CompletionItemKind::TEXT), + filter_text: Some(String::from("‍")), + ..Default::default() + }], + }))) + } +} + +impl Backend { + pub async fn turn_completion(&self, params: Value) { + let mut setting = self.setting.lock().await; + + if let Some(completion_on) = params.get("completion_on") { + if completion_on.is_boolean() { + (*setting).completion_on = completion_on.as_bool().unwrap_or(setting.completion_on); + } + } else { + (*setting).completion_on = !setting.completion_on; + } + + self.info(&format!( + "[ds-pinyin-lsp]: completion_on: {}", + setting.completion_on + )) + .await; + } + + async fn change_configuration(&self, params: &Value) { + let mut setting = self.setting.lock().await; + + for option_key in [ + "db_path", + "completion_on", + "completion_around_mode", + "completion_trigger_characters", + "show_symbols", + "show_symbols_only_follow_by_hanzi", + "show_symbols_by_n_times", + "match_as_same_as_input", + "match_long_input", + "max_suggest", + ] { + if let Some(option) = params.get(option_key) { + match option_key { + "db_path" => { + if let Some(db_path) = option.as_str() { + self.update_db_path(&mut setting, db_path).await; + } else { + // invalid db_path + self.error("[ds-pinyin-lsp]: db_path must be string!").await; + } + } + "completion_on" => { + (*setting).completion_on = + option.as_bool().unwrap_or(setting.completion_on); + } + "completion_around_mode" => { + (*setting).completion_around_mode = + option.as_bool().unwrap_or(setting.completion_around_mode); + } + "completion_trigger_characters" => { + (*setting).completion_trigger_characters = + option.as_str().unwrap_or("").to_string(); + } + "show_symbols" => { + (*setting).show_symbols = option.as_bool().unwrap_or(setting.show_symbols); + } + "show_symbols_only_follow_by_hanzi" => { + (*setting).show_symbols_only_follow_by_hanzi = option + .as_bool() + .unwrap_or(setting.show_symbols_only_follow_by_hanzi); + } + "show_symbols_by_n_times" => { + (*setting).show_symbols_by_n_times = + option.as_u64().unwrap_or(setting.show_symbols_by_n_times); + } + "match_as_same_as_input" => { + (*setting).match_as_same_as_input = + option.as_bool().unwrap_or(setting.match_as_same_as_input); + } + "match_long_input" => { + (*setting).match_long_input = + option.as_bool().unwrap_or(setting.match_long_input); + } + "max_suggest" => { + (*setting).max_suggest = option.as_u64().unwrap_or(setting.max_suggest); + } + _ => {} + } + + self.info(&format!("[ds-pinyin-lsp]: {} to {}!", option_key, option)) + .await + } + } + + // check db_path + if setting.db_path.is_empty() { + self.error("[ds-pinyin-lsp]: db_path is missing!").await; + } + } + + async fn update_db_path<'a>(&self, setting: &mut MutexGuard<'a, Setting>, db_path: &str) { + if db_path.is_empty() { + self.error("[ds-pinyin-lsp]: db_path is empty string!") + .await; + return; + } + if setting.db_path == db_path { + self.info("[ds-pinyin-lsp]: ignore same db_path!").await; + return; + } + match Connection::open(db_path) { + Ok(conn) => { + // cache setting + (*setting).db_path = db_path.to_string(); + // connection + let mut mutex = self.conn.lock().await; + *mutex = Some(conn); + self.info(&format!("[ds-pinyin-lsp]: db connection to {}!", db_path)) + .await; + } + Err(err) => { + self.error(&format!( + "[ds-pinyin-lsp]: open database: {} error: {}", + db_path, err + )) + .await; + } + } + } + + async fn info(&self, message: &str) { + self.client.log_message(MessageType::INFO, message).await; + } + + async fn error(&self, message: &str) { + self.client.log_message(MessageType::ERROR, message).await; + } +} diff --git a/packages/ds-pinyin-lsp/src/main.rs b/packages/ds-pinyin-lsp/src/main.rs index efa80a2..05e0793 100644 --- a/packages/ds-pinyin-lsp/src/main.rs +++ b/packages/ds-pinyin-lsp/src/main.rs @@ -1,338 +1,7 @@ use dashmap::DashMap; -use ds_pinyin_lsp::sqlite::query_dict; -use ds_pinyin_lsp::types::Setting; -use ds_pinyin_lsp::utils::{ - get_pinyin, get_pre_line, long_suggests_to_completion_item, query_long_sentence, - suggests_to_completion_item, symbols_to_completion_item, -}; -use lsp_document::{apply_change, IndexedText, TextAdapter}; -use regex::Regex; -use rusqlite::Connection; -use serde_json::Value; +use ds_pinyin_lsp::{lsp::Backend, types::Setting}; use tokio::sync::Mutex; -use tower_lsp::jsonrpc::Result; -use tower_lsp::lsp_types::*; -use tower_lsp::{Client, LanguageServer, LspService, Server}; - -#[derive(Debug)] -struct Backend { - client: Client, - setting: Mutex, - conn: Mutex>, - documents: DashMap>, - symbols: DashMap>, -} - -#[tower_lsp::async_trait] -impl LanguageServer for Backend { - async fn initialize(&self, params: InitializeParams) -> Result { - if let Some(initialization_options) = params.initialization_options { - self.change_configuration(&initialization_options).await; - } else { - self.error("[ds-pinyin-lsp]: initialization_options is missing, it must include db_path setting!").await; - } - - Ok(InitializeResult { - server_info: None, - capabilities: ServerCapabilities { - text_document_sync: Some(TextDocumentSyncCapability::Kind( - TextDocumentSyncKind::INCREMENTAL, - )), - completion_provider: Some(CompletionOptions { - resolve_provider: Some(false), - trigger_characters: Some( - self.symbols - .iter() - .map(|s| s.key().to_string()) - .collect::>(), - ), - work_done_progress_options: Default::default(), - all_commit_characters: None, - }), - ..ServerCapabilities::default() - }, - }) - } - - async fn shutdown(&self) -> Result<()> { - Ok(()) - } - - async fn did_change_configuration(&self, params: DidChangeConfigurationParams) { - self.change_configuration(¶ms.settings).await; - } - - async fn did_open(&self, params: DidOpenTextDocumentParams) { - self.documents.insert( - params.text_document.uri.to_string(), - IndexedText::new(params.text_document.text), - ); - } - - async fn did_change(&self, params: DidChangeTextDocumentParams) { - let mut document = self - .documents - .entry(params.text_document.uri.to_string()) - .or_insert(IndexedText::new(String::new())); - - let mut content: String; - - for change in params.content_changes { - if let Some(change) = document.lsp_change_to_change(change) { - content = apply_change(&document, change); - *document = IndexedText::new(content); - } - } - } - - async fn did_close(&self, params: DidCloseTextDocumentParams) { - let uri = params.text_document.uri.to_string(); - - // remove close document - self.documents.remove(&uri); - - self.info(&format!("Close file: {}", &uri)).await; - } - - async fn completion(&self, params: CompletionParams) -> Result> { - // check completion on/off - let setting = self.setting.lock().await; - if !setting.completion_on { - return Ok(Some(CompletionResponse::Array(vec![]))); - } - - let position = params.text_document_position.position; - let uri = params.text_document_position.text_document.uri.to_string(); - let document = self.documents.get(&uri); - let pre_line = get_pre_line(&document, &position).unwrap_or(""); - - if pre_line.is_empty() { - return Ok(Some(CompletionResponse::Array(vec![]))); - } - - let pinyin = get_pinyin(pre_line).unwrap_or(String::new()); - - if pinyin.is_empty() { - if setting.show_symbols { - // check symbol - if let Some(last_char) = pre_line.chars().last() { - if let Some(symbols) = self.symbols.get(&last_char) { - // show_symbols_by_n_times - let times = setting.show_symbols_by_n_times; - if times > 0 - && pre_line.len() as u64 >= times - && Regex::new(&format!( - "{}$", - regex::escape(&String::from(last_char).repeat(times as usize)) - )) - .unwrap() - .is_match(pre_line) - { - return Ok(Some(CompletionResponse::List(CompletionList { - is_incomplete: true, - items: symbols_to_completion_item( - last_char, symbols, position, times, - ), - }))); - } - // show_symbols_only_follow_by_hanzi - if !setting.show_symbols_only_follow_by_hanzi - || (pre_line.len() > 1 - && Regex::new(r"\p{Han}$") - .unwrap() - .is_match(&pre_line[..pre_line.len() - 1])) - { - return Ok(Some(CompletionResponse::List(CompletionList { - is_incomplete: true, - items: symbols_to_completion_item(last_char, symbols, position, 1), - }))); - } - } - } - } - - // return for empty pinyin - return Ok(Some(CompletionResponse::Array(vec![]))); - } - - // pinyin range - let range = Range::new( - Position { - line: position.line, - character: position.character - pinyin.len() as u32, - }, - position, - ); - - if let Some(ref conn) = *self.conn.lock().await { - // dict search match - if let Ok(suggests) = query_dict( - conn, - &pinyin, - setting.max_suggest, - setting.match_as_same_as_input, - ) { - if suggests.len() > 0 { - return Ok(Some(CompletionResponse::List(CompletionList { - is_incomplete: true, - items: suggests_to_completion_item(suggests, range), - }))); - } - } - - // long sentence - if setting.match_long_input { - if let Ok(Some(suggests)) = - query_long_sentence(conn, &pinyin, setting.match_as_same_as_input) - { - if suggests.len() > 0 { - return Ok(Some(CompletionResponse::List(CompletionList { - is_incomplete: true, - items: long_suggests_to_completion_item(suggests, range), - }))); - } - } - } - }; - - // Note: - // hack ghost item for more completion request from client - Ok(Some(CompletionResponse::List(CompletionList { - is_incomplete: true, - items: vec![CompletionItem { - label: String::from("Pinyin Placeholder"), - kind: Some(CompletionItemKind::TEXT), - filter_text: Some(String::from("‍")), - ..Default::default() - }], - }))) - } -} - -impl Backend { - async fn turn_completion(&self, params: Value) { - let mut setting = self.setting.lock().await; - - if let Some(completion_on) = params.get("completion_on") { - if completion_on.is_boolean() { - (*setting).completion_on = completion_on.as_bool().unwrap_or(setting.completion_on); - } - } else { - (*setting).completion_on = !setting.completion_on; - } - - self.info(&format!( - "[ds-pinyin-lsp]: completion_on: {}", - setting.completion_on - )) - .await; - } - - async fn change_configuration(&self, params: &Value) { - let mut setting = self.setting.lock().await; - - // db_path - if let Some(db_path) = params.get("db_path") { - if let Some(db_path) = db_path.as_str() { - if !db_path.is_empty() { - match setting.db_path { - Some(ref old_db_path) if old_db_path == db_path => { - self.info("[ds-pinyin-lsp]: ignore same db_path!").await; - } - _ => { - // cache setting - (*setting).db_path = Some(db_path.to_string()); - - // open db connection - let conn = Connection::open(db_path); - if let Ok(conn) = conn { - let mut mutex = self.conn.lock().await; - *mutex = Some(conn); - self.info(&format!( - "[ds-pinyin-lsp]: db connection to {}!", - db_path - )) - .await; - } else if let Err(err) = conn { - self.error(&format!( - "[ds-pinyin-lsp]: open database: {} error: {}", - db_path, err - )) - .await; - } - } - } - } else { - // db_path empty - self.error("[ds-pinyin-lsp]: db_path is empty string!") - .await; - } - } else { - // invalid db_path - self.error("[ds-pinyin-lsp]: db_path must be string!").await; - } - } - - // check db_path - if setting.db_path.is_none() { - self.error("[ds-pinyin-lsp]: db_path is missing!").await; - } - - for option_key in [ - "completion_on", - "show_symbols", - "show_symbols_only_follow_by_hanzi", - "show_symbols_by_n_times", - "match_as_same_as_input", - "match_long_input", - "max_suggest", - ] { - if let Some(option) = params.get(option_key) { - match option_key { - "completion_on" => { - (*setting).completion_on = - option.as_bool().unwrap_or(setting.completion_on); - } - "show_symbols" => { - (*setting).show_symbols = option.as_bool().unwrap_or(setting.show_symbols); - } - "show_symbols_only_follow_by_hanzi" => { - (*setting).show_symbols_only_follow_by_hanzi = option - .as_bool() - .unwrap_or(setting.show_symbols_only_follow_by_hanzi); - } - "show_symbols_by_n_times" => { - (*setting).show_symbols_by_n_times = - option.as_u64().unwrap_or(setting.show_symbols_by_n_times); - } - "match_as_same_as_input" => { - (*setting).match_as_same_as_input = - option.as_bool().unwrap_or(setting.match_as_same_as_input); - } - "match_long_input" => { - (*setting).match_long_input = - option.as_bool().unwrap_or(setting.match_long_input); - } - "max_suggest" => { - (*setting).max_suggest = option.as_u64().unwrap_or(setting.max_suggest); - } - _ => {} - } - - self.info(&format!("[ds-pinyin-lsp]: {} to {}!", option_key, option)) - .await - } - } - } - - async fn info(&self, message: &str) { - self.client.log_message(MessageType::INFO, message).await; - } - - async fn error(&self, message: &str) { - self.client.log_message(MessageType::ERROR, message).await; - } -} +use tower_lsp::{LspService, Server}; #[tokio::main] async fn main() { @@ -375,6 +44,9 @@ async fn main() { conn: Mutex::new(None), documents: DashMap::new(), symbols, + chinese_symbols: String::from( + "。|·|……|~|、|,|;|:|?|!|“|”|‘|’|(|)|——|《|》|【|】|¥", + ), }) .custom_method("$/turn/completion", Backend::turn_completion) .finish(); diff --git a/packages/ds-pinyin-lsp/src/types.rs b/packages/ds-pinyin-lsp/src/types.rs index e353b20..3e7c779 100644 --- a/packages/ds-pinyin-lsp/src/types.rs +++ b/packages/ds-pinyin-lsp/src/types.rs @@ -20,6 +20,12 @@ impl Suggest { pub struct Setting { /// 是否开启自动补全 pub completion_on: bool, + /// 环绕中文补全模式 + /// 只在中文周边输入拼音启用补全 + pub completion_around_mode: bool, + /// 触发补全 + /// 在该符号后面输入拼音会启用补全 + pub completion_trigger_characters: String, /// 是否补全中文符号 pub show_symbols: bool, /// 是否只有在汉字后面才显示中文符号,只有 show_symbols 为 true 才生效 @@ -32,7 +38,7 @@ pub struct Setting { /// 是否自动补全长句 pub match_long_input: bool, /// dict.db3 路径 - pub db_path: Option, + pub db_path: String, /// 最多显示多少补全结果 pub max_suggest: u64, } @@ -41,12 +47,14 @@ impl Setting { pub fn new() -> Setting { Setting { completion_on: true, + completion_around_mode: false, + completion_trigger_characters: String::new(), show_symbols: true, show_symbols_only_follow_by_hanzi: false, show_symbols_by_n_times: 0, match_as_same_as_input: false, match_long_input: true, - db_path: None, + db_path: String::new(), max_suggest: 50, } } diff --git a/packages/ds-pinyin-lsp/src/utils.rs b/packages/ds-pinyin-lsp/src/utils.rs index 53a3844..349a531 100644 --- a/packages/ds-pinyin-lsp/src/utils.rs +++ b/packages/ds-pinyin-lsp/src/utils.rs @@ -10,23 +10,57 @@ use tower_lsp::lsp_types::{ use crate::{sqlite::query_the_longest_match, types::Suggest}; -pub fn get_pre_line<'a>( - document: &'a Option>>, +pub fn get_current_line<'a>( + document: &'a Ref>, + position: &Position, +) -> Option<(&'a str, &'a str)> { + if let Some(backward_line) = get_backward_line(document, position) { + return Some(( + backward_line, + get_forward_line(document, position).unwrap_or(""), + )); + } + + None +} + +/// 获取光标前文字 +pub fn get_backward_line<'a>( + document: &'a Ref>, position: &Position, ) -> Option<&'a str> { - if let Some(ref document) = document { - if let Some(range) = document.lsp_range_to_range(&Range { - start: Position { - line: position.line, - character: 0, - }, - end: Position { - line: position.line, - character: position.character, - }, - }) { - return document.substr(range); - } + if let Some(range) = document.lsp_range_to_range(&Range { + start: Position { + line: position.line, + character: 0, + }, + end: Position { + line: position.line, + character: position.character, + }, + }) { + return document.substr(range); + } + + None +} + +/// 获取光标后文字 +pub fn get_forward_line<'a>( + document: &'a Ref>, + position: &Position, +) -> Option<&'a str> { + if let Some(range) = document.lsp_range_to_range(&Range { + start: Position { + line: position.line, + character: position.character, + }, + end: Position { + line: position.line + 1, + character: 0, + }, + }) { + return document.substr(range); } None diff --git a/upload_file.sh b/upload_file.sh index 1db5ca6..f98e652 100755 --- a/upload_file.sh +++ b/upload_file.sh @@ -12,7 +12,7 @@ AUTH="Authorization: token $GITHUB_API_TOKEN" # upload assets cd ./packages/ds-pinyin-lsp/target/aarch64-apple-darwin/release/ -declare -a files=("ds-pinyin-lsp_v0.2.0_aarch64-apple-darwin.zip") +declare -a files=("ds-pinyin-lsp_v0.4.0_aarch64-apple-darwin.zip") # Validate token. curl -o /dev/null -sH "$AUTH" $GH_REPO || { echo "Error: Invalid repo, token or network issue!"; exit 1; }