use compact_str::CompactString;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use crate::text::{SegmentedText, TextWidth};
use vize_carton::cstr;
#[derive(Debug, Clone, Default)]
pub struct Preedit {
text: CompactString,
cursor: usize,
segments: SmallVec<[PreeditSegment; 8]>,
}
impl Preedit {
pub fn new() -> Self {
Self::default()
}
pub fn with_text(text: impl Into<CompactString>) -> Self {
let text = text.into();
let len = SegmentedText::new(&text).grapheme_count;
Self {
text,
cursor: len,
segments: SmallVec::new(),
}
}
pub fn text(&self) -> &str {
&self.text
}
pub fn cursor(&self) -> usize {
self.cursor
}
pub fn width(&self) -> usize {
TextWidth::width(&self.text)
}
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
pub fn len(&self) -> usize {
SegmentedText::new(&self.text).grapheme_count
}
pub fn set_text(&mut self, text: impl Into<CompactString>) {
self.text = text.into();
let max = self.len();
if self.cursor > max {
self.cursor = max;
}
}
pub fn set_cursor(&mut self, cursor: usize) {
self.cursor = cursor.min(self.len());
}
pub fn clear(&mut self) {
self.text.clear();
self.cursor = 0;
self.segments.clear();
}
pub fn segments(&self) -> &[PreeditSegment] {
&self.segments
}
pub fn set_segments(&mut self, segments: impl IntoIterator<Item = PreeditSegment>) {
self.segments = segments.into_iter().collect();
}
pub fn add_segment(&mut self, segment: PreeditSegment) {
self.segments.push(segment);
}
pub fn cursor_column(&self) -> usize {
SegmentedText::new(&self.text).column_at_index(self.cursor)
}
pub fn insert(&mut self, text: &str) {
let st = SegmentedText::new(&self.text);
let byte_pos = if self.cursor >= st.grapheme_count {
self.text.len()
} else {
self.text
.char_indices()
.filter_map(|(i, _)| {
let prefix = &self.text[..i];
let count = SegmentedText::new(prefix).grapheme_count;
if count == self.cursor {
Some(i)
} else {
None
}
})
.next()
.unwrap_or(0)
};
let before = &self.text[..byte_pos];
let after = &self.text[byte_pos..];
self.text = cstr!("{before}{text}{after}");
self.cursor += SegmentedText::new(text).grapheme_count;
}
pub fn backspace(&mut self) {
if self.cursor == 0 {
return;
}
let st = SegmentedText::new(&self.text);
if self.cursor <= st.grapheme_count {
let before = st.slice(0, self.cursor - 1);
let after = st.slice(self.cursor, st.grapheme_count);
self.text = cstr!("{before}{after}");
self.cursor -= 1;
}
}
pub fn delete(&mut self) {
let st = SegmentedText::new(&self.text);
if self.cursor >= st.grapheme_count {
return;
}
let before = st.slice(0, self.cursor);
let after = st.slice(self.cursor + 1, st.grapheme_count);
self.text = cstr!("{before}{after}");
}
pub fn move_left(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
}
}
pub fn move_right(&mut self) {
if self.cursor < self.len() {
self.cursor += 1;
}
}
pub fn move_start(&mut self) {
self.cursor = 0;
}
pub fn move_end(&mut self) {
self.cursor = self.len();
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PreeditSegment {
pub start: usize,
pub end: usize,
pub style: SegmentStyle,
}
impl PreeditSegment {
pub fn new(start: usize, end: usize, style: SegmentStyle) -> Self {
Self { start, end, style }
}
pub fn len(&self) -> usize {
self.end.saturating_sub(self.start)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum SegmentStyle {
#[default]
Normal,
Converting,
Selected,
Converted,
}
#[cfg(test)]
mod tests {
use super::{Preedit, PreeditSegment, SegmentStyle};
#[test]
fn test_preedit_new() {
let preedit = Preedit::new();
assert!(preedit.is_empty());
assert_eq!(preedit.cursor(), 0);
}
#[test]
fn test_preedit_with_text() {
let preedit = Preedit::with_text("にほん");
assert_eq!(preedit.text(), "にほん");
assert_eq!(preedit.len(), 3);
assert_eq!(preedit.cursor(), 3);
}
#[test]
fn test_preedit_width() {
let preedit = Preedit::with_text("にほん");
assert_eq!(preedit.width(), 6); }
#[test]
fn test_preedit_insert() {
let mut preedit = Preedit::new();
preedit.insert("あ");
assert_eq!(preedit.text(), "あ");
assert_eq!(preedit.cursor(), 1);
preedit.insert("い");
assert_eq!(preedit.text(), "あい");
assert_eq!(preedit.cursor(), 2);
}
#[test]
fn test_preedit_backspace() {
let mut preedit = Preedit::with_text("あいう");
preedit.backspace();
assert_eq!(preedit.text(), "あい");
assert_eq!(preedit.cursor(), 2);
}
#[test]
fn test_preedit_cursor_movement() {
let mut preedit = Preedit::with_text("あいう");
assert_eq!(preedit.cursor(), 3);
preedit.move_left();
assert_eq!(preedit.cursor(), 2);
preedit.move_start();
assert_eq!(preedit.cursor(), 0);
preedit.move_end();
assert_eq!(preedit.cursor(), 3);
}
#[test]
fn test_preedit_segment() {
let segment = PreeditSegment::new(0, 3, SegmentStyle::Converting);
assert_eq!(segment.len(), 3);
assert_eq!(segment.style, SegmentStyle::Converting);
}
}