#include "LineEditor.h" #include "GlobalState.h" #include #include #include LineEditor::LineEditor() { } LineEditor::~LineEditor() { } void LineEditor::add_to_history(const String& line) { if ((m_history.size() + 1) > m_history_capacity) m_history.take_first(); m_history.append(line); } void LineEditor::clear_line() { for (int i = 0; i < m_cursor; ++i) fputc(0x8, stdout); fputs("\033[K", stdout); fflush(stdout); m_buffer.clear(); m_cursor = 0; } void LineEditor::append(const String& string) { m_buffer.append(string.characters(), string.length()); fputs(string.characters(), stdout); fflush(stdout); m_cursor = m_buffer.size(); } String LineEditor::get_line() { m_history_cursor = m_history.size(); m_cursor = 0; for (;;) { char keybuf[16]; ssize_t nread = read(0, keybuf, sizeof(keybuf)); // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead. if (nread == 0) exit(0); if (nread < 0) { if (errno == EINTR) { if (g.was_interrupted) { g.was_interrupted = false; if (!m_buffer.is_empty()) printf("^C"); } if (g.was_resized) { g.was_resized = false; printf("\033[2K\r"); m_buffer.clear(); return String::empty(); } m_buffer.clear(); putchar('\n'); return String::empty(); } perror("read failed"); // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead. exit(2); } for (ssize_t i = 0; i < nread; ++i) { char ch = keybuf[i]; if (ch == 0) continue; switch (m_state) { case InputState::ExpectBracket: if (ch == '[') { m_state = InputState::ExpectFinal; continue; } else { m_state = InputState::Free; break; } case InputState::ExpectFinal: switch (ch) { case 'A': // up if (m_history_cursor > 0) --m_history_cursor; clear_line(); if (m_history_cursor < m_history.size()) append(m_history[m_history_cursor]); m_state = InputState::Free; continue; case 'B': // down if (m_history_cursor < m_history.size()) ++m_history_cursor; clear_line(); if (m_history_cursor < m_history.size()) append(m_history[m_history_cursor]); m_state = InputState::Free; continue; case 'D': // left if (m_cursor > 0) { --m_cursor; fputs("\033[D", stdout); fflush(stdout); } m_state = InputState::Free; continue; case 'C': // right if (m_cursor < m_buffer.size()) { ++m_cursor; fputs("\033[C", stdout); fflush(stdout); } m_state = InputState::Free; continue; case 'H': if (m_cursor > 0) { fprintf(stdout, "\033[%dD", m_cursor); fflush(stdout); m_cursor = 0; } m_state = InputState::Free; continue; case 'F': if (m_cursor < m_buffer.size()) { fprintf(stdout, "\033[%dC", m_buffer.size() - m_cursor); fflush(stdout); m_cursor = m_buffer.size(); } m_state = InputState::Free; continue; default: dbgprintf("Shell: Unhandled final: %b (%c)\n", ch, ch); m_state = InputState::Free; continue; } break; case InputState::Free: if (ch == 27) { m_state = InputState::ExpectBracket; continue; } break; } if (ch == '\t') { // FIXME: Implement tab-completion. continue; } auto do_backspace = [&] { if (m_cursor == 0) { fputc('\a', stdout); fflush(stdout); return; } m_buffer.remove(m_cursor - 1); --m_cursor; putchar(8); vt_save_cursor(); vt_clear_to_end_of_line(); for (int i = m_cursor; i < m_buffer.size(); ++i) fputc(m_buffer[i], stdout); vt_restore_cursor(); }; if (ch == 8 || ch == g.termios.c_cc[VERASE]) { do_backspace(); continue; } if (ch == g.termios.c_cc[VWERASE]) { bool has_seen_nonspace = false; while (m_cursor > 0) { if (isspace(m_buffer[m_cursor - 1])) { if (has_seen_nonspace) break; } else { has_seen_nonspace = true; } do_backspace(); } continue; } if (ch == g.termios.c_cc[VKILL]) { while (m_cursor > 0) do_backspace(); continue; } putchar(ch); fflush(stdout); if (ch == '\n') { auto string = String::copy(m_buffer); m_buffer.clear(); return string; } if (m_cursor == m_buffer.size()) { m_buffer.append(ch); ++m_cursor; continue; } vt_save_cursor(); vt_clear_to_end_of_line(); for (int i = m_cursor; i < m_buffer.size(); ++i) fputc(m_buffer[i], stdout); vt_restore_cursor(); m_buffer.insert(m_cursor, move(ch)); ++m_cursor; } } } void LineEditor::vt_save_cursor() { fputs("\033[s", stdout); fflush(stdout); } void LineEditor::vt_restore_cursor() { fputs("\033[u", stdout); fflush(stdout); } void LineEditor::vt_clear_to_end_of_line() { fputs("\033[K", stdout); fflush(stdout); }