Skip to content

Commit

Permalink
feat(editor): implement SimpleEngine which doesn't use intelligent ph…
Browse files Browse the repository at this point in the history
…rasing
  • Loading branch information
kanru committed Jul 14, 2024
1 parent 5e0cf21 commit a372100
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 19 deletions.
30 changes: 25 additions & 5 deletions capi/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ use std::{
};

use chewing::{
conversion::{ChewingEngine, Interval, Symbol},
conversion::{ChewingEngine, Interval, SimpleEngine, Symbol},
dictionary::{Layered, SystemDictionaryLoader, UserDictionaryLoader},
editor::{
keyboard::{AnyKeyboardLayout, KeyCode, KeyboardLayout, Modifiers, Qwerty},
zhuyin_layout::{
DaiChien26, Et, Et26, GinYieh, Hsu, Ibm, KeyboardLayoutCompat, Pinyin, Standard,
SyllableEditor,
},
BasicEditor, CharacterForm, Editor, EditorKeyBehavior, LanguageMode, LaxUserFreqEstimate,
UserPhraseAddDirection,
BasicEditor, CharacterForm, ConversionEngineKind, Editor, EditorKeyBehavior, LanguageMode,
LaxUserFreqEstimate, UserPhraseAddDirection,
},
zhuyin::Syllable,
};
Expand Down Expand Up @@ -171,7 +171,7 @@ pub unsafe extern "C" fn chewing_new2(
let estimate = LaxUserFreqEstimate::max_from(user_dictionary.as_ref());

let dict = Layered::new(dictionaries, user_dictionary);
let conversion_engine = ChewingEngine::new();
let conversion_engine = Box::new(ChewingEngine::new());
let kb_compat = KeyboardLayoutCompat::Default;
let keyboard = AnyKeyboardLayout::Qwerty(Qwerty);
let editor = Editor::new(conversion_engine, dict, estimate, abbrev, sym_sel);
Expand Down Expand Up @@ -303,7 +303,8 @@ pub unsafe extern "C" fn chewing_config_has_option(
| "chewing.selection_keys"
| "chewing.character_form"
| "chewing.space_is_select_key"
| "chewing.fuzzy_search_mode" => true,
| "chewing.fuzzy_search_mode"
| "cheiwng.conversion_engine" => true,
_ => false,
};

Expand Down Expand Up @@ -346,6 +347,10 @@ pub unsafe extern "C" fn chewing_config_get_int(
},
"chewing.space_is_select_key" => option.space_is_select_key as c_int,
"chewing.fuzzy_search_mode" => option.fuzzy_search as c_int,
"chewing.conversion_engine" => match option.conversion_engine {
ConversionEngineKind::ChewingEngine => 0,
ConversionEngineKind::SimpleEngine => 1,
},
_ => ERROR,
}
}
Expand Down Expand Up @@ -438,6 +443,21 @@ pub unsafe extern "C" fn chewing_config_set_int(
ensure_bool!(value);
options.fuzzy_search = value > 0;
}
"chewing.conversion_engine" => {
options.conversion_engine = match value {
0 => {
ctx.editor
.set_conversion_engine(Box::new(ChewingEngine::new()));
ConversionEngineKind::ChewingEngine
}
1 => {
ctx.editor
.set_conversion_engine(Box::new(SimpleEngine::new()));
ConversionEngineKind::SimpleEngine
}
_ => return ERROR,
}
}
_ => return ERROR,
};

Expand Down
72 changes: 71 additions & 1 deletion src/conversion/chewing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use log::trace;

use crate::dictionary::{Dictionary, Phrase};

use super::{Composition, Gap, Interval, Symbol};
use super::{Composition, ConversionEngine, Gap, Interval, Symbol};

/// TODO: doc
#[derive(Debug, Default)]
Expand Down Expand Up @@ -51,6 +51,76 @@ impl ChewingEngine {
}
}

impl ConversionEngine for ChewingEngine {
fn convert<'a>(
&'a self,
dict: &'a dyn Dictionary,
comp: &'a Composition,
) -> Box<dyn Iterator<Item = Vec<Interval>> + 'a> {
Box::new(ChewingEngine::convert(self, dict, comp))
}
}

