mirror of
https://github.com/qingjian-team/qingjian.git
synced 2026-11-14 00:15:05 +00:00
247 lines
7.6 KiB
Rust
247 lines
7.6 KiB
Rust
//! Linux Server 云结果回显与私密输入边界;用内存通道代替网络服务。
|
|
|
|
use qingjian_core::{
|
|
CandidateKind, CloudWord, Engine, Prediction, PredictionKind, PredictionPolicy,
|
|
PredictionRequest, Predictor,
|
|
};
|
|
use qingjian_dictionary::Dictionary;
|
|
use qingjian_linux_server::{Router, RouterConfig};
|
|
use qingjian_platform::protocol::{
|
|
ClientMessage, Frame, KeyEvent, KeyModifiers, PROTOCOL_VERSION, ServerMessage, SessionId,
|
|
};
|
|
use serde_json::json;
|
|
use std::sync::mpsc::{self, Receiver, Sender};
|
|
|
|
struct ChannelPredictor {
|
|
requests: Sender<PredictionRequest>,
|
|
|
|
responses: Receiver<Prediction>,
|
|
}
|
|
|
|
impl Predictor for ChannelPredictor {
|
|
fn policy(&self) -> PredictionPolicy {
|
|
PredictionPolicy {
|
|
slots: 2,
|
|
..PredictionPolicy::default()
|
|
}
|
|
}
|
|
|
|
fn submit(&mut self, request: PredictionRequest) {
|
|
self.requests.send(request).unwrap();
|
|
}
|
|
|
|
fn poll(&mut self) -> Option<Prediction> {
|
|
self.responses.try_recv().ok()
|
|
}
|
|
}
|
|
|
|
fn setup() -> (Router, Receiver<PredictionRequest>, Sender<Prediction>) {
|
|
let dictionary = Dictionary::parse("你\tni\t100\n泥\tni\t90\n拟\tni\t80\n逆\tni\t70\n腻\tni\t60\n匿\tni\t50\n溺\tni\t40\n尼\tni\t30\n呢\tni\t20\n").unwrap();
|
|
let (request_tx, requests) = mpsc::channel();
|
|
let (responses, response_rx) = mpsc::channel();
|
|
let engine = Engine::new(dictionary).with_predictor(Box::new(ChannelPredictor {
|
|
requests: request_tx,
|
|
responses: response_rx,
|
|
}));
|
|
let config = RouterConfig {
|
|
cloud_slots: 2,
|
|
..RouterConfig::default()
|
|
};
|
|
let mut router = Router::new(engine, config);
|
|
router.handle(ClientMessage::OpenSession {
|
|
session: SessionId(1),
|
|
app: Some("test".into()),
|
|
protocol: PROTOCOL_VERSION,
|
|
});
|
|
router.handle(ClientMessage::Privacy {
|
|
session: SessionId(1),
|
|
private: false,
|
|
});
|
|
(router, requests, responses)
|
|
}
|
|
|
|
fn type_ni(router: &mut Router) {
|
|
for c in ['n', 'i'] {
|
|
router.handle(ClientMessage::Key {
|
|
session: SessionId(1),
|
|
event: KeyEvent::new(c as u32, Some(c), KeyModifiers::default()),
|
|
});
|
|
}
|
|
}
|
|
|
|
fn poll(router: &mut Router) -> Frame {
|
|
match router.handle(ClientMessage::Poll {
|
|
session: SessionId(1),
|
|
}) {
|
|
Some(ServerMessage::Update { frame, .. }) => frame,
|
|
_ => panic!("expected update"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cloud_word_and_sentence_arrive_after_local_frame() {
|
|
let (mut router, requests, responses) = setup();
|
|
type_ni(&mut router);
|
|
assert_eq!(poll(&mut router).candidates.items[0].text, "你");
|
|
let request = requests.try_iter().last().unwrap();
|
|
responses
|
|
.send(Prediction {
|
|
sequence: request.sequence,
|
|
words: vec![CloudWord {
|
|
text: "妮".into(),
|
|
syllables: vec!["ni".into()],
|
|
reading: None,
|
|
}],
|
|
sentence: Some("你好".into()),
|
|
})
|
|
.unwrap();
|
|
let frame = poll(&mut router);
|
|
assert_eq!(frame.candidates.items[0].text, "你");
|
|
assert!(
|
|
frame
|
|
.candidates
|
|
.items
|
|
.iter()
|
|
.any(|c| c.text == "妮" && c.kind == CandidateKind::Cloud)
|
|
);
|
|
assert_eq!(frame.sentence.as_deref(), Some("你好"));
|
|
}
|
|
|
|
#[test]
|
|
fn privacy_change_discards_pending_cloud_reply() {
|
|
let (mut router, requests, responses) = setup();
|
|
type_ni(&mut router);
|
|
let request = requests.try_iter().last().unwrap();
|
|
router.handle(ClientMessage::Privacy {
|
|
session: SessionId(1),
|
|
private: true,
|
|
});
|
|
responses
|
|
.send(Prediction {
|
|
sequence: request.sequence,
|
|
words: vec![CloudWord {
|
|
text: "妮".into(),
|
|
syllables: vec!["ni".into()],
|
|
reading: None,
|
|
}],
|
|
sentence: Some("你好".into()),
|
|
})
|
|
.unwrap();
|
|
assert!(poll(&mut router).is_empty());
|
|
type_ni(&mut router);
|
|
assert!(requests.try_recv().is_err());
|
|
assert!(
|
|
poll(&mut router)
|
|
.candidates
|
|
.items
|
|
.iter()
|
|
.all(|c| c.kind != CandidateKind::Cloud)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn question_answer_uses_cloud_candidate_without_pinyin_syllables() {
|
|
let (mut router, requests, responses) = setup();
|
|
for c in "umumumu".chars() {
|
|
router.handle(ClientMessage::Key {
|
|
session: SessionId(1),
|
|
event: KeyEvent::new(c as u32, Some(c), KeyModifiers::default()),
|
|
});
|
|
}
|
|
let request = requests.try_iter().last().unwrap();
|
|
assert_eq!(request.kind, PredictionKind::Question);
|
|
responses
|
|
.send(Prediction {
|
|
sequence: request.sequence,
|
|
words: vec![CloudWord {
|
|
text: "森".into(),
|
|
syllables: Vec::new(),
|
|
reading: Some("sēn".into()),
|
|
}],
|
|
sentence: None,
|
|
})
|
|
.unwrap();
|
|
let frame = poll(&mut router);
|
|
assert!(
|
|
frame
|
|
.candidates
|
|
.items
|
|
.iter()
|
|
.any(|candidate| candidate.text == "森" && candidate.kind == CandidateKind::Cloud)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cloud_reply_does_not_replace_the_highlighted_local_candidate() {
|
|
let (mut router, requests, responses) = setup();
|
|
type_ni(&mut router);
|
|
let request = requests.try_iter().last().unwrap();
|
|
for _ in 0..8 {
|
|
router.handle(ClientMessage::Key {
|
|
session: SessionId(1),
|
|
event: KeyEvent::new(0x28, None, KeyModifiers::default()),
|
|
});
|
|
}
|
|
let before = poll(&mut router);
|
|
assert_eq!(before.highlight, 8);
|
|
let selected = before.candidates.items[8].text.clone();
|
|
responses
|
|
.send(Prediction {
|
|
sequence: request.sequence,
|
|
words: vec![CloudWord {
|
|
text: "妮".into(),
|
|
syllables: vec!["ni".into()],
|
|
reading: None,
|
|
}],
|
|
sentence: Some("你好".into()),
|
|
})
|
|
.unwrap();
|
|
let frame = poll(&mut router);
|
|
assert_eq!(frame.candidates.items[8].text, selected);
|
|
assert!(
|
|
frame
|
|
.candidates
|
|
.items
|
|
.iter()
|
|
.all(|candidate| candidate.kind != CandidateKind::Cloud)
|
|
);
|
|
assert_eq!(frame.sentence.as_deref(), Some("你好"));
|
|
}
|
|
|
|
#[test]
|
|
fn linux_key_passes_application_context_and_ignores_it_in_sensitive_fields() {
|
|
let (mut router, requests, _) = setup();
|
|
let caps = |router: &mut Router, sensitive| {
|
|
router.handle_linux(
|
|
json!({"LinuxEvent": {"session": 1, "event": {"Capabilities": {
|
|
"sensitive": sensitive, "password": false, "disabled": false
|
|
}}}}),
|
|
);
|
|
};
|
|
let key = |router: &mut Router, character, surrounding: serde_json::Value| {
|
|
router.handle_linux(json!({"LinuxEvent": {"session": 1, "event": {"Key": {
|
|
"event": KeyEvent::new(character as u32, Some(character), KeyModifiers::default()),
|
|
"release": false, "surrounding": surrounding
|
|
}}}}));
|
|
};
|
|
caps(&mut router, false);
|
|
key(
|
|
&mut router,
|
|
'n',
|
|
json!({"before": "你好😀", "after": "世界"}),
|
|
);
|
|
key(
|
|
&mut router,
|
|
'i',
|
|
json!({"before": "你好😀", "after": "世界"}),
|
|
);
|
|
let request = requests.try_iter().last().unwrap();
|
|
assert_eq!(request.before, "你好😀");
|
|
assert_eq!(request.after, "世界");
|
|
|
|
caps(&mut router, true);
|
|
key(&mut router, 'n', json!({"before": "秘密", "after": "内容"}));
|
|
key(&mut router, 'i', json!({"before": "秘密", "after": "内容"}));
|
|
assert!(requests.try_recv().is_err());
|
|
}
|