foot/vt.c

353 lines
10 KiB
C
Raw Normal View History

#include "vt.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define LOG_MODULE "vt"
#define LOG_ENABLE_DBG 1
#include "log.h"
#include "csi.h"
#include "osc.h"
#include "grid.h"
/* https://vt100.net/emu/dec_ansi_parser */
enum state {
STATE_SAME, /* For state_transition */
STATE_ANYWHERE,
STATE_ESCAPE,
STATE_GROUND,
STATE_CSIENTRY,
STATE_CSIPARAM,
STATE_OSCSTRING,
STATE_UTF8,
};
enum action {
ACTION_NONE, /* For state_transition */
ACTION_IGNORE,
ACTION_CLEAR,
ACTION_EXECUTE,
ACTION_PRINT,
ACTION_PARAM,
ACTION_COLLECT,
ACTION_CSIDISPATCH,
ACTION_OSCSTART,
ACTION_OSCEND,
ACTION_OSCPUT,
ACTION_UTF8,
};
static const char *const state_names[] = {
[STATE_SAME] = "no change",
[STATE_ANYWHERE] = "anywhere",
[STATE_ESCAPE] = "escape",
[STATE_GROUND] = "ground",
[STATE_CSIENTRY] = "CSI entry",
[STATE_CSIPARAM] = "CSI param",
[STATE_OSCSTRING] = "OSC string",
[STATE_UTF8] = "UTF-8",
};
static const char *const action_names[] __attribute__((unused)) = {
[ACTION_NONE] = "no action",
[ACTION_IGNORE] = "ignore",
[ACTION_CLEAR] = "clear",
[ACTION_EXECUTE] = "execute",
[ACTION_PRINT] = "print",
[ACTION_PARAM] = "param",
[ACTION_COLLECT] = "collect",
[ACTION_CSIDISPATCH] = "CSI dispatch",
[ACTION_OSCSTART] = "OSC start",
[ACTION_OSCEND] = "OSC end",
[ACTION_OSCPUT] = "OSC put",
[ACTION_UTF8] = "begin UTF-8",
};
struct state_transition {
enum action action;
enum state state;
};
static const struct state_transition state_anywhere[256] = {
[0x1b] = {.state = STATE_ESCAPE},
};
static const struct state_transition state_ground[256] = {
[0x00 ... 0x17] = {.action = ACTION_EXECUTE},
[0x20 ... 0x7f] = {.action = ACTION_PRINT},
[0xc2 ... 0xdf] = {.action = ACTION_UTF8, .state = STATE_UTF8}, /* 2 chars */
[0xe0 ... 0xef] = {.action = ACTION_UTF8, .state = STATE_UTF8}, /* 3 chars */
[0xf0 ... 0xf4] = {.action = ACTION_UTF8, .state = STATE_UTF8}, /* 4 chars */
};
static const struct state_transition state_escape[256] = {
[0x30 ... 0x4f] = {.action = ACTION_CSIDISPATCH, .state = STATE_GROUND},
[0x5b] = {.state = STATE_CSIENTRY},
[0x5d] = {.state = STATE_OSCSTRING},
};
static const struct state_transition state_csientry[256] = {
[0x30 ... 0x39] = {.action = ACTION_PARAM, .state = STATE_CSIPARAM},
[0x3b] = {.action = ACTION_PARAM, .state = STATE_CSIPARAM},
[0x3c ... 0x3f] = {.action = ACTION_COLLECT, .state = STATE_CSIPARAM},
[0x40 ... 0x7e] = {.action = ACTION_CSIDISPATCH, .state = STATE_GROUND},
[0x6d] = {.action = ACTION_CSIDISPATCH, .state = STATE_GROUND},
};
static const struct state_transition state_csiparam[256] = {
[0x30 ... 0x39] = {.action = ACTION_PARAM},
[0x3b] = {.action = ACTION_PARAM},
[0x40 ... 0x7e] = {.action = ACTION_CSIDISPATCH, .state = STATE_GROUND},
[0x6d] = {.action = ACTION_CSIDISPATCH, .state = STATE_GROUND},
};
static const struct state_transition state_ocsstring[256] = {
[0x07] = {.state = STATE_GROUND}, /* Not in diagram */
[0x20 ... 0x7f] = {.action = ACTION_OSCPUT},
};
static const struct state_transition* states[] = {
[STATE_ANYWHERE] = state_anywhere,
[STATE_ESCAPE] = state_escape,
[STATE_GROUND] = state_ground,
[STATE_CSIENTRY] = state_csientry,
[STATE_CSIPARAM] = state_csiparam,
[STATE_OSCSTRING] = state_ocsstring,
};
static const enum action entry_actions[] = {
[STATE_SAME] = ACTION_NONE,
[STATE_ANYWHERE] = ACTION_NONE,
[STATE_ESCAPE] = ACTION_CLEAR,
[STATE_GROUND] = ACTION_NONE,
[STATE_CSIENTRY] = ACTION_CLEAR,
[STATE_CSIPARAM] = ACTION_NONE,
[STATE_OSCSTRING] = ACTION_OSCSTART,
[STATE_UTF8] = ACTION_NONE,
};
static const enum action exit_actions[] = {
[STATE_SAME] = ACTION_NONE,
[STATE_ANYWHERE] = ACTION_NONE,
[STATE_ESCAPE] = ACTION_NONE,
[STATE_GROUND] = ACTION_NONE,
[STATE_CSIENTRY] = ACTION_NONE,
[STATE_CSIPARAM] = ACTION_NONE,
[STATE_OSCSTRING] = ACTION_OSCEND,
[STATE_UTF8] = ACTION_NONE,
};
static bool
action(struct terminal *term, enum action action, uint8_t c)
{
switch (action) {
case ACTION_NONE:
break;
case ACTION_IGNORE:
break;
case ACTION_EXECUTE:
LOG_DBG("execute: 0x%02x", c);
switch (c) {
2019-06-19 10:04:47 +02:00
case '\n':
2019-06-21 14:29:15 +02:00
if (term->grid.cursor.row == term->grid.rows - 1) {
grid_scroll(&term->grid, 1);
2019-06-21 14:29:15 +02:00
/* TODO: simulate \r? */
} else
grid_cursor_down(&term->grid, 1);
2019-06-19 10:04:47 +02:00
break;
case '\r':
grid_cursor_left(&term->grid, term->grid.cursor.col);
2019-06-17 18:57:12 +02:00
break;
case '\b':
grid_cursor_left(&term->grid, 1);
break;
2019-06-19 10:04:47 +02:00
case '\x07':
LOG_WARN("BELL");
break;
default:
LOG_ERR("execute: unimplemented: %c", c);
return false;
}
2019-06-17 18:57:12 +02:00
return true;
case ACTION_CLEAR:
memset(&term->vt.params, 0, sizeof(term->vt.params));
memset(&term->vt.intermediates, 0, sizeof(term->vt.intermediates));
memset(&term->vt.osc, 0, sizeof(term->vt.osc));
memset(&term->vt.utf8, 0, sizeof(term->vt.utf8));
break;
case ACTION_PRINT: {
if (term->grid.print_needs_wrap) {
if (term->grid.cursor.row == term->grid.rows - 1) {
grid_scroll(&term->grid, 1);
grid_cursor_to(&term->grid, term->grid.cursor.row, 0);
} else
grid_cursor_to(&term->grid, term->grid.cursor.row + 1, 0);
}
struct cell *cell = &term->grid.cells[term->grid.linear_cursor];
grid_damage_update(&term->grid, term->grid.linear_cursor, 1);
if (term->vt.utf8.idx > 0) {
2019-06-18 21:54:42 +02:00
//LOG_DBG("print: UTF8: %.*s", (int)term->vt.utf8.idx, term->vt.utf8.data);
memcpy(cell->c, term->vt.utf8.data, term->vt.utf8.idx);
cell->c[term->vt.utf8.idx] = '\0';
memset(&term->vt.utf8, 0, sizeof(term->vt.utf8));
} else {
//LOG_DBG("print: ASCII: %c", c);
cell->c[0] = c;
cell->c[1] = '\0';
}
cell->attrs = term->vt.attrs;
if (term->grid.cursor.col < term->grid.cols - 1)
grid_cursor_right(&term->grid, 1);
else
term->grid.print_needs_wrap = true;
break;
}
case ACTION_PARAM:{
if (term->vt.params.idx == 0)
term->vt.params.idx = 1;
if (c == ';') {
term->vt.params.idx++;
} else if (c == ':') {
if (term->vt.params.v[term->vt.params.idx - 1].sub.idx == 0)
term->vt.params.v[term->vt.params.idx - 1].sub.idx = 1;
} else {
if (term->vt.params.v[term->vt.params.idx - 1].sub.idx > 0)
term->vt.params.v[term->vt.params.idx - 1].sub.value[term->vt.params.v[term->vt.params.idx - 1].sub.idx] *= 10;
else
term->vt.params.v[term->vt.params.idx - 1].value *= 10;
if (term->vt.params.v[term->vt.params.idx - 1].sub.idx > 0)
term->vt.params.v[term->vt.params.idx - 1].sub.value[term->vt.params.v[term->vt.params.idx - 1].sub.idx] += c - '0';
else
term->vt.params.v[term->vt.params.idx - 1].value += c - '0';
}
break;
}
case ACTION_COLLECT:
LOG_DBG("collect");
term->vt.intermediates.data[term->vt.intermediates.idx++] = c;
break;
case ACTION_CSIDISPATCH:
return csi_dispatch(term, c);
case ACTION_OSCSTART:
term->vt.osc.idx = 0;
break;
case ACTION_OSCPUT:
term->vt.osc.data[term->vt.osc.idx++] = c;
break;
case ACTION_OSCEND:
return osc_dispatch(term);
case ACTION_UTF8:
term->vt.utf8.idx = 0;
if (c >= 0x2c && c <= 0xdf)
term->vt.utf8.left = 2;
else if (c >= 0xe0 && c <= 0xef)
term->vt.utf8.left = 3;
else
term->vt.utf8.left = 4;
2019-06-18 21:54:42 +02:00
//LOG_DBG("begin UTF-8 (%zu chars)", term->vt.utf8.left);
term->vt.utf8.data[term->vt.utf8.idx++] = c;
term->vt.utf8.left--;
break;
}
return true;
}
static bool
process_utf8(struct terminal *term, uint8_t c)
{
2019-06-18 21:54:42 +02:00
//LOG_DBG("UTF-8: 0x%02x", c);
term->vt.utf8.data[term->vt.utf8.idx++] = c;
term->vt.utf8.left--;
if (term->vt.utf8.left == 0)
term->vt.state = STATE_GROUND;
return true;
}
void
vt_from_slave(struct terminal *term, const uint8_t *data, size_t len)
{
//int cursor = term->grid.cursor;
for (size_t i = 0; i < len; i++) {
//LOG_DBG("input: 0x%02x", data[i]);
enum state current_state = term->vt.state;
const struct state_transition *transition = &state_anywhere[data[i]];
if (transition->action == ACTION_NONE && transition->state == STATE_SAME) {
if (current_state == STATE_UTF8) {
if (!process_utf8(term, data[i]))
abort();
current_state = term->vt.state;
if (current_state == STATE_UTF8)
continue;
if (!action(term, ACTION_PRINT, 0))
abort();
continue;
}
transition = &states[current_state][data[i]];
if (transition->action == ACTION_NONE && transition->state == STATE_SAME) {
LOG_ERR("unimplemented transition from %s: 0x%02x",
state_names[current_state], data[i]);
abort();
}
}
if (transition->state != STATE_SAME) {
enum action exit_action = exit_actions[current_state];
if (exit_action != ACTION_NONE && !action(term, exit_action, data[i]))
abort();
}
if (!action(term, transition->action, data[i]))
abort();
if (transition->state != STATE_SAME) {
2019-06-17 18:57:12 +02:00
/*
* LOG_DBG("transition: %s -> %s", state_names[current_state],
* state_names[transition->state]);
*/
term->vt.state = transition->state;
enum action entry_action = entry_actions[transition->state];
if (entry_action != ACTION_NONE && !action(term, entry_action, data[i]))
abort();
}
}
}