1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 14:37:46 +00:00

LibLine: Make the DSR response parser a bit more robust

At the cost of using more read() syscalls, process the DSR response
character-by-character.
This avoids blocking forever waiting for an 'R' that will never come :P
This commit is contained in:
AnotherTest 2021-03-16 22:48:34 +03:30 committed by Andreas Kling
parent fe97aec8c3
commit 80d21f120f

View file

@ -1678,7 +1678,6 @@ Editor::VTState Editor::actual_rendered_string_length_step(StringMetrics& metric
Vector<size_t, 2> Editor::vt_dsr() Vector<size_t, 2> Editor::vt_dsr()
{ {
char buf[16]; char buf[16];
u32 length { 0 };
// Read whatever junk there is before talking to the terminal // Read whatever junk there is before talking to the terminal
// and insert them later when we're reading user input. // and insert them later when we're reading user input.
@ -1713,8 +1712,25 @@ Vector<size_t, 2> Editor::vt_dsr()
fputs("\033[6n", stderr); fputs("\033[6n", stderr);
fflush(stderr); fflush(stderr);
// Parse the DSR response
// it should be of the form .*\e[\d+;\d+R.*
// Anything not part of the response is just added to the incomplete data.
enum {
Free,
SawEsc,
SawBracket,
InFirstCoordinate,
SawSemicolon,
InSecondCoordinate,
SawR,
} state { Free };
auto has_error = false;
Vector<char, 4> coordinate_buffer;
size_t row { 1 }, col { 1 };
do { do {
auto nread = read(0, buf + length, 16 - length); char c;
auto nread = read(0, &c, 1);
if (nread < 0) { if (nread < 0) {
if (errno == 0 || errno == EINTR) { if (errno == 0 || errno == EINTR) {
// ???? // ????
@ -1731,25 +1747,80 @@ Vector<size_t, 2> Editor::vt_dsr()
dbgln("Terminal DSR issue; received no response"); dbgln("Terminal DSR issue; received no response");
return { 1, 1 }; return { 1, 1 };
} }
length += nread;
} while (buf[length - 1] != 'R' && length < 16);
size_t row { 1 }, col { 1 };
if (buf[0] == '\033' && buf[1] == '[') { switch (state) {
auto parts = StringView(buf + 2, length - 3).split_view(';'); case Free:
auto row_opt = parts[0].to_int(); if (c == '\x1b') {
if (!row_opt.has_value()) { state = SawEsc;
dbgln("Terminal DSR issue; received garbage row"); continue;
} else { }
row = row_opt.value(); m_incomplete_data.append(c);
continue;
case SawEsc:
if (c == '[') {
state = SawBracket;
continue;
}
m_incomplete_data.append(c);
continue;
case SawBracket:
if (isdigit(c)) {
state = InFirstCoordinate;
coordinate_buffer.append(c);
continue;
}
m_incomplete_data.append(c);
continue;
case InFirstCoordinate:
if (isdigit(c)) {
coordinate_buffer.append(c);
continue;
}
if (c == ';') {
auto maybe_row = StringView { coordinate_buffer.data(), coordinate_buffer.size() }.to_uint();
if (!maybe_row.has_value())
has_error = true;
row = maybe_row.value_or(1u);
coordinate_buffer.clear_with_capacity();
state = SawSemicolon;
continue;
}
m_incomplete_data.append(c);
continue;
case SawSemicolon:
if (isdigit(c)) {
state = InSecondCoordinate;
coordinate_buffer.append(c);
continue;
}
m_incomplete_data.append(c);
continue;
case InSecondCoordinate:
if (isdigit(c)) {
coordinate_buffer.append(c);
continue;
}
if (c == 'R') {
auto maybe_column = StringView { coordinate_buffer.data(), coordinate_buffer.size() }.to_uint();
if (!maybe_column.has_value())
has_error = true;
col = maybe_column.value_or(1u);
coordinate_buffer.clear_with_capacity();
state = SawR;
continue;
}
m_incomplete_data.append(c);
continue;
case SawR:
m_incomplete_data.append(c);
continue;
default:
VERIFY_NOT_REACHED();
} }
auto col_opt = parts[1].to_int(); } while (state != SawR);
if (!col_opt.has_value()) {
dbgln("Terminal DSR issue; received garbage col"); if (has_error)
} else { dbgln("Terminal DSR issue, couldn't parse DSR response");
col = col_opt.value();
}
}
return { row, col }; return { row, col };
} }