serenity/Shell/LineEditor.cpp
Andreas Kling fc4022d173 Shell: Handle SIGWINCH to get a nice behavior when resizing.
When resizing the terminal, we now clear the entire current line and reset
the shell's LineEditor input state. This makes it look and feel kinda the
same as xterm.

Fixes #286.
2019-07-08 19:06:06 +02:00

233 lines
7 KiB
C++

#include "LineEditor.h"
#include "GlobalState.h"
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
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);
}