/// Simple engine does not perform any intelligent conversion.
#[derive(Debug, Default)]
pub struct SimpleEngine;

impl SimpleEngine {
pub fn new() -> SimpleEngine {
SimpleEngine
}
pub fn convert<'a>(
&'a self,
dict: &'a dyn Dictionary,
comp: &'a Composition,
) -> impl Iterator<Item = Vec<Interval>> + Clone + 'a {
let mut intervals = vec![];

for (i, sym) in comp.symbols().iter().enumerate() {
if comp
.selections
.iter()
.any(|selection| selection.intersect_range(i, i + 1))
{
continue;
}
if sym.is_char() {
intervals.push(Interval {
start: i,
end: i + 1,
is_phrase: false,
str: sym.to_char().unwrap().to_string().into_boxed_str(),
});
} else {
let phrase = dict.lookup_first_phrase(&[sym.to_syllable().unwrap()]);
let phrase_str = phrase.map_or_else(
|| sym.to_syllable().unwrap().to_string(),
|phrase| phrase.to_string(),
);
intervals.push(Interval {
start: i,
end: i + 1,
is_phrase: true,
str: phrase_str.into_boxed_str(),
})
}
}
intervals.extend_from_slice(comp.selections());
intervals.sort_by_key(|int| int.start);
iter::once(intervals)
}
}

impl ConversionEngine for SimpleEngine {
fn convert<'a>(
&'a self,
dict: &'a dyn Dictionary,
comp: &'a Composition,
) -> Box<dyn Iterator<Item = Vec<Interval>> + 'a> {
Box::new(SimpleEngine::convert(self, dict, comp))
}
}

