cursor.rs 17 KB

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