cursor.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. use std::cmp::Ordering;
  2. use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers};
  3. #[derive(Debug, Clone, PartialEq, Eq)]
  4. pub struct Pos {
  5. pub col: usize,
  6. pub row: usize,
  7. }
  8. impl Pos {
  9. pub fn new(col: usize, row: usize) -> Self {
  10. Self { row, col }
  11. }
  12. pub fn up(&mut self, rope: &str) {
  13. self.move_row(-1, rope);
  14. }
  15. pub fn down(&mut self, rope: &str) {
  16. self.move_row(1, rope);
  17. }
  18. pub fn right(&mut self, rope: &str) {
  19. self.move_col(1, rope);
  20. }
  21. pub fn left(&mut self, rope: &str) {
  22. self.move_col(-1, rope);
  23. }
  24. pub fn move_row(&mut self, change: i32, rope: &str) {
  25. let new = self.row as i32 + change;
  26. if new >= 0 && new < rope.lines().count() as i32 {
  27. self.row = new as usize;
  28. }
  29. }
  30. pub fn move_col(&mut self, change: i32, rope: &str) {
  31. self.realize_col(rope);
  32. let idx = self.idx(rope) as i32;
  33. if idx + change >= 0 && idx + change <= rope.len() as i32 {
  34. let len_line = self.len_line(rope) as i32;
  35. let new_col = self.col as i32 + change;
  36. let diff = new_col - len_line;
  37. if diff > 0 {
  38. self.down(rope);
  39. self.col = 0;
  40. self.move_col(diff - 1, rope);
  41. } else if new_col < 0 {
  42. self.up(rope);
  43. self.col = self.len_line(rope);
  44. self.move_col(new_col + 1, rope);
  45. } else {
  46. self.col = new_col as usize;
  47. }
  48. }
  49. }
  50. pub fn col(&self, rope: &str) -> usize {
  51. self.col.min(self.len_line(rope))
  52. }
  53. pub fn row(&self) -> usize {
  54. self.row
  55. }
  56. fn len_line(&self, rope: &str) -> usize {
  57. let line = rope.lines().nth(self.row).unwrap_or_default();
  58. let len = line.len();
  59. if len > 0 && line.chars().nth(len - 1) == Some('\n') {
  60. len - 1
  61. } else {
  62. len
  63. }
  64. }
  65. pub fn idx(&self, rope: &str) -> usize {
  66. rope.lines().take(self.row).map(|l| l.len()).sum::<usize>() + self.col(rope)
  67. }
  68. // the column can be more than the line length, cap it
  69. pub fn realize_col(&mut self, rope: &str) {
  70. self.col = self.col(rope);
  71. }
  72. }
  73. impl Ord for Pos {
  74. fn cmp(&self, other: &Self) -> Ordering {
  75. self.row.cmp(&other.row).then(self.col.cmp(&other.col))
  76. }
  77. }
  78. impl PartialOrd for Pos {
  79. fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
  80. Some(self.cmp(other))
  81. }
  82. }
  83. #[derive(Debug, Clone, PartialEq, Eq)]
  84. pub struct Cursor {
  85. pub start: Pos,
  86. pub end: Option<Pos>,
  87. }
  88. impl Cursor {
  89. pub fn from_start(pos: Pos) -> Self {
  90. Self {
  91. start: pos,
  92. end: None,
  93. }
  94. }
  95. pub fn new(start: Pos, end: Pos) -> Self {
  96. Self {
  97. start,
  98. end: Some(end),
  99. }
  100. }
  101. fn move_cursor(&mut self, f: impl FnOnce(&mut Pos), shift: bool) {
  102. if shift {
  103. self.with_end(f);
  104. } else {
  105. f(&mut self.start);
  106. self.end = None;
  107. }
  108. }
  109. fn delete_selection(&mut self, text: &mut String) -> [i32; 2] {
  110. let first = self.first();
  111. let last = self.last();
  112. let dr = first.row as i32 - last.row as i32;
  113. let dc = if dr != 0 {
  114. -(last.col as i32)
  115. } else {
  116. first.col as i32 - last.col as i32
  117. };
  118. text.replace_range(first.idx(text)..last.idx(text), "");
  119. if let Some(end) = self.end.take() {
  120. if self.start > end {
  121. self.start = end;
  122. }
  123. }
  124. [dc, dr]
  125. }
  126. pub fn handle_input(
  127. &mut self,
  128. data: &dioxus_html::KeyboardData,
  129. text: &mut String,
  130. max_width: usize,
  131. ) {
  132. use Code::*;
  133. match data.code() {
  134. ArrowUp => {
  135. self.move_cursor(|c| c.up(text), data.modifiers().contains(Modifiers::SHIFT));
  136. }
  137. ArrowDown => {
  138. self.move_cursor(
  139. |c| c.down(text),
  140. data.modifiers().contains(Modifiers::SHIFT),
  141. );
  142. }
  143. ArrowRight => {
  144. if data.modifiers().contains(Modifiers::CONTROL) {
  145. self.move_cursor(
  146. |c| {
  147. let mut change = 1;
  148. let idx = c.idx(text);
  149. let length = text.len();
  150. while idx + change < length {
  151. let chr = text.chars().nth(idx + change).unwrap();
  152. if chr.is_whitespace() {
  153. break;
  154. }
  155. change += 1;
  156. }
  157. c.move_col(change as i32, text);
  158. },
  159. data.modifiers().contains(Modifiers::SHIFT),
  160. );
  161. } else {
  162. self.move_cursor(
  163. |c| c.right(text),
  164. data.modifiers().contains(Modifiers::SHIFT),
  165. );
  166. }
  167. }
  168. ArrowLeft => {
  169. if data.modifiers().contains(Modifiers::CONTROL) {
  170. self.move_cursor(
  171. |c| {
  172. let mut change = -1;
  173. let idx = c.idx(text) as i32;
  174. while idx + change > 0 {
  175. let chr = text.chars().nth((idx + change) as usize).unwrap();
  176. if chr == ' ' {
  177. break;
  178. }
  179. change -= 1;
  180. }
  181. c.move_col(change as i32, text);
  182. },
  183. data.modifiers().contains(Modifiers::SHIFT),
  184. );
  185. } else {
  186. self.move_cursor(
  187. |c| c.left(text),
  188. data.modifiers().contains(Modifiers::SHIFT),
  189. );
  190. }
  191. }
  192. End => {
  193. self.move_cursor(
  194. |c| c.col = c.len_line(text),
  195. data.modifiers().contains(Modifiers::SHIFT),
  196. );
  197. }
  198. Home => {
  199. self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT));
  200. }
  201. Backspace => {
  202. self.start.realize_col(text);
  203. let mut start_idx = self.start.idx(text);
  204. if self.end.is_some() {
  205. self.delete_selection(text);
  206. } else if start_idx > 0 {
  207. self.start.left(text);
  208. text.replace_range(start_idx - 1..start_idx, "");
  209. if data.modifiers().contains(Modifiers::CONTROL) {
  210. start_idx = self.start.idx(text);
  211. while start_idx > 0
  212. && text
  213. .chars()
  214. .nth(start_idx - 1)
  215. .filter(|c| *c != ' ')
  216. .is_some()
  217. {
  218. self.start.left(text);
  219. text.replace_range(start_idx - 1..start_idx, "");
  220. start_idx = self.start.idx(text);
  221. }
  222. }
  223. }
  224. }
  225. Enter => {
  226. if text.len() + 1 - self.selection_len(text) <= max_width {
  227. text.insert(self.start.idx(text), '\n');
  228. self.start.col = 0;
  229. self.start.down(text);
  230. }
  231. }
  232. Tab => {
  233. if text.len() + 1 - self.selection_len(text) <= max_width {
  234. self.start.realize_col(text);
  235. self.delete_selection(text);
  236. text.insert(self.start.idx(text), '\t');
  237. self.start.right(text);
  238. }
  239. }
  240. _ => {
  241. self.start.realize_col(text);
  242. if let Key::Character(character) = data.key() {
  243. if text.len() + 1 - self.selection_len(text) <= max_width {
  244. self.delete_selection(text);
  245. let character = character.chars().next().unwrap();
  246. text.insert(self.start.idx(text), character);
  247. self.start.right(text);
  248. }
  249. }
  250. }
  251. }
  252. }
  253. pub fn with_end(&mut self, f: impl FnOnce(&mut Pos)) {
  254. let mut new = self.end.take().unwrap_or_else(|| self.start.clone());
  255. f(&mut new);
  256. self.end.replace(new);
  257. }
  258. pub fn first(&self) -> &Pos {
  259. if let Some(e) = &self.end {
  260. e.min(&self.start)
  261. } else {
  262. &self.start
  263. }
  264. }
  265. pub fn last(&self) -> &Pos {
  266. if let Some(e) = &self.end {
  267. e.max(&self.start)
  268. } else {
  269. &self.start
  270. }
  271. }
  272. pub fn selection_len(&self, text: &str) -> usize {
  273. self.last().idx(text) - self.first().idx(text)
  274. }
  275. }
  276. impl Default for Cursor {
  277. fn default() -> Self {
  278. Self {
  279. start: Pos::new(0, 0),
  280. end: None,
  281. }
  282. }
  283. }
  284. #[test]
  285. fn pos_direction_movement() {
  286. let mut pos = Pos::new(100, 0);
  287. let text = "hello world\nhi";
  288. assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
  289. pos.down(text);
  290. assert_eq!(pos.col(text), text.lines().nth(1).unwrap_or_default().len());
  291. pos.up(text);
  292. assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
  293. pos.left(text);
  294. assert_eq!(
  295. pos.col(text),
  296. text.lines().next().unwrap_or_default().len() - 1
  297. );
  298. pos.right(text);
  299. assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
  300. }
  301. #[test]
  302. fn pos_col_movement() {
  303. let mut pos = Pos::new(100, 0);
  304. let text = "hello world\nhi";
  305. // move inside a row
  306. pos.move_col(-5, text);
  307. assert_eq!(
  308. pos.col(text),
  309. text.lines().next().unwrap_or_default().len() - 5
  310. );
  311. pos.move_col(5, text);
  312. assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
  313. // move between rows
  314. pos.move_col(3, text);
  315. assert_eq!(pos.col(text), 2);
  316. pos.move_col(-3, text);
  317. assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
  318. // don't panic if moving out of range
  319. pos.move_col(-100, text);
  320. pos.move_col(1000, text);
  321. }
  322. #[test]
  323. fn cursor_row_movement() {
  324. let mut pos = Pos::new(100, 0);
  325. let text = "hello world\nhi";
  326. pos.move_row(1, text);
  327. assert_eq!(pos.row(), 1);
  328. pos.move_row(-1, text);
  329. assert_eq!(pos.row(), 0);
  330. // don't panic if moving out of range
  331. pos.move_row(-100, text);
  332. pos.move_row(1000, text);
  333. }
  334. #[test]
  335. fn cursor_input() {
  336. let mut cursor = Cursor::from_start(Pos::new(0, 0));
  337. let mut text = "hello world\nhi".to_string();
  338. for _ in 0..5 {
  339. cursor.handle_input(
  340. &dioxus_html::KeyboardData::new(
  341. dioxus_html::input_data::keyboard_types::Key::ArrowRight,
  342. dioxus_html::input_data::keyboard_types::Code::ArrowRight,
  343. dioxus_html::input_data::keyboard_types::Location::Standard,
  344. false,
  345. Modifiers::empty(),
  346. ),
  347. &mut text,
  348. 10,
  349. );
  350. }
  351. for _ in 0..5 {
  352. cursor.handle_input(
  353. &dioxus_html::KeyboardData::new(
  354. dioxus_html::input_data::keyboard_types::Key::Backspace,
  355. dioxus_html::input_data::keyboard_types::Code::Backspace,
  356. dioxus_html::input_data::keyboard_types::Location::Standard,
  357. false,
  358. Modifiers::empty(),
  359. ),
  360. &mut text,
  361. 10,
  362. );
  363. }
  364. assert_eq!(text, " world\nhi");
  365. let goal_text = "hello world\nhi";
  366. let max_width = goal_text.len();
  367. cursor.handle_input(
  368. &dioxus_html::KeyboardData::new(
  369. dioxus_html::input_data::keyboard_types::Key::Character("h".to_string()),
  370. dioxus_html::input_data::keyboard_types::Code::KeyH,
  371. dioxus_html::input_data::keyboard_types::Location::Standard,
  372. false,
  373. Modifiers::empty(),
  374. ),
  375. &mut text,
  376. max_width,
  377. );
  378. cursor.handle_input(
  379. &dioxus_html::KeyboardData::new(
  380. dioxus_html::input_data::keyboard_types::Key::Character("e".to_string()),
  381. dioxus_html::input_data::keyboard_types::Code::KeyE,
  382. dioxus_html::input_data::keyboard_types::Location::Standard,
  383. false,
  384. Modifiers::empty(),
  385. ),
  386. &mut text,
  387. max_width,
  388. );
  389. cursor.handle_input(
  390. &dioxus_html::KeyboardData::new(
  391. dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
  392. dioxus_html::input_data::keyboard_types::Code::KeyL,
  393. dioxus_html::input_data::keyboard_types::Location::Standard,
  394. false,
  395. Modifiers::empty(),
  396. ),
  397. &mut text,
  398. max_width,
  399. );
  400. cursor.handle_input(
  401. &dioxus_html::KeyboardData::new(
  402. dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
  403. dioxus_html::input_data::keyboard_types::Code::KeyL,
  404. dioxus_html::input_data::keyboard_types::Location::Standard,
  405. false,
  406. Modifiers::empty(),
  407. ),
  408. &mut text,
  409. max_width,
  410. );
  411. cursor.handle_input(
  412. &dioxus_html::KeyboardData::new(
  413. dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
  414. dioxus_html::input_data::keyboard_types::Code::KeyO,
  415. dioxus_html::input_data::keyboard_types::Location::Standard,
  416. false,
  417. Modifiers::empty(),
  418. ),
  419. &mut text,
  420. max_width,
  421. );
  422. // these should be ignored
  423. for _ in 0..10 {
  424. cursor.handle_input(
  425. &dioxus_html::KeyboardData::new(
  426. dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
  427. dioxus_html::input_data::keyboard_types::Code::KeyO,
  428. dioxus_html::input_data::keyboard_types::Location::Standard,
  429. false,
  430. Modifiers::empty(),
  431. ),
  432. &mut text,
  433. max_width,
  434. );
  435. }
  436. assert_eq!(text.to_string(), goal_text);
  437. }