fn glue_fn(com: &Composition, mut acc: Vec<Interval>, interval: Interval) -> Vec<Interval> {
if acc.is_empty() {
acc.push(interval);
Expand Down
15 changes: 13 additions & 2 deletions src/conversion/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,22 @@ use std::{
fmt::Debug,
};

use crate::zhuyin::{Syllable, SyllableSlice};
use crate::{
dictionary::Dictionary,
zhuyin::{Syllable, SyllableSlice},
};

pub use self::chewing::ChewingEngine;
pub use self::chewing::{ChewingEngine, SimpleEngine};
pub(crate) use self::symbol::{full_width_symbol_input, special_symbol_input};

pub trait ConversionEngine: Debug {
fn convert<'a>(
&'a self,
dict: &'a dyn Dictionary,
comp: &'a Composition,
) -> Box<dyn Iterator<Item = Vec<Interval>> + 'a>;
}

/// TODO: doc
#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
pub struct Interval {
Expand Down
36 changes: 26 additions & 10 deletions src/editor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ pub use estimate::{LaxUserFreqEstimate, UserFreqEstimate};
use log::{debug, info, trace, warn};

use crate::{
conversion::{full_width_symbol_input, special_symbol_input, ChewingEngine, Interval, Symbol},
conversion::{
full_width_symbol_input, special_symbol_input, ChewingEngine, ConversionEngine, Interval,
Symbol,
},
dictionary::{
Dictionary, DictionaryMut, Layered, LookupStrategy, SystemDictionaryLoader,
UpdateDictionaryError, UserDictionaryLoader,
Expand Down Expand Up @@ -53,6 +56,12 @@ pub enum UserPhraseAddDirection {
Backward,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConversionEngineKind {
ChewingEngine,
SimpleEngine,
}

#[derive(Debug, Clone, Copy)]
pub struct EditorOptions {
pub easy_symbol_input: bool,
Expand All @@ -67,6 +76,7 @@ pub struct EditorOptions {
pub character_form: CharacterForm,
pub user_phrase_add_dir: UserPhraseAddDirection,
pub fuzzy_search: bool,
pub conversion_engine: ConversionEngineKind,
}

impl Default for EditorOptions {
Expand All @@ -84,6 +94,8 @@ impl Default for EditorOptions {
character_form: CharacterForm::Halfwidth,
user_phrase_add_dir: UserPhraseAddDirection::Forward,
fuzzy_search: false,
// FIXME may be out of sync with the engine used
conversion_engine: ConversionEngineKind::ChewingEngine,
}
}
}
Expand Down Expand Up @@ -160,7 +172,7 @@ impl Error for EditorError {}
pub(crate) struct SharedState {
com: CompositionEditor,
syl: Box<dyn SyllableEditor>,
conv: ChewingEngine,
conv: Box<dyn ConversionEngine>,
dict: Layered,
abbr: AbbrevTable,
sym_sel: SymbolSelector,
Expand All @@ -180,15 +192,15 @@ impl Editor {
let user_dict = UserDictionaryLoader::new().load()?;
let estimate = LaxUserFreqEstimate::max_from(user_dict.as_ref());
let dict = Layered::new(system_dict, user_dict);
let conversion_engine = ChewingEngine::new();
let conversion_engine = Box::new(ChewingEngine::new());
let abbrev = SystemDictionaryLoader::new().load_abbrev()?;
let sym_sel = SystemDictionaryLoader::new().load_symbol_selector()?;
let editor = Editor::new(conversion_engine, dict, estimate, abbrev, sym_sel);
Ok(editor)
}

pub fn new(
conv: ChewingEngine,
conv: Box<dyn ConversionEngine>,
dict: Layered,
estimate: LaxUserFreqEstimate,
abbr: AbbrevTable,
Expand Down Expand Up @@ -218,6 +230,10 @@ impl Editor {
self.shared.syl = syl;
info!("Set syllable editor: {:?}", self.shared.syl);
}
pub fn set_conversion_engine(&mut self, engine: Box<dyn ConversionEngine>) {
self.shared.conv = engine;
info!("Set conversion engine: {:?}", self.shared.conv);
}
pub fn clear(&mut self) {
self.state = Box::new(Entering);
self.shared.clear();
Expand Down Expand Up @@ -1486,7 +1502,7 @@ mod tests {
vec![Box::new(TrieBuf::new_in_memory())],
Box::new(TrieBuf::new_in_memory()),
);
let conversion_engine = ChewingEngine::new();
let conversion_engine = Box::new(ChewingEngine::new());
let estimate = LaxUserFreqEstimate::new(0);
let abbrev = AbbrevTable::new();
let sym_sel = SymbolSelector::default();
Expand All @@ -1513,7 +1529,7 @@ mod tests {
vec![("冊", 100)],
)]);
let dict = Layered::new(vec![Box::new(dict)], Box::new(TrieBuf::new_in_memory()));
let conversion_engine = ChewingEngine::new();
let conversion_engine = Box::new(ChewingEngine::new());
let estimate = LaxUserFreqEstimate::new(0);
let abbrev = AbbrevTable::new();
let sym_sel = SymbolSelector::default();
Expand Down Expand Up @@ -1546,7 +1562,7 @@ mod tests {
vec![("冊", 100)],
)]);
let dict = Layered::new(vec![Box::new(dict)], Box::new(TrieBuf::new_in_memory()));
let conversion_engine = ChewingEngine::new();
let conversion_engine = Box::new(ChewingEngine::new());
let estimate = LaxUserFreqEstimate::new(0);
let abbrev = AbbrevTable::new();
let sym_sel = SymbolSelector::default();
Expand Down Expand Up @@ -1589,7 +1605,7 @@ mod tests {
vec![("冊", 100)],
)]);
let dict = Layered::new(vec![Box::new(dict)], Box::new(TrieBuf::new_in_memory()));
let conversion_engine = ChewingEngine::new();
let conversion_engine = Box::new(ChewingEngine::new());
let estimate = LaxUserFreqEstimate::new(0);
let abbrev = AbbrevTable::new();
let sym_sel = SymbolSelector::default();
Expand Down Expand Up @@ -1647,7 +1663,7 @@ mod tests {
vec![("冊", 100)],
)]);
let dict = Layered::new(vec![Box::new(dict)], Box::new(TrieBuf::new_in_memory()));
let conversion_engine = ChewingEngine::new();
let conversion_engine = Box::new(ChewingEngine::new());
let estimate = LaxUserFreqEstimate::new(0);
let abbrev = AbbrevTable::new();
let sym_sel = SymbolSelector::default();
Expand Down Expand Up @@ -1685,7 +1701,7 @@ mod tests {
let keyboard = Qwerty;
let dict = TrieBuf::new_in_memory();
let dict = Layered::new(vec![Box::new(dict)], Box::new(TrieBuf::new_in_memory()));
let conversion_engine = ChewingEngine::new();
let conversion_engine = Box::new(ChewingEngine::new());
let estimate = LaxUserFreqEstimate::new(0);
let abbrev = AbbrevTable::new();
let sym_sel = SymbolSelector::default();
Expand Down
10 changes: 9 additions & 1 deletion tests/genkeystroke.c
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,9 @@ int main(int argc, char *argv[])
mvaddstr(9, 20, "Ctrl + b : toggle Eng/Chi mode");
mvaddstr(10, 0, "F1, F2, F3, ..., F9 : Add user defined phrase");
mvaddstr(11, 0, "Ctrl + h : toggle Full/Half shape mode");
mvaddstr(12, 0, "Ctrl + n/p : Next / Previous keyboard layout");
mvaddstr(12, 0, "Ctrl + f : toggle Fuzzy Search mode");
mvaddstr(13, 0, "Ctrl + s : toggle Simple mode");
mvaddstr(14, 0, "Ctrl + n/p : Next / Previous keyboard layout");
show_commit_string(14, 0, ctx);
show_userphrase(7, 14, ctx);
show_edit_buffer(1, 0, ctx);
Expand Down Expand Up @@ -459,6 +461,12 @@ int main(int argc, char *argv[])
else
chewing_config_set_int(ctx, "chewing.fuzzy_search_mode", TRUE);
break;
case KEY_CTRL_('S'):
if (chewing_config_get_int(ctx, "chewing.conversion_engine") == 1)
chewing_config_set_int(ctx, "chewing.conversion_engine", 0);
else
chewing_config_set_int(ctx, "chewing.conversion_engine", 1);
break;
case KEY_CTRL_('H'): /* emulate Shift */
if (chewing_get_ShapeMode(ctx) == FULLSHAPE_MODE)
chewing_set_ShapeMode(ctx, HALFSHAPE_MODE);
Expand Down
23 changes: 23 additions & 0 deletions tests/test-bopomofo.c
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,28 @@ void test_FuzzySearchMode_Hanyu()
chewing_delete(ctx);
}

