use crate::{
codec::{value::from_value, Value},
utils::{OwnedStringUtils, StringUtils},
};
use std::ops::Range;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Default, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TextEditingState {
composing_base: i64,
composing_extent: i64,
selection_affinity: String,
selection_base: i64,
selection_extent: i64,
selection_is_directional: bool,
text: String,
}
enum Direction {
Left,
Right,
}
impl TextEditingState {
pub fn from(v: Value) -> Option<Self> {
from_value(&v).ok()
}
fn get_selection_range(&self) -> Range<usize> {
if self.selection_base <= self.selection_extent {
self.selection_base as usize..self.selection_extent as usize
} else {
self.selection_extent as usize..self.selection_base as usize
}
}
pub fn move_to(&mut self, p: usize) {
self.selection_base = p as i64;
self.selection_extent = self.selection_base;
self.selection_is_directional = false;
}
pub fn select_to(&mut self, p: usize) {
self.selection_extent = p as i64;
self.selection_is_directional = true;
}
fn select_or_move_to(&mut self, p: usize, select: bool) {
if select {
self.select_to(p)
} else {
self.move_to(p)
}
}
pub fn select_all(&mut self) {
self.selection_base = 0;
self.move_to_end(true);
}
pub fn delete_selected(&mut self) -> bool {
let range = self.get_selection_range();
if range.start != range.end {
self.move_to(range.start);
self.text.remove_chars(range);
true
} else {
false
}
}
pub fn add_characters(&mut self, c: &str) {
self.delete_selected();
let index = self
.text
.byte_index_of_char(self.selection_extent as usize)
.unwrap_or_else(|| self.text.len());
self.text.insert_str(index, c);
self.move_to(self.selection_extent as usize + c.char_count());
}
pub fn backspace(&mut self) {
if !self.delete_selected() && self.selection_base > 0 {
if let Some(index) = self
.text
.byte_index_of_char(self.selection_base as usize - 1)
{
self.text.remove(index);
self.move_to(self.selection_base as usize - 1);
}
}
}
pub fn delete(&mut self) {
if !self.delete_selected() && (self.selection_base as usize) < self.text.char_count() {
if let Some(index) = self.text.byte_index_of_char(self.selection_base as usize) {
self.text.remove(index);
}
}
}
pub fn move_left(&mut self, by_word: bool, select: bool) {
let selection = self.get_selection_range();
let current_pos = if select {
self.selection_extent as usize
} else if self.selection_base != self.selection_extent {
selection.start + 1
} else {
selection.start
};
let next_pos = if by_word {
self.get_next_word_boundary(current_pos, Direction::Left)
} else {
(current_pos as i64 - 1).max(0) as usize
};
self.select_or_move_to(next_pos, select);
}
pub fn move_right(&mut self, by_word: bool, select: bool) {
let selection = self.get_selection_range();
let current_pos = if select {
self.selection_extent as usize
} else if self.selection_base != self.selection_extent {
selection.end - 1
} else {
selection.end
};
let next_pos = if by_word {
self.get_next_word_boundary(current_pos, Direction::Right)
} else {
(current_pos + 1).min(self.text.char_count())
};
self.select_or_move_to(next_pos, select);
}
pub fn move_to_beginning(&mut self, select: bool) {
self.select_or_move_to(0, select);
}
pub fn move_to_end(&mut self, select: bool) {
self.select_or_move_to(self.text.char_count(), select);
}
pub fn move_up(&mut self, select: bool) {
let selection = self.get_selection_range();
let p = self.get_next_line_pos(selection.start, false);
self.select_or_move_to(p, select);
}
pub fn move_down(&mut self, select: bool) {
let selection = self.get_selection_range();
let p = self.get_next_line_pos(selection.end, true);
self.select_or_move_to(p, select);
}
pub fn get_selected_text(&self) -> &str {
if let Some(range) = self.text.byte_range_of_chars(self.get_selection_range()) {
&self.text[range]
} else {
""
}
}
fn get_next_line_pos(&self, start: usize, forward: bool) -> usize {
let v: Vec<char> = self.text.chars().collect();
if forward {
let max = self.text.char_count();
if start >= max {
return max;
}
let s = &v[start + 1..];
s.iter().position(|&c| c == '\n').map_or(max, |n| {
start + n + 1
})
} else {
if start < 1 {
return 0;
}
let s = &v[..start - 1];
let len = s.iter().count();
s.iter().rposition(|&c| c == '\n').map_or(0, |n| {
start - len + n
})
}
}
fn get_next_word_boundary(&self, start: usize, direction: Direction) -> usize {
match direction {
Direction::Right => {
let max = self.text.char_count();
if start >= max {
return max;
}
let start = start + 1;
self.text
.chars()
.skip(start)
.position(|c| !c.is_alphanumeric())
.map_or(max, |n| start + n)
}
Direction::Left => {
if start == 0 {
return 0;
}
let len = self.text.char_count();
let start = start - 1;
self.text
.chars()
.rev()
.skip(len - start)
.position(|c| !c.is_alphanumeric())
.map_or(0, |n| start - n)
}
}
}
}