LineEditor.cpp 18 KB

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