void test_SimpleEngine()
{
const TestData SIMPLE_INPUT[] = {
{"ru0320 5j4up ai6g4<E>", "簡單住因模市" },
{"ru0320 5j4<D>4up <D>2ai6g4<D><D>2<E>", "簡單注音模式" },
};
size_t i;
ChewingContext *ctx;

ctx = chewing_new();
start_testcase(ctx, fd);
chewing_set_maxChiSymbolLen(ctx, 16);
chewing_config_set_int(ctx, "chewing.conversion_engine", 1);

for (i = 0; i < ARRAY_SIZE(SIMPLE_INPUT); ++i) {
type_keystroke_by_string(ctx, SIMPLE_INPUT[i].token);
ok_commit_buffer(ctx, SIMPLE_INPUT[i].expected);
}

chewing_delete(ctx);
}

void test_get_phoneSeq()
{
static const struct {
Expand Down Expand Up @@ -2366,6 +2388,7 @@ int main(int argc, char *argv[])
test_Space();
test_FuzzySearchMode();
test_FuzzySearchMode_Hanyu();
test_SimpleEngine();

test_get_phoneSeq();
test_bopomofo_buffer();
Expand Down
1 change: 1 addition & 0 deletions tests/test-config.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ void test_has_option()
,"chewing.character_form"
,"chewing.space_is_select_key"
,"chewing.fuzzy_search_mode"
,"chewing.conversion_engine"
};

ctx = chewing_new();
Expand Down

0 comments on commit a372100

Please sign in to comment.