use std::{
borrow::Cow,
cmp::Ordering,
fmt::Display,
ops::Range,
};
use dioxus_sdk::clipboard::UseClipboard;
use freya_elements::events::keyboard::{
Code,
Key,
Modifiers,
};
#[derive(Clone, Default, PartialEq, Debug)]
pub struct TextCursor(usize);
impl TextCursor {
pub fn new(pos: usize) -> Self {
Self(pos)
}
pub fn pos(&self) -> usize {
self.0
}
pub fn set(&mut self, pos: usize) {
self.0 = pos;
}
pub fn write(&mut self) -> &mut usize {
&mut self.0
}
}
#[derive(Clone)]
pub struct Line<'a> {
pub text: Cow<'a, str>,
pub utf16_len: usize,
}
impl Line<'_> {
pub fn utf16_len(&self) -> usize {
self.utf16_len
}
}
impl Display for Line<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.text)
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct TextEvent: u8 {
const CURSOR_CHANGED = 0x01;
const TEXT_CHANGED = 0x02;
const SELECTION_CHANGED = 0x04;
}
}
pub trait TextEditor {
type LinesIterator<'a>: Iterator<Item = Line<'a>>
where
Self: 'a;
fn set(&mut self, text: &str);
fn lines(&self) -> Self::LinesIterator<'_>;
fn insert_char(&mut self, char: char, char_idx: usize) -> usize;
fn insert(&mut self, text: &str, char_idx: usize) -> usize;
fn remove(&mut self, range: Range<usize>) -> usize;
fn char_to_line(&self, char_idx: usize) -> usize;
fn line_to_char(&self, line_idx: usize) -> usize;
fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize;
fn char_to_utf16_cu(&self, idx: usize) -> usize;
fn line(&self, line_idx: usize) -> Option<Line<'_>>;
fn len_lines(&self) -> usize;
fn len_chars(&self) -> usize;
fn len_utf16_cu(&self) -> usize;
fn cursor(&self) -> &TextCursor;
fn cursor_mut(&mut self) -> &mut TextCursor;
fn cursor_row(&self) -> usize {
let pos = self.cursor_pos();
let pos_utf8 = self.utf16_cu_to_char(pos);
self.char_to_line(pos_utf8)
}
fn cursor_col(&self) -> usize {
let pos = self.cursor_pos();
let pos_utf8 = self.utf16_cu_to_char(pos);
let line = self.char_to_line(pos_utf8);
let line_char_utf8 = self.line_to_char(line);
let line_char = self.char_to_utf16_cu(line_char_utf8);
pos - line_char
}
fn cursor_row_and_col(&self) -> (usize, usize) {
(self.cursor_row(), self.cursor_col())
}
fn cursor_down(&mut self) -> bool {
let old_row = self.cursor_row();
let old_col = self.cursor_col();
match old_row.cmp(&(self.len_lines() - 1)) {
Ordering::Less => {
let new_row = old_row + 1;
let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
let new_row_len = self.line(new_row).unwrap().utf16_len();
let new_col = old_col.min(new_row_len);
self.cursor_mut().set(new_row_char + new_col);
true
}
Ordering::Equal => {
let end = self.len_utf16_cu();
self.cursor_mut().set(end);
true
}
Ordering::Greater => {
false
}
}
}
fn cursor_up(&mut self) -> bool {
let pos = self.cursor_pos();
let old_row = self.cursor_row();
let old_col = self.cursor_col();
if pos > 0 {
if old_row == 0 {
self.cursor_mut().set(0);
} else {
let new_row = old_row - 1;
let new_row_char = self.line_to_char(new_row);
let new_row_len = self.line(new_row).unwrap().utf16_len();
let new_col = old_col.min(new_row_len);
self.cursor_mut().set(new_row_char + new_col);
}
true
} else {
false
}
}
fn cursor_right(&mut self) -> bool {
if self.cursor_pos() < self.len_utf16_cu() {
*self.cursor_mut().write() += 1;
true
} else {
false
}
}
fn cursor_left(&mut self) -> bool {
if self.cursor_pos() > 0 {
*self.cursor_mut().write() -= 1;
true
} else {
false
}
}
fn cursor_pos(&self) -> usize {
self.cursor().pos()
}
fn set_cursor_pos(&mut self, pos: usize) {
self.cursor_mut().set(pos);
}
fn has_any_selection(&self) -> bool;
fn get_selection(&self) -> Option<(usize, usize)>;
fn get_visible_selection(&self, editor_id: usize) -> Option<(usize, usize)>;
fn clear_selection(&mut self);
fn set_selection(&mut self, selected: (usize, usize));
fn measure_new_selection(&self, from: usize, to: usize, editor_id: usize) -> (usize, usize);
fn measure_new_cursor(&self, to: usize, editor_id: usize) -> TextCursor;
fn expand_selection_to_cursor(&mut self);
fn get_clipboard(&mut self) -> &mut UseClipboard;
fn process_key(&mut self, key: &Key, code: &Code, modifiers: &Modifiers) -> TextEvent {
let mut event = if self.has_any_selection() {
TextEvent::SELECTION_CHANGED
} else {
TextEvent::empty()
};
match key {
Key::Shift => {
event.remove(TextEvent::SELECTION_CHANGED);
}
Key::Control => {
event.remove(TextEvent::SELECTION_CHANGED);
}
Key::Alt => {
event.remove(TextEvent::SELECTION_CHANGED);
}
Key::Escape => {
event.insert(TextEvent::SELECTION_CHANGED);
}
Key::ArrowDown => {
if modifiers.contains(Modifiers::SHIFT) {
event.remove(TextEvent::SELECTION_CHANGED);
self.expand_selection_to_cursor();
}
if self.cursor_down() {
event.insert(TextEvent::CURSOR_CHANGED);
}
if modifiers.contains(Modifiers::SHIFT) {
self.expand_selection_to_cursor();
}
}
Key::ArrowLeft => {
if modifiers.contains(Modifiers::SHIFT) {
event.remove(TextEvent::SELECTION_CHANGED);
self.expand_selection_to_cursor();
}
if self.cursor_left() {
event.insert(TextEvent::CURSOR_CHANGED);
}
if modifiers.contains(Modifiers::SHIFT) {
self.expand_selection_to_cursor();
}
}
Key::ArrowRight => {
if modifiers.contains(Modifiers::SHIFT) {
event.remove(TextEvent::SELECTION_CHANGED);
self.expand_selection_to_cursor();
}
if self.cursor_right() {
event.insert(TextEvent::CURSOR_CHANGED);
}
if modifiers.contains(Modifiers::SHIFT) {
self.expand_selection_to_cursor();
}
}
Key::ArrowUp => {
if modifiers.contains(Modifiers::SHIFT) {
event.remove(TextEvent::SELECTION_CHANGED);
self.expand_selection_to_cursor();
}
if self.cursor_up() {
event.insert(TextEvent::CURSOR_CHANGED);
}
if modifiers.contains(Modifiers::SHIFT) {
self.expand_selection_to_cursor();
}
}
Key::Backspace => {
let cursor_pos = self.cursor_pos();
let selection = self.get_selection_range();
if let Some((start, end)) = selection {
self.remove(start..end);
self.set_cursor_pos(start);
event.insert(TextEvent::TEXT_CHANGED);
} else if cursor_pos > 0 {
let removed_text_len = self.remove(cursor_pos - 1..cursor_pos);
self.set_cursor_pos(cursor_pos - removed_text_len);
event.insert(TextEvent::TEXT_CHANGED);
}
}
Key::Delete => {
let cursor_pos = self.cursor_pos();
let selection = self.get_selection_range();
if let Some((start, end)) = selection {
self.remove(start..end);
self.set_cursor_pos(start);
event.insert(TextEvent::TEXT_CHANGED);
} else if cursor_pos < self.len_utf16_cu() {
self.remove(cursor_pos..cursor_pos + 1);
event.insert(TextEvent::TEXT_CHANGED);
}
}
Key::Enter => {
let cursor_pos = self.cursor_pos();
self.insert_char('\n', cursor_pos);
self.cursor_right();
event.insert(TextEvent::TEXT_CHANGED);
}
Key::Tab => {
let text = " ".repeat(self.get_identation().into());
let cursor_pos = self.cursor_pos();
self.insert(&text, cursor_pos);
self.set_cursor_pos(cursor_pos + text.chars().count());
event.insert(TextEvent::TEXT_CHANGED);
}
Key::Character(character) => {
let meta_or_ctrl = if cfg!(target_os = "macos") {
modifiers.meta()
} else {
modifiers.ctrl()
};
match code {
Code::Delete => {}
Code::Space => {
let cursor_pos = self.cursor_pos();
self.insert_char(' ', cursor_pos);
self.cursor_right();
event.insert(TextEvent::TEXT_CHANGED);
}
Code::KeyA if meta_or_ctrl => {
let len = self.len_utf16_cu();
self.set_selection((0, len));
event.remove(TextEvent::SELECTION_CHANGED);
}
Code::KeyC if meta_or_ctrl => {
let selected = self.get_selected_text();
if let Some(selected) = selected {
self.get_clipboard().set(selected).ok();
}
event.remove(TextEvent::SELECTION_CHANGED);
}
Code::KeyX if meta_or_ctrl => {
let selection = self.get_selection_range();
if let Some((start, end)) = selection {
let text = self.get_selected_text().unwrap();
self.remove(start..end);
self.get_clipboard().set(text).ok();
self.set_cursor_pos(start);
event.insert(TextEvent::TEXT_CHANGED);
}
}
Code::KeyV if meta_or_ctrl => {
let copied_text = self.get_clipboard().get();
if let Ok(copied_text) = copied_text {
let cursor_pos = self.cursor_pos();
self.insert(&copied_text, cursor_pos);
let last_idx = copied_text.encode_utf16().count() + cursor_pos;
self.set_cursor_pos(last_idx);
event.insert(TextEvent::TEXT_CHANGED);
}
}
Code::KeyZ if meta_or_ctrl => {
let undo_result = self.undo();
if let Some(idx) = undo_result {
self.set_cursor_pos(idx);
event.insert(TextEvent::TEXT_CHANGED);
}
}
Code::KeyY if meta_or_ctrl => {
let redo_result = self.redo();
if let Some(idx) = redo_result {
self.set_cursor_pos(idx);
event.insert(TextEvent::TEXT_CHANGED);
}
}
_ => {
let selection = self.get_selection_range();
if let Some((start, end)) = selection {
self.remove(start..end);
self.set_cursor_pos(start);
event.insert(TextEvent::TEXT_CHANGED);
}
if let Ok(ch) = character.parse::<char>() {
let cursor_pos = self.cursor_pos();
let inserted_text_len = self.insert_char(ch, cursor_pos);
self.set_cursor_pos(cursor_pos + inserted_text_len);
event.insert(TextEvent::TEXT_CHANGED);
} else {
let cursor_pos = self.cursor_pos();
let inserted_text_len = self.insert(character, cursor_pos);
self.set_cursor_pos(cursor_pos + inserted_text_len);
event.insert(TextEvent::TEXT_CHANGED);
}
}
}
}
_ => {}
}
if event.contains(TextEvent::SELECTION_CHANGED) {
self.clear_selection();
}
event
}
fn get_selected_text(&self) -> Option<String>;
fn undo(&mut self) -> Option<usize>;
fn redo(&mut self) -> Option<usize>;
fn get_selection_range(&self) -> Option<(usize, usize)>;
fn get_identation(&self) -> u8;
}