use std::collections::HashMap;
use zbus::zvariant::{Array, Structure, Value};
use super::ibus_serde::make_ibus_text;
#[derive(Debug, Copy, Clone)]
pub enum IBusOrientation {
Horizontal,
Vertical,
System,
}
impl From<IBusOrientation> for i32 {
fn from(value: IBusOrientation) -> Self {
match value {
IBusOrientation::Horizontal => 0,
IBusOrientation::Vertical => 1,
IBusOrientation::System => 2,
}
}
}
#[derive(Debug, Clone)]
pub struct LookupTable {
candidates: Vec<String>,
pub labels: Vec<String>,
page_size: u32,
cursor_pos: u32,
pub cursor_visible: bool,
pub round: bool,
pub orientation: IBusOrientation,
}
impl LookupTable {
pub fn new(
candidates: Vec<String>,
page_size: u32,
cursor_visible: bool,
round: bool,
) -> Result<Self, ()> {
if page_size == 0 || page_size > 16 {
return Err(());
}
Ok(LookupTable {
candidates,
labels: vec![],
page_size,
cursor_pos: 0,
cursor_visible,
round,
orientation: IBusOrientation::System,
})
}
pub(crate) fn serialize(&self) -> Value<'static> {
let special = HashMap::<String, Value<'static>>::new();
let candidates: Array = self
.candidates
.iter()
.cloned()
.map(make_ibus_text)
.collect::<Vec<Value<'static>>>()
.into();
let labels: Array = self
.labels
.iter()
.cloned()
.map(make_ibus_text)
.collect::<Vec<Value<'static>>>()
.into();
let structure = Structure::from((
"IBusLookupTable",
special,
self.page_size,
self.cursor_pos,
self.cursor_visible,
self.round,
i32::from(self.orientation),
candidates,
labels,
));
Value::new(structure)
}
#[inline]
pub fn set_cursor_pos(&mut self, desired_pos: i64) {
if self.round {
if self.candidates.is_empty() {
self.cursor_pos = 0
} else {
self.cursor_pos = desired_pos.rem_euclid(self.candidates.len() as i64) as u32
}
} else if desired_pos < 0 {
self.cursor_pos = 0
} else if desired_pos >= self.candidates.len() as i64 {
self.cursor_pos = (self.candidates.len() as u32).saturating_sub(1)
} else {
self.cursor_pos = desired_pos as u32
}
}
pub fn cursor_down(&mut self) {
self.set_cursor_pos(self.cursor_pos as i64 + 1)
}
pub fn cursor_up(&mut self) {
self.set_cursor_pos(self.cursor_pos as i64 - 1)
}
pub fn page_down(&mut self) {
self.set_cursor_pos(
self.cursor_pos as i64 - (self.cursor_pos % self.page_size) as i64
+ self.page_size as i64,
)
}
pub fn page_up(&mut self) {
self.set_cursor_pos(self.cursor_pos as i64 - (self.cursor_pos % self.page_size) as i64 - 1)
}
pub fn cursor_pos(&self) -> u32 {
self.cursor_pos
}
pub fn page_size(&self) -> u32 {
self.page_size
}
pub fn cursor_pos_in_page(&self) -> u32 {
self.cursor_pos % self.page_size
}
pub fn get_candidate_by_index_in_page(&self, index_in_page: u32) -> Option<&String> {
if index_in_page >= self.page_size {
None
} else {
let page = self.cursor_pos / self.page_size;
let index = self.page_size * page + index_in_page;
self.candidates.get(index as usize)
}
}
#[inline]
pub fn set_page_size(&mut self, page_size: u32) -> Result<(), ()> {
if page_size == 0 || page_size > 16 {
return Err(());
}
self.page_size = page_size;
Ok(())
}
#[inline]
pub fn modify_candidates(&mut self, f: impl FnOnce(&mut Vec<String>)) {
f(&mut self.candidates);
self.cursor_pos = self
.cursor_pos
.min(self.candidates.len().saturating_sub(1) as u32);
}
pub fn candidates(&self) -> &[String] {
&self.candidates[..]
}
pub fn clear(&mut self) {
self.modify_candidates(|c| c.clear());
}
pub fn push_candidate(&mut self, candidate: String) {
self.modify_candidates(|c| c.push(candidate))
}
pub fn reset_candidates(&mut self, candidates: Vec<String>) {
self.candidates = candidates;
self.cursor_pos = 0;
}
}
impl Extend<String> for LookupTable {
fn extend<I: IntoIterator<Item = String>>(&mut self, iter: I) {
self.modify_candidates(|c| c.extend(iter))
}
}
#[test]
fn lookup_table_zero_page_size() {
assert!(LookupTable::new(vec![], 0, false, false).is_err());
}
#[test]
fn lookup_table_large_page_size() {
assert!(LookupTable::new(vec![], 17, false, false).is_err());
}
#[test]
fn cursor_down_saturate() {
let mut table =
LookupTable::new(vec!["one".to_string(), "two".to_string()], 1, false, false).unwrap();
table.set_cursor_pos(1);
assert_eq!(table.cursor_pos(), 1);
table.cursor_down();
assert_eq!(table.cursor_pos(), 1);
}
#[test]
fn cursor_down_wrap() {
let mut table =
LookupTable::new(vec!["one".to_string(), "two".to_string()], 1, false, true).unwrap();
table.set_cursor_pos(1);
assert_eq!(table.cursor_pos(), 1);
table.cursor_down();
assert_eq!(table.cursor_pos(), 0);
}
#[test]
fn cursor_down_nominal() {
let mut table =
LookupTable::new(vec!["one".to_string(), "two".to_string()], 1, false, true).unwrap();
table.cursor_down();
assert_eq!(table.cursor_pos(), 1);
}
#[test]
fn cursor_up_saturate() {
let mut table =
LookupTable::new(vec!["one".to_string(), "two".to_string()], 1, false, false).unwrap();
table.cursor_up();
assert_eq!(table.cursor_pos(), 0);
}
#[test]
fn cursor_up_wrap() {
let mut table =
LookupTable::new(vec!["one".to_string(), "two".to_string()], 1, false, true).unwrap();
table.cursor_up();
assert_eq!(table.cursor_pos(), 1);
}
#[test]
fn cursor_up_nominal() {
let mut table =
LookupTable::new(vec!["one".to_string(), "two".to_string()], 1, false, true).unwrap();
table.set_cursor_pos(1);
assert_eq!(table.cursor_pos(), 1);
table.cursor_up();
assert_eq!(table.cursor_pos(), 0);
}
#[test]
fn page_up_nominal() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
true,
)
.unwrap();
table.set_cursor_pos(2);
assert_eq!(table.cursor_pos(), 2);
table.page_up();
assert_eq!(table.cursor_pos(), 1);
}
#[test]
fn page_up_saturate() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
false,
)
.unwrap();
table.set_cursor_pos(1);
assert_eq!(table.cursor_pos(), 1);
table.page_up();
assert_eq!(table.cursor_pos(), 0);
}
#[test]
fn page_up_wrap() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
true,
)
.unwrap();
table.set_cursor_pos(1);
assert_eq!(table.cursor_pos(), 1);
table.page_up();
assert_eq!(table.cursor_pos(), 3);
}
#[test]
fn page_up_wrap_empty() {
let mut table = LookupTable::new(vec![], 2, false, true).unwrap();
table.page_up();
assert_eq!(table.cursor_pos(), 0);
}
#[test]
fn page_down_nominal() {
let mut table = LookupTable::new(
vec!["one".to_string(), "two".to_string(), "three".to_string()],
2,
false,
true,
)
.unwrap();
table.set_cursor_pos(1);
assert_eq!(table.cursor_pos(), 1);
table.page_down();
assert_eq!(table.cursor_pos(), 2);
}
#[test]
fn page_down_saturate() {
let mut table = LookupTable::new(
vec!["one".to_string(), "two".to_string(), "three".to_string()],
2,
false,
false,
)
.unwrap();
table.set_cursor_pos(2);
assert_eq!(table.cursor_pos(), 2);
table.page_down();
assert_eq!(table.cursor_pos(), 2);
}
#[test]
fn page_down_wrap() {
let mut table = LookupTable::new(
vec!["one".to_string(), "two".to_string(), "three".to_string()],
2,
false,
true,
)
.unwrap();
table.set_cursor_pos(2);
assert_eq!(table.cursor_pos(), 2);
table.page_down();
assert_eq!(table.cursor_pos(), 1);
}
#[test]
fn cursor_pos_in_page() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
true,
)
.unwrap();
table.set_cursor_pos(2);
assert_eq!(table.cursor_pos(), 2);
assert_eq!(table.cursor_pos_in_page(), 0);
table.set_cursor_pos(3);
assert_eq!(table.cursor_pos(), 3);
assert_eq!(table.cursor_pos_in_page(), 1);
}
#[test]
fn get_candidate_by_index_in_page() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
true,
)
.unwrap();
assert_eq!(
table.get_candidate_by_index_in_page(0),
Some(&"one".to_string())
);
assert_eq!(
table.get_candidate_by_index_in_page(1),
Some(&"two".to_string())
);
assert_eq!(table.get_candidate_by_index_in_page(2), None);
table.set_cursor_pos(3);
assert_eq!(
table.get_candidate_by_index_in_page(0),
Some(&"three".to_string())
);
assert_eq!(
table.get_candidate_by_index_in_page(1),
Some(&"four".to_string())
);
assert_eq!(table.get_candidate_by_index_in_page(2), None);
}
#[test]
fn set_page_size() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
true,
)
.unwrap();
assert_eq!(table.set_page_size(0), Err(()));
assert_eq!(table.set_page_size(17), Err(()));
assert_eq!(table.set_page_size(3), Ok(()));
assert_eq!(table.page_size(), 3)
}
#[test]
fn modify_candidates_nominal() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
true,
)
.unwrap();
table.set_cursor_pos(3);
table.modify_candidates(|c| {
for candidate in c.iter_mut() {
candidate.push('!');
}
c.push("five!!!".to_string());
});
assert_eq!(table.cursor_pos(), 3);
assert_eq!(
table.candidates(),
&["one!", "two!", "three!", "four!", "five!!!"]
);
}
#[test]
fn modify_candidates_smaller() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
true,
)
.unwrap();
table.set_cursor_pos(3);
table.modify_candidates(|c| {
c.pop();
c.pop();
});
assert_eq!(table.candidates(), &["one", "two"]);
assert_eq!(table.cursor_pos(), 1);
}
#[test]
fn clear() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
true,
)
.unwrap();
table.set_cursor_pos(3);
table.clear();
assert_eq!(table.cursor_pos(), 0);
assert_eq!(table.candidates(), &Vec::<String>::new());
}
#[test]
fn push_candidate() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
true,
)
.unwrap();
table.set_cursor_pos(3);
table.push_candidate("five".to_string());
assert_eq!(table.cursor_pos(), 3);
assert_eq!(table.candidates(), &["one", "two", "three", "four", "five"]);
}
#[test]
fn reset_candidates() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
true,
)
.unwrap();
table.set_cursor_pos(3);
table.reset_candidates(vec!["new1".to_string(), "new2".to_string()]);
assert_eq!(table.cursor_pos(), 0);
assert_eq!(table.candidates(), &["new1", "new2"]);
}
#[test]
fn extend() {
let mut table = LookupTable::new(
vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
],
2,
false,
true,
)
.unwrap();
table.set_cursor_pos(3);
table.extend(vec!["new1".to_string(), "new2".to_string()]);
assert_eq!(table.cursor_pos(), 3);
assert_eq!(
table.candidates(),
&["one", "two", "three", "four", "new1", "new2"]
);
}