Editor.cpp 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  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 "Editor.h"
  27. #include <AK/StringBuilder.h>
  28. #include <ctype.h>
  29. #include <stdio.h>
  30. #include <sys/ioctl.h>
  31. #include <sys/select.h>
  32. #include <sys/time.h>
  33. #include <unistd.h>
  34. namespace Line {
  35. Editor::Editor(Configuration configuration)
  36. : m_configuration(configuration)
  37. {
  38. m_always_refresh = configuration.refresh_behaviour == Configuration::RefreshBehaviour::Eager;
  39. m_pending_chars = ByteBuffer::create_uninitialized(0);
  40. struct winsize ws;
  41. if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) {
  42. m_num_columns = 80;
  43. m_num_lines = 25;
  44. } else {
  45. m_num_columns = ws.ws_col;
  46. m_num_lines = ws.ws_row;
  47. }
  48. }
  49. Editor::~Editor()
  50. {
  51. if (m_initialized)
  52. restore();
  53. }
  54. void Editor::add_to_history(const String& line)
  55. {
  56. if ((m_history.size() + 1) > m_history_capacity)
  57. m_history.take_first();
  58. m_history.append(line);
  59. }
  60. void Editor::clear_line()
  61. {
  62. for (size_t i = 0; i < m_cursor; ++i)
  63. fputc(0x8, stdout);
  64. fputs("\033[K", stdout);
  65. fflush(stdout);
  66. m_buffer.clear();
  67. m_cursor = 0;
  68. m_inline_search_cursor = m_cursor;
  69. }
  70. void Editor::insert(const String& string)
  71. {
  72. for (auto ch : string)
  73. insert(ch);
  74. }
  75. void Editor::insert(const char ch)
  76. {
  77. m_pending_chars.append(&ch, 1);
  78. if (m_cursor == m_buffer.size()) {
  79. m_buffer.append(ch);
  80. m_cursor = m_buffer.size();
  81. m_inline_search_cursor = m_cursor;
  82. return;
  83. }
  84. m_buffer.insert(m_cursor, ch);
  85. ++m_chars_inserted_in_the_middle;
  86. ++m_cursor;
  87. m_inline_search_cursor = m_cursor;
  88. }
  89. void Editor::register_character_input_callback(char ch, Function<bool(Editor&)> callback)
  90. {
  91. if (m_key_callbacks.contains(ch)) {
  92. dbg() << "Key callback registered twice for " << ch;
  93. ASSERT_NOT_REACHED();
  94. }
  95. m_key_callbacks.set(ch, make<KeyCallback>(move(callback)));
  96. }
  97. void Editor::stylize(const Span& span, const Style& style)
  98. {
  99. auto starting_map = m_spans_starting.get(span.beginning()).value_or({});
  100. if (!starting_map.contains(span.end()))
  101. m_refresh_needed = true;
  102. starting_map.set(span.end(), style);
  103. m_spans_starting.set(span.beginning(), starting_map);
  104. auto ending_map = m_spans_ending.get(span.end()).value_or({});
  105. if (!ending_map.contains(span.beginning()))
  106. m_refresh_needed = true;
  107. ending_map.set(span.beginning(), style);
  108. m_spans_ending.set(span.end(), ending_map);
  109. }
  110. String Editor::get_line(const String& prompt)
  111. {
  112. initialize();
  113. m_is_editing = true;
  114. set_prompt(prompt);
  115. reset();
  116. set_origin();
  117. m_history_cursor = m_history.size();
  118. for (;;) {
  119. if (m_always_refresh)
  120. m_refresh_needed = true;
  121. refresh_display();
  122. if (m_finish) {
  123. m_finish = false;
  124. printf("\n");
  125. fflush(stdout);
  126. auto string = String::copy(m_buffer);
  127. m_buffer.clear();
  128. m_is_editing = false;
  129. restore();
  130. return string;
  131. }
  132. char keybuf[16];
  133. ssize_t nread = read(0, keybuf, sizeof(keybuf));
  134. // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
  135. if (nread == 0)
  136. exit(0);
  137. if (nread < 0) {
  138. if (errno == EINTR) {
  139. if (!m_was_interrupted) {
  140. if (m_was_resized)
  141. continue;
  142. finish();
  143. continue;
  144. }
  145. m_was_interrupted = false;
  146. if (!m_buffer.is_empty())
  147. printf("^C");
  148. m_buffer.clear();
  149. m_cursor = 0;
  150. m_refresh_needed = true;
  151. continue;
  152. }
  153. perror("read failed");
  154. // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
  155. exit(2);
  156. }
  157. auto reverse_tab = false;
  158. auto increment_suggestion_index = [&] {
  159. if (m_suggestions.size())
  160. m_next_suggestion_index = (m_next_suggestion_index + 1) % m_suggestions.size();
  161. else
  162. m_next_suggestion_index = 0;
  163. };
  164. auto decrement_suggestion_index = [&] {
  165. if (m_next_suggestion_index == 0)
  166. m_next_suggestion_index = m_suggestions.size();
  167. m_next_suggestion_index--;
  168. };
  169. auto ctrl_held = false;
  170. for (ssize_t i = 0; i < nread; ++i) {
  171. char ch = keybuf[i];
  172. if (ch == 0)
  173. continue;
  174. switch (m_state) {
  175. case InputState::ExpectBracket:
  176. if (ch == '[') {
  177. m_state = InputState::ExpectFinal;
  178. continue;
  179. } else {
  180. m_state = InputState::Free;
  181. break;
  182. }
  183. case InputState::ExpectFinal:
  184. switch (ch) {
  185. case 'O': // mod_ctrl
  186. ctrl_held = true;
  187. continue;
  188. case 'A': // up
  189. {
  190. m_searching_backwards = true;
  191. auto inline_search_cursor = m_inline_search_cursor;
  192. String search_phrase { m_buffer.data(), inline_search_cursor };
  193. if (search(search_phrase, true, true)) {
  194. ++m_search_offset;
  195. } else {
  196. insert(search_phrase);
  197. }
  198. m_inline_search_cursor = inline_search_cursor;
  199. m_state = InputState::Free;
  200. ctrl_held = false;
  201. continue;
  202. }
  203. case 'B': // down
  204. {
  205. auto inline_search_cursor = m_inline_search_cursor;
  206. String search_phrase { m_buffer.data(), inline_search_cursor };
  207. auto search_changed_directions = m_searching_backwards;
  208. m_searching_backwards = false;
  209. if (m_search_offset > 0) {
  210. m_search_offset -= 1 + search_changed_directions;
  211. if (!search(search_phrase, true, true)) {
  212. insert(search_phrase);
  213. }
  214. } else {
  215. m_search_offset = 0;
  216. m_cursor = 0;
  217. m_buffer.clear();
  218. insert(search_phrase);
  219. m_refresh_needed = true;
  220. }
  221. m_inline_search_cursor = inline_search_cursor;
  222. m_state = InputState::Free;
  223. ctrl_held = false;
  224. continue;
  225. }
  226. case 'D': // left
  227. if (m_cursor > 0) {
  228. if (ctrl_held) {
  229. auto skipped_at_least_one_character = false;
  230. for (;;) {
  231. if (m_cursor == 0)
  232. break;
  233. if (skipped_at_least_one_character && isspace(m_buffer[m_cursor - 1])) // stop *after* a space, but only if it changes the position
  234. break;
  235. skipped_at_least_one_character = true;
  236. --m_cursor;
  237. }
  238. } else {
  239. --m_cursor;
  240. }
  241. }
  242. m_inline_search_cursor = m_cursor;
  243. m_state = InputState::Free;
  244. ctrl_held = false;
  245. continue;
  246. case 'C': // right
  247. if (m_cursor < m_buffer.size()) {
  248. if (ctrl_held) {
  249. // temporarily put a space at the end of our buffer
  250. // this greatly simplifies the logic below
  251. m_buffer.append(' ');
  252. for (;;) {
  253. if (m_cursor >= m_buffer.size())
  254. break;
  255. if (isspace(m_buffer[++m_cursor]))
  256. break;
  257. }
  258. m_buffer.take_last();
  259. } else {
  260. ++m_cursor;
  261. }
  262. }
  263. m_inline_search_cursor = m_cursor;
  264. m_search_offset = 0;
  265. m_state = InputState::Free;
  266. ctrl_held = false;
  267. continue;
  268. case 'H':
  269. m_cursor = 0;
  270. m_inline_search_cursor = m_cursor;
  271. m_search_offset = 0;
  272. m_state = InputState::Free;
  273. ctrl_held = false;
  274. continue;
  275. case 'F':
  276. m_cursor = m_buffer.size();
  277. m_state = InputState::Free;
  278. m_inline_search_cursor = m_cursor;
  279. m_search_offset = 0;
  280. ctrl_held = false;
  281. continue;
  282. case 'Z': // shift+tab
  283. reverse_tab = true;
  284. m_state = InputState::Free;
  285. ctrl_held = false;
  286. break;
  287. case '3':
  288. if (m_cursor == m_buffer.size()) {
  289. fputc('\a', stdout);
  290. fflush(stdout);
  291. continue;
  292. }
  293. m_buffer.remove(m_cursor);
  294. m_refresh_needed = true;
  295. m_search_offset = 0;
  296. m_state = InputState::ExpectTerminator;
  297. ctrl_held = false;
  298. continue;
  299. default:
  300. dbgprintf("Shell: Unhandled final: %02x (%c)\r\n", ch, ch);
  301. m_state = InputState::Free;
  302. ctrl_held = false;
  303. continue;
  304. }
  305. break;
  306. case InputState::ExpectTerminator:
  307. m_state = InputState::Free;
  308. continue;
  309. case InputState::Free:
  310. if (ch == 27) {
  311. m_state = InputState::ExpectBracket;
  312. continue;
  313. }
  314. break;
  315. }
  316. auto cb = m_key_callbacks.get(ch);
  317. if (cb.has_value()) {
  318. if (!cb.value()->callback(*this)) {
  319. continue;
  320. }
  321. }
  322. m_search_offset = 0; // reset search offset on any key
  323. if (ch == '\t' || reverse_tab) {
  324. if (!on_tab_complete_first_token || !on_tab_complete_other_token)
  325. continue;
  326. auto should_break_token = [mode = m_configuration.split_mechanism](auto& buffer, size_t index) {
  327. switch (mode) {
  328. case Configuration::TokenSplitMechanism::Spaces:
  329. return buffer[index] == ' ';
  330. case Configuration::TokenSplitMechanism::UnescapedSpaces:
  331. return buffer[index] == ' ' && (index == 0 || buffer[index - 1] != '\\');
  332. }
  333. ASSERT_NOT_REACHED();
  334. return true;
  335. };
  336. bool is_empty_token = m_cursor == 0 || should_break_token(m_buffer, m_cursor - 1);
  337. // reverse tab can count as regular tab here
  338. m_times_tab_pressed++;
  339. int token_start = m_cursor - 1;
  340. if (!is_empty_token) {
  341. while (token_start >= 0 && !should_break_token(m_buffer, token_start))
  342. --token_start;
  343. ++token_start;
  344. }
  345. bool is_first_token = true;
  346. for (int i = token_start - 1; i >= 0; --i) {
  347. if (should_break_token(m_buffer, i)) {
  348. is_first_token = false;
  349. break;
  350. }
  351. }
  352. String token = is_empty_token ? String() : String(&m_buffer[token_start], m_cursor - token_start);
  353. // ask for completions only on the first tab
  354. // and scan for the largest common prefix to display
  355. // further tabs simply show the cached completions
  356. if (m_times_tab_pressed == 1) {
  357. if (is_first_token)
  358. m_suggestions = on_tab_complete_first_token(token);
  359. else
  360. m_suggestions = on_tab_complete_other_token(token);
  361. size_t common_suggestion_prefix { 0 };
  362. if (m_suggestions.size() == 1) {
  363. m_largest_common_suggestion_prefix_length = m_suggestions[0].text.length();
  364. } else if (m_suggestions.size()) {
  365. char last_valid_suggestion_char;
  366. for (;; ++common_suggestion_prefix) {
  367. if (m_suggestions[0].text.length() <= common_suggestion_prefix)
  368. goto no_more_commons;
  369. last_valid_suggestion_char = m_suggestions[0].text[common_suggestion_prefix];
  370. for (const auto& suggestion : m_suggestions) {
  371. if (suggestion.text.length() <= common_suggestion_prefix || suggestion.text[common_suggestion_prefix] != last_valid_suggestion_char) {
  372. goto no_more_commons;
  373. }
  374. }
  375. }
  376. no_more_commons:;
  377. m_largest_common_suggestion_prefix_length = common_suggestion_prefix;
  378. } else {
  379. m_largest_common_suggestion_prefix_length = 0;
  380. // there are no suggestions, beep~
  381. putchar('\a');
  382. fflush(stdout);
  383. }
  384. m_prompt_lines_at_suggestion_initiation = num_lines();
  385. }
  386. // Adjust already incremented / decremented index when switching tab direction
  387. if (reverse_tab && m_tab_direction != TabDirection::Backward) {
  388. decrement_suggestion_index();
  389. decrement_suggestion_index();
  390. m_tab_direction = TabDirection::Backward;
  391. }
  392. if (!reverse_tab && m_tab_direction != TabDirection::Forward) {
  393. increment_suggestion_index();
  394. increment_suggestion_index();
  395. m_tab_direction = TabDirection::Forward;
  396. }
  397. reverse_tab = false;
  398. auto current_suggestion_index = m_next_suggestion_index;
  399. if (m_next_suggestion_index < m_suggestions.size()) {
  400. auto can_complete = m_next_suggestion_invariant_offset <= m_largest_common_suggestion_prefix_length;
  401. if (!m_last_shown_suggestion.text.is_null()) {
  402. size_t actual_offset;
  403. size_t shown_length = m_last_shown_suggestion_display_length;
  404. switch (m_times_tab_pressed) {
  405. case 1:
  406. actual_offset = m_cursor;
  407. break;
  408. case 2:
  409. actual_offset = m_cursor - m_largest_common_suggestion_prefix_length + m_next_suggestion_invariant_offset;
  410. if (can_complete)
  411. shown_length = m_largest_common_suggestion_prefix_length + m_last_shown_suggestion.trailing_trivia.length();
  412. break;
  413. default:
  414. if (m_last_shown_suggestion_display_length == 0)
  415. actual_offset = m_cursor;
  416. else
  417. actual_offset = m_cursor - m_last_shown_suggestion_display_length + m_next_suggestion_invariant_offset;
  418. break;
  419. }
  420. for (size_t i = m_next_suggestion_invariant_offset; i < shown_length; ++i)
  421. m_buffer.remove(actual_offset);
  422. m_cursor = actual_offset;
  423. m_inline_search_cursor = m_cursor;
  424. m_refresh_needed = true;
  425. }
  426. m_last_shown_suggestion = m_suggestions[m_next_suggestion_index];
  427. m_last_shown_suggestion_display_length = m_last_shown_suggestion.text.length();
  428. m_last_shown_suggestion_was_complete = true;
  429. if (m_times_tab_pressed == 1) {
  430. // This is the first time, so only auto-complete *if possible*
  431. if (can_complete) {
  432. insert(m_last_shown_suggestion.text.substring_view(m_next_suggestion_invariant_offset, m_largest_common_suggestion_prefix_length - m_next_suggestion_invariant_offset));
  433. m_last_shown_suggestion_display_length = m_largest_common_suggestion_prefix_length;
  434. // do not increment the suggestion index, as the first tab should only be a *peek*
  435. if (m_suggestions.size() == 1) {
  436. // if there's one suggestion, commit and forget
  437. m_times_tab_pressed = 0;
  438. // add in the trivia of the last selected suggestion
  439. insert(m_last_shown_suggestion.trailing_trivia);
  440. m_last_shown_suggestion_display_length += m_last_shown_suggestion.trailing_trivia.length();
  441. }
  442. } else {
  443. m_last_shown_suggestion_display_length = 0;
  444. }
  445. ++m_times_tab_pressed;
  446. m_last_shown_suggestion_was_complete = false;
  447. } else {
  448. insert(m_last_shown_suggestion.text.substring_view(m_next_suggestion_invariant_offset, m_last_shown_suggestion.text.length() - m_next_suggestion_invariant_offset));
  449. // add in the trivia of the last selected suggestion
  450. insert(m_last_shown_suggestion.trailing_trivia);
  451. m_last_shown_suggestion_display_length += m_last_shown_suggestion.trailing_trivia.length();
  452. if (m_tab_direction == TabDirection::Forward)
  453. increment_suggestion_index();
  454. else
  455. decrement_suggestion_index();
  456. }
  457. } else {
  458. m_next_suggestion_index = 0;
  459. }
  460. if (m_times_tab_pressed > 1 && !m_suggestions.is_empty()) {
  461. size_t longest_suggestion_length = 0;
  462. for (auto& suggestion : m_suggestions) {
  463. longest_suggestion_length = max(longest_suggestion_length, suggestion.text.length());
  464. }
  465. size_t num_printed = 0;
  466. size_t lines_used { 1 };
  467. size_t index { 0 };
  468. vt_save_cursor();
  469. vt_clear_lines(0, m_lines_used_for_last_suggestions);
  470. vt_restore_cursor();
  471. auto spans_entire_line { false };
  472. auto max_line_count = (m_cached_prompt_length + longest_suggestion_length + m_num_columns - 1) / m_num_columns;
  473. if (longest_suggestion_length >= m_num_columns - 2) {
  474. spans_entire_line = true;
  475. // we should make enough space for the biggest entry in
  476. // the suggestion list to fit in the prompt line
  477. auto start = max_line_count - m_prompt_lines_at_suggestion_initiation;
  478. for (size_t i = start; i < max_line_count; ++i) {
  479. putchar('\n');
  480. }
  481. lines_used += max_line_count;
  482. longest_suggestion_length = 0;
  483. }
  484. vt_move_absolute(max_line_count + m_origin_x, 1);
  485. for (auto& suggestion : m_suggestions) {
  486. size_t next_column = num_printed + suggestion.text.length() + longest_suggestion_length + 2;
  487. if (next_column > m_num_columns) {
  488. auto lines = (suggestion.text.length() + m_num_columns - 1) / m_num_columns;
  489. lines_used += lines;
  490. putchar('\n');
  491. num_printed = 0;
  492. }
  493. // show just enough suggestions to fill up the screen
  494. // without moving the prompt out of view
  495. if (lines_used + m_prompt_lines_at_suggestion_initiation >= m_num_lines)
  496. break;
  497. // only apply colour to the selection if something is *actually* added to the buffer
  498. if (m_last_shown_suggestion_was_complete && index == current_suggestion_index) {
  499. vt_apply_style({ Style::Foreground(Style::XtermColor::Blue) });
  500. fflush(stdout);
  501. }
  502. if (spans_entire_line) {
  503. num_printed += m_num_columns;
  504. fprintf(stderr, "%s", suggestion.text.characters());
  505. } else {
  506. num_printed += fprintf(stderr, "%-*s", static_cast<int>(longest_suggestion_length) + 2, suggestion.text.characters());
  507. }
  508. if (m_last_shown_suggestion_was_complete && index == current_suggestion_index) {
  509. vt_apply_style({});
  510. fflush(stdout);
  511. }
  512. ++index;
  513. }
  514. m_lines_used_for_last_suggestions = lines_used;
  515. // adjust for the case that we scroll up after writing the suggestions
  516. if (m_origin_x + lines_used >= m_num_lines) {
  517. m_origin_x = m_num_lines - lines_used;
  518. }
  519. reposition_cursor();
  520. }
  521. if (m_suggestions.size() < 2) {
  522. // we have none, or just one suggestion
  523. // we should just commit that and continue
  524. // after it, as if it were auto-completed
  525. suggest(0, 0);
  526. m_last_shown_suggestion = String::empty();
  527. m_last_shown_suggestion_display_length = 0;
  528. m_suggestions.clear();
  529. m_times_tab_pressed = 0;
  530. }
  531. continue;
  532. }
  533. if (m_times_tab_pressed) {
  534. // we probably have some suggestions drawn
  535. // let's clean them up
  536. if (m_lines_used_for_last_suggestions) {
  537. vt_clear_lines(0, m_lines_used_for_last_suggestions);
  538. reposition_cursor();
  539. m_refresh_needed = true;
  540. m_lines_used_for_last_suggestions = 0;
  541. }
  542. m_last_shown_suggestion_display_length = 0;
  543. m_last_shown_suggestion = String::empty();
  544. m_suggestions.clear();
  545. suggest(0, 0);
  546. }
  547. m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB
  548. auto do_backspace = [&] {
  549. if (m_is_searching) {
  550. return;
  551. }
  552. if (m_cursor == 0) {
  553. fputc('\a', stdout);
  554. fflush(stdout);
  555. return;
  556. }
  557. m_buffer.remove(m_cursor - 1);
  558. --m_cursor;
  559. m_inline_search_cursor = m_cursor;
  560. // we will have to redraw :(
  561. m_refresh_needed = true;
  562. };
  563. if (ch == 8 || ch == m_termios.c_cc[VERASE]) {
  564. do_backspace();
  565. continue;
  566. }
  567. if (ch == m_termios.c_cc[VWERASE]) {
  568. bool has_seen_nonspace = false;
  569. while (m_cursor > 0) {
  570. if (isspace(m_buffer[m_cursor - 1])) {
  571. if (has_seen_nonspace)
  572. break;
  573. } else {
  574. has_seen_nonspace = true;
  575. }
  576. do_backspace();
  577. }
  578. continue;
  579. }
  580. if (ch == m_termios.c_cc[VKILL]) {
  581. for (size_t i = 0; i < m_cursor; ++i)
  582. m_buffer.remove(0);
  583. m_cursor = 0;
  584. m_refresh_needed = true;
  585. continue;
  586. }
  587. // ^L
  588. if (ch == 0xc) {
  589. printf("\033[3J\033[H\033[2J"); // Clear screen.
  590. vt_move_absolute(1, 1);
  591. m_origin_x = 1;
  592. m_origin_y = 1;
  593. m_refresh_needed = true;
  594. continue;
  595. }
  596. // ^A
  597. if (ch == 0x01) {
  598. m_cursor = 0;
  599. continue;
  600. }
  601. // ^R
  602. if (ch == 0x12) {
  603. if (m_is_searching) {
  604. // how did we get here?
  605. ASSERT_NOT_REACHED();
  606. } else {
  607. m_is_searching = true;
  608. m_search_offset = 0;
  609. m_pre_search_buffer.clear();
  610. for (auto ch : m_buffer)
  611. m_pre_search_buffer.append(ch);
  612. m_pre_search_cursor = m_cursor;
  613. m_search_editor = make<Editor>(Configuration { Configuration::Eager, m_configuration.split_mechanism }); // Has anyone seen 'Inception'?
  614. m_search_editor->on_display_refresh = [this](Editor& search_editor) {
  615. search(StringView { search_editor.buffer().data(), search_editor.buffer().size() });
  616. refresh_display();
  617. return;
  618. };
  619. // whenever the search editor gets a ^R, cycle between history entries
  620. m_search_editor->register_character_input_callback(0x12, [this](Editor& search_editor) {
  621. ++m_search_offset;
  622. search_editor.m_refresh_needed = true;
  623. return false; // Do not process this key event
  624. });
  625. // whenever the search editor gets a backspace, cycle back between history entries
  626. // unless we're at the zeroth entry, in which case, allow the deletion
  627. m_search_editor->register_character_input_callback(m_termios.c_cc[VERASE], [this](Editor& search_editor) {
  628. if (m_search_offset > 0) {
  629. --m_search_offset;
  630. search_editor.m_refresh_needed = true;
  631. return false; // Do not process this key event
  632. }
  633. return true;
  634. });
  635. // ^L - This is a source of issues, as the search editor refreshes first,
  636. // and we end up with the wrong order of prompts, so we will first refresh
  637. // ourselves, then refresh the search editor, and then tell him not to process
  638. // this event
  639. m_search_editor->register_character_input_callback(0x0c, [this](auto& search_editor) {
  640. printf("\033[3J\033[H\033[2J"); // Clear screen.
  641. // refresh our own prompt
  642. m_origin_x = 1;
  643. m_origin_y = 1;
  644. m_refresh_needed = true;
  645. refresh_display();
  646. // move the search prompt below ours
  647. // and tell it to redraw itself
  648. search_editor.m_origin_x = 2;
  649. search_editor.m_origin_y = 1;
  650. search_editor.m_refresh_needed = true;
  651. return false;
  652. });
  653. // quit without clearing the current buffer
  654. m_search_editor->register_character_input_callback('\t', [this](Editor& search_editor) {
  655. search_editor.finish();
  656. m_reset_buffer_on_search_end = false;
  657. return false;
  658. });
  659. printf("\n");
  660. fflush(stdout);
  661. auto search_prompt = "\x1b[32msearch:\x1b[0m ";
  662. auto search_string = m_search_editor->get_line(search_prompt);
  663. m_search_editor = nullptr;
  664. m_is_searching = false;
  665. m_search_offset = 0;
  666. // manually cleanup the search line
  667. reposition_cursor();
  668. vt_clear_lines(0, (search_string.length() + actual_rendered_string_length(search_prompt) + m_num_columns - 1) / m_num_columns);
  669. reposition_cursor();
  670. if (!m_reset_buffer_on_search_end || search_string.length() == 0) {
  671. // if the entry was empty, or we purposely quit without a newline,
  672. // do not return anything
  673. // instead, just end the search
  674. end_search();
  675. continue;
  676. }
  677. // return the string
  678. finish();
  679. continue;
  680. }
  681. continue;
  682. }
  683. // Normally ^D
  684. if (ch == m_termios.c_cc[VEOF]) {
  685. if (m_buffer.is_empty()) {
  686. printf("<EOF>\n");
  687. if (!m_always_refresh) // this is a little off, but it'll do for now
  688. exit(0);
  689. }
  690. continue;
  691. }
  692. // ^E
  693. if (ch == 0x05) {
  694. m_cursor = m_buffer.size();
  695. continue;
  696. }
  697. if (ch == '\n') {
  698. finish();
  699. continue;
  700. }
  701. insert(ch);
  702. }
  703. }
  704. }
  705. bool Editor::search(const StringView& phrase, bool allow_empty, bool from_beginning)
  706. {
  707. int last_matching_offset = -1;
  708. // do not search for empty strings
  709. if (allow_empty || phrase.length() > 0) {
  710. size_t search_offset = m_search_offset;
  711. for (size_t i = m_history_cursor; i > 0; --i) {
  712. auto contains = from_beginning ? m_history[i - 1].starts_with(phrase) : m_history[i - 1].contains(phrase);
  713. if (contains) {
  714. last_matching_offset = i - 1;
  715. if (search_offset == 0)
  716. break;
  717. --search_offset;
  718. }
  719. }
  720. if (last_matching_offset == -1) {
  721. fputc('\a', stdout);
  722. fflush(stdout);
  723. }
  724. }
  725. m_buffer.clear();
  726. m_cursor = 0;
  727. if (last_matching_offset >= 0) {
  728. insert(m_history[last_matching_offset]);
  729. }
  730. // always needed
  731. m_refresh_needed = true;
  732. return last_matching_offset >= 0;
  733. }
  734. void Editor::recalculate_origin()
  735. {
  736. // changing the columns can affect our origin if
  737. // the new size is smaller than our prompt, which would
  738. // cause said prompt to take up more space, so we should
  739. // compensate for that
  740. if (m_cached_prompt_length >= m_num_columns) {
  741. auto added_lines = (m_cached_prompt_length + 1) / m_num_columns - 1;
  742. m_origin_x += added_lines;
  743. }
  744. // we also need to recalculate our cursor position
  745. // but that will be calculated and applied at the next
  746. // refresh cycle
  747. }
  748. void Editor::cleanup()
  749. {
  750. vt_move_relative(0, m_pending_chars.size() - m_chars_inserted_in_the_middle);
  751. auto current_line = cursor_line();
  752. vt_clear_lines(current_line - 1, num_lines() - current_line);
  753. vt_move_relative(-num_lines() + 1, -offset_in_line() - m_old_prompt_length - m_pending_chars.size() + m_chars_inserted_in_the_middle);
  754. };
  755. void Editor::refresh_display()
  756. {
  757. auto has_cleaned_up = false;
  758. // someone changed the window size, figure it out
  759. // and react to it, we might need to redraw
  760. if (m_was_resized) {
  761. auto previous_num_columns = m_num_columns;
  762. struct winsize ws;
  763. if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) {
  764. m_num_columns = 80;
  765. m_num_lines = 25;
  766. } else {
  767. m_num_columns = ws.ws_col;
  768. m_num_lines = ws.ws_row;
  769. }
  770. if (previous_num_columns != m_num_columns) {
  771. // we need to cleanup and redo everything
  772. m_cached_prompt_valid = false;
  773. m_refresh_needed = true;
  774. swap(previous_num_columns, m_num_columns);
  775. recalculate_origin();
  776. cleanup();
  777. swap(previous_num_columns, m_num_columns);
  778. has_cleaned_up = true;
  779. }
  780. }
  781. // do not call hook on pure cursor movement
  782. if (m_cached_prompt_valid && !m_refresh_needed && m_pending_chars.size() == 0) {
  783. // probably just moving around
  784. reposition_cursor();
  785. m_cached_buffer_size = m_buffer.size();
  786. return;
  787. }
  788. if (on_display_refresh)
  789. on_display_refresh(*this);
  790. if (m_cached_prompt_valid) {
  791. if (!m_refresh_needed && m_cursor == m_buffer.size()) {
  792. // just write the characters out and continue
  793. // no need to refresh the entire line
  794. char null = 0;
  795. m_pending_chars.append(&null, 1);
  796. fputs((char*)m_pending_chars.data(), stdout);
  797. m_pending_chars.clear();
  798. m_drawn_cursor = m_cursor;
  799. m_cached_buffer_size = m_buffer.size();
  800. fflush(stdout);
  801. return;
  802. }
  803. }
  804. // ouch, reflow entire line
  805. // FIXME: handle multiline stuff
  806. if (!has_cleaned_up) {
  807. cleanup();
  808. }
  809. vt_move_absolute(m_origin_x, m_origin_y);
  810. fputs(m_new_prompt.characters(), stdout);
  811. vt_clear_to_end_of_line();
  812. HashMap<u32, Style> empty_styles {};
  813. for (size_t i = 0; i < m_buffer.size(); ++i) {
  814. auto ends = m_spans_ending.get(i).value_or(empty_styles);
  815. auto starts = m_spans_starting.get(i).value_or(empty_styles);
  816. if (ends.size()) {
  817. // go back to defaults
  818. vt_apply_style(find_applicable_style(i));
  819. }
  820. if (starts.size()) {
  821. // set new options
  822. vt_apply_style(starts.begin()->value); // apply some random style that starts here
  823. }
  824. fputc(m_buffer[i], stdout);
  825. }
  826. vt_apply_style({}); // don't bleed to EOL
  827. m_pending_chars.clear();
  828. m_refresh_needed = false;
  829. m_cached_buffer_size = m_buffer.size();
  830. m_chars_inserted_in_the_middle = 0;
  831. if (!m_cached_prompt_valid) {
  832. m_cached_prompt_valid = true;
  833. }
  834. reposition_cursor();
  835. fflush(stdout);
  836. }
  837. void Editor::reposition_cursor()
  838. {
  839. m_drawn_cursor = m_cursor;
  840. auto line = cursor_line() - 1;
  841. auto column = offset_in_line();
  842. vt_move_absolute(line + m_origin_x, column + m_origin_y);
  843. }
  844. void Editor::vt_move_absolute(u32 x, u32 y)
  845. {
  846. printf("\033[%d;%dH", x, y);
  847. fflush(stdout);
  848. }
  849. void Editor::vt_move_relative(int x, int y)
  850. {
  851. char x_op = 'A', y_op = 'D';
  852. if (x > 0)
  853. x_op = 'B';
  854. else
  855. x = -x;
  856. if (y > 0)
  857. y_op = 'C';
  858. else
  859. y = -y;
  860. if (x > 0)
  861. printf("\033[%d%c", x, x_op);
  862. if (y > 0)
  863. printf("\033[%d%c", y, y_op);
  864. }
  865. Style Editor::find_applicable_style(size_t offset) const
  866. {
  867. // walk through our styles and find one that fits in the offset
  868. for (auto& entry : m_spans_starting) {
  869. if (entry.key > offset)
  870. continue;
  871. for (auto& style_value : entry.value) {
  872. if (style_value.key <= offset)
  873. continue;
  874. return style_value.value;
  875. }
  876. }
  877. return {};
  878. }
  879. String Style::Background::to_vt_escape() const
  880. {
  881. if (m_is_rgb) {
  882. return String::format("\033[48;2;%d;%d;%dm", m_rgb_color[0], m_rgb_color[1], m_rgb_color[2]);
  883. } else {
  884. return String::format("\033[%dm", (u8)m_xterm_color + 40);
  885. }
  886. }
  887. String Style::Foreground::to_vt_escape() const
  888. {
  889. if (m_is_rgb) {
  890. return String::format("\033[38;2;%d;%d;%dm", m_rgb_color[0], m_rgb_color[1], m_rgb_color[2]);
  891. } else {
  892. return String::format("\033[%dm", (u8)m_xterm_color + 30);
  893. }
  894. }
  895. void Editor::vt_apply_style(const Style& style)
  896. {
  897. printf(
  898. "\033[%d;%d;%dm%s%s",
  899. style.bold() ? 1 : 22,
  900. style.underline() ? 4 : 24,
  901. style.italic() ? 3 : 23,
  902. style.background().to_vt_escape().characters(),
  903. style.foreground().to_vt_escape().characters());
  904. }
  905. void Editor::vt_clear_lines(size_t count_above, size_t count_below)
  906. {
  907. // go down count_below lines
  908. if (count_below > 0)
  909. printf("\033[%dB", (int)count_below);
  910. // then clear lines going upwards
  911. for (size_t i = count_below + count_above; i > 0; --i)
  912. fputs(i == 1 ? "\033[2K" : "\033[2K\033[A", stdout);
  913. }
  914. void Editor::vt_save_cursor()
  915. {
  916. fputs("\033[s", stdout);
  917. fflush(stdout);
  918. }
  919. void Editor::vt_restore_cursor()
  920. {
  921. fputs("\033[u", stdout);
  922. fflush(stdout);
  923. }
  924. void Editor::vt_clear_to_end_of_line()
  925. {
  926. fputs("\033[K", stdout);
  927. fflush(stdout);
  928. }
  929. size_t Editor::actual_rendered_string_length(const StringView& string) const
  930. {
  931. size_t length { 0 };
  932. enum VTState {
  933. Free = 1,
  934. Escape = 3,
  935. Bracket = 5,
  936. BracketArgsSemi = 7,
  937. Title = 9,
  938. } state { Free };
  939. for (size_t i = 0; i < string.length(); ++i) {
  940. auto c = string[i];
  941. switch (state) {
  942. case Free:
  943. if (c == '\x1b') {
  944. // escape
  945. state = Escape;
  946. continue;
  947. }
  948. if (c == '\r' || c == '\n') {
  949. // reset length to 0, since we either overwrite, or are on a newline
  950. length = 0;
  951. continue;
  952. }
  953. // FIXME: This will not support anything sophisticated
  954. ++length;
  955. break;
  956. case Escape:
  957. if (c == ']') {
  958. if (string.length() > i && string[i + 1] == '0')
  959. state = Title;
  960. continue;
  961. }
  962. if (c == '[') {
  963. state = Bracket;
  964. continue;
  965. }
  966. // FIXME: This does not support non-VT (aside from set-title) escapes
  967. break;
  968. case Bracket:
  969. if (isdigit(c)) {
  970. state = BracketArgsSemi;
  971. continue;
  972. }
  973. break;
  974. case BracketArgsSemi:
  975. if (c == ';') {
  976. state = Bracket;
  977. continue;
  978. }
  979. if (!isdigit(c))
  980. state = Free;
  981. break;
  982. case Title:
  983. if (c == 7)
  984. state = Free;
  985. break;
  986. }
  987. }
  988. return length;
  989. }
  990. Vector<size_t, 2> Editor::vt_dsr()
  991. {
  992. char buf[16];
  993. u32 length { 0 };
  994. // read whatever junk there is before talking to the terminal
  995. bool more_junk_to_read { false };
  996. timeval timeout { 0, 0 };
  997. fd_set readfds;
  998. FD_ZERO(&readfds);
  999. FD_SET(0, &readfds);
  1000. do {
  1001. more_junk_to_read = false;
  1002. (void)select(1, &readfds, nullptr, nullptr, &timeout);
  1003. if (FD_ISSET(0, &readfds)) {
  1004. auto nread = read(0, buf, 16);
  1005. (void)nread;
  1006. more_junk_to_read = true;
  1007. }
  1008. } while (more_junk_to_read);
  1009. fputs("\033[6n", stdout);
  1010. fflush(stdout);
  1011. do {
  1012. auto nread = read(0, buf + length, 16 - length);
  1013. if (nread < 0) {
  1014. if (errno == 0) {
  1015. // ????
  1016. continue;
  1017. }
  1018. dbg() << "Error while reading DSR: " << strerror(errno);
  1019. return { 1, 1 };
  1020. }
  1021. if (nread == 0) {
  1022. dbg() << "Terminal DSR issue; received no response";
  1023. return { 1, 1 };
  1024. }
  1025. length += nread;
  1026. } while (buf[length - 1] != 'R' && length < 16);
  1027. size_t x { 1 }, y { 1 };
  1028. if (buf[0] == '\033' && buf[1] == '[') {
  1029. auto parts = StringView(buf + 2, length - 3).split_view(';');
  1030. bool ok;
  1031. x = parts[0].to_int(ok);
  1032. if (!ok) {
  1033. dbg() << "Terminal DSR issue; received garbage x";
  1034. }
  1035. y = parts[1].to_int(ok);
  1036. if (!ok) {
  1037. dbg() << "Terminal DSR issue; received garbage y";
  1038. }
  1039. }
  1040. return { x, y };
  1041. }
  1042. }