VimEditingEngine.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibGUI/Event.h>
  7. #include <LibGUI/TextEditor.h>
  8. #include <LibGUI/VimEditingEngine.h>
  9. namespace GUI {
  10. CursorWidth VimEditingEngine::cursor_width() const
  11. {
  12. return m_vim_mode == VimMode::Insert ? CursorWidth::NARROW : CursorWidth::WIDE;
  13. }
  14. bool VimEditingEngine::on_key(const KeyEvent& event)
  15. {
  16. if (EditingEngine::on_key(event))
  17. return true;
  18. switch (m_vim_mode) {
  19. case (VimMode::Insert):
  20. return on_key_in_insert_mode(event);
  21. case (VimMode::Visual):
  22. return on_key_in_visual_mode(event);
  23. case (VimMode::Normal):
  24. return on_key_in_normal_mode(event);
  25. default:
  26. VERIFY_NOT_REACHED();
  27. }
  28. return false;
  29. }
  30. bool VimEditingEngine::on_key_in_insert_mode(const KeyEvent& event)
  31. {
  32. if (event.key() == KeyCode::Key_Escape || (event.ctrl() && event.key() == KeyCode::Key_LeftBracket) || (event.ctrl() && event.key() == KeyCode::Key_C)) {
  33. switch_to_normal_mode();
  34. return true;
  35. }
  36. return false;
  37. }
  38. bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event)
  39. {
  40. // FIXME: changing or deleting word methods don't correctly support 1 letter words.
  41. // For example, in the line 'return 0;' with the cursor on the '0',
  42. // keys 'cw' will move to the delete '0;' rather than just the '0'.
  43. // FIXME: Changing or deleting the last word on a line will bring the next line
  44. // up to the cursor.
  45. if (m_previous_key == KeyCode::Key_D) {
  46. if (event.key() == KeyCode::Key_D) {
  47. yank(Line);
  48. delete_line();
  49. } else if (event.key() == KeyCode::Key_W) {
  50. // If the current char is an alnum or punct, delete from said char, to the
  51. // beginning of the next word including any whitespace in between the two words.
  52. // If the current char is whitespace, delete from the cursor to the beginning of the next world.
  53. u32 current_char = m_editor->current_line().view().code_points()[m_editor->cursor().column()];
  54. TextPosition delete_to;
  55. if (isspace(current_char)) {
  56. delete_to = find_beginning_of_next_word();
  57. } else {
  58. delete_to = find_end_of_next_word();
  59. delete_to.set_column(delete_to.column() + 1);
  60. }
  61. m_editor->delete_text_range(TextRange(m_editor->cursor(), delete_to).normalized());
  62. } else if (event.key() == KeyCode::Key_B) {
  63. // Will delete from the current char to the beginning of the previous word regardless of whitespace.
  64. // Does not delete the starting char, see note below.
  65. TextPosition delete_to = find_beginning_of_previous_word();
  66. // NOTE: Intentionally don't adjust m_editor->cursor() for the wide cursor's column
  67. // because in the original vim... they don't.
  68. m_editor->delete_text_range(TextRange(delete_to, m_editor->cursor()).normalized());
  69. } else if (event.key() == KeyCode::Key_E) {
  70. // Delete from the current char to the end of the next word regardless of whitespace.
  71. TextPosition delete_to = find_end_of_next_word();
  72. delete_to.set_column(delete_to.column() + 1);
  73. m_editor->delete_text_range(TextRange(m_editor->cursor(), delete_to).normalized());
  74. }
  75. m_previous_key = {};
  76. } else if (m_previous_key == KeyCode::Key_G) {
  77. if (event.key() == KeyCode::Key_G) {
  78. move_to_first_line();
  79. } else if (event.key() == KeyCode::Key_E) {
  80. move_to_end_of_previous_word();
  81. }
  82. m_previous_key = {};
  83. } else if (m_previous_key == KeyCode::Key_Y) {
  84. if (event.key() == KeyCode::Key_Y) {
  85. yank(Line);
  86. }
  87. m_previous_key = {};
  88. } else if (m_previous_key == KeyCode::Key_C) {
  89. if (event.key() == KeyCode::Key_C) {
  90. // Needed because the code to replace the deleted line is called after delete_line() so
  91. // what was the second last line before the delete, is now the last line.
  92. bool was_second_last_line = m_editor->cursor().line() == m_editor->line_count() - 2;
  93. yank(Line);
  94. delete_line();
  95. if (was_second_last_line || (m_editor->cursor().line() != 0 && m_editor->cursor().line() != m_editor->line_count() - 1)) {
  96. move_one_up(event);
  97. move_to_line_end(event);
  98. m_editor->add_code_point(0x0A);
  99. } else if (m_editor->cursor().line() == 0) {
  100. move_to_line_beginning(event);
  101. m_editor->add_code_point(0x0A);
  102. move_one_up(event);
  103. } else if (m_editor->cursor().line() == m_editor->line_count() - 1) {
  104. m_editor->add_code_point(0x0A);
  105. }
  106. switch_to_insert_mode();
  107. } else if (event.key() == KeyCode::Key_W) {
  108. // Delete to the end of the next word, if in the middle of a word, this will delete
  109. // from the cursor to the said of said word. If the cursor is on whitespace,
  110. // any whitespace between the cursor and the beginning of the next word will be deleted.
  111. u32 current_char = m_editor->current_line().view().code_points()[m_editor->cursor().column()];
  112. TextPosition delete_to;
  113. if (isspace(current_char)) {
  114. delete_to = find_beginning_of_next_word();
  115. } else {
  116. delete_to = find_end_of_next_word();
  117. delete_to.set_column(delete_to.column() + 1);
  118. }
  119. m_editor->delete_text_range(TextRange(m_editor->cursor(), delete_to).normalized());
  120. switch_to_insert_mode();
  121. } else if (event.key() == KeyCode::Key_B) {
  122. // Delete to the beginning of the previous word, if in the middle of a word, this will delete
  123. // from the cursor to the beginning of said word. If the cursor is on whitespace
  124. // it, and the previous word will be deleted.
  125. u32 current_char = m_editor->current_line().view().code_points()[m_editor->cursor().column()];
  126. TextPosition delete_to = find_beginning_of_previous_word();
  127. TextPosition adjusted_cursor = m_editor->cursor();
  128. // Adjust cursor for the column the wide cursor is covering
  129. if (isalnum(current_char) || ispunct(current_char))
  130. adjusted_cursor.set_column(adjusted_cursor.column() + 1);
  131. m_editor->delete_text_range(TextRange(delete_to, adjusted_cursor).normalized());
  132. switch_to_insert_mode();
  133. } else if (event.key() == KeyCode::Key_E) {
  134. // Delete to the end of the next word, if in the middle of a word, this will delete
  135. // from the cursor to the end of said word. If the cursor is on whitespace
  136. // it, and the next word will be deleted.
  137. TextPosition delete_to = find_end_of_next_word();
  138. TextPosition adjusted_cursor = m_editor->cursor();
  139. delete_to.set_column(delete_to.column() + 1);
  140. m_editor->delete_text_range(TextRange(adjusted_cursor, delete_to).normalized());
  141. switch_to_insert_mode();
  142. }
  143. m_previous_key = {};
  144. } else {
  145. // Handle first any key codes that are to be applied regardless of modifiers.
  146. switch (event.key()) {
  147. case (KeyCode::Key_Dollar):
  148. move_to_line_end(event);
  149. break;
  150. case (KeyCode::Key_Escape):
  151. if (m_editor->on_escape_pressed)
  152. m_editor->on_escape_pressed();
  153. break;
  154. default:
  155. break;
  156. }
  157. // SHIFT is pressed.
  158. if (event.shift() && !event.ctrl() && !event.alt()) {
  159. switch (event.key()) {
  160. case (KeyCode::Key_A):
  161. move_to_line_end(event);
  162. switch_to_insert_mode();
  163. break;
  164. case (KeyCode::Key_G):
  165. move_to_last_line();
  166. break;
  167. case (KeyCode::Key_I):
  168. move_to_line_beginning(event);
  169. switch_to_insert_mode();
  170. break;
  171. case (KeyCode::Key_O):
  172. move_to_line_beginning(event);
  173. m_editor->add_code_point(0x0A);
  174. move_one_up(event);
  175. switch_to_insert_mode();
  176. break;
  177. case (KeyCode::Key_LeftBrace):
  178. move_to_previous_empty_lines_block();
  179. break;
  180. case (KeyCode::Key_RightBrace):
  181. move_to_next_empty_lines_block();
  182. break;
  183. default:
  184. break;
  185. }
  186. }
  187. // CTRL is pressed.
  188. if (event.ctrl() && !event.shift() && !event.alt()) {
  189. switch (event.key()) {
  190. case (KeyCode::Key_D):
  191. move_half_page_down(event);
  192. break;
  193. case (KeyCode::Key_R):
  194. m_editor->redo();
  195. break;
  196. case (KeyCode::Key_U):
  197. move_half_page_up(event);
  198. break;
  199. default:
  200. break;
  201. }
  202. }
  203. // FIXME: H and L movement keys will move to the previous or next line when reaching the beginning or end
  204. // of the line and pressed again.
  205. // No modifier is pressed.
  206. if (!event.ctrl() && !event.shift() && !event.alt()) {
  207. switch (event.key()) {
  208. case (KeyCode::Key_A):
  209. move_one_right(event);
  210. switch_to_insert_mode();
  211. break;
  212. case (KeyCode::Key_B):
  213. move_to_beginning_of_previous_word();
  214. break;
  215. case (KeyCode::Key_C):
  216. m_previous_key = event.key();
  217. break;
  218. case (KeyCode::Key_Backspace):
  219. case (KeyCode::Key_H):
  220. case (KeyCode::Key_Left):
  221. move_one_left(event);
  222. break;
  223. case (KeyCode::Key_D):
  224. m_previous_key = event.key();
  225. break;
  226. case (KeyCode::Key_E):
  227. move_to_end_of_next_word();
  228. break;
  229. case (KeyCode::Key_G):
  230. m_previous_key = event.key();
  231. break;
  232. case (KeyCode::Key_Down):
  233. case (KeyCode::Key_J):
  234. move_one_down(event);
  235. break;
  236. case (KeyCode::Key_I):
  237. switch_to_insert_mode();
  238. break;
  239. case (KeyCode::Key_K):
  240. case (KeyCode::Key_Up):
  241. move_one_up(event);
  242. break;
  243. case (KeyCode::Key_L):
  244. case (KeyCode::Key_Right):
  245. move_one_right(event);
  246. break;
  247. case (KeyCode::Key_O):
  248. move_to_line_end(event);
  249. m_editor->add_code_point(0x0A);
  250. switch_to_insert_mode();
  251. break;
  252. case (KeyCode::Key_U):
  253. m_editor->undo();
  254. break;
  255. case (KeyCode::Key_W):
  256. move_to_beginning_of_next_word();
  257. break;
  258. case (KeyCode::Key_X):
  259. yank({ m_editor->cursor(), { m_editor->cursor().line(), m_editor->cursor().column() + 1 } });
  260. delete_char();
  261. break;
  262. case (KeyCode::Key_0):
  263. move_to_line_beginning(event);
  264. break;
  265. case (KeyCode::Key_V):
  266. switch_to_visual_mode();
  267. break;
  268. case (KeyCode::Key_Y):
  269. m_previous_key = event.key();
  270. break;
  271. case (KeyCode::Key_P):
  272. put(event);
  273. break;
  274. default:
  275. break;
  276. }
  277. }
  278. }
  279. return true;
  280. }
  281. bool VimEditingEngine::on_key_in_visual_mode(const KeyEvent& event)
  282. {
  283. if (m_previous_key == KeyCode::Key_G) {
  284. if (event.key() == KeyCode::Key_G) {
  285. move_to_first_line();
  286. update_selection_on_cursor_move();
  287. } else if (event.key() == KeyCode::Key_E) {
  288. move_to_end_of_previous_word();
  289. update_selection_on_cursor_move();
  290. }
  291. m_previous_key = {};
  292. } else {
  293. // Handle first any key codes that are to be applied regardless of modifiers.
  294. switch (event.key()) {
  295. case (KeyCode::Key_Dollar):
  296. move_to_line_end(event);
  297. update_selection_on_cursor_move();
  298. break;
  299. case (KeyCode::Key_Escape):
  300. switch_to_normal_mode();
  301. if (m_editor->on_escape_pressed)
  302. m_editor->on_escape_pressed();
  303. break;
  304. default:
  305. break;
  306. }
  307. // SHIFT is pressed.
  308. if (event.shift() && !event.ctrl() && !event.alt()) {
  309. switch (event.key()) {
  310. case (KeyCode::Key_A):
  311. move_to_line_end(event);
  312. switch_to_insert_mode();
  313. break;
  314. case (KeyCode::Key_G):
  315. move_to_last_line();
  316. break;
  317. case (KeyCode::Key_I):
  318. move_to_line_beginning(event);
  319. switch_to_insert_mode();
  320. break;
  321. default:
  322. break;
  323. }
  324. }
  325. // CTRL is pressed.
  326. if (event.ctrl() && !event.shift() && !event.alt()) {
  327. switch (event.key()) {
  328. case (KeyCode::Key_D):
  329. move_half_page_down(event);
  330. update_selection_on_cursor_move();
  331. break;
  332. case (KeyCode::Key_U):
  333. move_half_page_up(event);
  334. update_selection_on_cursor_move();
  335. break;
  336. default:
  337. break;
  338. }
  339. }
  340. // No modifier is pressed.
  341. if (!event.ctrl() && !event.shift() && !event.alt()) {
  342. switch (event.key()) {
  343. case (KeyCode::Key_B):
  344. move_to_beginning_of_previous_word();
  345. update_selection_on_cursor_move();
  346. break;
  347. case (KeyCode::Key_Backspace):
  348. case (KeyCode::Key_H):
  349. case (KeyCode::Key_Left):
  350. move_one_left(event);
  351. update_selection_on_cursor_move();
  352. break;
  353. case (KeyCode::Key_D):
  354. yank(Selection);
  355. m_editor->do_delete();
  356. switch_to_normal_mode();
  357. break;
  358. case (KeyCode::Key_E):
  359. move_to_end_of_next_word();
  360. update_selection_on_cursor_move();
  361. break;
  362. case (KeyCode::Key_G):
  363. m_previous_key = event.key();
  364. break;
  365. case (KeyCode::Key_Down):
  366. case (KeyCode::Key_J):
  367. move_one_down(event);
  368. update_selection_on_cursor_move();
  369. break;
  370. case (KeyCode::Key_K):
  371. case (KeyCode::Key_Up):
  372. move_one_up(event);
  373. update_selection_on_cursor_move();
  374. break;
  375. case (KeyCode::Key_L):
  376. case (KeyCode::Key_Right):
  377. move_one_right(event);
  378. update_selection_on_cursor_move();
  379. break;
  380. case (KeyCode::Key_U):
  381. // FIXME: Set selection to uppercase.
  382. break;
  383. case (KeyCode::Key_W):
  384. move_to_beginning_of_next_word();
  385. update_selection_on_cursor_move();
  386. break;
  387. case (KeyCode::Key_X):
  388. yank(Selection);
  389. m_editor->do_delete();
  390. switch_to_normal_mode();
  391. break;
  392. case (KeyCode::Key_0):
  393. move_to_line_beginning(event);
  394. update_selection_on_cursor_move();
  395. break;
  396. case (KeyCode::Key_V):
  397. switch_to_normal_mode();
  398. break;
  399. case (KeyCode::Key_C):
  400. yank(Selection);
  401. m_editor->do_delete();
  402. switch_to_insert_mode();
  403. break;
  404. case (KeyCode::Key_Y):
  405. yank(Selection);
  406. switch_to_normal_mode();
  407. break;
  408. default:
  409. break;
  410. }
  411. }
  412. }
  413. return true;
  414. }
  415. void VimEditingEngine::switch_to_normal_mode()
  416. {
  417. m_vim_mode = VimMode::Normal;
  418. m_editor->reset_cursor_blink();
  419. m_previous_key = {};
  420. clear_visual_mode_data();
  421. };
  422. void VimEditingEngine::switch_to_insert_mode()
  423. {
  424. m_vim_mode = VimMode::Insert;
  425. m_editor->reset_cursor_blink();
  426. m_previous_key = {};
  427. clear_visual_mode_data();
  428. };
  429. void VimEditingEngine::switch_to_visual_mode()
  430. {
  431. m_vim_mode = VimMode::Visual;
  432. m_editor->reset_cursor_blink();
  433. m_previous_key = {};
  434. m_selection_start_position = m_editor->cursor();
  435. m_editor->selection()->set(m_editor->cursor(), { m_editor->cursor().line(), m_editor->cursor().column() + 1 });
  436. m_editor->did_update_selection();
  437. }
  438. void VimEditingEngine::update_selection_on_cursor_move()
  439. {
  440. auto cursor = m_editor->cursor();
  441. auto start = m_selection_start_position < cursor ? m_selection_start_position : cursor;
  442. auto end = m_selection_start_position < cursor ? cursor : m_selection_start_position;
  443. end.set_column(end.column() + 1);
  444. m_editor->selection()->set(start, end);
  445. m_editor->did_update_selection();
  446. }
  447. void VimEditingEngine::clear_visual_mode_data()
  448. {
  449. if (m_editor->has_selection()) {
  450. m_editor->selection()->clear();
  451. m_editor->did_update_selection();
  452. }
  453. m_selection_start_position = {};
  454. }
  455. void VimEditingEngine::move_half_page_up(const KeyEvent& event)
  456. {
  457. move_up(event, 0.5);
  458. };
  459. void VimEditingEngine::move_half_page_down(const KeyEvent& event)
  460. {
  461. move_down(event, 0.5);
  462. };
  463. void VimEditingEngine::yank(YankType type)
  464. {
  465. m_yank_type = type;
  466. if (type == YankType::Line) {
  467. m_yank_buffer = m_editor->current_line().to_utf8();
  468. } else {
  469. m_yank_buffer = m_editor->selected_text();
  470. }
  471. // When putting this, auto indentation (if enabled) will indent as far as
  472. // is necessary, then any whitespace captured before the yanked text will be placed
  473. // after the indentation, doubling the indentation.
  474. if (m_editor->is_automatic_indentation_enabled())
  475. m_yank_buffer = m_yank_buffer.trim_whitespace(TrimMode::Left);
  476. }
  477. void VimEditingEngine::yank(TextRange range)
  478. {
  479. m_yank_type = YankType::Selection;
  480. m_yank_buffer = m_editor->document().text_in_range(range);
  481. }
  482. void VimEditingEngine::put(const GUI::KeyEvent& event)
  483. {
  484. if (m_yank_type == YankType::Line) {
  485. move_to_line_end(event);
  486. StringBuilder sb = StringBuilder(m_yank_buffer.length() + 1);
  487. sb.append_code_point(0x0A);
  488. sb.append(m_yank_buffer);
  489. m_editor->insert_at_cursor_or_replace_selection(sb.to_string());
  490. m_editor->set_cursor({ m_editor->cursor().line(), m_editor->current_line().first_non_whitespace_column() });
  491. } else {
  492. // FIXME: If attempting to put on the last column a line,
  493. // the buffer will bne placed on the next line due to the move_one_left/right behaviour.
  494. move_one_right(event);
  495. m_editor->insert_at_cursor_or_replace_selection(m_yank_buffer);
  496. move_one_left(event);
  497. }
  498. }
  499. void VimEditingEngine::move_to_previous_empty_lines_block()
  500. {
  501. VERIFY(!m_editor.is_null());
  502. size_t line_idx = m_editor->cursor().line();
  503. bool skipping_initial_empty_lines = true;
  504. while (line_idx > 0) {
  505. if (m_editor->document().line(line_idx).is_empty()) {
  506. if (!skipping_initial_empty_lines)
  507. break;
  508. } else {
  509. skipping_initial_empty_lines = false;
  510. }
  511. line_idx--;
  512. }
  513. TextPosition new_cursor = { line_idx, 0 };
  514. m_editor->set_cursor(new_cursor);
  515. };
  516. void VimEditingEngine::move_to_next_empty_lines_block()
  517. {
  518. VERIFY(!m_editor.is_null());
  519. size_t line_idx = m_editor->cursor().line();
  520. bool skipping_initial_empty_lines = true;
  521. while (line_idx < m_editor->line_count() - 1) {
  522. if (m_editor->document().line(line_idx).is_empty()) {
  523. if (!skipping_initial_empty_lines)
  524. break;
  525. } else {
  526. skipping_initial_empty_lines = false;
  527. }
  528. line_idx++;
  529. }
  530. TextPosition new_cursor = { line_idx, 0 };
  531. m_editor->set_cursor(new_cursor);
  532. };
  533. }