LineEditor.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  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 "GlobalState.h"
  28. #include <ctype.h>
  29. #include <stdio.h>
  30. #include <sys/ioctl.h>
  31. #include <unistd.h>
  32. LineEditor::LineEditor()
  33. {
  34. struct winsize ws;
  35. if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0)
  36. m_num_columns = 80;
  37. else
  38. m_num_columns = ws.ws_col;
  39. }
  40. LineEditor::~LineEditor()
  41. {
  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 == (size_t)m_buffer.size()) {
  63. m_buffer.append(string.characters(), (int)string.length());
  64. m_cursor = (size_t)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 < (size_t)m_buffer.size(); ++i)
  70. fputc(m_buffer[(int)i], stdout);
  71. vt_restore_cursor();
  72. m_buffer.ensure_capacity(m_buffer.size() + (int)string.length());
  73. for (size_t i = 0; i < string.length(); ++i)
  74. m_buffer.insert((int)m_cursor + (int)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 == (size_t)m_buffer.size()) {
  82. m_buffer.append(ch);
  83. m_cursor = (size_t)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 < (size_t)m_buffer.size(); ++i)
  89. fputc(m_buffer[(int)i], stdout);
  90. vt_restore_cursor();
  91. m_buffer.insert((int)m_cursor, ch);
  92. ++m_cursor;
  93. }
  94. void LineEditor::cache_path()
  95. {
  96. if (!m_path.is_empty())
  97. m_path.clear_with_capacity();
  98. String path = getenv("PATH");
  99. if (path.is_empty())
  100. return;
  101. auto directories = path.split(':');
  102. for (const auto& directory : directories) {
  103. Core::DirIterator programs(directory.characters(), Core::DirIterator::SkipDots);
  104. while (programs.has_next()) {
  105. auto program = programs.next_path();
  106. String program_path = String::format("%s/%s", directory.characters(), program.characters());
  107. struct stat program_status;
  108. int stat_error = stat(program_path.characters(), &program_status);
  109. if (!stat_error && (program_status.st_mode & S_IXUSR))
  110. m_path.append(program.characters());
  111. }
  112. }
  113. quick_sort(m_path.begin(), m_path.end(), AK::is_less_than<String>);
  114. }
  115. void LineEditor::cut_mismatching_chars(String& completion, const String& other, size_t start_compare)
  116. {
  117. size_t i = start_compare;
  118. while (i < completion.length() && i < other.length() && completion[i] == other[i])
  119. ++i;
  120. completion = completion.substring(0, i);
  121. }
  122. // Function returns Vector<String> as assignment is made from return value at callsite
  123. // (instead of StringView)
  124. Vector<String> LineEditor::tab_complete_first_token(const String& token)
  125. {
  126. auto match = binary_search(m_path.data(), m_path.size(), token, [](const String& token, const String& program) -> int {
  127. return strncmp(token.characters(), program.characters(), token.length());
  128. });
  129. if (!match)
  130. return Vector<String>();
  131. String completion = *match;
  132. Vector<String> suggestions;
  133. // Now that we have a program name starting with our token, we look at
  134. // other program names starting with our token and cut off any mismatching
  135. // characters.
  136. bool seen_others = false;
  137. int index = match - m_path.data();
  138. for (int i = index - 1; i >= 0 && m_path[i].starts_with(token); --i) {
  139. suggestions.append(m_path[i]);
  140. cut_mismatching_chars(completion, m_path[i], token.length());
  141. seen_others = true;
  142. }
  143. for (size_t i = index + 1; i < m_path.size() && m_path[i].starts_with(token); ++i) {
  144. cut_mismatching_chars(completion, m_path[i], token.length());
  145. suggestions.append(m_path[i]);
  146. seen_others = true;
  147. }
  148. suggestions.append(m_path[index]);
  149. // If we have characters to add, add them.
  150. if (completion.length() > token.length())
  151. insert(completion.substring(token.length(), completion.length() - token.length()));
  152. // If we have a single match, we add a space, unless we already have one.
  153. if (!seen_others && (m_cursor == (size_t)m_buffer.size() || m_buffer[(int)m_cursor] != ' '))
  154. insert(' ');
  155. return suggestions;
  156. }
  157. Vector<String> LineEditor::tab_complete_other_token(String& token)
  158. {
  159. String path;
  160. Vector<String> suggestions;
  161. int last_slash = (int)token.length() - 1;
  162. while (last_slash >= 0 && token[last_slash] != '/')
  163. --last_slash;
  164. if (last_slash >= 0) {
  165. // Split on the last slash. We'll use the first part as the directory
  166. // to search and the second part as the token to complete.
  167. path = token.substring(0, (size_t)last_slash + 1);
  168. if (path[0] != '/')
  169. path = String::format("%s/%s", g.cwd.characters(), path.characters());
  170. path = canonicalized_path(path);
  171. token = token.substring((size_t)last_slash + 1, token.length() - (size_t)last_slash - 1);
  172. } else {
  173. // We have no slashes, so the directory to search is the current
  174. // directory and the token to complete is just the original token.
  175. path = g.cwd;
  176. }
  177. // This is a bit naughty, but necessary without reordering the loop
  178. // below. The loop terminates early, meaning that
  179. // the suggestions list is incomplete.
  180. // We only do this if the token is empty though.
  181. if (token.is_empty()) {
  182. Core::DirIterator suggested_files(path, Core::DirIterator::SkipDots);
  183. while (suggested_files.has_next()) {
  184. suggestions.append(suggested_files.next_path());
  185. }
  186. }
  187. String completion;
  188. bool seen_others = false;
  189. Core::DirIterator files(path, Core::DirIterator::SkipDots);
  190. while (files.has_next()) {
  191. auto file = files.next_path();
  192. if (file.starts_with(token)) {
  193. if (!token.is_empty())
  194. suggestions.append(file);
  195. if (completion.is_empty()) {
  196. completion = file; // Will only be set once.
  197. } else {
  198. cut_mismatching_chars(completion, file, token.length());
  199. if (completion.is_empty()) // We cut everything off!
  200. return suggestions;
  201. seen_others = true;
  202. }
  203. }
  204. }
  205. if (completion.is_empty())
  206. return suggestions;
  207. // If we have characters to add, add them.
  208. if (completion.length() > token.length())
  209. insert(completion.substring(token.length(), completion.length() - token.length()));
  210. // If we have a single match and it's a directory, we add a slash. If it's
  211. // a regular file, we add a space, unless we already have one.
  212. if (!seen_others) {
  213. String file_path = String::format("%s/%s", path.characters(), completion.characters());
  214. struct stat program_status;
  215. int stat_error = stat(file_path.characters(), &program_status);
  216. if (!stat_error) {
  217. if (S_ISDIR(program_status.st_mode))
  218. insert('/');
  219. else if (m_cursor == (size_t)m_buffer.size() || m_buffer[(int)m_cursor] != ' ')
  220. insert(' ');
  221. }
  222. }
  223. return {}; // Return an empty vector
  224. }
  225. String LineEditor::get_line(const String& prompt)
  226. {
  227. fputs(prompt.characters(), stdout);
  228. fflush(stdout);
  229. m_history_cursor = m_history.size();
  230. m_cursor = 0;
  231. for (;;) {
  232. char keybuf[16];
  233. ssize_t nread = read(0, keybuf, sizeof(keybuf));
  234. // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
  235. if (nread == 0)
  236. exit(0);
  237. if (nread < 0) {
  238. if (errno == EINTR) {
  239. if (g.was_interrupted) {
  240. g.was_interrupted = false;
  241. if (!m_buffer.is_empty())
  242. printf("^C");
  243. }
  244. if (g.was_resized) {
  245. g.was_resized = false;
  246. printf("\033[2K\r");
  247. m_buffer.clear();
  248. struct winsize ws;
  249. int rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
  250. ASSERT(rc == 0);
  251. m_num_columns = ws.ws_col;
  252. return String::empty();
  253. }
  254. m_buffer.clear();
  255. putchar('\n');
  256. return String::empty();
  257. }
  258. perror("read failed");
  259. // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
  260. exit(2);
  261. }
  262. auto do_delete = [&] {
  263. if (m_cursor == (size_t)m_buffer.size()) {
  264. fputc('\a', stdout);
  265. fflush(stdout);
  266. return;
  267. }
  268. m_buffer.remove((int)m_cursor - 1);
  269. fputs("\033[3~", stdout);
  270. fflush(stdout);
  271. vt_save_cursor();
  272. vt_clear_to_end_of_line();
  273. for (size_t i = m_cursor; i < (size_t)m_buffer.size(); ++i)
  274. fputc(m_buffer[i], stdout);
  275. vt_restore_cursor();
  276. };
  277. for (ssize_t i = 0; i < nread; ++i) {
  278. char ch = keybuf[i];
  279. if (ch == 0)
  280. continue;
  281. switch (m_state) {
  282. case InputState::ExpectBracket:
  283. if (ch == '[') {
  284. m_state = InputState::ExpectFinal;
  285. continue;
  286. } else {
  287. m_state = InputState::Free;
  288. break;
  289. }
  290. case InputState::ExpectFinal:
  291. switch (ch) {
  292. case 'A': // up
  293. if (m_history_cursor > 0)
  294. --m_history_cursor;
  295. clear_line();
  296. if (m_history_cursor < m_history.size())
  297. insert(m_history[m_history_cursor]);
  298. m_state = InputState::Free;
  299. continue;
  300. case 'B': // down
  301. if (m_history_cursor < m_history.size())
  302. ++m_history_cursor;
  303. clear_line();
  304. if (m_history_cursor < m_history.size())
  305. insert(m_history[m_history_cursor]);
  306. m_state = InputState::Free;
  307. continue;
  308. case 'D': // left
  309. if (m_cursor > 0) {
  310. --m_cursor;
  311. fputs("\033[D", stdout);
  312. fflush(stdout);
  313. }
  314. m_state = InputState::Free;
  315. continue;
  316. case 'C': // right
  317. if (m_cursor < (size_t)m_buffer.size()) {
  318. ++m_cursor;
  319. fputs("\033[C", stdout);
  320. fflush(stdout);
  321. }
  322. m_state = InputState::Free;
  323. continue;
  324. case 'H':
  325. if (m_cursor > 0) {
  326. fprintf(stdout, "\033[%zuD", m_cursor);
  327. fflush(stdout);
  328. m_cursor = 0;
  329. }
  330. m_state = InputState::Free;
  331. continue;
  332. case 'F':
  333. if (m_cursor < (size_t)m_buffer.size()) {
  334. fprintf(stdout, "\033[%zuC", (size_t)m_buffer.size() - m_cursor);
  335. fflush(stdout);
  336. m_cursor = (size_t)m_buffer.size();
  337. }
  338. m_state = InputState::Free;
  339. continue;
  340. case '3':
  341. do_delete();
  342. m_state = InputState::ExpectTerminator;
  343. continue;
  344. default:
  345. dbgprintf("Shell: Unhandled final: %b (%c)\n", ch, ch);
  346. m_state = InputState::Free;
  347. continue;
  348. }
  349. break;
  350. case InputState::ExpectTerminator:
  351. m_state = InputState::Free;
  352. continue;
  353. case InputState::Free:
  354. if (ch == 27) {
  355. m_state = InputState::ExpectBracket;
  356. continue;
  357. }
  358. break;
  359. }
  360. if (ch == '\t') {
  361. bool is_empty_token = m_cursor == 0 || m_buffer[(int)m_cursor - 1] == ' ';
  362. m_times_tab_pressed++;
  363. int token_start = (int)m_cursor - 1;
  364. if (!is_empty_token) {
  365. while (token_start >= 0 && m_buffer[token_start] != ' ')
  366. --token_start;
  367. ++token_start;
  368. }
  369. bool is_first_token = true;
  370. for (int i = token_start - 1; i >= 0; --i) {
  371. if (m_buffer[i] != ' ') {
  372. is_first_token = false;
  373. break;
  374. }
  375. }
  376. String token = is_empty_token ? String() : String(&m_buffer[token_start], m_cursor - (size_t)token_start);
  377. Vector<String> suggestions;
  378. if (is_first_token)
  379. suggestions = tab_complete_first_token(token);
  380. else
  381. suggestions = tab_complete_other_token(token);
  382. if (m_times_tab_pressed > 1 && !suggestions.is_empty()) {
  383. size_t longest_suggestion_length = 0;
  384. for (auto& suggestion : suggestions)
  385. longest_suggestion_length = max(longest_suggestion_length, suggestion.length());
  386. size_t num_printed = 0;
  387. putchar('\n');
  388. for (auto& suggestion : suggestions) {
  389. size_t next_column = num_printed + suggestion.length() + longest_suggestion_length + 2;
  390. if (next_column > m_num_columns) {
  391. putchar('\n');
  392. num_printed = 0;
  393. }
  394. num_printed += fprintf(stderr, "%-*s", static_cast<int>(longest_suggestion_length) + 2, suggestion.characters());
  395. }
  396. putchar('\n');
  397. write(STDOUT_FILENO, prompt.characters(), prompt.length());
  398. write(STDOUT_FILENO, m_buffer.data(), m_cursor);
  399. // Prevent not printing characters in case the user has moved the cursor and then pressed tab
  400. write(STDOUT_FILENO, m_buffer.data() + m_cursor, m_buffer.size() - m_cursor);
  401. m_cursor = m_buffer.size(); // bash doesn't do this, but it makes a little bit more sense
  402. }
  403. suggestions.clear_with_capacity();
  404. continue;
  405. }
  406. m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB
  407. auto do_backspace = [&] {
  408. if (m_cursor == 0) {
  409. fputc('\a', stdout);
  410. fflush(stdout);
  411. return;
  412. }
  413. m_buffer.remove((int)m_cursor - 1);
  414. --m_cursor;
  415. putchar(8);
  416. vt_save_cursor();
  417. vt_clear_to_end_of_line();
  418. for (size_t i = m_cursor; i < (size_t)m_buffer.size(); ++i)
  419. fputc(m_buffer[(int)i], stdout);
  420. vt_restore_cursor();
  421. };
  422. if (ch == 8 || ch == g.termios.c_cc[VERASE]) {
  423. do_backspace();
  424. continue;
  425. }
  426. if (ch == g.termios.c_cc[VWERASE]) {
  427. bool has_seen_nonspace = false;
  428. while (m_cursor > 0) {
  429. if (isspace(m_buffer[(int)m_cursor - 1])) {
  430. if (has_seen_nonspace)
  431. break;
  432. } else {
  433. has_seen_nonspace = true;
  434. }
  435. do_backspace();
  436. }
  437. continue;
  438. }
  439. if (ch == g.termios.c_cc[VKILL]) {
  440. while (m_cursor > 0)
  441. do_backspace();
  442. continue;
  443. }
  444. if (ch == 0xc) { // ^L
  445. printf("\033[3J\033[H\033[2J"); // Clear screen.
  446. fputs(prompt.characters(), stdout);
  447. for (size_t i = 0; i < m_buffer.size(); ++i)
  448. fputc(m_buffer[i], stdout);
  449. if (m_cursor < (size_t)m_buffer.size())
  450. printf("\033[%zuD", (size_t)m_buffer.size() - m_cursor); // Move cursor N steps left.
  451. fflush(stdout);
  452. continue;
  453. }
  454. if (ch == 0x01) { // ^A
  455. if (m_cursor > 0) {
  456. printf("\033[%zuD", m_cursor);
  457. fflush(stdout);
  458. m_cursor = 0;
  459. }
  460. continue;
  461. }
  462. if (ch == g.termios.c_cc[VEOF]) { // Normally ^D
  463. if (m_buffer.is_empty()) {
  464. printf("<EOF>\n");
  465. exit(0);
  466. }
  467. continue;
  468. }
  469. if (ch == 0x05) { // ^E
  470. if (m_cursor < (size_t)m_buffer.size()) {
  471. printf("\033[%zuC", (size_t)m_buffer.size() - m_cursor);
  472. fflush(stdout);
  473. m_cursor = (size_t)m_buffer.size();
  474. }
  475. continue;
  476. }
  477. if (ch == '\n') {
  478. putchar('\n');
  479. fflush(stdout);
  480. auto string = String::copy(m_buffer);
  481. m_buffer.clear();
  482. return string;
  483. }
  484. insert(ch);
  485. }
  486. }
  487. }
  488. void LineEditor::vt_save_cursor()
  489. {
  490. fputs("\033[s", stdout);
  491. fflush(stdout);
  492. }
  493. void LineEditor::vt_restore_cursor()
  494. {
  495. fputs("\033[u", stdout);
  496. fflush(stdout);
  497. }
  498. void LineEditor::vt_clear_to_end_of_line()
  499. {
  500. fputs("\033[K", stdout);
  501. fflush(stdout);
  502. }