mirror of
https://github.com/qingjian-team/qingjian.git
synced 2026-11-07 00:13:18 +00:00
Kotlin 的 InputMethodService 壳 + Rust 的 libqingjian_android.so(Engine 装配、Session、JNI),功能与 PC 对齐: - 键盘:拼音 26 键、九宫格、双拼、五笔、注音大千、英文、日语罗马音(测试版);数字键盘、符号与表情面板、功能面板、中英键换语言、长按空格挪光标 - 候选条用 Rust 渲染器出位图,长按看译词与删除,展开全部候选,邮箱后缀补全 - 输入设置:模糊音、标点全半角、中英混输、分次选词留在组句里、学习开关与私密输入、繁体字形、导入词库与领域词库、算式与日期 - 剪贴板与常用语(拆词、代码高亮、图片)、快捷翻译、系统文字选择菜单里的「青简翻译」、释义兜底、云联想与整句补全 - 学习语言可选,生词本筛选、等级、搜索与标认识,输入统计看板 - 外观:PC 全部内置主题、深浅色、候选字号,键盘配色由渲染器按主题推出 - 语音输入(火山引擎识别,长按空格说话)与滑行输入(拼音全键盘与英文键盘,缺省关) - App:首页、生词本、我的,键盘设置、外观、语音识别、高级(导出与清空学习数据)
247 lines
7.8 KiB
Rust
247 lines
7.8 KiB
Rust
//! 会话层端到端:样例词库上走一遍 敲键 → 选词 → 上屏。
|
|
use std::path::Path;
|
|
|
|
use qingjian_android::{Frame, InputOptions, Session};
|
|
use qingjian_core::{Engine, Language};
|
|
use qingjian_dictionary::Dictionary;
|
|
use qingjian_translate::Glossary;
|
|
|
|
fn sample(name: &str) -> std::path::PathBuf {
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../../../assets/sample")
|
|
.join(name)
|
|
}
|
|
|
|
fn session() -> Session {
|
|
let glossary = Glossary::from_path(Language::English, sample("glossary-en.tsv")).unwrap();
|
|
let engine = Engine::new(Dictionary::from_path(sample("dict.tsv")).unwrap())
|
|
.with_translator(Box::new(glossary));
|
|
let code_table =
|
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../assets/schemes/wubi86/table.tsv");
|
|
let user_dir = std::env::temp_dir().join(format!("qingjian-session-{}", std::process::id()));
|
|
Session::new(engine, code_table, sample(""), user_dir)
|
|
}
|
|
|
|
fn type_keys(session: &mut Session, keys: &str) {
|
|
for c in keys.chars() {
|
|
session.push(c);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn selecting_first_candidate_commits_and_clears() {
|
|
let mut session = session();
|
|
type_keys(&mut session, "niha");
|
|
let frame = session.push('o');
|
|
assert_eq!(frame.preedit, "ni'hao");
|
|
assert_eq!(frame.candidates[0].text, "你好");
|
|
|
|
let frame = session.select(0);
|
|
assert_eq!(frame.commit, "你好");
|
|
assert!(frame.preedit.is_empty());
|
|
assert!(frame.candidates.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn commit_raw_returns_typed_letters() {
|
|
let mut session = session();
|
|
type_keys(&mut session, "niha");
|
|
let frame = session.commit_raw();
|
|
assert_eq!(frame.commit, "niha");
|
|
assert!(frame.preedit.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn backspace_to_empty_gives_empty_frame() {
|
|
let mut session = session();
|
|
session.push('n');
|
|
let frame = session.backspace();
|
|
assert!(frame.preedit.is_empty());
|
|
assert!(frame.candidates.is_empty());
|
|
assert!(frame.commit.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn frame_json_shape() {
|
|
let mut session = session();
|
|
type_keys(&mut session, "niha");
|
|
let json = session.push('o').to_json();
|
|
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(value["preedit"], "ni'hao");
|
|
assert_eq!(value["candidates"][0]["text"], "你好");
|
|
}
|
|
|
|
#[test]
|
|
fn switching_to_english_commits_typed_letters() {
|
|
let mut session = session();
|
|
type_keys(&mut session, "niha");
|
|
let frame = session.set_english_mode(true);
|
|
assert_eq!(frame.commit, "niha");
|
|
assert!(frame.preedit.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn selecting_translation_commits_the_gloss() {
|
|
let mut session = session();
|
|
type_keys(&mut session, "kaif");
|
|
let frame = session.push('a');
|
|
let index = frame
|
|
.candidates
|
|
.iter()
|
|
.position(|candidate| candidate.text == "开发")
|
|
.unwrap();
|
|
let gloss = frame.candidates[index].senses[0].text.clone();
|
|
|
|
let frame = session.select_translation(index, 0);
|
|
assert_eq!(frame.commit, gloss);
|
|
assert!(frame.preedit.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn missing_sense_leaves_composition_alone() {
|
|
let mut session = session();
|
|
type_keys(&mut session, "kaif");
|
|
session.push('a');
|
|
let frame = session.select_translation(0, 9);
|
|
assert!(frame.commit.is_empty());
|
|
assert_eq!(frame.preedit, "kai'fa");
|
|
}
|
|
|
|
#[test]
|
|
fn forgetting_reports_a_notice() {
|
|
let mut session = session();
|
|
type_keys(&mut session, "kaif");
|
|
session.push('a');
|
|
let frame = session.forget(0);
|
|
assert!(frame.notice.contains("开发"));
|
|
assert_eq!(frame.preedit, "kai'fa");
|
|
}
|
|
|
|
#[test]
|
|
fn nine_key_digits_compose_and_pin() {
|
|
let mut session = session();
|
|
session.set_nine_key(true);
|
|
type_keys(&mut session, "5243");
|
|
let frame = session.push('2');
|
|
assert_eq!(frame.preedit, "kai'fa");
|
|
assert_eq!(frame.candidates[0].text, "开发");
|
|
assert_eq!(frame.pinyin_choices[0], "kai");
|
|
|
|
let frame = session.pin("kai");
|
|
assert_eq!(frame.candidates[0].text, "开发");
|
|
let frame = session.select(0);
|
|
assert_eq!(frame.commit, "开发");
|
|
assert!(frame.preedit.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn shuangpin_keys_decode_with_the_chosen_scheme() {
|
|
let mut session = session();
|
|
session.set_shuangpin("xiaohe", false);
|
|
type_keys(&mut session, "nihc");
|
|
let frame = session.select(0);
|
|
assert_eq!(frame.commit, "你好");
|
|
session.set_shuangpin("", false);
|
|
type_keys(&mut session, "nihao");
|
|
assert_eq!(session.select(0).commit, "你好");
|
|
}
|
|
|
|
#[test]
|
|
fn wubi_codes_look_up_the_code_table() {
|
|
let mut session = session();
|
|
session.set_wubi(true, false);
|
|
type_keys(&mut session, "wqvb");
|
|
assert_eq!(session.select(0).commit, "你好");
|
|
session.set_wubi(true, true);
|
|
type_keys(&mut session, "nihao");
|
|
assert_eq!(session.select(0).commit, "你好");
|
|
session.set_wubi(false, false);
|
|
type_keys(&mut session, "nihao");
|
|
assert_eq!(session.select(0).commit, "你好");
|
|
}
|
|
|
|
#[test]
|
|
fn learning_language_off_drops_translations() {
|
|
let mut session = session();
|
|
let options = |language: &str| -> InputOptions {
|
|
serde_json::from_str(&format!(
|
|
r#"{{"full_width_punctuation":true,"english_in_chinese":true,"chinese_first":false,"learning_language":"{language}"}}"#
|
|
))
|
|
.unwrap()
|
|
};
|
|
session.set_options(&options("off"));
|
|
type_keys(&mut session, "kaifa");
|
|
let frame = session.set_options(&options("off"));
|
|
assert!(frame.candidates.iter().all(|c| c.senses.is_empty()));
|
|
|
|
let frame = session.set_options(&options("en"));
|
|
let kaifa = frame.candidates.iter().find(|c| c.text == "开发").unwrap();
|
|
assert!(!kaifa.senses.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn translation_engine_loads_without_learning_data_and_needs_cloud() {
|
|
let user_dir =
|
|
std::env::temp_dir().join(format!("qingjian-translation-{}", std::process::id()));
|
|
let engine =
|
|
qingjian_android::assemble_translation(&sample(""), &user_dir, Some(Language::English))
|
|
.unwrap();
|
|
assert_eq!(engine.learning_language(), Language::English);
|
|
let mut session = Session::new(engine, sample("wubi86.tsv"), sample(""), user_dir);
|
|
// 没接云端时不发
|
|
assert!(!session.translate("hello"));
|
|
}
|
|
|
|
fn select_text(session: &mut Session, frame: &Frame, text: &str) -> Frame {
|
|
let index = frame
|
|
.candidates
|
|
.iter()
|
|
.position(|c| c.text == text)
|
|
.unwrap();
|
|
session.select(index)
|
|
}
|
|
|
|
#[test]
|
|
fn partial_selection_stays_in_the_composition() {
|
|
let mut session = session();
|
|
type_keys(&mut session, "nihaokaif");
|
|
let frame = session.push('a');
|
|
let frame = select_text(&mut session, &frame, "你好");
|
|
assert_eq!(frame.commit, "");
|
|
assert!(frame.preedit.starts_with("你好"));
|
|
// 空格接着选剩下的,整段选完一起上屏
|
|
let frame = session.space();
|
|
assert_eq!(frame.commit, "你好开发");
|
|
assert!(frame.preedit.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn backspace_undoes_and_enter_keeps_held_words() {
|
|
let mut session = session();
|
|
type_keys(&mut session, "nihaokaif");
|
|
let frame = session.push('a');
|
|
let frame = select_text(&mut session, &frame, "你好");
|
|
assert!(frame.preedit.starts_with("你好"));
|
|
let frame = session.backspace();
|
|
assert!(!frame.preedit.contains("你好"));
|
|
assert!(frame.candidates.iter().any(|c| c.text == "你好"));
|
|
|
|
let frame = select_text(&mut session, &frame, "你好");
|
|
assert_eq!(frame.commit, "");
|
|
assert_eq!(session.commit_raw().commit, "你好kaifa");
|
|
}
|
|
|
|
#[test]
|
|
fn holding_selections_can_be_turned_off() {
|
|
let mut session = session();
|
|
let options: InputOptions = serde_json::from_str(
|
|
r#"{"full_width_punctuation":true,"english_in_chinese":true,"chinese_first":false,"hold_selections":false}"#,
|
|
)
|
|
.unwrap();
|
|
session.set_options(&options);
|
|
type_keys(&mut session, "nihaokaif");
|
|
let frame = session.push('a');
|
|
let frame = select_text(&mut session, &frame, "你好");
|
|
assert_eq!(frame.commit, "你好");
|
|
}
|