InternalFunctions.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include <AK/StringBuilder.h>
  27. #include <AK/TemporaryChange.h>
  28. #include <LibLine/Editor.h>
  29. #include <ctype.h>
  30. #include <stdio.h>
  31. namespace {
  32. constexpr u32 ctrl(char c) { return c & 0x3f; }
  33. }
  34. namespace Line {
  35. Function<bool(Editor&)> Editor::find_internal_function(const StringView& name)
  36. {
  37. #define __ENUMERATE(internal_name) \
  38. if (name == #internal_name) \
  39. return EDITOR_INTERNAL_FUNCTION(internal_name);
  40. ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(__ENUMERATE)
  41. return {};
  42. }
  43. void Editor::search_forwards()
  44. {
  45. ScopedValueRollback inline_search_cursor_rollback { m_inline_search_cursor };
  46. StringBuilder builder;
  47. builder.append(Utf32View { m_buffer.data(), m_inline_search_cursor });
  48. String search_phrase = builder.to_string();
  49. if (m_search_offset_state == SearchOffsetState::Backwards)
  50. --m_search_offset;
  51. if (m_search_offset > 0) {
  52. ScopedValueRollback search_offset_rollback { m_search_offset };
  53. --m_search_offset;
  54. if (search(search_phrase, true)) {
  55. m_search_offset_state = SearchOffsetState::Forwards;
  56. search_offset_rollback.set_override_rollback_value(m_search_offset);
  57. } else {
  58. m_search_offset_state = SearchOffsetState::Unbiased;
  59. }
  60. } else {
  61. m_search_offset_state = SearchOffsetState::Unbiased;
  62. m_chars_touched_in_the_middle = buffer().size();
  63. m_cursor = 0;
  64. m_buffer.clear();
  65. insert(search_phrase);
  66. m_refresh_needed = true;
  67. }
  68. }
  69. void Editor::search_backwards()
  70. {
  71. ScopedValueRollback inline_search_cursor_rollback { m_inline_search_cursor };
  72. StringBuilder builder;
  73. builder.append(Utf32View { m_buffer.data(), m_inline_search_cursor });
  74. String search_phrase = builder.to_string();
  75. if (m_search_offset_state == SearchOffsetState::Forwards)
  76. ++m_search_offset;
  77. if (search(search_phrase, true)) {
  78. m_search_offset_state = SearchOffsetState::Backwards;
  79. ++m_search_offset;
  80. } else {
  81. m_search_offset_state = SearchOffsetState::Unbiased;
  82. --m_search_offset;
  83. }
  84. }
  85. void Editor::cursor_left_word()
  86. {
  87. if (m_cursor > 0) {
  88. auto skipped_at_least_one_character = false;
  89. for (;;) {
  90. if (m_cursor == 0)
  91. break;
  92. if (skipped_at_least_one_character && !isalnum(m_buffer[m_cursor - 1])) // stop *after* a non-alnum, but only if it changes the position
  93. break;
  94. skipped_at_least_one_character = true;
  95. --m_cursor;
  96. }
  97. }
  98. m_inline_search_cursor = m_cursor;
  99. }
  100. void Editor::cursor_left_character()
  101. {
  102. if (m_cursor > 0)
  103. --m_cursor;
  104. m_inline_search_cursor = m_cursor;
  105. }
  106. void Editor::cursor_right_word()
  107. {
  108. if (m_cursor < m_buffer.size()) {
  109. // Temporarily put a space at the end of our buffer,
  110. // doing this greatly simplifies the logic below.
  111. m_buffer.append(' ');
  112. for (;;) {
  113. if (m_cursor >= m_buffer.size())
  114. break;
  115. if (!isalnum(m_buffer[++m_cursor]))
  116. break;
  117. }
  118. m_buffer.take_last();
  119. }
  120. m_inline_search_cursor = m_cursor;
  121. m_search_offset = 0;
  122. }
  123. void Editor::cursor_right_character()
  124. {
  125. if (m_cursor < m_buffer.size()) {
  126. ++m_cursor;
  127. }
  128. m_inline_search_cursor = m_cursor;
  129. m_search_offset = 0;
  130. }
  131. void Editor::erase_character_backwards()
  132. {
  133. if (m_is_searching) {
  134. return;
  135. }
  136. if (m_cursor == 0) {
  137. fputc('\a', stderr);
  138. fflush(stderr);
  139. return;
  140. }
  141. remove_at_index(m_cursor - 1);
  142. --m_cursor;
  143. m_inline_search_cursor = m_cursor;
  144. // We will have to redraw :(
  145. m_refresh_needed = true;
  146. }
  147. void Editor::erase_character_forwards()
  148. {
  149. if (m_cursor == m_buffer.size()) {
  150. fputc('\a', stderr);
  151. fflush(stderr);
  152. return;
  153. }
  154. remove_at_index(m_cursor);
  155. m_refresh_needed = true;
  156. }
  157. void Editor::finish_edit()
  158. {
  159. fprintf(stderr, "<EOF>\n");
  160. if (!m_always_refresh) {
  161. m_input_error = Error::Eof;
  162. finish();
  163. really_quit_event_loop();
  164. }
  165. }
  166. void Editor::kill_line()
  167. {
  168. for (size_t i = 0; i < m_cursor; ++i)
  169. remove_at_index(0);
  170. m_cursor = 0;
  171. m_refresh_needed = true;
  172. }
  173. void Editor::erase_word_backwards()
  174. {
  175. // A word here is space-separated. `foo=bar baz` is two words.
  176. bool has_seen_nonspace = false;
  177. while (m_cursor > 0) {
  178. if (isspace(m_buffer[m_cursor - 1])) {
  179. if (has_seen_nonspace)
  180. break;
  181. } else {
  182. has_seen_nonspace = true;
  183. }
  184. erase_character_backwards();
  185. }
  186. }
  187. void Editor::erase_to_end()
  188. {
  189. while (m_cursor < m_buffer.size())
  190. erase_character_forwards();
  191. }
  192. void Editor::erase_to_beginning()
  193. {
  194. }
  195. void Editor::transpose_characters()
  196. {
  197. if (m_cursor > 0 && m_buffer.size() >= 2) {
  198. if (m_cursor < m_buffer.size())
  199. ++m_cursor;
  200. swap(m_buffer[m_cursor - 1], m_buffer[m_cursor - 2]);
  201. // FIXME: Update anchored styles too.
  202. m_refresh_needed = true;
  203. m_chars_touched_in_the_middle += 2;
  204. }
  205. }
  206. void Editor::enter_search()
  207. {
  208. if (m_is_searching) {
  209. // How did we get here?
  210. VERIFY_NOT_REACHED();
  211. } else {
  212. m_is_searching = true;
  213. m_search_offset = 0;
  214. m_pre_search_buffer.clear();
  215. for (auto code_point : m_buffer)
  216. m_pre_search_buffer.append(code_point);
  217. m_pre_search_cursor = m_cursor;
  218. // Disable our own notifier so as to avoid interfering with the search editor.
  219. m_notifier->set_enabled(false);
  220. m_search_editor = Editor::construct(Configuration { Configuration::Eager, Configuration::NoSignalHandlers }); // Has anyone seen 'Inception'?
  221. m_search_editor->initialize();
  222. add_child(*m_search_editor);
  223. m_search_editor->on_display_refresh = [this](Editor& search_editor) {
  224. // Remove the search editor prompt before updating ourselves (this avoids artifacts when we move the search editor around).
  225. search_editor.cleanup();
  226. StringBuilder builder;
  227. builder.append(Utf32View { search_editor.buffer().data(), search_editor.buffer().size() });
  228. if (!search(builder.build(), false, false)) {
  229. m_chars_touched_in_the_middle = m_buffer.size();
  230. m_refresh_needed = true;
  231. m_buffer.clear();
  232. m_cursor = 0;
  233. }
  234. refresh_display();
  235. // Move the search prompt below ours and tell it to redraw itself.
  236. auto prompt_end_line = current_prompt_metrics().lines_with_addition(m_cached_buffer_metrics, m_num_columns);
  237. search_editor.set_origin(prompt_end_line + m_origin_row, 1);
  238. search_editor.m_refresh_needed = true;
  239. };
  240. // Whenever the search editor gets a ^R, cycle between history entries.
  241. m_search_editor->register_key_input_callback(ctrl('R'), [this](Editor& search_editor) {
  242. ++m_search_offset;
  243. search_editor.m_refresh_needed = true;
  244. return false; // Do not process this key event
  245. });
  246. // ^C should cancel the search.
  247. m_search_editor->register_key_input_callback(ctrl('C'), [this](Editor& search_editor) {
  248. search_editor.finish();
  249. m_reset_buffer_on_search_end = true;
  250. search_editor.deferred_invoke([&search_editor](auto&) { search_editor.really_quit_event_loop(); });
  251. return false;
  252. });
  253. // Whenever the search editor gets a backspace, cycle back between history entries
  254. // unless we're at the zeroth entry, in which case, allow the deletion.
  255. m_search_editor->register_key_input_callback(m_termios.c_cc[VERASE], [this](Editor& search_editor) {
  256. if (m_search_offset > 0) {
  257. --m_search_offset;
  258. search_editor.m_refresh_needed = true;
  259. return false; // Do not process this key event
  260. }
  261. search_editor.erase_character_backwards();
  262. return false;
  263. });
  264. // ^L - This is a source of issues, as the search editor refreshes first,
  265. // and we end up with the wrong order of prompts, so we will first refresh
  266. // ourselves, then refresh the search editor, and then tell him not to process
  267. // this event.
  268. m_search_editor->register_key_input_callback(ctrl('L'), [this](auto& search_editor) {
  269. fprintf(stderr, "\033[3J\033[H\033[2J"); // Clear screen.
  270. // refresh our own prompt
  271. {
  272. TemporaryChange refresh_change { m_always_refresh, true };
  273. set_origin(1, 1);
  274. m_refresh_needed = true;
  275. refresh_display();
  276. }
  277. // move the search prompt below ours
  278. // and tell it to redraw itself
  279. auto prompt_end_line = current_prompt_metrics().lines_with_addition(m_cached_buffer_metrics, m_num_columns);
  280. search_editor.set_origin(prompt_end_line + 1, 1);
  281. search_editor.m_refresh_needed = true;
  282. return false;
  283. });
  284. // quit without clearing the current buffer
  285. m_search_editor->register_key_input_callback('\t', [this](Editor& search_editor) {
  286. search_editor.finish();
  287. m_reset_buffer_on_search_end = false;
  288. return false;
  289. });
  290. fprintf(stderr, "\n");
  291. fflush(stderr);
  292. auto search_prompt = "\x1b[32msearch:\x1b[0m ";
  293. // While the search editor is active, we do not want editing events.
  294. m_is_editing = false;
  295. auto search_string_result = m_search_editor->get_line(search_prompt);
  296. // Grab where the search origin last was, anything up to this point will be cleared.
  297. auto search_end_row = m_search_editor->m_origin_row;
  298. remove_child(*m_search_editor);
  299. m_search_editor = nullptr;
  300. m_is_searching = false;
  301. m_is_editing = true;
  302. m_search_offset = 0;
  303. // Re-enable the notifier after discarding the search editor.
  304. m_notifier->set_enabled(true);
  305. if (search_string_result.is_error()) {
  306. // Somethine broke, fail
  307. m_input_error = search_string_result.error();
  308. finish();
  309. return;
  310. }
  311. auto& search_string = search_string_result.value();
  312. // Manually cleanup the search line.
  313. reposition_cursor();
  314. auto search_metrics = actual_rendered_string_metrics(search_string);
  315. auto metrics = actual_rendered_string_metrics(search_prompt);
  316. VT::clear_lines(0, metrics.lines_with_addition(search_metrics, m_num_columns) + search_end_row - m_origin_row - 1);
  317. reposition_cursor();
  318. if (!m_reset_buffer_on_search_end || search_metrics.total_length == 0) {
  319. // If the entry was empty, or we purposely quit without a newline,
  320. // do not return anything; instead, just end the search.
  321. end_search();
  322. return;
  323. }
  324. // Return the string,
  325. finish();
  326. }
  327. }
  328. void Editor::transpose_words()
  329. {
  330. // A word here is contiguous alnums. `foo=bar baz` is three words.
  331. // 'abcd,.:efg...' should become 'efg...,.:abcd' if caret is after
  332. // 'efg...'. If it's in 'efg', it should become 'efg,.:abcd...'
  333. // with the caret after it, which then becomes 'abcd...,.:efg'
  334. // when alt-t is pressed a second time.
  335. // Move to end of word under (or after) caret.
  336. size_t cursor = m_cursor;
  337. while (cursor < m_buffer.size() && !isalnum(m_buffer[cursor]))
  338. ++cursor;
  339. while (cursor < m_buffer.size() && isalnum(m_buffer[cursor]))
  340. ++cursor;
  341. // Move left over second word and the space to its right.
  342. size_t end = cursor;
  343. size_t start = cursor;
  344. while (start > 0 && !isalnum(m_buffer[start - 1]))
  345. --start;
  346. while (start > 0 && isalnum(m_buffer[start - 1]))
  347. --start;
  348. size_t start_second_word = start;
  349. // Move left over space between the two words.
  350. while (start > 0 && !isalnum(m_buffer[start - 1]))
  351. --start;
  352. size_t start_gap = start;
  353. // Move left over first word.
  354. while (start > 0 && isalnum(m_buffer[start - 1]))
  355. --start;
  356. if (start != start_gap) {
  357. // To swap the two words, swap each word (and the gap) individually, and then swap the whole range.
  358. auto swap_range = [this](auto from, auto to) {
  359. for (size_t i = 0; i < (to - from) / 2; ++i)
  360. swap(m_buffer[from + i], m_buffer[to - 1 - i]);
  361. };
  362. swap_range(start, start_gap);
  363. swap_range(start_gap, start_second_word);
  364. swap_range(start_second_word, end);
  365. swap_range(start, end);
  366. m_cursor = cursor;
  367. // FIXME: Update anchored styles too.
  368. m_refresh_needed = true;
  369. m_chars_touched_in_the_middle += end - start;
  370. }
  371. }
  372. void Editor::go_home()
  373. {
  374. m_cursor = 0;
  375. m_inline_search_cursor = m_cursor;
  376. m_search_offset = 0;
  377. }
  378. void Editor::go_end()
  379. {
  380. m_cursor = m_buffer.size();
  381. m_inline_search_cursor = m_cursor;
  382. m_search_offset = 0;
  383. }
  384. void Editor::clear_screen()
  385. {
  386. fprintf(stderr, "\033[3J\033[H\033[2J"); // Clear screen.
  387. VT::move_absolute(1, 1);
  388. set_origin(1, 1);
  389. m_refresh_needed = true;
  390. m_cached_prompt_valid = false;
  391. }
  392. void Editor::insert_last_words()
  393. {
  394. if (!m_history.is_empty()) {
  395. // FIXME: This isn't quite right: if the last arg was `"foo bar"` or `foo\ bar` (but not `foo\\ bar`), we should insert that whole arg as last token.
  396. if (auto last_words = m_history.last().entry.split_view(' '); !last_words.is_empty())
  397. insert(last_words.last());
  398. }
  399. }
  400. void Editor::erase_alnum_word_backwards()
  401. {
  402. // A word here is contiguous alnums. `foo=bar baz` is three words.
  403. bool has_seen_alnum = false;
  404. while (m_cursor > 0) {
  405. if (!isalnum(m_buffer[m_cursor - 1])) {
  406. if (has_seen_alnum)
  407. break;
  408. } else {
  409. has_seen_alnum = true;
  410. }
  411. erase_character_backwards();
  412. }
  413. }
  414. void Editor::erase_alnum_word_forwards()
  415. {
  416. // A word here is contiguous alnums. `foo=bar baz` is three words.
  417. bool has_seen_alnum = false;
  418. while (m_cursor < m_buffer.size()) {
  419. if (!isalnum(m_buffer[m_cursor])) {
  420. if (has_seen_alnum)
  421. break;
  422. } else {
  423. has_seen_alnum = true;
  424. }
  425. erase_character_forwards();
  426. }
  427. }
  428. void Editor::case_change_word(Editor::CaseChangeOp change_op)
  429. {
  430. // A word here is contiguous alnums. `foo=bar baz` is three words.
  431. while (m_cursor < m_buffer.size() && !isalnum(m_buffer[m_cursor]))
  432. ++m_cursor;
  433. size_t start = m_cursor;
  434. while (m_cursor < m_buffer.size() && isalnum(m_buffer[m_cursor])) {
  435. if (change_op == CaseChangeOp::Uppercase || (change_op == CaseChangeOp::Capital && m_cursor == start)) {
  436. m_buffer[m_cursor] = toupper(m_buffer[m_cursor]);
  437. } else {
  438. VERIFY(change_op == CaseChangeOp::Lowercase || (change_op == CaseChangeOp::Capital && m_cursor > start));
  439. m_buffer[m_cursor] = tolower(m_buffer[m_cursor]);
  440. }
  441. ++m_cursor;
  442. m_refresh_needed = true;
  443. }
  444. }
  445. void Editor::capitalize_word()
  446. {
  447. case_change_word(CaseChangeOp::Capital);
  448. }
  449. void Editor::lowercase_word()
  450. {
  451. case_change_word(CaseChangeOp::Lowercase);
  452. }
  453. void Editor::uppercase_word()
  454. {
  455. case_change_word(CaseChangeOp::Uppercase);
  456. }
  457. }