123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- use std::cmp::Ordering;
- use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers};
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct Pos {
- pub col: usize,
- pub row: usize,
- }
- impl Pos {
- pub fn new(col: usize, row: usize) -> Self {
- Self { row, col }
- }
- pub fn up(&mut self, rope: &str) {
- self.move_row(-1, rope);
- }
- pub fn down(&mut self, rope: &str) {
- self.move_row(1, rope);
- }
- pub fn right(&mut self, rope: &str) {
- self.move_col(1, rope);
- }
- pub fn left(&mut self, rope: &str) {
- self.move_col(-1, rope);
- }
- pub fn move_row(&mut self, change: i32, rope: &str) {
- let new = self.row as i32 + change;
- if new >= 0 && new < rope.lines().count() as i32 {
- self.row = new as usize;
- }
- }
- pub fn move_col(&mut self, change: i32, rope: &str) {
- self.realize_col(rope);
- let idx = self.idx(rope) as i32;
- if idx + change >= 0 && idx + change <= rope.len() as i32 {
- let len_line = self.len_line(rope) as i32;
- let new_col = self.col as i32 + change;
- let diff = new_col - len_line;
- if diff > 0 {
- self.down(rope);
- self.col = 0;
- self.move_col(diff - 1, rope);
- } else if new_col < 0 {
- self.up(rope);
- self.col = self.len_line(rope);
- self.move_col(new_col + 1, rope);
- } else {
- self.col = new_col as usize;
- }
- }
- }
- pub fn col(&self, rope: &str) -> usize {
- self.col.min(self.len_line(rope))
- }
- pub fn row(&self) -> usize {
- self.row
- }
- fn len_line(&self, rope: &str) -> usize {
- let line = rope.lines().nth(self.row).unwrap_or_default();
- let len = line.len();
- if len > 0 && line.chars().nth(len - 1) == Some('\n') {
- len - 1
- } else {
- len
- }
- }
- pub fn idx(&self, rope: &str) -> usize {
- rope.lines().take(self.row).map(|l| l.len()).sum::<usize>() + self.col(rope)
- }
- // the column can be more than the line length, cap it
- pub fn realize_col(&mut self, rope: &str) {
- self.col = self.col(rope);
- }
- }
- impl Ord for Pos {
- fn cmp(&self, other: &Self) -> Ordering {
- self.row.cmp(&other.row).then(self.col.cmp(&other.col))
- }
- }
- impl PartialOrd for Pos {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- Some(self.cmp(other))
- }
- }
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct Cursor {
- pub start: Pos,
- pub end: Option<Pos>,
- }
- impl Cursor {
- pub fn from_start(pos: Pos) -> Self {
- Self {
- start: pos,
- end: None,
- }
- }
- pub fn new(start: Pos, end: Pos) -> Self {
- Self {
- start,
- end: Some(end),
- }
- }
- fn move_cursor(&mut self, f: impl FnOnce(&mut Pos), shift: bool) {
- if shift {
- self.with_end(f);
- } else {
- f(&mut self.start);
- self.end = None;
- }
- }
- fn delete_selection(&mut self, text: &mut String) -> [i32; 2] {
- let first = self.first();
- let last = self.last();
- let dr = first.row as i32 - last.row as i32;
- let dc = if dr != 0 {
- -(last.col as i32)
- } else {
- first.col as i32 - last.col as i32
- };
- text.replace_range(first.idx(text)..last.idx(text), "");
- if let Some(end) = self.end.take() {
- if self.start > end {
- self.start = end;
- }
- }
- [dc, dr]
- }
- pub fn handle_input(
- &mut self,
- data: &dioxus_html::KeyboardData,
- text: &mut String,
- max_width: usize,
- ) {
- use Code::*;
- match data.code() {
- ArrowUp => {
- self.move_cursor(|c| c.up(text), data.modifiers().contains(Modifiers::SHIFT));
- }
- ArrowDown => {
- self.move_cursor(
- |c| c.down(text),
- data.modifiers().contains(Modifiers::SHIFT),
- );
- }
- ArrowRight => {
- if data.modifiers().contains(Modifiers::CONTROL) {
- self.move_cursor(
- |c| {
- let mut change = 1;
- let idx = c.idx(text);
- let length = text.len();
- while idx + change < length {
- let chr = text.chars().nth(idx + change).unwrap();
- if chr.is_whitespace() {
- break;
- }
- change += 1;
- }
- c.move_col(change as i32, text);
- },
- data.modifiers().contains(Modifiers::SHIFT),
- );
- } else {
- self.move_cursor(
- |c| c.right(text),
- data.modifiers().contains(Modifiers::SHIFT),
- );
- }
- }
- ArrowLeft => {
- if data.modifiers().contains(Modifiers::CONTROL) {
- self.move_cursor(
- |c| {
- let mut change = -1;
- let idx = c.idx(text) as i32;
- while idx + change > 0 {
- let chr = text.chars().nth((idx + change) as usize).unwrap();
- if chr == ' ' {
- break;
- }
- change -= 1;
- }
- c.move_col(change as i32, text);
- },
- data.modifiers().contains(Modifiers::SHIFT),
- );
- } else {
- self.move_cursor(
- |c| c.left(text),
- data.modifiers().contains(Modifiers::SHIFT),
- );
- }
- }
- End => {
- self.move_cursor(
- |c| c.col = c.len_line(text),
- data.modifiers().contains(Modifiers::SHIFT),
- );
- }
- Home => {
- self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT));
- }
- Backspace => {
- self.start.realize_col(text);
- let mut start_idx = self.start.idx(text);
- if self.end.is_some() {
- self.delete_selection(text);
- } else if start_idx > 0 {
- self.start.left(text);
- text.replace_range(start_idx - 1..start_idx, "");
- if data.modifiers().contains(Modifiers::CONTROL) {
- start_idx = self.start.idx(text);
- while start_idx > 0
- && text
- .chars()
- .nth(start_idx - 1)
- .filter(|c| *c != ' ')
- .is_some()
- {
- self.start.left(text);
- text.replace_range(start_idx - 1..start_idx, "");
- start_idx = self.start.idx(text);
- }
- }
- }
- }
- Enter => {
- if text.len() + 1 - self.selection_len(text) <= max_width {
- text.insert(self.start.idx(text), '\n');
- self.start.col = 0;
- self.start.down(text);
- }
- }
- Tab => {
- if text.len() + 1 - self.selection_len(text) <= max_width {
- self.start.realize_col(text);
- self.delete_selection(text);
- text.insert(self.start.idx(text), '\t');
- self.start.right(text);
- }
- }
- _ => {
- self.start.realize_col(text);
- if let Key::Character(character) = data.key() {
- if text.len() + 1 - self.selection_len(text) <= max_width {
- self.delete_selection(text);
- let character = character.chars().next().unwrap();
- text.insert(self.start.idx(text), character);
- self.start.right(text);
- }
- }
- }
- }
- }
- pub fn with_end(&mut self, f: impl FnOnce(&mut Pos)) {
- let mut new = self.end.take().unwrap_or_else(|| self.start.clone());
- f(&mut new);
- self.end.replace(new);
- }
- pub fn first(&self) -> &Pos {
- if let Some(e) = &self.end {
- e.min(&self.start)
- } else {
- &self.start
- }
- }
- pub fn last(&self) -> &Pos {
- if let Some(e) = &self.end {
- e.max(&self.start)
- } else {
- &self.start
- }
- }
- pub fn selection_len(&self, text: &str) -> usize {
- self.last().idx(text) - self.first().idx(text)
- }
- }
- impl Default for Cursor {
- fn default() -> Self {
- Self {
- start: Pos::new(0, 0),
- end: None,
- }
- }
- }
- #[test]
- fn pos_direction_movement() {
- let mut pos = Pos::new(100, 0);
- let text = "hello world\nhi";
- assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
- pos.down(text);
- assert_eq!(pos.col(text), text.lines().nth(1).unwrap_or_default().len());
- pos.up(text);
- assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
- pos.left(text);
- assert_eq!(
- pos.col(text),
- text.lines().next().unwrap_or_default().len() - 1
- );
- pos.right(text);
- assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
- }
- #[test]
- fn pos_col_movement() {
- let mut pos = Pos::new(100, 0);
- let text = "hello world\nhi";
- // move inside a row
- pos.move_col(-5, text);
- assert_eq!(
- pos.col(text),
- text.lines().next().unwrap_or_default().len() - 5
- );
- pos.move_col(5, text);
- assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
- // move between rows
- pos.move_col(3, text);
- assert_eq!(pos.col(text), 2);
- pos.move_col(-3, text);
- assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
- // don't panic if moving out of range
- pos.move_col(-100, text);
- pos.move_col(1000, text);
- }
- #[test]
- fn cursor_row_movement() {
- let mut pos = Pos::new(100, 0);
- let text = "hello world\nhi";
- pos.move_row(1, text);
- assert_eq!(pos.row(), 1);
- pos.move_row(-1, text);
- assert_eq!(pos.row(), 0);
- // don't panic if moving out of range
- pos.move_row(-100, text);
- pos.move_row(1000, text);
- }
- #[test]
- fn cursor_input() {
- let mut cursor = Cursor::from_start(Pos::new(0, 0));
- let mut text = "hello world\nhi".to_string();
- for _ in 0..5 {
- cursor.handle_input(
- &dioxus_html::KeyboardData::new(
- dioxus_html::input_data::keyboard_types::Key::ArrowRight,
- dioxus_html::input_data::keyboard_types::Code::ArrowRight,
- dioxus_html::input_data::keyboard_types::Location::Standard,
- false,
- Modifiers::empty(),
- ),
- &mut text,
- 10,
- );
- }
- for _ in 0..5 {
- cursor.handle_input(
- &dioxus_html::KeyboardData::new(
- dioxus_html::input_data::keyboard_types::Key::Backspace,
- dioxus_html::input_data::keyboard_types::Code::Backspace,
- dioxus_html::input_data::keyboard_types::Location::Standard,
- false,
- Modifiers::empty(),
- ),
- &mut text,
- 10,
- );
- }
- assert_eq!(text, " world\nhi");
- let goal_text = "hello world\nhi";
- let max_width = goal_text.len();
- cursor.handle_input(
- &dioxus_html::KeyboardData::new(
- dioxus_html::input_data::keyboard_types::Key::Character("h".to_string()),
- dioxus_html::input_data::keyboard_types::Code::KeyH,
- dioxus_html::input_data::keyboard_types::Location::Standard,
- false,
- Modifiers::empty(),
- ),
- &mut text,
- max_width,
- );
- cursor.handle_input(
- &dioxus_html::KeyboardData::new(
- dioxus_html::input_data::keyboard_types::Key::Character("e".to_string()),
- dioxus_html::input_data::keyboard_types::Code::KeyE,
- dioxus_html::input_data::keyboard_types::Location::Standard,
- false,
- Modifiers::empty(),
- ),
- &mut text,
- max_width,
- );
- cursor.handle_input(
- &dioxus_html::KeyboardData::new(
- dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
- dioxus_html::input_data::keyboard_types::Code::KeyL,
- dioxus_html::input_data::keyboard_types::Location::Standard,
- false,
- Modifiers::empty(),
- ),
- &mut text,
- max_width,
- );
- cursor.handle_input(
- &dioxus_html::KeyboardData::new(
- dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
- dioxus_html::input_data::keyboard_types::Code::KeyL,
- dioxus_html::input_data::keyboard_types::Location::Standard,
- false,
- Modifiers::empty(),
- ),
- &mut text,
- max_width,
- );
- cursor.handle_input(
- &dioxus_html::KeyboardData::new(
- dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
- dioxus_html::input_data::keyboard_types::Code::KeyO,
- dioxus_html::input_data::keyboard_types::Location::Standard,
- false,
- Modifiers::empty(),
- ),
- &mut text,
- max_width,
- );
- // these should be ignored
- for _ in 0..10 {
- cursor.handle_input(
- &dioxus_html::KeyboardData::new(
- dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
- dioxus_html::input_data::keyboard_types::Code::KeyO,
- dioxus_html::input_data::keyboard_types::Location::Standard,
- false,
- Modifiers::empty(),
- ),
- &mut text,
- max_width,
- );
- }
- assert_eq!(text.to_string(), goal_text);
- }
|