diff --git a/src/predict/ibus/build.rs b/src/predict/ibus/build.rs index ae324df..08d5dce 100644 --- a/src/predict/ibus/build.rs +++ b/src/predict/ibus/build.rs @@ -4,18 +4,21 @@ static ENGINE_HEADER: &str = "../../../include/eei/engine.h"; static BINDING_FILE: &str = "src/ibus_bindings.rs"; fn main() { - let ibus_lib = pkg_config::Config::new().atleast_version("1.5.0").probe("ibus-1.0").unwrap(); - + let ibus_lib = pkg_config::Config::new() + .atleast_version("1.5.0") + .probe("ibus-1.0") + .unwrap(); // Tell cargo to invalidate the built crate whenever the wrapper changes println!("cargo:rerun-if-changed={}", ENGINE_HEADER); - let bindings = ibus_lib.include_paths.iter().fold( - bindgen::Builder::default().header(ENGINE_HEADER), - | b, path| { - b.clang_arg("-I".to_string()+path.to_str().unwrap()) - } - ) + let bindings = ibus_lib + .include_paths + .iter() + .fold( + bindgen::Builder::default().header(ENGINE_HEADER), + |b, path| b.clang_arg("-I".to_string() + path.to_str().unwrap()), + ) .allowlist_function("ibus_.*") .allowlist_type("IBus.*") .allowlist_var("(IBUS_.*|GBOOL_.*)") @@ -27,6 +30,7 @@ fn main() { // Unwrap the Result and panic on failure. .expect("Unable to generate bindings"); - bindings.write_to_file(Path::new(BINDING_FILE)) + bindings + .write_to_file(Path::new(BINDING_FILE)) .expect("Couldn't write bindings!"); } diff --git a/src/predict/ibus/src/lib.rs b/src/predict/ibus/src/lib.rs index a725148..46a6c51 100644 --- a/src/predict/ibus/src/lib.rs +++ b/src/predict/ibus/src/lib.rs @@ -1,2 +1,2 @@ #![allow(warnings)] -include!("ibus_bindings.rs"); \ No newline at end of file +include!("ibus_bindings.rs"); diff --git a/src/predict/lib/build.rs b/src/predict/lib/build.rs index 9dd56b4..33f7a27 100644 --- a/src/predict/lib/build.rs +++ b/src/predict/lib/build.rs @@ -1,6 +1,7 @@ extern crate cbindgen; -use std::env;static GENERATED_HEADER: &str = "../../../include/eei/predict.h"; +use std::env; +static GENERATED_HEADER: &str = "../../../include/eei/predict.h"; fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); diff --git a/src/predict/lib/src/lib.rs b/src/predict/lib/src/lib.rs index a925be0..555280a 100644 --- a/src/predict/lib/src/lib.rs +++ b/src/predict/lib/src/lib.rs @@ -1,23 +1,40 @@ #![allow(non_upper_case_globals)] +#![allow(clippy::missing_safety_doc)] mod predict; -use std::ffi::{CString, NulError, CStr}; -use std::os::raw::{c_char, c_int}; -use log::{LevelFilter}; -use log4rs::encode::pattern::PatternEncoder; +use log::LevelFilter; use log4rs::config::{Appender, Config, Root}; +use log4rs::encode::pattern::PatternEncoder; +use std::ffi::{CStr, CString, NulError}; +use std::os::raw::{c_char, c_int}; use crate::predict::PREDICTOR; -use ibus::{IBusEEIEngine, gboolean, GBOOL_FALSE, ibus_engine_update_lookup_table, IBusEngine, GBOOL_TRUE, ibus_engine_hide_lookup_table, guint, IBusModifierType_IBUS_CONTROL_MASK, IBUS_e, IBUS_w, IBUS_asciitilde, IBUS_space, IBUS_Return, IBUS_BackSpace, IBUS_Escape, IBUS_Page_Down, IBUS_Page_Up, ibus_engine_commit_text, ibus_text_new_from_unichar, ibus_text_new_from_string, gchar, ibus_lookup_table_clear, ibus_lookup_table_append_candidate, IBusText, ibus_engine_update_auxiliary_text, IBUS_Up, IBUS_Down, ibus_lookup_table_get_cursor_pos, IBusLookupTable, ibus_lookup_table_get_label, ibus_lookup_table_cursor_up, ibus_lookup_table_cursor_down, ibus_engine_hide_auxiliary_text, ibus_lookup_table_set_label, ibus_lookup_table_page_down, ibus_lookup_table_page_up, ibus_lookup_table_get_number_of_candidates, ibus_text_new_from_static_string, ibus_lookup_table_get_cursor_in_page, gunichar, IBusModifierType_IBUS_SHIFT_MASK, ibus_lookup_table_get_candidate, ibus_engine_update_preedit_text, ibus_engine_hide_preedit_text, ibus_text_get_length, ibus_text_append_attribute, IBusAttrType_IBUS_ATTR_TYPE_UNDERLINE, IBusAttrUnderline_IBUS_ATTR_UNDERLINE_SINGLE, gint, IBUS_Right, IBUS_Left, IBusEngineClass}; -use std::cmp::min; +use ibus::{ + gboolean, gchar, gint, guint, gunichar, ibus_engine_commit_text, + ibus_engine_hide_auxiliary_text, ibus_engine_hide_lookup_table, ibus_engine_hide_preedit_text, + ibus_engine_update_auxiliary_text, ibus_engine_update_lookup_table, + ibus_engine_update_preedit_text, ibus_lookup_table_append_candidate, ibus_lookup_table_clear, + ibus_lookup_table_cursor_down, ibus_lookup_table_cursor_up, ibus_lookup_table_get_candidate, + ibus_lookup_table_get_cursor_in_page, ibus_lookup_table_get_cursor_pos, + ibus_lookup_table_get_label, ibus_lookup_table_get_number_of_candidates, + ibus_lookup_table_page_down, ibus_lookup_table_page_up, ibus_lookup_table_set_label, + ibus_text_append_attribute, ibus_text_get_length, ibus_text_new_from_static_string, + ibus_text_new_from_string, ibus_text_new_from_unichar, IBUS_BackSpace, IBUS_Down, IBUS_Escape, + IBUS_Left, IBUS_Page_Down, IBUS_Page_Up, IBUS_Return, IBUS_Right, IBUS_Up, IBUS_asciitilde, + IBUS_e, IBUS_space, IBUS_w, IBusAttrType_IBUS_ATTR_TYPE_UNDERLINE, + IBusAttrUnderline_IBUS_ATTR_UNDERLINE_SINGLE, IBusEEIEngine, IBusEngine, IBusEngineClass, + IBusLookupTable, IBusModifierType_IBUS_CONTROL_MASK, IBusModifierType_IBUS_SHIFT_MASK, + IBusText, GBOOL_FALSE, GBOOL_TRUE, +}; use lazy_static::lazy_static; -use InputMode::*; -use std::path::Path; use log4rs::append::rolling_file::policy::compound::roll::fixed_window::FixedWindowRoller; use log4rs::append::rolling_file::policy::compound::trigger::size::SizeTrigger; use log4rs::append::rolling_file::policy::compound::CompoundPolicy; -use log4rs::filter::threshold::ThresholdFilter; use log4rs::append::rolling_file::RollingFileAppender; +use log4rs::filter::threshold::ThresholdFilter; +use std::cmp::min; +use std::path::Path; +use InputMode::*; lazy_static! { static ref empty_cstring: CString = CString::new("").unwrap(); @@ -27,7 +44,7 @@ lazy_static! { enum InputMode { Normal, SymbolTable, - WordTable + WordTable, } pub struct EngineCore { @@ -42,7 +59,10 @@ pub struct EngineCore { } #[no_mangle] -pub unsafe extern "C" fn new_engine_core(parent_engine: *mut IBusEEIEngine, parent_engine_class: *mut IBusEngineClass) -> *mut EngineCore { +pub unsafe extern "C" fn new_engine_core( + parent_engine: *mut IBusEEIEngine, + parent_engine_class: *mut IBusEngineClass, +) -> *mut EngineCore { Box::into_raw(Box::new(EngineCore { table_visible: false, word_buffer: String::new(), @@ -50,20 +70,20 @@ pub unsafe extern "C" fn new_engine_core(parent_engine: *mut IBusEEIEngine, pare symbol_preedit: String::new(), symbol_label_vec: Vec::new(), symbol_last_page: 0, - parent_engine: parent_engine, - parent_engine_class: parent_engine_class + parent_engine, + parent_engine_class, })) } unsafe fn into_ibus_string(input: String) -> Result<*mut IBusText, NulError> { - CString::new(input.into_bytes()).map(|cstr| ibus_text_new_from_string(cstr.into_raw() as *const gchar)) + CString::new(input.into_bytes()) + .map(|cstr| ibus_text_new_from_string(cstr.into_raw() as *const gchar)) } impl EngineCore { - /* - ** General Methods ** - */ + ** General Methods ** + */ unsafe fn page_down_and_update(&mut self) -> gboolean { if self.table_visible { @@ -87,16 +107,12 @@ impl EngineCore { unsafe fn abort_table_input(&mut self) -> gboolean { match self.input_mode { - SymbolTable => { - self.symbol_table_disable() - } + SymbolTable => self.symbol_table_disable(), WordTable => { self.word_buffer.clear(); self.word_table_disable() } - Normal => { - GBOOL_FALSE - } + Normal => GBOOL_FALSE, } } @@ -110,9 +126,7 @@ impl EngineCore { self.word_commit(idx); GBOOL_TRUE } - Normal => { - GBOOL_FALSE - } + Normal => GBOOL_FALSE, }; self.word_buffer.clear(); ret @@ -132,11 +146,19 @@ impl EngineCore { unsafe fn commit_char(&mut self, keyval: guint) { self.word_buffer.push((keyval as u8) as char); - ibus_engine_commit_text(self.parent_engine_as_ibus_engine(), ibus_text_new_from_unichar(keyval as gunichar)); + ibus_engine_commit_text( + self.parent_engine_as_ibus_engine(), + ibus_text_new_from_unichar(keyval as gunichar), + ); } unsafe fn commit_text(&mut self, text: *mut IBusText) { - log::info!("commit text {}", CStr::from_ptr((*text).text as *mut c_char).to_str().unwrap()); + log::info!( + "commit text {}", + CStr::from_ptr((*text).text as *mut c_char) + .to_str() + .unwrap() + ); ibus_engine_commit_text(self.parent_engine_as_ibus_engine(), text); } @@ -147,21 +169,45 @@ impl EngineCore { let page_num = idx / page_size; if self.symbol_last_page != page_num { self.symbol_last_page = page_num; - for (idx, table_idx) in (page_num * page_size..min(page_size * (page_size+1), self.symbol_label_vec.len() as u32)).enumerate() { - ibus_lookup_table_set_label(self.get_table(), idx as guint, ibus_text_new_from_static_string(self.symbol_label_vec.get_unchecked(table_idx as usize).as_ptr())) + for (idx, table_idx) in (page_num * page_size + ..min( + page_size * (page_size + 1), + self.symbol_label_vec.len() as u32, + )) + .enumerate() + { + ibus_lookup_table_set_label( + self.get_table(), + idx as guint, + ibus_text_new_from_static_string( + self.symbol_label_vec + .get_unchecked(table_idx as usize) + .as_ptr(), + ), + ) } } } - ibus_engine_update_lookup_table(self.parent_engine_as_ibus_engine(), self.get_table(), GBOOL_TRUE); + ibus_engine_update_lookup_table( + self.parent_engine_as_ibus_engine(), + self.get_table(), + GBOOL_TRUE, + ); self.update_preedit(); } unsafe fn update_preedit(&mut self) { //Clear preedit if no candidates are available - if self.input_mode != Normal && ibus_lookup_table_get_number_of_candidates(self.get_table()) == 0 { - ibus_engine_update_preedit_text(self.parent_engine_as_ibus_engine(), into_ibus_string(String::from("")).unwrap(), - 0,GBOOL_TRUE); - return + if self.input_mode != Normal + && ibus_lookup_table_get_number_of_candidates(self.get_table()) == 0 + { + ibus_engine_update_preedit_text( + self.parent_engine_as_ibus_engine(), + into_ibus_string(String::from("")).unwrap(), + 0, + GBOOL_TRUE, + ); + return; } match self.input_mode { @@ -170,34 +216,52 @@ impl EngineCore { let idx = ibus_lookup_table_get_cursor_pos(self.get_table()) % page_size; let symbol = ibus_lookup_table_get_label(self.get_table(), idx); let len = ibus_text_get_length(symbol); - ibus_text_append_attribute(symbol, IBusAttrType_IBUS_ATTR_TYPE_UNDERLINE, - IBusAttrUnderline_IBUS_ATTR_UNDERLINE_SINGLE, 0, len as gint); - ibus_engine_update_preedit_text(self.parent_engine_as_ibus_engine(), symbol, - len,GBOOL_TRUE); + ibus_text_append_attribute( + symbol, + IBusAttrType_IBUS_ATTR_TYPE_UNDERLINE, + IBusAttrUnderline_IBUS_ATTR_UNDERLINE_SINGLE, + 0, + len as gint, + ); + ibus_engine_update_preedit_text( + self.parent_engine_as_ibus_engine(), + symbol, + len, + GBOOL_TRUE, + ); } WordTable => { let idx = ibus_lookup_table_get_cursor_pos(self.get_table()); let candidate = ibus_lookup_table_get_candidate(self.get_table(), idx); - self.get_word_remainder(candidate).map(|remainder| { + if let Some(remainder) = self.get_word_remainder(candidate) { let len = ibus_text_get_length(remainder); - ibus_text_append_attribute(remainder, IBusAttrType_IBUS_ATTR_TYPE_UNDERLINE, - IBusAttrUnderline_IBUS_ATTR_UNDERLINE_SINGLE, 0, len as gint); - ibus_engine_update_preedit_text(self.parent_engine_as_ibus_engine(), remainder, - len, GBOOL_TRUE); - }); + ibus_text_append_attribute( + remainder, + IBusAttrType_IBUS_ATTR_TYPE_UNDERLINE, + IBusAttrUnderline_IBUS_ATTR_UNDERLINE_SINGLE, + 0, + len as gint, + ); + ibus_engine_update_preedit_text( + self.parent_engine_as_ibus_engine(), + remainder, + len, + GBOOL_TRUE, + ); + }; } Normal => {} } } /* - ** Word input methods + ** Word input methods */ unsafe fn word_table_enable(&mut self) -> gboolean { if self.table_visible || self.word_buffer.is_empty() { //not an error if this is called while word buffer is empty, so don't log - return GBOOL_FALSE + return GBOOL_FALSE; } self.input_mode = WordTable; @@ -223,29 +287,34 @@ impl EngineCore { if !self.table_visible || self.input_mode != WordTable { log::error!("Word table update called while table invisible or input mode is not word"); return; - } - else if self.word_buffer.is_empty() { + } else if self.word_buffer.is_empty() { self.word_table_disable(); return; } - let search_result = PREDICTOR.word(self.word_buffer.as_str()); + let search_result = PREDICTOR.word(self.word_buffer.as_str()); match search_result { Ok(candidates) => { - log::info!("Word search for {} and got {:?}", self.word_buffer, candidates); + log::info!( + "Word search for {} and got {:?}", + self.word_buffer, + candidates + ); let table = self.get_table(); ibus_lookup_table_clear(table); for word in candidates { match into_ibus_string(word) { - Ok(ibus_text) => { - ibus_lookup_table_append_candidate(table, ibus_text) - } + Ok(ibus_text) => ibus_lookup_table_append_candidate(table, ibus_text), Err(err) => { log::error!("Failed string conversion for word lookup: {}", err); } } } - ibus_engine_update_lookup_table(self.parent_engine_as_ibus_engine(), table, GBOOL_TRUE); + ibus_engine_update_lookup_table( + self.parent_engine_as_ibus_engine(), + table, + GBOOL_TRUE, + ); self.update_preedit(); } Err(err) => { @@ -260,15 +329,12 @@ impl EngineCore { return; } - let idx = input_idx.unwrap_or_else(|| { - ibus_lookup_table_get_cursor_pos(self.get_table()) - }); + let idx = input_idx.unwrap_or_else(|| ibus_lookup_table_get_cursor_pos(self.get_table())); log::info!("Word commit for idx {}", idx); let candidate = ibus_lookup_table_get_candidate(self.get_table(), idx); - self.get_word_remainder(candidate) - .map(|remainder| { - self.commit_text(remainder) - }); + if let Some(remainder) = self.get_word_remainder(candidate) { + self.commit_text(remainder) + } self.word_buffer.clear(); self.word_table_disable(); @@ -276,17 +342,13 @@ impl EngineCore { unsafe fn get_word_remainder(&self, candidate: *mut IBusText) -> Option<*mut IBusText> { match CStr::from_ptr((*candidate).text as *const c_char).to_str() { - Ok(word) => { - match into_ibus_string(String::from(&word[self.word_buffer.len()..])) { - Ok(ibus_word) => { - Some(ibus_word) - } - Err(err) => { - log::error!("Failed to convert slice back into ibus string: {}", err); - None - } + Ok(word) => match into_ibus_string(String::from(&word[self.word_buffer.len()..])) { + Ok(ibus_word) => Some(ibus_word), + Err(err) => { + log::error!("Failed to convert slice back into ibus string: {}", err); + None } - } + }, Err(err) => { log::error!("Failed to convert word to string: {}", err); None @@ -294,9 +356,8 @@ impl EngineCore { } } - /* - ** Symbol input methods ** + ** Symbol input methods ** */ unsafe fn symbol_table_enable(&mut self) -> gboolean { @@ -309,7 +370,11 @@ impl EngineCore { self.table_visible = true; self.symbol_last_page = 0; ibus_lookup_table_clear(self.get_table()); - ibus_engine_update_lookup_table(self.parent_engine_as_ibus_engine(), self.get_table(), GBOOL_TRUE); + ibus_engine_update_lookup_table( + self.parent_engine_as_ibus_engine(), + self.get_table(), + GBOOL_TRUE, + ); GBOOL_TRUE } @@ -326,23 +391,36 @@ impl EngineCore { ibus_engine_hide_lookup_table(self.parent_engine_as_ibus_engine()); ibus_engine_hide_auxiliary_text(self.parent_engine_as_ibus_engine()); for i in 0..(*self.get_table()).page_size { - ibus_lookup_table_set_label(self.get_table(), i, ibus_text_new_from_static_string(empty_cstring.as_ptr())); + ibus_lookup_table_set_label( + self.get_table(), + i, + ibus_text_new_from_static_string(empty_cstring.as_ptr()), + ); } GBOOL_TRUE } unsafe fn symbol_input_update(&mut self) { if !self.table_visible || self.input_mode != SymbolTable { - log::error!("Word table update called while table invisible or input mode is not symbol"); + log::error!( + "Word table update called while table invisible or input mode is not symbol" + ); return; } match into_ibus_string(self.symbol_preedit.clone()) { Ok(ibus_string) => { - ibus_engine_update_auxiliary_text(self.parent_engine_as_ibus_engine(), ibus_string, GBOOL_TRUE); + ibus_engine_update_auxiliary_text( + self.parent_engine_as_ibus_engine(), + ibus_string, + GBOOL_TRUE, + ); } Err(err) => { - log::error!("Failed string conversion for symbol aux text update: {}", err); + log::error!( + "Failed string conversion for symbol aux text update: {}", + err + ); } } @@ -350,22 +428,40 @@ impl EngineCore { return; } - let search_result = PREDICTOR.symbol(self.symbol_preedit.as_str()); + let search_result = PREDICTOR.symbol(self.symbol_preedit.as_str()); match search_result { Ok(candidates) => { - log::info!("Symbol search for {} and got {:?}", self.symbol_preedit, candidates); + log::info!( + "Symbol search for {} and got {:?}", + self.symbol_preedit, + candidates + ); let table = self.get_table(); // Must clear table first, since the table may have IBusText referencing the // symbol_label_vec strings ibus_lookup_table_clear(table); self.symbol_label_vec.clear(); for (idx, (shortcode, ident)) in candidates.into_iter().enumerate() { - match (CString::new(shortcode.into_bytes()), CString::new(ident.into_bytes())) { + match ( + CString::new(shortcode.into_bytes()), + CString::new(ident.into_bytes()), + ) { (Ok(shortcode_cstring), Ok(ident_cstring)) => { - ibus_lookup_table_append_candidate(table, ibus_text_new_from_string(shortcode_cstring.into_raw() as *mut gchar)); + ibus_lookup_table_append_candidate( + table, + ibus_text_new_from_string( + shortcode_cstring.into_raw() as *mut gchar + ), + ); self.symbol_label_vec.push(ident_cstring); if idx < (*table).page_size as usize { - ibus_lookup_table_set_label(table, idx as guint, ibus_text_new_from_static_string(self.symbol_label_vec.get_unchecked(idx).as_ptr())); + ibus_lookup_table_set_label( + table, + idx as guint, + ibus_text_new_from_static_string( + self.symbol_label_vec.get_unchecked(idx).as_ptr(), + ), + ); } } _ => { @@ -373,9 +469,17 @@ impl EngineCore { } } } - log::info!("{} candidates and {} labels", ibus_lookup_table_get_number_of_candidates(self.get_table()), self.symbol_label_vec.len()); - ibus_engine_update_lookup_table(self.parent_engine_as_ibus_engine(), table, GBOOL_TRUE); - }, + log::info!( + "{} candidates and {} labels", + ibus_lookup_table_get_number_of_candidates(self.get_table()), + self.symbol_label_vec.len() + ); + ibus_engine_update_lookup_table( + self.parent_engine_as_ibus_engine(), + table, + GBOOL_TRUE, + ); + } Err(err) => { log::error!("{}", err); } @@ -389,9 +493,8 @@ impl EngineCore { } if !self.symbol_preedit.is_empty() { - let idx = input_idx.unwrap_or_else(|| { - ibus_lookup_table_get_cursor_in_page(self.get_table()) - }); + let idx = + input_idx.unwrap_or_else(|| ibus_lookup_table_get_cursor_in_page(self.get_table())); let symbol = ibus_lookup_table_get_label(self.get_table(), idx); self.commit_text(symbol); } @@ -400,7 +503,6 @@ impl EngineCore { } } - #[no_mangle] pub unsafe extern "C" fn free_engine_core(engine_state: *mut EngineCore) { std::mem::drop(Box::from_raw(engine_state)); @@ -409,14 +511,14 @@ pub unsafe extern "C" fn free_engine_core(engine_state: *mut EngineCore) { #[repr(C)] pub struct WordPredictions { len: c_int, - words: *mut *mut c_char + words: *mut *mut c_char, } #[repr(C)] pub struct SymbolPredictions { len: c_int, symbols: *mut *mut c_char, - shortcodes: *mut *mut c_char + shortcodes: *mut *mut c_char, } #[no_mangle] @@ -483,9 +585,12 @@ pub unsafe extern "C" fn ibus_eei_engine_reset(engine: *mut IBusEngine) { } #[no_mangle] -pub unsafe extern "C" fn ibus_eei_engine_candidate_clicked(engine: *mut IBusEngine, indx: guint, _button_state: guint, - _keyboard_state: guint) { - +pub unsafe extern "C" fn ibus_eei_engine_candidate_clicked( + engine: *mut IBusEngine, + indx: guint, + _button_state: guint, + _keyboard_state: guint, +) { match EngineCore::get(engine) { Some(engine_core) => { let offset = if engine_core.input_mode == WordTable { @@ -508,47 +613,35 @@ pub unsafe extern "C" fn ibus_eei_engine_candidate_clicked(engine: *mut IBusEngi } #[no_mangle] -pub unsafe extern "C" fn ibus_eei_engine_process_key_event(engine: *mut IBusEngine, keyval: guint, - _keycode: guint, modifiers: guint) -> gboolean { - +pub unsafe extern "C" fn ibus_eei_engine_process_key_event( + engine: *mut IBusEngine, + keyval: guint, + _keycode: guint, + modifiers: guint, +) -> gboolean { let engine_core = match EngineCore::get(engine) { Some(engine_ref) => engine_ref, None => { log::error!("Could not retrieve engine core for key eent"); - return GBOOL_FALSE + return GBOOL_FALSE; } }; - if modifiers == IBusModifierType_IBUS_CONTROL_MASK { //control key (and only control key) is held down return match keyval { - IBUS_e => { - match engine_core.input_mode { - SymbolTable => { - engine_core.symbol_table_disable() - } - WordTable => {GBOOL_FALSE} - Normal => { - engine_core.symbol_table_enable() - } - } - } - IBUS_w => { - match engine_core.input_mode { - SymbolTable => {GBOOL_FALSE} - WordTable => { - engine_core.word_table_disable() - } - Normal => { - engine_core.word_table_enable() - } - } - } - _ => { - GBOOL_FALSE - } - } + IBUS_e => match engine_core.input_mode { + SymbolTable => engine_core.symbol_table_disable(), + WordTable => GBOOL_FALSE, + Normal => engine_core.symbol_table_enable(), + }, + IBUS_w => match engine_core.input_mode { + SymbolTable => GBOOL_FALSE, + WordTable => engine_core.word_table_disable(), + Normal => engine_core.word_table_enable(), + }, + _ => GBOOL_FALSE, + }; } else if (modifiers & !IBusModifierType_IBUS_SHIFT_MASK) != 0 { return GBOOL_FALSE; //This also covers released keys with IBUS_RELEASE_MASK } @@ -558,7 +651,7 @@ pub unsafe extern "C" fn ibus_eei_engine_process_key_event(engine: *mut IBusEngi match engine_core.input_mode { SymbolTable => { engine_core.symbol_table_disable(); - }, + } WordTable => { engine_core.word_table_disable(); } @@ -568,9 +661,7 @@ pub unsafe extern "C" fn ibus_eei_engine_process_key_event(engine: *mut IBusEngi engine_core.word_buffer.clear(); GBOOL_TRUE } - IBUS_Return => { - engine_core.commit_from_table(None) - } + IBUS_Return => engine_core.commit_from_table(None), IBUS_Right | IBUS_Left => { if engine_core.input_mode == WordTable { engine_core.word_table_disable(); @@ -600,7 +691,7 @@ pub unsafe extern "C" fn ibus_eei_engine_process_key_event(engine: *mut IBusEngi match engine_core.input_mode { SymbolTable => { engine_core.symbol_preedit.pop(); - if engine_core.symbol_preedit.len() == 0 { + if engine_core.symbol_preedit.is_empty() { engine_core.symbol_table_disable(); } else { engine_core.symbol_input_update(); @@ -618,15 +709,9 @@ pub unsafe extern "C" fn ibus_eei_engine_process_key_event(engine: *mut IBusEngi } } } - IBUS_Page_Down => { - engine_core.page_down_and_update() - } - IBUS_Page_Up => { - engine_core.page_up_and_update() - } - IBUS_Escape => { - engine_core.abort_table_input() - } + IBUS_Page_Down => engine_core.page_down_and_update(), + IBUS_Page_Up => engine_core.page_up_and_update(), + IBUS_Escape => engine_core.abort_table_input(), IBUS_space..=IBUS_asciitilde => { match engine_core.input_mode { SymbolTable => { @@ -643,28 +728,39 @@ pub unsafe extern "C" fn ibus_eei_engine_process_key_event(engine: *mut IBusEngi } GBOOL_TRUE } - _ => GBOOL_FALSE + _ => GBOOL_FALSE, } } - static DATA_DIRNAME: &str = "eei"; #[no_mangle] pub unsafe extern "C" fn configure_logging() { //https://stackoverflow.com/questions/56345288/how-do-i-use-log4rs-rollingfileappender-to-incorporate-rolling-logging - let log_location = std::env::var("XDG_DATA_HOME").map(|dir| Path::new(dir.as_str()).join(DATA_DIRNAME)) - .or(std::env::var("HOME").map(|home| Path::new(home.as_str()).join(".local").join("share").join(DATA_DIRNAME))); + let log_location = std::env::var("XDG_DATA_HOME") + .map(|dir| Path::new(dir.as_str()).join(DATA_DIRNAME)) + .or(std::env::var("HOME").map(|home| { + Path::new(home.as_str()) + .join(".local") + .join("share") + .join(DATA_DIRNAME) + })); match log_location { Ok(location) => { // https://stackoverflow.com/questions/56345288/how-do-i-use-log4rs-rollingfileappender-to-incorporate-rolling-logging let window_size = 3; // log0, log1, log2 - let fixed_window_roller = FixedWindowRoller::builder().build(location.join("log_archive_{}.txt").to_str().unwrap(), window_size).unwrap(); + let fixed_window_roller = FixedWindowRoller::builder() + .build( + location.join("log_archive_{}.txt").to_str().unwrap(), + window_size, + ) + .unwrap(); let size_limit = 1024 * 1000; let size_trigger = SizeTrigger::new(size_limit); - let compound_policy = CompoundPolicy::new(Box::new(size_trigger), Box::new(fixed_window_roller)); + let compound_policy = + CompoundPolicy::new(Box::new(size_trigger), Box::new(fixed_window_roller)); let log_level = if cfg!(debug_assertions) { LevelFilter::Debug @@ -680,8 +776,11 @@ pub unsafe extern "C" fn configure_logging() { "logfile", Box::new( RollingFileAppender::builder() - .encoder(Box::new(PatternEncoder::new("{d(%Y-%m-%d %H:%M:%S)} {l}::{m}{n}"))) - .build(location.join("log.txt"), Box::new(compound_policy)).unwrap(), + .encoder(Box::new(PatternEncoder::new( + "{d(%Y-%m-%d %H:%M:%S)} {l}::{m}{n}", + ))) + .build(location.join("log.txt"), Box::new(compound_policy)) + .unwrap(), ), ), ) @@ -689,7 +788,8 @@ pub unsafe extern "C" fn configure_logging() { Root::builder() .appender("logfile") .build(LevelFilter::Debug), - ).unwrap(); + ) + .unwrap(); log4rs::init_config(config).unwrap(); @@ -700,5 +800,3 @@ pub unsafe extern "C" fn configure_logging() { } } } - - diff --git a/src/predict/lib/src/predict.rs b/src/predict/lib/src/predict.rs index dd674f0..e0a9020 100644 --- a/src/predict/lib/src/predict.rs +++ b/src/predict/lib/src/predict.rs @@ -1,13 +1,13 @@ -use fst::{Map, IntoStreamer}; +use crate::predict::PredictionError::*; use fst::automaton::{Automaton, Str}; +use fst::{IntoStreamer, Map}; use lazy_static::lazy_static; -use crate::predict::PredictionError::*; use std::fmt; pub struct Predictor { dictionary: Map>, shortcode_dictionary: Map>, - symbols: Vec + symbols: Vec, } #[derive(Debug)] @@ -20,19 +20,23 @@ impl fmt::Display for PredictionError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { FstError(err) => write!(f, "FST error: {}", err), - MissingSymbol(sym, codepoint) => write!(f, "Missing shortcode: {}, for codepoint {}", sym, codepoint), + MissingSymbol(sym, codepoint) => { + write!(f, "Missing shortcode: {}, for codepoint {}", sym, codepoint) + } } } } - impl Predictor { const WORD_COUNT: usize = 25; fn is_title_cased(context: &str) -> bool { let mut chars = context.chars(); - let first_letter_capitalized = chars.next().map(|char| char.is_ascii_uppercase()).unwrap_or(false); + let first_letter_capitalized = chars + .next() + .map(|char| char.is_ascii_uppercase()) + .unwrap_or(false); let other_capitals: i8 = chars.map(|char| char.is_ascii_uppercase() as i8).sum(); first_letter_capitalized & (other_capitals == 0) } @@ -42,14 +46,17 @@ impl Predictor { chars.next().unwrap().to_ascii_uppercase().to_string() + chars.as_str() } - pub fn word(&self, context: &str) -> Result, PredictionError> { + pub fn word(&self, context: &str) -> Result, PredictionError> { let title_cased = Predictor::is_title_cased(context); let lowercase_context = context.to_ascii_lowercase(); let matcher = Str::new(lowercase_context.as_str()).starts_with(); - let mut search_results = self.dictionary.search(matcher) + let mut search_results = self + .dictionary + .search(matcher) .into_stream() - .into_str_vec().map_err(FstError)?; + .into_str_vec() + .map_err(FstError)?; search_results.sort_by(|(_w1, f1), (_w2, f2)| f2.cmp(f1)); let final_results = search_results @@ -61,29 +68,34 @@ impl Predictor { word } }) - .take(Predictor::WORD_COUNT).collect(); + .take(Predictor::WORD_COUNT) + .collect(); Ok(final_results) } - pub fn symbol(&self, context: &str) -> Result, PredictionError> { + pub fn symbol(&self, context: &str) -> Result, PredictionError> { let matcher = Str::new(context).starts_with(); - let search_results = self.shortcode_dictionary.search(matcher) + let search_results = self + .shortcode_dictionary + .search(matcher) .into_stream() - .into_str_vec().map_err(FstError)?; + .into_str_vec() + .map_err(FstError)?; //must be into_iter() and not iter() - the latter iterates over references, but we need //to take ownership to return the shortcode data without clone() - Ok(search_results.into_iter().map(|(shortcode, ident)| { - match self.symbols.get(ident as usize) { - Some(symbol) => Ok((shortcode, symbol.clone())), - None => Err(MissingSymbol(shortcode, ident)) - } - }).collect::, _>>()?) + search_results + .into_iter() + .map( + |(shortcode, ident)| match self.symbols.get(ident as usize) { + Some(symbol) => Ok((shortcode, symbol.clone())), + None => Err(MissingSymbol(shortcode, ident)), + }, + ) + .collect::, _>>() } } - - lazy_static! { pub static ref PREDICTOR: Predictor = Predictor { dictionary: Map::new(include_bytes!("../../dictionary.fst").to_vec()).unwrap(), @@ -98,18 +110,22 @@ mod tests { fn symbol_test(head: &str) { let symbol_results = PREDICTOR.symbol(head).unwrap(); - println!("symbols for {head}", head=head); + println!("symbols for {head}", head = head); for (shortcode, symbol) in symbol_results { - println!("{shortcode} : {symbol}", shortcode=shortcode, symbol=symbol); + println!( + "{shortcode} : {symbol}", + shortcode = shortcode, + symbol = symbol + ); } } fn word_test(head: &str) { let word_results = PREDICTOR.word(head).unwrap(); - println!("words for {head}:", head=head); + println!("words for {head}:", head = head); for word in word_results { - println!("{word}", word=word); + println!("{word}", word = word); } } diff --git a/src/predict/preproc/src/main.rs b/src/predict/preproc/src/main.rs index 34a9638..0fb776a 100644 --- a/src/predict/preproc/src/main.rs +++ b/src/predict/preproc/src/main.rs @@ -1,105 +1,126 @@ -use std::collections::{HashMap, HashSet}; use fst::MapBuilder; -use std::num::ParseIntError; -use std::fs::File; -use std::io; +use std::collections::{HashMap, HashSet}; use std::error; -use std::io::{Write, BufRead}; -use crate::ParseError::*; use std::fmt::{Display, Formatter}; - +use std::fs::File; +use std::io; +use std::io::{BufRead, Write}; +use std::num::ParseIntError; #[derive(Debug, Clone)] -enum ParseError { - InvalidJson(String), - InvalidHex(ParseIntError), - InvalidCodepoint(u32), - InvalidWordFreq(String) +#[allow(dead_code)] +enum InvalidParseError { + Json(String), + Hex(ParseIntError), + Codepoint(u32), + WordFreq(String), } //code point;class;char;entity name;entity set;note/description;CHARACTER NAME - - -impl Display for ParseError { +impl Display for InvalidParseError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } -impl error::Error for ParseError {} +impl error::Error for InvalidParseError {} //https://stackoverflow.com/questions/69152223/unicode-codepoint-to-rust-string -fn parse_unicode(input: &str) -> Result { - let unicode = u32::from_str_radix(input, 16).map_err(InvalidHex)?; - char::from_u32(unicode).ok_or_else(|| InvalidCodepoint(unicode)) +fn parse_unicode(input: &str) -> Result { + let unicode = u32::from_str_radix(input, 16).map_err(InvalidParseError::Hex)?; + char::from_u32(unicode).ok_or(InvalidParseError::Codepoint(unicode)) } -fn parse_github_emoji_url(url: &String) -> Result { - let bytecode_strings = url.split('/') - .last().ok_or(InvalidJson(url.clone()))? - .split('.').next().ok_or(InvalidJson(url.clone()))?.split('-'); - - bytecode_strings.map(|codepoint| parse_unicode(codepoint)) - .collect::, _>>().map(|char_vec|char_vec.into_iter().collect::()) +fn parse_github_emoji_url(url: &str) -> Result { + let bytecode_strings = url + .split('/') + .last() + .ok_or(InvalidParseError::Json(url.to_owned()))? + .split('.') + .next() + .ok_or(InvalidParseError::Json(url.to_owned()))? + .split('-'); + + bytecode_strings + .map(parse_unicode) + .collect::, _>>() + .map(|char_vec| char_vec.into_iter().collect::()) } fn math_symbol_shortcodes() -> Vec<(String, String)> { let whitelist = io::BufReader::new(File::open("math_whitelist.txt").unwrap()) - .lines().collect::, _>>().unwrap(); + .lines() + .collect::, _>>() + .unwrap(); - let reader = ureq::get("https://www.unicode.org/Public/math/revision-15/MathClassEx-15.txt").call() + let reader = ureq::get("https://www.unicode.org/Public/math/revision-15/MathClassEx-15.txt") + .call() .unwrap() .into_reader(); - let rdr = csv::ReaderBuilder::new() .comment(Some(b'#')) .delimiter(b';') .has_headers(false) .from_reader(reader); - rdr.into_records().filter_map(|result| { - result.ok().map(|record| { - let symbol = &record[2]; - (String::from(&record[3]), String::from(symbol)) //shortcode, symbol + rdr.into_records() + .filter_map(|result| { + result.ok().map(|record| { + let symbol = &record[2]; + (String::from(&record[3]), String::from(symbol)) //shortcode, symbol + }) }) - }).filter(|(_shortcode, symbol)| {whitelist.contains(symbol)} ).collect() + .filter(|(_shortcode, symbol)| whitelist.contains(symbol)) + .collect() } fn github_emoji_shortcodes() -> Vec<(String, String)> { - let json: HashMap = ureq::get("https://api.github.com/emojis").call() + let json: HashMap = ureq::get("https://api.github.com/emojis") + .call() .unwrap() .into_json() .unwrap(); //have to filter out bad URLs like // "https://github.githubassets.com/images/icons/emoji/bowtie.png?v8" - json.iter().filter_map(|(key, url)| { + json.iter() + .filter_map(|(key, url)| { let key_chars: Vec = key.chars().collect(); - if key_chars.get(0).map(|c| c == &'u').unwrap_or(false) && - key_chars.get(1).map(|c| c.is_ascii_digit()).unwrap_or(false) { + if key_chars.first().map(|c| c == &'u').unwrap_or(false) + && key_chars + .get(1) + .map(|c| c.is_ascii_digit()) + .unwrap_or(false) + { None //filter out "u5272" etc. for Japanese emoji } else { - parse_github_emoji_url(url).map(|unicode_str| (key.clone(), unicode_str)).ok() + parse_github_emoji_url(url) + .map(|unicode_str| (key.clone(), unicode_str)) + .ok() } - }).collect::>() + }) + .collect::>() } - -fn write_symbols_and_shortcodes(mut shortcodes_symbols: Vec<(String, String)>) -> Result<(), Box> { +fn write_symbols_and_shortcodes( + mut shortcodes_symbols: Vec<(String, String)>, +) -> Result<(), Box> { let writer = io::BufWriter::new(File::create("shortcodes.fst")?); let mut map_builder = MapBuilder::new(writer)?; shortcodes_symbols.sort(); - let symbols = shortcodes_symbols.iter() - .map(|(_shortcode, symbol)| { symbol }) + let symbols = shortcodes_symbols + .iter() + .map(|(_shortcode, symbol)| symbol) .collect::>(); - let symbol_id_map: HashMap<&String, u64> = symbols.iter() + let symbol_id_map: HashMap<&String, u64> = symbols + .iter() .enumerate() - .map(|(idx, symbol)| { (*symbol, idx as u64) }) + .map(|(idx, symbol)| (*symbol, idx as u64)) .collect(); for (shortcode, symbol) in shortcodes_symbols.iter() { @@ -112,9 +133,11 @@ fn write_symbols_and_shortcodes(mut shortcodes_symbols: Vec<(String, String)>) - let mut symbol_file = File::create("symbols.bin")?; symbol_file.write_all(&bincode::serialize(&symbols)?)?; - println!("Wrote {shortcodes} shortcodes for {symbols} symbols", - shortcodes=shortcodes_symbols.len(), - symbols=symbols.len()); + println!( + "Wrote {shortcodes} shortcodes for {symbols} symbols", + shortcodes = shortcodes_symbols.len(), + symbols = symbols.len() + ); Ok(()) } @@ -124,19 +147,31 @@ fn load_word_freq_data() -> Result, Box> .lines() .collect::, _>>()?; - Ok(lines.into_iter().filter_map(|line| { - if line.len() == 0 { - None - } - else { - let mut split_line = line.split("\t"); - Some(split_line.next().ok_or(InvalidWordFreq(line.clone())).and_then(|word| { - split_line.last().ok_or(InvalidWordFreq(line.clone())) - .and_then(|x| x.parse::().map_err(|_| {InvalidWordFreq(line.clone())})) - .map(|count| (word.to_lowercase(), count)) - })) - } - }).collect::, ParseError>>()?) + Ok(lines + .into_iter() + .filter_map(|line| { + if line.is_empty() { + None + } else { + let mut split_line = line.split("\t"); + Some( + split_line + .next() + .ok_or(InvalidParseError::WordFreq(line.clone())) + .and_then(|word| { + split_line + .last() + .ok_or(InvalidParseError::WordFreq(line.clone())) + .and_then(|x| { + x.parse::() + .map_err(|_| InvalidParseError::WordFreq(line.clone())) + }) + .map(|count| (word.to_lowercase(), count)) + }), + ) + } + }) + .collect::, InvalidParseError>>()?) } fn process_dictionary() -> Result<(), Box> { @@ -145,11 +180,7 @@ fn process_dictionary() -> Result<(), Box> { let mut lines = io::BufReader::new(File::open("hunspell_US.txt")?) .lines() - .map(|line_res| { - line_res.map(|line| { - line.to_lowercase() - }) - }) + .map(|line_res| line_res.map(|line| line.to_lowercase())) .collect::, _>>()?; //must be in lexographical order to build the FST @@ -160,23 +191,28 @@ fn process_dictionary() -> Result<(), Box> { let mut words_without_freq = 0; for line in lines.iter() { - map_builder.insert(line, - *word_freq.get(line.as_str()).unwrap_or_else(|| { - words_without_freq += 1; - &0 - }))?; + map_builder.insert( + line, + *word_freq.get(line.as_str()).unwrap_or_else(|| { + words_without_freq += 1; + &0 + }), + )?; } let words_with_freq = lines.len() - words_without_freq; map_builder.finish()?; - println!("Wrote {entries} dictionary entries, of which {with_freq} had frequency ({perc:.2}%)", - entries=lines.len(), with_freq=words_with_freq, - perc=(words_with_freq as f64 / lines.len() as f64)); + println!( + "Wrote {entries} dictionary entries, of which {with_freq} had frequency ({perc:.2}%)", + entries = lines.len(), + with_freq = words_with_freq, + perc = (words_with_freq as f64 / lines.len() as f64) + ); Ok(()) } -fn main() -> Result<(), Box>{ +fn main() -> Result<(), Box> { println!("Fetching math symbols"); let math_symbols = math_symbol_shortcodes(); @@ -192,5 +228,3 @@ fn main() -> Result<(), Box>{ Ok(()) } - -