Editor.cpp 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169
  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. size_t start_index = 0;
  463. for (auto& suggestion : m_suggestions) {
  464. if (start_index++ < m_last_displayed_suggestion_index)
  465. continue;
  466. longest_suggestion_length = max(longest_suggestion_length, suggestion.text.length());
  467. }
  468. size_t num_printed = 0;
  469. size_t lines_used { 1 };
  470. size_t index { 0 };
  471. vt_save_cursor();
  472. vt_clear_lines(0, m_lines_used_for_last_suggestions);
  473. vt_restore_cursor();
  474. auto spans_entire_line { false };
  475. auto max_line_count = (m_cached_prompt_length + longest_suggestion_length + m_num_columns - 1) / m_num_columns;
  476. if (longest_suggestion_length >= m_num_columns - 2) {
  477. spans_entire_line = true;
  478. // we should make enough space for the biggest entry in
  479. // the suggestion list to fit in the prompt line
  480. auto start = max_line_count - m_prompt_lines_at_suggestion_initiation;
  481. for (size_t i = start; i < max_line_count; ++i) {
  482. putchar('\n');
  483. }
  484. lines_used += max_line_count;
  485. longest_suggestion_length = 0;
  486. }
  487. vt_move_absolute(max_line_count + m_origin_x, 1);
  488. for (auto& suggestion : m_suggestions) {
  489. if (index < m_last_displayed_suggestion_index) {
  490. ++index;
  491. continue;
  492. }
  493. size_t next_column = num_printed + suggestion.text.length() + longest_suggestion_length + 2;
  494. if (next_column > m_num_columns) {
  495. auto lines = (suggestion.text.length() + m_num_columns - 1) / m_num_columns;
  496. lines_used += lines;
  497. putchar('\n');
  498. num_printed = 0;
  499. }
  500. // show just enough suggestions to fill up the screen
  501. // without moving the prompt out of view
  502. if (lines_used + m_prompt_lines_at_suggestion_initiation >= m_num_lines)
  503. break;
  504. // only apply colour to the selection if something is *actually* added to the buffer
  505. if (m_last_shown_suggestion_was_complete && index == current_suggestion_index) {
  506. vt_apply_style({ Style::Foreground(Style::XtermColor::Blue) });
  507. fflush(stdout);
  508. }
  509. if (spans_entire_line) {
  510. num_printed += m_num_columns;
  511. fprintf(stderr, "%s", suggestion.text.characters());
  512. } else {
  513. num_printed += fprintf(stderr, "%-*s", static_cast<int>(longest_suggestion_length) + 2, suggestion.text.characters());
  514. }
  515. if (m_last_shown_suggestion_was_complete && index == current_suggestion_index) {
  516. vt_apply_style({});
  517. fflush(stdout);
  518. }
  519. ++index;
  520. }
  521. m_lines_used_for_last_suggestions = lines_used;
  522. // if we filled the screen, move back the origin
  523. if (m_origin_x + lines_used >= m_num_lines) {
  524. m_origin_x = m_num_lines - lines_used;
  525. }
  526. --index;
  527. // cycle pages of suggestions
  528. if (index == current_suggestion_index)
  529. m_last_displayed_suggestion_index = index;
  530. if (m_last_displayed_suggestion_index >= m_suggestions.size() - 1)
  531. m_last_displayed_suggestion_index = 0;
  532. }
  533. if (m_suggestions.size() < 2) {
  534. // we have none, or just one suggestion
  535. // we should just commit that and continue
  536. // after it, as if it were auto-completed
  537. suggest(0, 0);
  538. m_last_shown_suggestion = String::empty();
  539. m_last_shown_suggestion_display_length = 0;
  540. m_suggestions.clear();
  541. m_times_tab_pressed = 0;
  542. m_last_displayed_suggestion_index = 0;
  543. }
  544. continue;
  545. }
  546. if (m_times_tab_pressed) {
  547. // we probably have some suggestions drawn
  548. // let's clean them up
  549. if (m_lines_used_for_last_suggestions) {
  550. vt_clear_lines(0, m_lines_used_for_last_suggestions);
  551. reposition_cursor();
  552. m_refresh_needed = true;
  553. m_lines_used_for_last_suggestions = 0;
  554. }
  555. m_last_shown_suggestion_display_length = 0;
  556. m_last_shown_suggestion = String::empty();
  557. m_last_displayed_suggestion_index = 0;
  558. m_suggestions.clear();
  559. suggest(0, 0);
  560. }
  561. m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB
  562. auto do_backspace = [&] {
  563. if (m_is_searching) {
  564. return;
  565. }
  566. if (m_cursor == 0) {
  567. fputc('\a', stdout);
  568. fflush(stdout);
  569. return;
  570. }
  571. m_buffer.remove(m_cursor - 1);
  572. --m_cursor;
  573. m_inline_search_cursor = m_cursor;
  574. // we will have to redraw :(
  575. m_refresh_needed = true;
  576. };
  577. if (ch == 8 || ch == m_termios.c_cc[VERASE]) {
  578. do_backspace();
  579. continue;
  580. }
  581. if (ch == m_termios.c_cc[VWERASE]) {
  582. bool has_seen_nonspace = false;
  583. while (m_cursor > 0) {
  584. if (isspace(m_buffer[m_cursor - 1])) {
  585. if (has_seen_nonspace)
  586. break;
  587. } else {
  588. has_seen_nonspace = true;
  589. }
  590. do_backspace();
  591. }
  592. continue;
  593. }
  594. if (ch == m_termios.c_cc[VKILL]) {
  595. for (size_t i = 0; i < m_cursor; ++i)
  596. m_buffer.remove(0);
  597. m_cursor = 0;
  598. m_refresh_needed = true;
  599. continue;
  600. }
  601. // ^L
  602. if (ch == 0xc) {
  603. printf("\033[3J\033[H\033[2J"); // Clear screen.
  604. vt_move_absolute(1, 1);
  605. m_origin_x = 1;
  606. m_origin_y = 1;
  607. m_refresh_needed = true;
  608. continue;
  609. }
  610. // ^A
  611. if (ch == 0x01) {
  612. m_cursor = 0;
  613. continue;
  614. }
  615. // ^R
  616. if (ch == 0x12) {
  617. if (m_is_searching) {
  618. // how did we get here?
  619. ASSERT_NOT_REACHED();
  620. } else {
  621. m_is_searching = true;
  622. m_search_offset = 0;
  623. m_pre_search_buffer.clear();
  624. for (auto ch : m_buffer)
  625. m_pre_search_buffer.append(ch);
  626. m_pre_search_cursor = m_cursor;
  627. m_search_editor = make<Editor>(Configuration { Configuration::Eager, m_configuration.split_mechanism }); // Has anyone seen 'Inception'?
  628. m_search_editor->on_display_refresh = [this](Editor& search_editor) {
  629. search(StringView { search_editor.buffer().data(), search_editor.buffer().size() });
  630. refresh_display();
  631. return;
  632. };
  633. // whenever the search editor gets a ^R, cycle between history entries
  634. m_search_editor->register_character_input_callback(0x12, [this](Editor& search_editor) {
  635. ++m_search_offset;
  636. search_editor.m_refresh_needed = true;
  637. return false; // Do not process this key event
  638. });
  639. // whenever the search editor gets a backspace, cycle back between history entries
  640. // unless we're at the zeroth entry, in which case, allow the deletion
  641. m_search_editor->register_character_input_callback(m_termios.c_cc[VERASE], [this](Editor& search_editor) {
  642. if (m_search_offset > 0) {
  643. --m_search_offset;
  644. search_editor.m_refresh_needed = true;
  645. return false; // Do not process this key event
  646. }
  647. return true;
  648. });
  649. // ^L - This is a source of issues, as the search editor refreshes first,
  650. // and we end up with the wrong order of prompts, so we will first refresh
  651. // ourselves, then refresh the search editor, and then tell him not to process
  652. // this event
  653. m_search_editor->register_character_input_callback(0x0c, [this](auto& search_editor) {
  654. printf("\033[3J\033[H\033[2J"); // Clear screen.
  655. // refresh our own prompt
  656. m_origin_x = 1;
  657. m_origin_y = 1;
  658. m_refresh_needed = true;
  659. refresh_display();
  660. // move the search prompt below ours
  661. // and tell it to redraw itself
  662. search_editor.m_origin_x = 2;
  663. search_editor.m_origin_y = 1;
  664. search_editor.m_refresh_needed = true;
  665. return false;
  666. });
  667. // quit without clearing the current buffer
  668. m_search_editor->register_character_input_callback('\t', [this](Editor& search_editor) {
  669. search_editor.finish();
  670. m_reset_buffer_on_search_end = false;
  671. return false;
  672. });
  673. printf("\n");
  674. fflush(stdout);
  675. auto search_prompt = "\x1b[32msearch:\x1b[0m ";
  676. auto search_string = m_search_editor->get_line(search_prompt);
  677. m_search_editor = nullptr;
  678. m_is_searching = false;
  679. m_search_offset = 0;
  680. // manually cleanup the search line
  681. reposition_cursor();
  682. vt_clear_lines(0, (search_string.length() + actual_rendered_string_length(search_prompt) + m_num_columns - 1) / m_num_columns);
  683. reposition_cursor();
  684. if (!m_reset_buffer_on_search_end || search_string.length() == 0) {
  685. // if the entry was empty, or we purposely quit without a newline,
  686. // do not return anything
  687. // instead, just end the search
  688. end_search();
  689. continue;
  690. }
  691. // return the string
  692. finish();
  693. continue;
  694. }
  695. continue;
  696. }
  697. // Normally ^D
  698. if (ch == m_termios.c_cc[VEOF]) {
  699. if (m_buffer.is_empty()) {
  700. printf("<EOF>\n");
  701. if (!m_always_refresh) // this is a little off, but it'll do for now
  702. exit(0);
  703. }
  704. continue;
  705. }
  706. // ^E
  707. if (ch == 0x05) {
  708. m_cursor = m_buffer.size();
  709. continue;
  710. }
  711. if (ch == '\n') {
  712. finish();
  713. continue;
  714. }
  715. insert(ch);
  716. }
  717. }
  718. }
  719. bool Editor::search(const StringView& phrase, bool allow_empty, bool from_beginning)
  720. {
  721. int last_matching_offset = -1;
  722. // do not search for empty strings
  723. if (allow_empty || phrase.length() > 0) {
  724. size_t search_offset = m_search_offset;
  725. for (size_t i = m_history_cursor; i > 0; --i) {
  726. auto contains = from_beginning ? m_history[i - 1].starts_with(phrase) : m_history[i - 1].contains(phrase);
  727. if (contains) {
  728. last_matching_offset = i - 1;
  729. if (search_offset == 0)
  730. break;
  731. --search_offset;
  732. }
  733. }
  734. if (last_matching_offset == -1) {
  735. fputc('\a', stdout);
  736. fflush(stdout);
  737. }
  738. }
  739. m_buffer.clear();
  740. m_cursor = 0;
  741. if (last_matching_offset >= 0) {
  742. insert(m_history[last_matching_offset]);
  743. }
  744. // always needed
  745. m_refresh_needed = true;
  746. return last_matching_offset >= 0;
  747. }
  748. void Editor::recalculate_origin()
  749. {
  750. // changing the columns can affect our origin if
  751. // the new size is smaller than our prompt, which would
  752. // cause said prompt to take up more space, so we should
  753. // compensate for that
  754. if (m_cached_prompt_length >= m_num_columns) {
  755. auto added_lines = (m_cached_prompt_length + 1) / m_num_columns - 1;
  756. m_origin_x += added_lines;
  757. }
  758. // we also need to recalculate our cursor position
  759. // but that will be calculated and applied at the next
  760. // refresh cycle
  761. }
  762. void Editor::cleanup()
  763. {
  764. vt_move_relative(0, m_pending_chars.size() - m_chars_inserted_in_the_middle);
  765. auto current_line = cursor_line();
  766. vt_clear_lines(current_line - 1, num_lines() - current_line);
  767. vt_move_relative(-num_lines() + 1, -offset_in_line() - m_old_prompt_length - m_pending_chars.size() + m_chars_inserted_in_the_middle);
  768. };
  769. void Editor::refresh_display()
  770. {
  771. auto has_cleaned_up = false;
  772. // someone changed the window size, figure it out
  773. // and react to it, we might need to redraw
  774. if (m_was_resized) {
  775. auto previous_num_columns = m_num_columns;
  776. struct winsize ws;
  777. if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) {
  778. m_num_columns = 80;
  779. m_num_lines = 25;
  780. } else {
  781. m_num_columns = ws.ws_col;
  782. m_num_lines = ws.ws_row;
  783. }
  784. if (previous_num_columns != m_num_columns) {
  785. // we need to cleanup and redo everything
  786. m_cached_prompt_valid = false;
  787. m_refresh_needed = true;
  788. swap(previous_num_columns, m_num_columns);
  789. recalculate_origin();
  790. cleanup();
  791. swap(previous_num_columns, m_num_columns);
  792. has_cleaned_up = true;
  793. }
  794. }
  795. // do not call hook on pure cursor movement
  796. if (m_cached_prompt_valid && !m_refresh_needed && m_pending_chars.size() == 0) {
  797. // probably just moving around
  798. reposition_cursor();
  799. m_cached_buffer_size = m_buffer.size();
  800. return;
  801. }
  802. if (on_display_refresh)
  803. on_display_refresh(*this);
  804. if (m_cached_prompt_valid) {
  805. if (!m_refresh_needed && m_cursor == m_buffer.size()) {
  806. // just write the characters out and continue
  807. // no need to refresh the entire line
  808. char null = 0;
  809. m_pending_chars.append(&null, 1);
  810. fputs((char*)m_pending_chars.data(), stdout);
  811. m_pending_chars.clear();
  812. m_drawn_cursor = m_cursor;
  813. m_cached_buffer_size = m_buffer.size();
  814. fflush(stdout);
  815. return;
  816. }
  817. }
  818. // ouch, reflow entire line
  819. // FIXME: handle multiline stuff
  820. if (!has_cleaned_up) {
  821. cleanup();
  822. }
  823. vt_move_absolute(m_origin_x, m_origin_y);
  824. fputs(m_new_prompt.characters(), stdout);
  825. vt_clear_to_end_of_line();
  826. HashMap<u32, Style> empty_styles {};
  827. for (size_t i = 0; i < m_buffer.size(); ++i) {
  828. auto ends = m_spans_ending.get(i).value_or(empty_styles);
  829. auto starts = m_spans_starting.get(i).value_or(empty_styles);
  830. if (ends.size()) {
  831. // go back to defaults
  832. vt_apply_style(find_applicable_style(i));
  833. }
  834. if (starts.size()) {
  835. // set new options
  836. vt_apply_style(starts.begin()->value); // apply some random style that starts here
  837. }
  838. fputc(m_buffer[i], stdout);
  839. }
  840. vt_apply_style({}); // don't bleed to EOL
  841. m_pending_chars.clear();
  842. m_refresh_needed = false;
  843. m_cached_buffer_size = m_buffer.size();
  844. m_chars_inserted_in_the_middle = 0;
  845. if (!m_cached_prompt_valid) {
  846. m_cached_prompt_valid = true;
  847. }
  848. reposition_cursor();
  849. fflush(stdout);
  850. }
  851. void Editor::reposition_cursor()
  852. {
  853. m_drawn_cursor = m_cursor;
  854. auto line = cursor_line() - 1;
  855. auto column = offset_in_line();
  856. vt_move_absolute(line + m_origin_x, column + m_origin_y);
  857. }
  858. void Editor::vt_move_absolute(u32 x, u32 y)
  859. {
  860. printf("\033[%d;%dH", x, y);
  861. fflush(stdout);
  862. }
  863. void Editor::vt_move_relative(int x, int y)
  864. {
  865. char x_op = 'A', y_op = 'D';
  866. if (x > 0)
  867. x_op = 'B';
  868. else
  869. x = -x;
  870. if (y > 0)
  871. y_op = 'C';
  872. else
  873. y = -y;
  874. if (x > 0)
  875. printf("\033[%d%c", x, x_op);
  876. if (y > 0)
  877. printf("\033[%d%c", y, y_op);
  878. }
  879. Style Editor::find_applicable_style(size_t offset) const
  880. {
  881. // walk through our styles and find one that fits in the offset
  882. for (auto& entry : m_spans_starting) {
  883. if (entry.key > offset)
  884. continue;
  885. for (auto& style_value : entry.value) {
  886. if (style_value.key <= offset)
  887. continue;
  888. return style_value.value;
  889. }
  890. }
  891. return {};
  892. }
  893. String Style::Background::to_vt_escape() const
  894. {
  895. if (m_is_rgb) {
  896. return String::format("\033[48;2;%d;%d;%dm", m_rgb_color[0], m_rgb_color[1], m_rgb_color[2]);
  897. } else {
  898. return String::format("\033[%dm", (u8)m_xterm_color + 40);
  899. }
  900. }
  901. String Style::Foreground::to_vt_escape() const
  902. {
  903. if (m_is_rgb) {
  904. return String::format("\033[38;2;%d;%d;%dm", m_rgb_color[0], m_rgb_color[1], m_rgb_color[2]);
  905. } else {
  906. return String::format("\033[%dm", (u8)m_xterm_color + 30);
  907. }
  908. }
  909. void Editor::vt_apply_style(const Style& style)
  910. {
  911. printf(
  912. "\033[%d;%d;%dm%s%s",
  913. style.bold() ? 1 : 22,
  914. style.underline() ? 4 : 24,
  915. style.italic() ? 3 : 23,
  916. style.background().to_vt_escape().characters(),
  917. style.foreground().to_vt_escape().characters());
  918. }
  919. void Editor::vt_clear_lines(size_t count_above, size_t count_below)
  920. {
  921. // go down count_below lines
  922. if (count_below > 0)
  923. printf("\033[%dB", (int)count_below);
  924. // then clear lines going upwards
  925. for (size_t i = count_below + count_above; i > 0; --i)
  926. fputs(i == 1 ? "\033[2K" : "\033[2K\033[A", stdout);
  927. }
  928. void Editor::vt_save_cursor()
  929. {
  930. fputs("\033[s", stdout);
  931. fflush(stdout);
  932. }
  933. void Editor::vt_restore_cursor()
  934. {
  935. fputs("\033[u", stdout);
  936. fflush(stdout);
  937. }
  938. void Editor::vt_clear_to_end_of_line()
  939. {
  940. fputs("\033[K", stdout);
  941. fflush(stdout);
  942. }
  943. size_t Editor::actual_rendered_string_length(const StringView& string) const
  944. {
  945. size_t length { 0 };
  946. enum VTState {
  947. Free = 1,
  948. Escape = 3,
  949. Bracket = 5,
  950. BracketArgsSemi = 7,
  951. Title = 9,
  952. } state { Free };
  953. for (size_t i = 0; i < string.length(); ++i) {
  954. auto c = string[i];
  955. switch (state) {
  956. case Free:
  957. if (c == '\x1b') {
  958. // escape
  959. state = Escape;
  960. continue;
  961. }
  962. if (c == '\r' || c == '\n') {
  963. // reset length to 0, since we either overwrite, or are on a newline
  964. length = 0;
  965. continue;
  966. }
  967. // FIXME: This will not support anything sophisticated
  968. ++length;
  969. break;
  970. case Escape:
  971. if (c == ']') {
  972. if (string.length() > i && string[i + 1] == '0')
  973. state = Title;
  974. continue;
  975. }
  976. if (c == '[') {
  977. state = Bracket;
  978. continue;
  979. }
  980. // FIXME: This does not support non-VT (aside from set-title) escapes
  981. break;
  982. case Bracket:
  983. if (isdigit(c)) {
  984. state = BracketArgsSemi;
  985. continue;
  986. }
  987. break;
  988. case BracketArgsSemi:
  989. if (c == ';') {
  990. state = Bracket;
  991. continue;
  992. }
  993. if (!isdigit(c))
  994. state = Free;
  995. break;
  996. case Title:
  997. if (c == 7)
  998. state = Free;
  999. break;
  1000. }
  1001. }
  1002. return length;
  1003. }
  1004. Vector<size_t, 2> Editor::vt_dsr()
  1005. {
  1006. char buf[16];
  1007. u32 length { 0 };
  1008. // read whatever junk there is before talking to the terminal
  1009. bool more_junk_to_read { false };
  1010. timeval timeout { 0, 0 };
  1011. fd_set readfds;
  1012. FD_ZERO(&readfds);
  1013. FD_SET(0, &readfds);
  1014. do {
  1015. more_junk_to_read = false;
  1016. (void)select(1, &readfds, nullptr, nullptr, &timeout);
  1017. if (FD_ISSET(0, &readfds)) {
  1018. auto nread = read(0, buf, 16);
  1019. (void)nread;
  1020. more_junk_to_read = true;
  1021. }
  1022. } while (more_junk_to_read);
  1023. fputs("\033[6n", stdout);
  1024. fflush(stdout);
  1025. do {
  1026. auto nread = read(0, buf + length, 16 - length);
  1027. if (nread < 0) {
  1028. if (errno == 0) {
  1029. // ????
  1030. continue;
  1031. }
  1032. dbg() << "Error while reading DSR: " << strerror(errno);
  1033. return { 1, 1 };
  1034. }
  1035. if (nread == 0) {
  1036. dbg() << "Terminal DSR issue; received no response";
  1037. return { 1, 1 };
  1038. }
  1039. length += nread;
  1040. } while (buf[length - 1] != 'R' && length < 16);
  1041. size_t x { 1 }, y { 1 };
  1042. if (buf[0] == '\033' && buf[1] == '[') {
  1043. auto parts = StringView(buf + 2, length - 3).split_view(';');
  1044. bool ok;
  1045. x = parts[0].to_int(ok);
  1046. if (!ok) {
  1047. dbg() << "Terminal DSR issue; received garbage x";
  1048. }
  1049. y = parts[1].to_int(ok);
  1050. if (!ok) {
  1051. dbg() << "Terminal DSR issue; received garbage y";
  1052. }
  1053. }
  1054. return { x, y };
  1055. }
  1056. }