mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-23 18:02:05 -05:00
fc4022d173
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.
233 lines
7 KiB
C++
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);
|
|
}
|