LineEditor.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  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 "LineEditor.h"
  27. #include <ctype.h>
  28. #include <stdio.h>
  29. #include <sys/ioctl.h>
  30. #include <unistd.h>
  31. LineEditor::LineEditor()
  32. {
  33. struct winsize ws;
  34. if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0)
  35. m_num_columns = 80;
  36. else
  37. m_num_columns = ws.ws_col;
  38. }
  39. LineEditor::~LineEditor()
  40. {
  41. tcsetattr(0, TCSANOW, &m_default_termios);
  42. }
  43. void LineEditor::add_to_history(const String& line)
  44. {
  45. if ((m_history.size() + 1) > m_history_capacity)
  46. m_history.take_first();
  47. m_history.append(line);
  48. }
  49. void LineEditor::clear_line()
  50. {
  51. for (size_t i = 0; i < m_cursor; ++i)
  52. fputc(0x8, stdout);
  53. fputs("\033[K", stdout);
  54. fflush(stdout);
  55. m_buffer.clear();
  56. m_cursor = 0;
  57. }
  58. void LineEditor::insert(const String& string)
  59. {
  60. fputs(string.characters(), stdout);
  61. fflush(stdout);
  62. if (m_cursor == m_buffer.size()) {
  63. m_buffer.append(string.characters(), string.length());
  64. m_cursor = m_buffer.size();
  65. return;
  66. }
  67. vt_save_cursor();
  68. vt_clear_to_end_of_line();
  69. for (size_t i = m_cursor; i < m_buffer.size(); ++i)
  70. fputc(m_buffer[i], stdout);
  71. vt_restore_cursor();
  72. m_buffer.ensure_capacity(m_buffer.size() + string.length());
  73. for (size_t i = 0; i < string.length(); ++i)
  74. m_buffer.insert(m_cursor + i, string[i]);
  75. m_cursor += string.length();
  76. }
  77. void LineEditor::insert(const char ch)
  78. {
  79. putchar(ch);
  80. fflush(stdout);
  81. if (m_cursor == m_buffer.size()) {
  82. m_buffer.append(ch);
  83. m_cursor = m_buffer.size();
  84. return;
  85. }
  86. vt_save_cursor();
  87. vt_clear_to_end_of_line();
  88. for (size_t i = m_cursor; i < m_buffer.size(); ++i)
  89. fputc(m_buffer[i], stdout);
  90. vt_restore_cursor();
  91. m_buffer.insert(m_cursor, ch);
  92. ++m_cursor;
  93. }
  94. void LineEditor::on_char_input(char ch, Function<bool(LineEditor&)> callback)
  95. {
  96. if (m_key_callbacks.contains(ch)) {
  97. dbg() << "Key callback registered twice for " << ch;
  98. ASSERT_NOT_REACHED();
  99. }
  100. m_key_callbacks.set(ch, make<KeyCallback>(move(callback)));
  101. }
  102. void LineEditor::cut_mismatching_chars(String& completion, const String& other, size_t start_compare)
  103. {
  104. size_t i = start_compare;
  105. while (i < completion.length() && i < other.length() && completion[i] == other[i])
  106. ++i;
  107. completion = completion.substring(0, i);
  108. }
  109. String LineEditor::get_line(const String& prompt)
  110. {
  111. fputs(prompt.characters(), stdout);
  112. fflush(stdout);
  113. m_history_cursor = m_history.size();
  114. m_cursor = 0;
  115. for (;;) {
  116. char keybuf[16];
  117. ssize_t nread = read(0, keybuf, sizeof(keybuf));
  118. // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
  119. if (nread == 0)
  120. exit(0);
  121. if (nread < 0) {
  122. if (errno == EINTR) {
  123. if (m_was_interrupted) {
  124. m_was_interrupted = false;
  125. if (!m_buffer.is_empty())
  126. printf("^C");
  127. }
  128. if (m_was_resized) {
  129. m_was_resized = false;
  130. printf("\033[2K\r");
  131. m_buffer.clear();
  132. struct winsize ws;
  133. int rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
  134. ASSERT(rc == 0);
  135. m_num_columns = ws.ws_col;
  136. return String::empty();
  137. }
  138. m_buffer.clear();
  139. putchar('\n');
  140. return String::empty();
  141. }
  142. perror("read failed");
  143. // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
  144. exit(2);
  145. }
  146. auto do_delete = [&] {
  147. if (m_cursor == m_buffer.size()) {
  148. fputc('\a', stdout);
  149. fflush(stdout);
  150. return;
  151. }
  152. m_buffer.remove(m_cursor);
  153. fputs("\033[3~", stdout);
  154. fflush(stdout);
  155. vt_save_cursor();
  156. vt_clear_to_end_of_line();
  157. for (size_t i = m_cursor; i < m_buffer.size(); ++i)
  158. fputc(m_buffer[i], stdout);
  159. vt_restore_cursor();
  160. };
  161. for (ssize_t i = 0; i < nread; ++i) {
  162. char ch = keybuf[i];
  163. if (ch == 0)
  164. continue;
  165. switch (m_state) {
  166. case InputState::ExpectBracket:
  167. if (ch == '[') {
  168. m_state = InputState::ExpectFinal;
  169. continue;
  170. } else {
  171. m_state = InputState::Free;
  172. break;
  173. }
  174. case InputState::ExpectFinal:
  175. switch (ch) {
  176. case 'A': // up
  177. if (m_history_cursor > 0)
  178. --m_history_cursor;
  179. clear_line();
  180. if (m_history_cursor < m_history.size())
  181. insert(m_history[m_history_cursor]);
  182. m_state = InputState::Free;
  183. continue;
  184. case 'B': // down
  185. if (m_history_cursor < m_history.size())
  186. ++m_history_cursor;
  187. clear_line();
  188. if (m_history_cursor < m_history.size())
  189. insert(m_history[m_history_cursor]);
  190. m_state = InputState::Free;
  191. continue;
  192. case 'D': // left
  193. if (m_cursor > 0) {
  194. --m_cursor;
  195. fputs("\033[D", stdout);
  196. fflush(stdout);
  197. }
  198. m_state = InputState::Free;
  199. continue;
  200. case 'C': // right
  201. if (m_cursor < m_buffer.size()) {
  202. ++m_cursor;
  203. fputs("\033[C", stdout);
  204. fflush(stdout);
  205. }
  206. m_state = InputState::Free;
  207. continue;
  208. case 'H':
  209. if (m_cursor > 0) {
  210. fprintf(stdout, "\033[%zuD", m_cursor);
  211. fflush(stdout);
  212. m_cursor = 0;
  213. }
  214. m_state = InputState::Free;
  215. continue;
  216. case 'F':
  217. if (m_cursor < m_buffer.size()) {
  218. fprintf(stdout, "\033[%zuC", m_buffer.size() - m_cursor);
  219. fflush(stdout);
  220. m_cursor = m_buffer.size();
  221. }
  222. m_state = InputState::Free;
  223. continue;
  224. case '3':
  225. do_delete();
  226. m_state = InputState::ExpectTerminator;
  227. continue;
  228. default:
  229. dbgprintf("Shell: Unhandled final: %b (%c)\n", ch, ch);
  230. m_state = InputState::Free;
  231. continue;
  232. }
  233. break;
  234. case InputState::ExpectTerminator:
  235. m_state = InputState::Free;
  236. continue;
  237. case InputState::Free:
  238. if (ch == 27) {
  239. m_state = InputState::ExpectBracket;
  240. continue;
  241. }
  242. break;
  243. }
  244. auto cb = m_key_callbacks.get(ch);
  245. if (cb.has_value()) {
  246. if (!cb.value()->callback(*this)) {
  247. continue;
  248. }
  249. }
  250. if (ch == '\t') {
  251. if (!on_tab_complete_first_token || !on_tab_complete_other_token)
  252. continue;
  253. bool is_empty_token = m_cursor == 0 || m_buffer[m_cursor - 1] == ' ';
  254. m_times_tab_pressed++;
  255. int token_start = m_cursor - 1;
  256. if (!is_empty_token) {
  257. while (token_start >= 0 && m_buffer[token_start] != ' ')
  258. --token_start;
  259. ++token_start;
  260. }
  261. bool is_first_token = true;
  262. for (int i = token_start - 1; i >= 0; --i) {
  263. if (m_buffer[i] != ' ') {
  264. is_first_token = false;
  265. break;
  266. }
  267. }
  268. String token = is_empty_token ? String() : String(&m_buffer[token_start], m_cursor - token_start);
  269. Vector<String> suggestions;
  270. if (is_first_token)
  271. suggestions = on_tab_complete_first_token(token);
  272. else
  273. suggestions = on_tab_complete_other_token(token);
  274. if (m_times_tab_pressed > 1 && !suggestions.is_empty()) {
  275. size_t longest_suggestion_length = 0;
  276. for (auto& suggestion : suggestions)
  277. longest_suggestion_length = max(longest_suggestion_length, suggestion.length());
  278. size_t num_printed = 0;
  279. putchar('\n');
  280. for (auto& suggestion : suggestions) {
  281. size_t next_column = num_printed + suggestion.length() + longest_suggestion_length + 2;
  282. if (next_column > m_num_columns) {
  283. putchar('\n');
  284. num_printed = 0;
  285. }
  286. num_printed += fprintf(stderr, "%-*s", static_cast<int>(longest_suggestion_length) + 2, suggestion.characters());
  287. }
  288. putchar('\n');
  289. write(STDOUT_FILENO, prompt.characters(), prompt.length());
  290. write(STDOUT_FILENO, m_buffer.data(), m_cursor);
  291. // Prevent not printing characters in case the user has moved the cursor and then pressed tab
  292. write(STDOUT_FILENO, m_buffer.data() + m_cursor, m_buffer.size() - m_cursor);
  293. m_cursor = m_buffer.size(); // bash doesn't do this, but it makes a little bit more sense
  294. }
  295. suggestions.clear_with_capacity();
  296. continue;
  297. }
  298. m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB
  299. auto do_backspace = [&] {
  300. if (m_cursor == 0) {
  301. fputc('\a', stdout);
  302. fflush(stdout);
  303. return;
  304. }
  305. m_buffer.remove(m_cursor - 1);
  306. --m_cursor;
  307. putchar(8);
  308. vt_save_cursor();
  309. vt_clear_to_end_of_line();
  310. for (size_t i = m_cursor; i < m_buffer.size(); ++i)
  311. fputc(m_buffer[i], stdout);
  312. vt_restore_cursor();
  313. };
  314. if (ch == 8 || ch == m_termios.c_cc[VERASE]) {
  315. do_backspace();
  316. continue;
  317. }
  318. if (ch == m_termios.c_cc[VWERASE]) {
  319. bool has_seen_nonspace = false;
  320. while (m_cursor > 0) {
  321. if (isspace(m_buffer[m_cursor - 1])) {
  322. if (has_seen_nonspace)
  323. break;
  324. } else {
  325. has_seen_nonspace = true;
  326. }
  327. do_backspace();
  328. }
  329. continue;
  330. }
  331. if (ch == m_termios.c_cc[VKILL]) {
  332. while (m_cursor > 0)
  333. do_backspace();
  334. continue;
  335. }
  336. if (ch == 0xc) { // ^L
  337. printf("\033[3J\033[H\033[2J"); // Clear screen.
  338. fputs(prompt.characters(), stdout);
  339. for (size_t i = 0; i < m_buffer.size(); ++i)
  340. fputc(m_buffer[i], stdout);
  341. if (m_cursor < m_buffer.size())
  342. printf("\033[%zuD", m_buffer.size() - m_cursor); // Move cursor N steps left.
  343. fflush(stdout);
  344. continue;
  345. }
  346. if (ch == 0x01) { // ^A
  347. if (m_cursor > 0) {
  348. printf("\033[%zuD", m_cursor);
  349. fflush(stdout);
  350. m_cursor = 0;
  351. }
  352. continue;
  353. }
  354. if (ch == m_termios.c_cc[VEOF]) { // Normally ^D
  355. if (m_buffer.is_empty()) {
  356. printf("<EOF>\n");
  357. exit(0);
  358. }
  359. continue;
  360. }
  361. if (ch == 0x05) { // ^E
  362. if (m_cursor < m_buffer.size()) {
  363. printf("\033[%zuC", m_buffer.size() - m_cursor);
  364. fflush(stdout);
  365. m_cursor = m_buffer.size();
  366. }
  367. continue;
  368. }
  369. if (ch == '\n') {
  370. putchar('\n');
  371. fflush(stdout);
  372. auto string = String::copy(m_buffer);
  373. m_buffer.clear();
  374. return string;
  375. }
  376. insert(ch);
  377. }
  378. }
  379. }
  380. void LineEditor::vt_save_cursor()
  381. {
  382. fputs("\033[s", stdout);
  383. fflush(stdout);
  384. }
  385. void LineEditor::vt_restore_cursor()
  386. {
  387. fputs("\033[u", stdout);
  388. fflush(stdout);
  389. }
  390. void LineEditor::vt_clear_to_end_of_line()
  391. {
  392. fputs("\033[K", stdout);
  393. fflush(stdout);
  394. }