mirror of
https://github.com/neovim/neovim.git
synced 2026-01-29 08:30:46 +10:00
These are not needed after #35129 but making uncrustify still play nice with them was a bit tricky. Unfortunately `uncrustify --update-config-with-doc` breaks strings with backslashes. This issue has been reported upstream, and in the meanwhile auto-update on every single run has been disabled.
2469 lines
63 KiB
C
2469 lines
63 KiB
C
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "nvim/grid.h"
|
|
#include "nvim/mbyte.h"
|
|
#include "nvim/vterm/encoding.h"
|
|
#include "nvim/vterm/parser.h"
|
|
#include "nvim/vterm/pen.h"
|
|
#include "nvim/vterm/state.h"
|
|
#include "nvim/vterm/vterm.h"
|
|
#include "nvim/vterm/vterm_internal_defs.h"
|
|
|
|
#include "vterm/state.c.generated.h"
|
|
|
|
#define strneq(a, b, n) (strncmp(a, b, n) == 0)
|
|
|
|
// Primary Device Attributes (DA1) response.
|
|
// We make this a global (extern) variable so that we can override it with FFI
|
|
// in tests.
|
|
char vterm_primary_device_attr[] = "61;22;52";
|
|
|
|
// Some convenient wrappers to make callback functions easier
|
|
|
|
static void putglyph(VTermState *state, const schar_T schar, int width, VTermPos pos)
|
|
{
|
|
VTermGlyphInfo info = {
|
|
.schar = schar,
|
|
.width = width,
|
|
.protected_cell = state->protected_cell,
|
|
.dwl = state->lineinfo[pos.row].doublewidth,
|
|
.dhl = state->lineinfo[pos.row].doubleheight,
|
|
};
|
|
|
|
if (state->callbacks && state->callbacks->putglyph) {
|
|
if ((*state->callbacks->putglyph)(&info, pos, state->cbdata)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row);
|
|
}
|
|
|
|
static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom)
|
|
{
|
|
if (state->pos.col == oldpos->col && state->pos.row == oldpos->row) {
|
|
return;
|
|
}
|
|
|
|
if (cancel_phantom) {
|
|
state->at_phantom = 0;
|
|
}
|
|
|
|
if (state->callbacks && state->callbacks->movecursor) {
|
|
if ((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible,
|
|
state->cbdata)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void erase(VTermState *state, VTermRect rect, int selective)
|
|
{
|
|
if (rect.end_col == state->cols) {
|
|
// If we're erasing the final cells of any lines, cancel the continuation marker on the
|
|
// subsequent line
|
|
for (int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++) {
|
|
state->lineinfo[row].continuation = 0;
|
|
}
|
|
}
|
|
|
|
if (state->callbacks && state->callbacks->erase) {
|
|
if ((*state->callbacks->erase)(rect, selective, state->cbdata)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static VTermState *vterm_state_new(VTerm *vt)
|
|
{
|
|
VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState));
|
|
|
|
state->vt = vt;
|
|
|
|
state->rows = vt->rows;
|
|
state->cols = vt->cols;
|
|
|
|
state->mouse_col = 0;
|
|
state->mouse_row = 0;
|
|
state->mouse_buttons = 0;
|
|
|
|
state->mouse_protocol = MOUSE_X10;
|
|
|
|
state->callbacks = NULL;
|
|
state->cbdata = NULL;
|
|
|
|
state->selection.callbacks = NULL;
|
|
state->selection.user = NULL;
|
|
state->selection.buffer = NULL;
|
|
|
|
vterm_state_newpen(state);
|
|
|
|
state->bold_is_highbright = 0;
|
|
|
|
state->combine_pos.row = -1;
|
|
|
|
state->tabstops = vterm_allocator_malloc(state->vt, ((size_t)state->cols + 7) / 8);
|
|
|
|
state->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt,
|
|
(size_t)state->rows *
|
|
sizeof(VTermLineInfo));
|
|
// TODO(vterm): Make an 'enable' function
|
|
state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt,
|
|
(size_t)state->rows *
|
|
sizeof(VTermLineInfo));
|
|
state->lineinfo = state->lineinfos[BUFIDX_PRIMARY];
|
|
|
|
state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
|
|
if (*state->encoding_utf8.enc->init) {
|
|
(*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
|
|
}
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(state->key_encoding_stacks); i++) {
|
|
struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[i];
|
|
for (size_t j = 0; j < ARRAY_SIZE(stack->items); j++) {
|
|
memset(&stack->items[j], 0, sizeof(stack->items[j]));
|
|
}
|
|
|
|
stack->size = 1;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
void vterm_state_free(VTermState *state)
|
|
{
|
|
vterm_allocator_free(state->vt, state->tabstops);
|
|
vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]);
|
|
if (state->lineinfos[BUFIDX_ALTSCREEN]) {
|
|
vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]);
|
|
}
|
|
vterm_allocator_free(state->vt, state);
|
|
}
|
|
|
|
static void scroll(VTermState *state, VTermRect rect, int downward, int rightward)
|
|
{
|
|
if (!downward && !rightward) {
|
|
return;
|
|
}
|
|
|
|
int rows = rect.end_row - rect.start_row;
|
|
if (downward > rows) {
|
|
downward = rows;
|
|
} else if (downward < -rows) {
|
|
downward = -rows;
|
|
}
|
|
|
|
int cols = rect.end_col - rect.start_col;
|
|
if (rightward > cols) {
|
|
rightward = cols;
|
|
} else if (rightward < -cols) {
|
|
rightward = -cols;
|
|
}
|
|
|
|
// Update lineinfo if full line
|
|
if (rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {
|
|
int height = rect.end_row - rect.start_row - abs(downward);
|
|
|
|
if (downward > 0) {
|
|
memmove(state->lineinfo + rect.start_row,
|
|
state->lineinfo + rect.start_row + downward,
|
|
(size_t)height * sizeof(state->lineinfo[0]));
|
|
for (int row = rect.end_row - downward; row < rect.end_row; row++) {
|
|
state->lineinfo[row] = (VTermLineInfo){ 0 };
|
|
}
|
|
} else {
|
|
memmove(state->lineinfo + rect.start_row - downward,
|
|
state->lineinfo + rect.start_row,
|
|
(size_t)height * sizeof(state->lineinfo[0]));
|
|
for (int row = rect.start_row; row < rect.start_row - downward; row++) {
|
|
state->lineinfo[row] = (VTermLineInfo){ 0 };
|
|
}
|
|
}
|
|
}
|
|
|
|
if (state->callbacks && state->callbacks->scrollrect) {
|
|
if ((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (state->callbacks) {
|
|
vterm_scroll_rect(rect, downward, rightward,
|
|
state->callbacks->moverect, state->callbacks->erase, state->cbdata);
|
|
}
|
|
}
|
|
|
|
static void linefeed(VTermState *state)
|
|
{
|
|
if (state->pos.row == SCROLLREGION_BOTTOM(state) - 1) {
|
|
VTermRect rect = {
|
|
.start_row = state->scrollregion_top,
|
|
.end_row = SCROLLREGION_BOTTOM(state),
|
|
.start_col = SCROLLREGION_LEFT(state),
|
|
.end_col = SCROLLREGION_RIGHT(state),
|
|
};
|
|
|
|
scroll(state, rect, 1, 0);
|
|
} else if (state->pos.row < state->rows - 1) {
|
|
state->pos.row++;
|
|
}
|
|
}
|
|
|
|
static void set_col_tabstop(VTermState *state, int col)
|
|
{
|
|
uint8_t mask = (uint8_t)(1 << (col & 7));
|
|
state->tabstops[col >> 3] |= mask;
|
|
}
|
|
|
|
static void clear_col_tabstop(VTermState *state, int col)
|
|
{
|
|
uint8_t mask = (uint8_t)(1 << (col & 7));
|
|
state->tabstops[col >> 3] &= ~mask;
|
|
}
|
|
|
|
static int is_col_tabstop(VTermState *state, int col)
|
|
{
|
|
uint8_t mask = (uint8_t)(1 << (col & 7));
|
|
return state->tabstops[col >> 3] & mask;
|
|
}
|
|
|
|
static int is_cursor_in_scrollregion(const VTermState *state)
|
|
{
|
|
if (state->pos.row < state->scrollregion_top
|
|
|| state->pos.row >= SCROLLREGION_BOTTOM(state)) {
|
|
return 0;
|
|
}
|
|
if (state->pos.col < SCROLLREGION_LEFT(state)
|
|
|| state->pos.col >= SCROLLREGION_RIGHT(state)) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void tab(VTermState *state, int count, int direction)
|
|
{
|
|
while (count > 0) {
|
|
if (direction > 0) {
|
|
if (state->pos.col >= THISROWWIDTH(state) - 1) {
|
|
return;
|
|
}
|
|
|
|
state->pos.col++;
|
|
} else if (direction < 0) {
|
|
if (state->pos.col < 1) {
|
|
return;
|
|
}
|
|
|
|
state->pos.col--;
|
|
}
|
|
|
|
if (is_col_tabstop(state, state->pos.col)) {
|
|
count--;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define NO_FORCE 0
|
|
#define FORCE 1
|
|
|
|
#define DWL_OFF 0
|
|
#define DWL_ON 1
|
|
|
|
#define DHL_OFF 0
|
|
#define DHL_TOP 1
|
|
#define DHL_BOTTOM 2
|
|
|
|
static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl)
|
|
{
|
|
VTermLineInfo info = state->lineinfo[row];
|
|
|
|
if (dwl == DWL_OFF) {
|
|
info.doublewidth = DWL_OFF;
|
|
} else if (dwl == DWL_ON) {
|
|
info.doublewidth = DWL_ON;
|
|
}
|
|
// else -1 to ignore
|
|
|
|
if (dhl == DHL_OFF) {
|
|
info.doubleheight = DHL_OFF;
|
|
} else if (dhl == DHL_TOP) {
|
|
info.doubleheight = DHL_TOP;
|
|
} else if (dhl == DHL_BOTTOM) {
|
|
info.doubleheight = DHL_BOTTOM;
|
|
}
|
|
|
|
if ((state->callbacks
|
|
&& state->callbacks->setlineinfo
|
|
&& (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata))
|
|
|| force) {
|
|
state->lineinfo[row] = info;
|
|
}
|
|
}
|
|
|
|
static int on_text(const char bytes[], size_t len, void *user)
|
|
{
|
|
VTermState *state = user;
|
|
|
|
VTermPos oldpos = state->pos;
|
|
|
|
uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer);
|
|
size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t);
|
|
|
|
int npoints = 0;
|
|
size_t eaten = 0;
|
|
|
|
VTermEncodingInstance *encoding =
|
|
state->gsingle_set ? &state->encoding[state->gsingle_set]
|
|
: !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set]
|
|
: state->vt->mode.utf8 ? &state->encoding_utf8
|
|
: &state->encoding[state->
|
|
gr_set];
|
|
|
|
(*encoding->enc->decode)(encoding->enc, encoding->data,
|
|
codepoints, &npoints, state->gsingle_set ? 1 : (int)maxpoints,
|
|
bytes, &eaten, len);
|
|
|
|
// There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet for even a single codepoint
|
|
if (!npoints) {
|
|
return (int)eaten;
|
|
}
|
|
|
|
if (state->gsingle_set && npoints) {
|
|
state->gsingle_set = 0;
|
|
}
|
|
|
|
int i = 0;
|
|
GraphemeState grapheme_state = GRAPHEME_STATE_INIT;
|
|
size_t grapheme_len = 0;
|
|
bool recombine = false;
|
|
|
|
// See if the cursor has moved since
|
|
if (state->pos.row == state->combine_pos.row
|
|
&& state->pos.col == state->combine_pos.col + state->combine_width) {
|
|
// This is a combining char. that needs to be merged with the previous glyph output
|
|
if (utf_iscomposing((int)state->grapheme_last, (int)codepoints[i], &state->grapheme_state)) {
|
|
// Find where we need to append these combining chars
|
|
grapheme_len = state->grapheme_len;
|
|
grapheme_state = state->grapheme_state;
|
|
state->pos.col = state->combine_pos.col;
|
|
recombine = true;
|
|
} else {
|
|
DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n");
|
|
}
|
|
}
|
|
|
|
while (i < npoints) {
|
|
// Try to find combining characters following this
|
|
do {
|
|
if (grapheme_len < sizeof(state->grapheme_buf) - 4) {
|
|
grapheme_len += (size_t)utf_char2bytes((int)codepoints[i],
|
|
state->grapheme_buf + grapheme_len);
|
|
}
|
|
i++;
|
|
} while (i < npoints && utf_iscomposing((int)codepoints[i - 1], (int)codepoints[i],
|
|
&grapheme_state));
|
|
|
|
int width = utf_ptr2cells_len(state->grapheme_buf, (int)grapheme_len);
|
|
|
|
if (state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
|
|
linefeed(state);
|
|
state->pos.col = 0;
|
|
state->at_phantom = 0;
|
|
state->lineinfo[state->pos.row].continuation = 1;
|
|
}
|
|
|
|
if (state->mode.insert && !recombine) {
|
|
// TODO(vterm): This will be a little inefficient for large bodies of text, as it'll have to
|
|
// 'ICH' effectively before every glyph. We should scan ahead and ICH as many times as
|
|
// required
|
|
VTermRect rect = {
|
|
.start_row = state->pos.row,
|
|
.end_row = state->pos.row + 1,
|
|
.start_col = state->pos.col,
|
|
.end_col = THISROWWIDTH(state),
|
|
};
|
|
scroll(state, rect, 0, -1);
|
|
}
|
|
|
|
schar_T sc = schar_from_buf(state->grapheme_buf, grapheme_len);
|
|
putglyph(state, sc, width, state->pos);
|
|
|
|
if (i == npoints) {
|
|
// End of the buffer. Save the chars in case we have to combine with more on the next call
|
|
state->grapheme_len = grapheme_len;
|
|
state->grapheme_last = codepoints[i - 1];
|
|
state->grapheme_state = grapheme_state;
|
|
state->combine_width = width;
|
|
state->combine_pos = state->pos;
|
|
} else {
|
|
grapheme_len = 0;
|
|
recombine = false;
|
|
}
|
|
|
|
if (state->pos.col + width >= THISROWWIDTH(state)) {
|
|
if (state->mode.autowrap) {
|
|
state->at_phantom = 1;
|
|
}
|
|
} else {
|
|
state->pos.col += width;
|
|
}
|
|
}
|
|
|
|
updatecursor(state, &oldpos, 0);
|
|
|
|
#ifdef DEBUG
|
|
if (state->pos.row < 0 || state->pos.row >= state->rows
|
|
|| state->pos.col < 0 || state->pos.col >= state->cols) {
|
|
fprintf(stderr, "Position out of bounds after text: (%d,%d)\n",
|
|
state->pos.row, state->pos.col);
|
|
abort();
|
|
}
|
|
#endif
|
|
|
|
return (int)eaten;
|
|
}
|
|
|
|
static int on_control(uint8_t control, void *user)
|
|
{
|
|
VTermState *state = user;
|
|
|
|
VTermPos oldpos = state->pos;
|
|
|
|
switch (control) {
|
|
case 0x07: // BEL - ECMA-48 8.3.3
|
|
if (state->callbacks && state->callbacks->bell) {
|
|
(*state->callbacks->bell)(state->cbdata);
|
|
}
|
|
break;
|
|
|
|
case 0x08: // BS - ECMA-48 8.3.5
|
|
if (state->pos.col > 0) {
|
|
state->pos.col--;
|
|
}
|
|
break;
|
|
|
|
case 0x09: // HT - ECMA-48 8.3.60
|
|
tab(state, 1, +1);
|
|
break;
|
|
|
|
case 0x0a: // LF - ECMA-48 8.3.74
|
|
case 0x0b: // VT
|
|
case 0x0c: // FF
|
|
linefeed(state);
|
|
if (state->mode.newline) {
|
|
state->pos.col = 0;
|
|
}
|
|
break;
|
|
|
|
case 0x0d: // CR - ECMA-48 8.3.15
|
|
state->pos.col = 0;
|
|
break;
|
|
|
|
case 0x0e: // LS1 - ECMA-48 8.3.76
|
|
state->gl_set = 1;
|
|
break;
|
|
|
|
case 0x0f: // LS0 - ECMA-48 8.3.75
|
|
state->gl_set = 0;
|
|
break;
|
|
|
|
case 0x84: // IND - DEPRECATED but implemented for completeness
|
|
linefeed(state);
|
|
break;
|
|
|
|
case 0x85: // NEL - ECMA-48 8.3.86
|
|
linefeed(state);
|
|
state->pos.col = 0;
|
|
break;
|
|
|
|
case 0x88: // HTS - ECMA-48 8.3.62
|
|
set_col_tabstop(state, state->pos.col);
|
|
break;
|
|
|
|
case 0x8d: // RI - ECMA-48 8.3.104
|
|
if (state->pos.row == state->scrollregion_top) {
|
|
VTermRect rect = {
|
|
.start_row = state->scrollregion_top,
|
|
.end_row = SCROLLREGION_BOTTOM(state),
|
|
.start_col = SCROLLREGION_LEFT(state),
|
|
.end_col = SCROLLREGION_RIGHT(state),
|
|
};
|
|
|
|
scroll(state, rect, -1, 0);
|
|
} else if (state->pos.row > 0) {
|
|
state->pos.row--;
|
|
}
|
|
break;
|
|
|
|
case 0x8e: // SS2 - ECMA-48 8.3.141
|
|
state->gsingle_set = 2;
|
|
break;
|
|
|
|
case 0x8f: // SS3 - ECMA-48 8.3.142
|
|
state->gsingle_set = 3;
|
|
break;
|
|
|
|
default:
|
|
if (state->fallbacks && state->fallbacks->control) {
|
|
if ((*state->fallbacks->control)(control, state->fbdata)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
updatecursor(state, &oldpos, 1);
|
|
|
|
#ifdef DEBUG
|
|
if (state->pos.row < 0 || state->pos.row >= state->rows
|
|
|| state->pos.col < 0 || state->pos.col >= state->cols) {
|
|
fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n",
|
|
control, state->pos.row, state->pos.col);
|
|
abort();
|
|
}
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int settermprop_bool(VTermState *state, VTermProp prop, int v)
|
|
{
|
|
VTermValue val = { .boolean = v };
|
|
return vterm_state_set_termprop(state, prop, &val);
|
|
}
|
|
|
|
static int settermprop_int(VTermState *state, VTermProp prop, int v)
|
|
{
|
|
VTermValue val = { .number = v };
|
|
return vterm_state_set_termprop(state, prop, &val);
|
|
}
|
|
|
|
static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag)
|
|
{
|
|
VTermValue val = { .string = frag };
|
|
return vterm_state_set_termprop(state, prop, &val);
|
|
}
|
|
|
|
static void savecursor(VTermState *state, int save)
|
|
{
|
|
if (save) {
|
|
state->saved.pos = state->pos;
|
|
state->saved.mode.cursor_visible = state->mode.cursor_visible;
|
|
state->saved.mode.cursor_blink = state->mode.cursor_blink;
|
|
state->saved.mode.cursor_shape = state->mode.cursor_shape;
|
|
|
|
vterm_state_savepen(state, 1);
|
|
} else {
|
|
VTermPos oldpos = state->pos;
|
|
|
|
state->pos = state->saved.pos;
|
|
|
|
settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible);
|
|
settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink);
|
|
settermprop_int(state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape);
|
|
|
|
vterm_state_savepen(state, 0);
|
|
|
|
updatecursor(state, &oldpos, 1);
|
|
}
|
|
}
|
|
|
|
static int on_escape(const char *bytes, size_t len, void *user)
|
|
{
|
|
VTermState *state = user;
|
|
|
|
// Easier to decode this from the first byte, even though the final byte terminates it
|
|
switch (bytes[0]) {
|
|
case ' ':
|
|
if (len != 2) {
|
|
return 0;
|
|
}
|
|
|
|
switch (bytes[1]) {
|
|
case 'F': // S7C1T
|
|
state->vt->mode.ctrl8bit = 0;
|
|
break;
|
|
|
|
case 'G': // S8C1T
|
|
state->vt->mode.ctrl8bit = 1;
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
return 2;
|
|
|
|
case '#':
|
|
if (len != 2) {
|
|
return 0;
|
|
}
|
|
|
|
switch (bytes[1]) {
|
|
case '3': // DECDHL top
|
|
if (state->mode.leftrightmargin) {
|
|
break;
|
|
}
|
|
set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP);
|
|
break;
|
|
|
|
case '4': // DECDHL bottom
|
|
if (state->mode.leftrightmargin) {
|
|
break;
|
|
}
|
|
set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM);
|
|
break;
|
|
|
|
case '5': // DECSWL
|
|
if (state->mode.leftrightmargin) {
|
|
break;
|
|
}
|
|
set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF);
|
|
break;
|
|
|
|
case '6': // DECDWL
|
|
if (state->mode.leftrightmargin) {
|
|
break;
|
|
}
|
|
set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF);
|
|
break;
|
|
|
|
case '8': // DECALN
|
|
{
|
|
VTermPos pos;
|
|
schar_T E = schar_from_ascii('E'); // E
|
|
for (pos.row = 0; pos.row < state->rows; pos.row++) {
|
|
for (pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) {
|
|
putglyph(state, E, 1, pos);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
return 2;
|
|
|
|
case '(':
|
|
case ')':
|
|
case '*':
|
|
case '+': // SCS
|
|
if (len != 2) {
|
|
return 0;
|
|
}
|
|
|
|
{
|
|
int setnum = bytes[0] - 0x28;
|
|
VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]);
|
|
|
|
if (newenc) {
|
|
state->encoding[setnum].enc = newenc;
|
|
|
|
if (newenc->init) {
|
|
(*newenc->init)(newenc, state->encoding[setnum].data);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 2;
|
|
|
|
case '7': // DECSC
|
|
savecursor(state, 1);
|
|
return 1;
|
|
|
|
case '8': // DECRC
|
|
savecursor(state, 0);
|
|
return 1;
|
|
|
|
case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100
|
|
return 1;
|
|
|
|
case '=': // DECKPAM
|
|
state->mode.keypad = 1;
|
|
return 1;
|
|
|
|
case '>': // DECKPNM
|
|
state->mode.keypad = 0;
|
|
return 1;
|
|
|
|
case 'c': // RIS - ECMA-48 8.3.105
|
|
{
|
|
VTermPos oldpos = state->pos;
|
|
vterm_state_reset(state, 1);
|
|
if (state->callbacks && state->callbacks->movecursor) {
|
|
(*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible,
|
|
state->cbdata);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
case 'n': // LS2 - ECMA-48 8.3.78
|
|
state->gl_set = 2;
|
|
return 1;
|
|
|
|
case 'o': // LS3 - ECMA-48 8.3.80
|
|
state->gl_set = 3;
|
|
return 1;
|
|
|
|
case '~': // LS1R - ECMA-48 8.3.77
|
|
state->gr_set = 1;
|
|
return 1;
|
|
|
|
case '}': // LS2R - ECMA-48 8.3.79
|
|
state->gr_set = 2;
|
|
return 1;
|
|
|
|
case '|': // LS3R - ECMA-48 8.3.81
|
|
state->gr_set = 3;
|
|
return 1;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void set_mode(VTermState *state, int num, int val)
|
|
{
|
|
switch (num) {
|
|
case 4: // IRM - ECMA-48 7.2.10
|
|
state->mode.insert = (unsigned)val;
|
|
break;
|
|
|
|
case 20: // LNM - ANSI X3.4-1977
|
|
state->mode.newline = (unsigned)val;
|
|
break;
|
|
|
|
default:
|
|
DEBUG_LOG("libvterm: Unknown mode %d\n", num);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void set_dec_mode(VTermState *state, int num, int val)
|
|
{
|
|
switch (num) {
|
|
case 1:
|
|
state->mode.cursor = (unsigned)val;
|
|
break;
|
|
|
|
case 5: // DECSCNM - screen mode
|
|
settermprop_bool(state, VTERM_PROP_REVERSE, val);
|
|
break;
|
|
|
|
case 6: // DECOM - origin mode
|
|
{
|
|
VTermPos oldpos = state->pos;
|
|
state->mode.origin = (unsigned)val;
|
|
state->pos.row = state->mode.origin ? state->scrollregion_top : 0;
|
|
state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0;
|
|
updatecursor(state, &oldpos, 1);
|
|
}
|
|
break;
|
|
|
|
case 7:
|
|
state->mode.autowrap = (unsigned)val;
|
|
break;
|
|
|
|
case 12:
|
|
settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);
|
|
break;
|
|
|
|
case 25:
|
|
settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val);
|
|
break;
|
|
|
|
case 69: // DECVSSM - vertical split screen mode
|
|
// DECLRMM - left/right margin mode
|
|
state->mode.leftrightmargin = (unsigned)val;
|
|
if (val) {
|
|
// Setting DECVSSM must clear doublewidth/doubleheight state of every line
|
|
for (int row = 0; row < state->rows; row++) {
|
|
set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case 1000:
|
|
case 1002:
|
|
case 1003:
|
|
settermprop_int(state, VTERM_PROP_MOUSE,
|
|
!val ? VTERM_PROP_MOUSE_NONE
|
|
: (num == 1000) ? VTERM_PROP_MOUSE_CLICK
|
|
: (num == 1002) ? VTERM_PROP_MOUSE_DRAG
|
|
: VTERM_PROP_MOUSE_MOVE);
|
|
break;
|
|
|
|
case 1004:
|
|
settermprop_bool(state, VTERM_PROP_FOCUSREPORT, val);
|
|
state->mode.report_focus = (unsigned)val;
|
|
break;
|
|
|
|
case 1005:
|
|
state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;
|
|
break;
|
|
|
|
case 1006:
|
|
state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10;
|
|
break;
|
|
|
|
case 1015:
|
|
state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10;
|
|
break;
|
|
|
|
case 1047:
|
|
settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
|
|
break;
|
|
|
|
case 1048:
|
|
savecursor(state, val);
|
|
break;
|
|
|
|
case 1049:
|
|
settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
|
|
savecursor(state, val);
|
|
break;
|
|
|
|
case 2004:
|
|
state->mode.bracketpaste = (unsigned)val;
|
|
break;
|
|
|
|
case 2031:
|
|
settermprop_bool(state, VTERM_PROP_THEMEUPDATES, val);
|
|
break;
|
|
|
|
default:
|
|
DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void request_dec_mode(VTermState *state, int num)
|
|
{
|
|
int reply;
|
|
|
|
switch (num) {
|
|
case 1:
|
|
reply = state->mode.cursor;
|
|
break;
|
|
|
|
case 5:
|
|
reply = state->mode.screen;
|
|
break;
|
|
|
|
case 6:
|
|
reply = state->mode.origin;
|
|
break;
|
|
|
|
case 7:
|
|
reply = state->mode.autowrap;
|
|
break;
|
|
|
|
case 12:
|
|
reply = state->mode.cursor_blink;
|
|
break;
|
|
|
|
case 25:
|
|
reply = state->mode.cursor_visible;
|
|
break;
|
|
|
|
case 69:
|
|
reply = state->mode.leftrightmargin;
|
|
break;
|
|
|
|
case 1000:
|
|
reply = state->mouse_flags == MOUSE_WANT_CLICK;
|
|
break;
|
|
|
|
case 1002:
|
|
reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG);
|
|
break;
|
|
|
|
case 1003:
|
|
reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE);
|
|
break;
|
|
|
|
case 1004:
|
|
reply = state->mode.report_focus;
|
|
break;
|
|
|
|
case 1005:
|
|
reply = state->mouse_protocol == MOUSE_UTF8;
|
|
break;
|
|
|
|
case 1006:
|
|
reply = state->mouse_protocol == MOUSE_SGR;
|
|
break;
|
|
|
|
case 1015:
|
|
reply = state->mouse_protocol == MOUSE_RXVT;
|
|
break;
|
|
|
|
case 1047:
|
|
reply = state->mode.alt_screen;
|
|
break;
|
|
|
|
case 2004:
|
|
reply = state->mode.bracketpaste;
|
|
break;
|
|
|
|
case 2031:
|
|
reply = state->mode.theme_updates;
|
|
break;
|
|
|
|
default:
|
|
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0);
|
|
return;
|
|
}
|
|
|
|
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2);
|
|
}
|
|
|
|
static void request_version_string(VTermState *state)
|
|
{
|
|
vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)",
|
|
VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR);
|
|
}
|
|
|
|
static void request_key_encoding_flags(VTermState *state)
|
|
{
|
|
int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
|
|
struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
|
|
|
|
int reply = 0;
|
|
|
|
assert(stack->size > 0);
|
|
VTermKeyEncodingFlags flags = stack->items[stack->size - 1];
|
|
|
|
if (flags.disambiguate) {
|
|
reply |= KEY_ENCODING_DISAMBIGUATE;
|
|
}
|
|
|
|
if (flags.report_events) {
|
|
reply |= KEY_ENCODING_REPORT_EVENTS;
|
|
}
|
|
|
|
if (flags.report_alternate) {
|
|
reply |= KEY_ENCODING_REPORT_ALTERNATE;
|
|
}
|
|
|
|
if (flags.report_all_keys) {
|
|
reply |= KEY_ENCODING_REPORT_ALL_KEYS;
|
|
}
|
|
|
|
if (flags.report_associated) {
|
|
reply |= KEY_ENCODING_REPORT_ASSOCIATED;
|
|
}
|
|
|
|
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%du", reply);
|
|
}
|
|
|
|
static void set_key_encoding_flags(VTermState *state, int arg, int mode)
|
|
{
|
|
// When mode is 3, bits set in arg reset the corresponding mode
|
|
bool set = mode != 3;
|
|
|
|
// When mode is 1, unset bits are reset
|
|
bool reset_unset = mode == 1;
|
|
|
|
struct VTermKeyEncodingFlags flags = { 0 };
|
|
if (arg & KEY_ENCODING_DISAMBIGUATE) {
|
|
flags.disambiguate = set;
|
|
} else if (reset_unset) {
|
|
flags.disambiguate = false;
|
|
}
|
|
|
|
if (arg & KEY_ENCODING_REPORT_EVENTS) {
|
|
flags.report_events = set;
|
|
} else if (reset_unset) {
|
|
flags.report_events = false;
|
|
}
|
|
|
|
if (arg & KEY_ENCODING_REPORT_ALTERNATE) {
|
|
flags.report_alternate = set;
|
|
} else if (reset_unset) {
|
|
flags.report_alternate = false;
|
|
}
|
|
if (arg & KEY_ENCODING_REPORT_ALL_KEYS) {
|
|
flags.report_all_keys = set;
|
|
} else if (reset_unset) {
|
|
flags.report_all_keys = false;
|
|
}
|
|
|
|
if (arg & KEY_ENCODING_REPORT_ASSOCIATED) {
|
|
flags.report_associated = set;
|
|
} else if (reset_unset) {
|
|
flags.report_associated = false;
|
|
}
|
|
|
|
int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
|
|
struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
|
|
assert(stack->size > 0);
|
|
stack->items[stack->size - 1] = flags;
|
|
}
|
|
|
|
static void push_key_encoding_flags(VTermState *state, int arg)
|
|
{
|
|
int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
|
|
struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
|
|
assert(stack->size <= ARRAY_SIZE(stack->items));
|
|
|
|
if (stack->size == ARRAY_SIZE(stack->items)) {
|
|
// Evict oldest entry when stack is full
|
|
for (size_t i = 0; i < ARRAY_SIZE(stack->items) - 1; i++) {
|
|
stack->items[i] = stack->items[i + 1];
|
|
}
|
|
} else {
|
|
stack->size++;
|
|
}
|
|
|
|
set_key_encoding_flags(state, arg, 1);
|
|
}
|
|
|
|
static void pop_key_encoding_flags(VTermState *state, int arg)
|
|
{
|
|
int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
|
|
struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
|
|
if (arg >= stack->size) {
|
|
stack->size = 1;
|
|
|
|
// If a pop request is received that empties the stack, all flags are reset.
|
|
memset(&stack->items[0], 0, sizeof(stack->items[0]));
|
|
} else if (arg > 0) {
|
|
stack->size -= arg;
|
|
}
|
|
}
|
|
|
|
static int on_csi(const char *leader, const long args[], int argcount, const char *intermed,
|
|
char command, void *user)
|
|
{
|
|
VTermState *state = user;
|
|
int leader_byte = 0;
|
|
int intermed_byte = 0;
|
|
int cancel_phantom = 1;
|
|
|
|
if (leader && leader[0]) {
|
|
if (leader[1]) { // longer than 1 char
|
|
return 0;
|
|
}
|
|
|
|
switch (leader[0]) {
|
|
case '?':
|
|
case '>':
|
|
case '<':
|
|
case '=':
|
|
leader_byte = (int)leader[0];
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (intermed && intermed[0]) {
|
|
if (intermed[1]) { // longer than 1 char
|
|
return 0;
|
|
}
|
|
|
|
switch (intermed[0]) {
|
|
case ' ':
|
|
case '!':
|
|
case '"':
|
|
case '$':
|
|
case '\'':
|
|
intermed_byte = (int)intermed[0];
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
VTermPos oldpos = state->pos;
|
|
|
|
// Some temporaries for later code
|
|
int count, val;
|
|
int row, col;
|
|
VTermRect rect;
|
|
int selective;
|
|
|
|
#define LBOUND(v, min) if ((v) < (min))(v) = (min)
|
|
#define UBOUND(v, max) if ((v) > (max))(v) = (max)
|
|
|
|
#define LEADER(l, b) ((l << 8) | b)
|
|
#define INTERMED(i, b) ((i << 16) | b)
|
|
|
|
switch (intermed_byte << 16 | leader_byte << 8 | command) {
|
|
case 0x40: // ICH - ECMA-48 8.3.64
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
|
|
if (!is_cursor_in_scrollregion(state)) {
|
|
break;
|
|
}
|
|
|
|
rect.start_row = state->pos.row;
|
|
rect.end_row = state->pos.row + 1;
|
|
rect.start_col = state->pos.col;
|
|
if (state->mode.leftrightmargin) {
|
|
rect.end_col = SCROLLREGION_RIGHT(state);
|
|
} else {
|
|
rect.end_col = THISROWWIDTH(state);
|
|
}
|
|
|
|
scroll(state, rect, 0, -count);
|
|
|
|
break;
|
|
|
|
case 0x41: // CUU - ECMA-48 8.3.22
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
state->pos.row -= count;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x42: // CUD - ECMA-48 8.3.19
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
state->pos.row += count;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x43: // CUF - ECMA-48 8.3.20
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
state->pos.col += count;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x44: // CUB - ECMA-48 8.3.18
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
state->pos.col -= count;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x45: // CNL - ECMA-48 8.3.12
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
state->pos.col = 0;
|
|
state->pos.row += count;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x46: // CPL - ECMA-48 8.3.13
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
state->pos.col = 0;
|
|
state->pos.row -= count;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x47: // CHA - ECMA-48 8.3.9
|
|
val = CSI_ARG_OR(args[0], 1);
|
|
state->pos.col = val - 1;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x48: // CUP - ECMA-48 8.3.21
|
|
row = CSI_ARG_OR(args[0], 1);
|
|
col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
|
|
// zero-based
|
|
state->pos.row = row - 1;
|
|
state->pos.col = col - 1;
|
|
if (state->mode.origin) {
|
|
state->pos.row += state->scrollregion_top;
|
|
state->pos.col += SCROLLREGION_LEFT(state);
|
|
}
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x49: // CHT - ECMA-48 8.3.10
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
tab(state, count, +1);
|
|
break;
|
|
|
|
case 0x4a: // ED - ECMA-48 8.3.39
|
|
case LEADER('?', 0x4a): // DECSED - Selective Erase in Display
|
|
selective = (leader_byte == '?');
|
|
switch (CSI_ARG(args[0])) {
|
|
case CSI_ARG_MISSING:
|
|
case 0:
|
|
rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
|
|
rect.start_col = state->pos.col; rect.end_col = state->cols;
|
|
if (rect.end_col > rect.start_col) {
|
|
erase(state, rect, selective);
|
|
}
|
|
|
|
rect.start_row = state->pos.row + 1; rect.end_row = state->rows;
|
|
rect.start_col = 0;
|
|
for (int row_ = rect.start_row; row_ < rect.end_row; row_++) {
|
|
set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF);
|
|
}
|
|
if (rect.end_row > rect.start_row) {
|
|
erase(state, rect, selective);
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
rect.start_row = 0; rect.end_row = state->pos.row;
|
|
rect.start_col = 0; rect.end_col = state->cols;
|
|
for (int row_ = rect.start_row; row_ < rect.end_row; row_++) {
|
|
set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF);
|
|
}
|
|
if (rect.end_col > rect.start_col) {
|
|
erase(state, rect, selective);
|
|
}
|
|
|
|
rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
|
|
rect.end_col = state->pos.col + 1;
|
|
if (rect.end_row > rect.start_row) {
|
|
erase(state, rect, selective);
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
rect.start_row = 0; rect.end_row = state->rows;
|
|
rect.start_col = 0; rect.end_col = state->cols;
|
|
for (int row_ = rect.start_row; row_ < rect.end_row; row_++) {
|
|
set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF);
|
|
}
|
|
erase(state, rect, selective);
|
|
break;
|
|
|
|
case 3:
|
|
if (state->callbacks && state->callbacks->sb_clear) {
|
|
if ((*state->callbacks->sb_clear)(state->cbdata)) {
|
|
return 1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x4b: // EL - ECMA-48 8.3.41
|
|
case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line
|
|
selective = (leader_byte == '?');
|
|
rect.start_row = state->pos.row;
|
|
rect.end_row = state->pos.row + 1;
|
|
|
|
switch (CSI_ARG(args[0])) {
|
|
case CSI_ARG_MISSING:
|
|
case 0:
|
|
rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break;
|
|
case 1:
|
|
rect.start_col = 0; rect.end_col = state->pos.col + 1; break;
|
|
case 2:
|
|
rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if (rect.end_col > rect.start_col) {
|
|
erase(state, rect, selective);
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x4c: // IL - ECMA-48 8.3.67
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
|
|
if (!is_cursor_in_scrollregion(state)) {
|
|
break;
|
|
}
|
|
|
|
rect.start_row = state->pos.row;
|
|
rect.end_row = SCROLLREGION_BOTTOM(state);
|
|
rect.start_col = SCROLLREGION_LEFT(state);
|
|
rect.end_col = SCROLLREGION_RIGHT(state);
|
|
|
|
scroll(state, rect, -count, 0);
|
|
|
|
break;
|
|
|
|
case 0x4d: // DL - ECMA-48 8.3.32
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
|
|
if (!is_cursor_in_scrollregion(state)) {
|
|
break;
|
|
}
|
|
|
|
rect.start_row = state->pos.row;
|
|
rect.end_row = SCROLLREGION_BOTTOM(state);
|
|
rect.start_col = SCROLLREGION_LEFT(state);
|
|
rect.end_col = SCROLLREGION_RIGHT(state);
|
|
|
|
scroll(state, rect, count, 0);
|
|
|
|
break;
|
|
|
|
case 0x50: // DCH - ECMA-48 8.3.26
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
|
|
if (!is_cursor_in_scrollregion(state)) {
|
|
break;
|
|
}
|
|
|
|
rect.start_row = state->pos.row;
|
|
rect.end_row = state->pos.row + 1;
|
|
rect.start_col = state->pos.col;
|
|
if (state->mode.leftrightmargin) {
|
|
rect.end_col = SCROLLREGION_RIGHT(state);
|
|
} else {
|
|
rect.end_col = THISROWWIDTH(state);
|
|
}
|
|
|
|
scroll(state, rect, 0, count);
|
|
|
|
break;
|
|
|
|
case 0x53: // SU - ECMA-48 8.3.147
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
|
|
rect.start_row = state->scrollregion_top;
|
|
rect.end_row = SCROLLREGION_BOTTOM(state);
|
|
rect.start_col = SCROLLREGION_LEFT(state);
|
|
rect.end_col = SCROLLREGION_RIGHT(state);
|
|
|
|
scroll(state, rect, count, 0);
|
|
|
|
break;
|
|
|
|
case 0x54: // SD - ECMA-48 8.3.113
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
|
|
rect.start_row = state->scrollregion_top;
|
|
rect.end_row = SCROLLREGION_BOTTOM(state);
|
|
rect.start_col = SCROLLREGION_LEFT(state);
|
|
rect.end_col = SCROLLREGION_RIGHT(state);
|
|
|
|
scroll(state, rect, -count, 0);
|
|
|
|
break;
|
|
|
|
case 0x58: // ECH - ECMA-48 8.3.38
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
|
|
rect.start_row = state->pos.row;
|
|
rect.end_row = state->pos.row + 1;
|
|
rect.start_col = state->pos.col;
|
|
rect.end_col = state->pos.col + count;
|
|
UBOUND(rect.end_col, THISROWWIDTH(state));
|
|
|
|
erase(state, rect, 0);
|
|
break;
|
|
|
|
case 0x5a: // CBT - ECMA-48 8.3.7
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
tab(state, count, -1);
|
|
break;
|
|
|
|
case 0x60: // HPA - ECMA-48 8.3.57
|
|
col = CSI_ARG_OR(args[0], 1);
|
|
state->pos.col = col - 1;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x61: // HPR - ECMA-48 8.3.59
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
state->pos.col += count;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x62: { // REP - ECMA-48 8.3.103
|
|
const int row_width = THISROWWIDTH(state);
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
col = state->pos.col + count;
|
|
UBOUND(col, row_width);
|
|
schar_T sc = schar_from_buf(state->grapheme_buf, state->grapheme_len);
|
|
while (state->pos.col < col) {
|
|
putglyph(state, sc, state->combine_width, state->pos);
|
|
state->pos.col += state->combine_width;
|
|
}
|
|
if (state->pos.col + state->combine_width >= row_width) {
|
|
if (state->mode.autowrap) {
|
|
state->at_phantom = 1;
|
|
cancel_phantom = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 0x63: // DA - ECMA-48 8.3.24
|
|
val = CSI_ARG_OR(args[0], 0);
|
|
if (val == 0) {
|
|
// DEC VT100 response
|
|
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%sc", vterm_primary_device_attr);
|
|
}
|
|
break;
|
|
|
|
case LEADER('>', 0x63): // DEC secondary Device Attributes
|
|
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0);
|
|
break;
|
|
|
|
case 0x64: // VPA - ECMA-48 8.3.158
|
|
row = CSI_ARG_OR(args[0], 1);
|
|
state->pos.row = row - 1;
|
|
if (state->mode.origin) {
|
|
state->pos.row += state->scrollregion_top;
|
|
}
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x65: // VPR - ECMA-48 8.3.160
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
state->pos.row += count;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x66: // HVP - ECMA-48 8.3.63
|
|
row = CSI_ARG_OR(args[0], 1);
|
|
col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
|
|
// zero-based
|
|
state->pos.row = row - 1;
|
|
state->pos.col = col - 1;
|
|
if (state->mode.origin) {
|
|
state->pos.row += state->scrollregion_top;
|
|
state->pos.col += SCROLLREGION_LEFT(state);
|
|
}
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x67: // TBC - ECMA-48 8.3.154
|
|
val = CSI_ARG_OR(args[0], 0);
|
|
|
|
switch (val) {
|
|
case 0:
|
|
clear_col_tabstop(state, state->pos.col);
|
|
break;
|
|
case 3:
|
|
case 5:
|
|
for (col = 0; col < state->cols; col++) {
|
|
clear_col_tabstop(state, col);
|
|
}
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
case 4:
|
|
break;
|
|
// TODO(vterm): 1, 2 and 4 aren't meaningful yet without line tab stops
|
|
default:
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case 0x68: // SM - ECMA-48 8.3.125
|
|
if (!CSI_ARG_IS_MISSING(args[0])) {
|
|
set_mode(state, CSI_ARG(args[0]), 1);
|
|
}
|
|
break;
|
|
|
|
case LEADER('?', 0x68): // DEC private mode set
|
|
for (int i = 0; i < argcount; i++) {
|
|
if (!CSI_ARG_IS_MISSING(args[i])) {
|
|
set_dec_mode(state, CSI_ARG(args[i]), 1);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x6a: // HPB - ECMA-48 8.3.58
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
state->pos.col -= count;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x6b: // VPB - ECMA-48 8.3.159
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
state->pos.row -= count;
|
|
state->at_phantom = 0;
|
|
break;
|
|
|
|
case 0x6c: // RM - ECMA-48 8.3.106
|
|
if (!CSI_ARG_IS_MISSING(args[0])) {
|
|
set_mode(state, CSI_ARG(args[0]), 0);
|
|
}
|
|
break;
|
|
|
|
case LEADER('?', 0x6c): // DEC private mode reset
|
|
for (int i = 0; i < argcount; i++) {
|
|
if (!CSI_ARG_IS_MISSING(args[i])) {
|
|
set_dec_mode(state, CSI_ARG(args[i]), 0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x6d: // SGR - ECMA-48 8.3.117
|
|
vterm_state_setpen(state, args, argcount);
|
|
break;
|
|
|
|
case LEADER('?', 0x6d): // DECSGR
|
|
// No actual DEC terminal recognised these, but some printers did. These are alternative ways to
|
|
// request subscript/superscript/off
|
|
for (int argi = 0; argi < argcount; argi++) {
|
|
long arg;
|
|
switch (arg = CSI_ARG(args[argi])) {
|
|
case 4: // Superscript on
|
|
arg = 73;
|
|
vterm_state_setpen(state, &arg, 1);
|
|
break;
|
|
case 5: // Subscript on
|
|
arg = 74;
|
|
vterm_state_setpen(state, &arg, 1);
|
|
break;
|
|
case 24: // Super+subscript off
|
|
arg = 75;
|
|
vterm_state_setpen(state, &arg, 1);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x6e: // DSR - ECMA-48 8.3.35
|
|
case LEADER('?', 0x6e): // DECDSR
|
|
val = CSI_ARG_OR(args[0], 0);
|
|
|
|
{
|
|
char *qmark = (leader_byte == '?') ? "?" : "";
|
|
bool dark = false;
|
|
|
|
switch (val) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
// ignore - these are replies
|
|
break;
|
|
case 5:
|
|
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark);
|
|
break;
|
|
case 6: // CPR - cursor position report
|
|
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1,
|
|
state->pos.col + 1);
|
|
break;
|
|
case 996:
|
|
if (state->callbacks && state->callbacks->theme) {
|
|
if (state->callbacks->theme(&dark, state->cbdata)) {
|
|
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?997;%cn", dark ? '1' : '2');
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case INTERMED('!', 0x70): // DECSTR - DEC soft terminal reset
|
|
vterm_state_reset(state, 0);
|
|
break;
|
|
|
|
case LEADER('?', INTERMED('$', 0x70)):
|
|
request_dec_mode(state, CSI_ARG(args[0]));
|
|
break;
|
|
|
|
case LEADER('>', 0x71): // XTVERSION - xterm query version string
|
|
request_version_string(state);
|
|
break;
|
|
|
|
case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape
|
|
val = CSI_ARG_OR(args[0], 1);
|
|
|
|
switch (val) {
|
|
case 0:
|
|
case 1:
|
|
settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
|
|
settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
|
|
break;
|
|
case 2:
|
|
settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
|
|
settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
|
|
break;
|
|
case 3:
|
|
settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
|
|
settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
|
|
break;
|
|
case 4:
|
|
settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
|
|
settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
|
|
break;
|
|
case 5:
|
|
settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
|
|
settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
|
|
break;
|
|
case 6:
|
|
settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
|
|
settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute
|
|
val = CSI_ARG_OR(args[0], 0);
|
|
|
|
switch (val) {
|
|
case 0:
|
|
case 2:
|
|
state->protected_cell = 0;
|
|
break;
|
|
case 1:
|
|
state->protected_cell = 1;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x72: // DECSTBM - DEC custom
|
|
state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1;
|
|
state->scrollregion_bottom = argcount < 2
|
|
|| CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
|
|
LBOUND(state->scrollregion_top, 0);
|
|
UBOUND(state->scrollregion_top, state->rows);
|
|
LBOUND(state->scrollregion_bottom, -1);
|
|
if (state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) {
|
|
state->scrollregion_bottom = -1;
|
|
} else {
|
|
UBOUND(state->scrollregion_bottom, state->rows);
|
|
}
|
|
|
|
if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
|
|
// Invalid
|
|
state->scrollregion_top = 0;
|
|
state->scrollregion_bottom = -1;
|
|
}
|
|
|
|
// Setting the scrolling region restores the cursor to the home position
|
|
state->pos.row = 0;
|
|
state->pos.col = 0;
|
|
if (state->mode.origin) {
|
|
state->pos.row += state->scrollregion_top;
|
|
state->pos.col += SCROLLREGION_LEFT(state);
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x73: // DECSLRM - DEC custom
|
|
// Always allow setting these margins, just they won't take effect without DECVSSM
|
|
state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1;
|
|
state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
|
|
LBOUND(state->scrollregion_left, 0);
|
|
UBOUND(state->scrollregion_left, state->cols);
|
|
LBOUND(state->scrollregion_right, -1);
|
|
if (state->scrollregion_left == 0 && state->scrollregion_right == state->cols) {
|
|
state->scrollregion_right = -1;
|
|
} else {
|
|
UBOUND(state->scrollregion_right, state->cols);
|
|
}
|
|
|
|
if (state->scrollregion_right > -1
|
|
&& state->scrollregion_right <= state->scrollregion_left) {
|
|
// Invalid
|
|
state->scrollregion_left = 0;
|
|
state->scrollregion_right = -1;
|
|
}
|
|
|
|
// Setting the scrolling region restores the cursor to the home position
|
|
state->pos.row = 0;
|
|
state->pos.col = 0;
|
|
if (state->mode.origin) {
|
|
state->pos.row += state->scrollregion_top;
|
|
state->pos.col += SCROLLREGION_LEFT(state);
|
|
}
|
|
|
|
break;
|
|
|
|
case LEADER('?', 0x75): // Kitty query
|
|
request_key_encoding_flags(state);
|
|
break;
|
|
|
|
case LEADER('>', 0x75): // Kitty push flags
|
|
push_key_encoding_flags(state, CSI_ARG_OR(args[0], 0));
|
|
break;
|
|
|
|
case LEADER('<', 0x75): // Kitty pop flags
|
|
pop_key_encoding_flags(state, CSI_ARG_OR(args[0], 1));
|
|
break;
|
|
|
|
case LEADER('=', 0x75): // Kitty set flags
|
|
val = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
|
|
set_key_encoding_flags(state, CSI_ARG_OR(args[0], 0), val);
|
|
break;
|
|
|
|
case INTERMED('\'', 0x7D): // DECIC
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
|
|
if (!is_cursor_in_scrollregion(state)) {
|
|
break;
|
|
}
|
|
|
|
rect.start_row = state->scrollregion_top;
|
|
rect.end_row = SCROLLREGION_BOTTOM(state);
|
|
rect.start_col = state->pos.col;
|
|
rect.end_col = SCROLLREGION_RIGHT(state);
|
|
|
|
scroll(state, rect, 0, -count);
|
|
|
|
break;
|
|
|
|
case INTERMED('\'', 0x7E): // DECDC
|
|
count = CSI_ARG_COUNT(args[0]);
|
|
|
|
if (!is_cursor_in_scrollregion(state)) {
|
|
break;
|
|
}
|
|
|
|
rect.start_row = state->scrollregion_top;
|
|
rect.end_row = SCROLLREGION_BOTTOM(state);
|
|
rect.start_col = state->pos.col;
|
|
rect.end_col = SCROLLREGION_RIGHT(state);
|
|
|
|
scroll(state, rect, 0, count);
|
|
|
|
break;
|
|
|
|
default:
|
|
if (state->fallbacks && state->fallbacks->csi) {
|
|
if ((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (state->mode.origin) {
|
|
LBOUND(state->pos.row, state->scrollregion_top);
|
|
UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state) - 1);
|
|
LBOUND(state->pos.col, SCROLLREGION_LEFT(state));
|
|
UBOUND(state->pos.col, SCROLLREGION_RIGHT(state) - 1);
|
|
} else {
|
|
LBOUND(state->pos.row, 0);
|
|
UBOUND(state->pos.row, state->rows - 1);
|
|
LBOUND(state->pos.col, 0);
|
|
UBOUND(state->pos.col, THISROWWIDTH(state) - 1);
|
|
}
|
|
|
|
updatecursor(state, &oldpos, cancel_phantom);
|
|
|
|
#ifdef DEBUG
|
|
if (state->pos.row < 0 || state->pos.row >= state->rows
|
|
|| state->pos.col < 0 || state->pos.col >= state->cols) {
|
|
fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n",
|
|
command, state->pos.row, state->pos.col);
|
|
abort();
|
|
}
|
|
|
|
if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
|
|
fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n",
|
|
command, SCROLLREGION_BOTTOM(state), state->scrollregion_top);
|
|
abort();
|
|
}
|
|
|
|
if (SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) {
|
|
fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n",
|
|
command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state));
|
|
abort();
|
|
}
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
static uint8_t unbase64one(char c)
|
|
{
|
|
if (c >= 'A' && c <= 'Z') {
|
|
return (uint8_t)c - 'A';
|
|
} else if (c >= 'a' && c <= 'z') {
|
|
return (uint8_t)c - 'a' + 26;
|
|
} else if (c >= '0' && c <= '9') {
|
|
return (uint8_t)c - '0' + 52;
|
|
} else if (c == '+') {
|
|
return 62;
|
|
} else if (c == '/') {
|
|
return 63;
|
|
}
|
|
|
|
return 0xFF;
|
|
}
|
|
|
|
static void osc_selection(VTermState *state, VTermStringFragment frag)
|
|
{
|
|
if (frag.initial) {
|
|
state->tmp.selection.mask = 0;
|
|
state->tmp.selection.state = SELECTION_INITIAL;
|
|
}
|
|
|
|
while (!state->tmp.selection.state && frag.len) {
|
|
// Parse selection parameter
|
|
switch (frag.str[0]) {
|
|
case 'c':
|
|
state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD;
|
|
break;
|
|
case 'p':
|
|
state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY;
|
|
break;
|
|
case 'q':
|
|
state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY;
|
|
break;
|
|
case 's':
|
|
state->tmp.selection.mask |= VTERM_SELECTION_SELECT;
|
|
break;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0'));
|
|
break;
|
|
|
|
case ';':
|
|
state->tmp.selection.state = SELECTION_SELECTED;
|
|
if (!state->tmp.selection.mask) {
|
|
state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
frag.str++;
|
|
frag.len--;
|
|
}
|
|
|
|
if (!frag.len) {
|
|
// Clear selection if we're already finished but didn't do anything
|
|
if (frag.final && state->selection.callbacks->set) {
|
|
(*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){
|
|
.str = NULL,
|
|
.len = 0,
|
|
.initial = state->tmp.selection.state != SELECTION_SET,
|
|
.final = true,
|
|
}, state->selection.user);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (state->tmp.selection.state == SELECTION_SELECTED) {
|
|
if (frag.str[0] == '?') {
|
|
state->tmp.selection.state = SELECTION_QUERY;
|
|
} else {
|
|
state->tmp.selection.state = SELECTION_SET_INITIAL;
|
|
state->tmp.selection.recvpartial = 0;
|
|
}
|
|
}
|
|
|
|
if (state->tmp.selection.state == SELECTION_QUERY) {
|
|
if (state->selection.callbacks->query) {
|
|
(*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (state->tmp.selection.state == SELECTION_INVALID) {
|
|
return;
|
|
}
|
|
|
|
if (state->selection.callbacks->set) {
|
|
size_t bufcur = 0;
|
|
char *buffer = state->selection.buffer;
|
|
|
|
uint32_t x = 0; // Current decoding value
|
|
int n = 0; // Number of sextets consumed
|
|
|
|
if (state->tmp.selection.recvpartial) {
|
|
n = state->tmp.selection.recvpartial >> 24;
|
|
x = state->tmp.selection.recvpartial & 0x03FFFF; // could be up to 18 bits of state in here
|
|
|
|
state->tmp.selection.recvpartial = 0;
|
|
}
|
|
|
|
while ((state->selection.buflen - bufcur) >= 3 && frag.len) {
|
|
if (frag.str[0] == '=') {
|
|
if (n == 2) {
|
|
buffer[0] = (char)(x >> 4 & 0xFF);
|
|
buffer += 1, bufcur += 1;
|
|
}
|
|
if (n == 3) {
|
|
buffer[0] = (char)(x >> 10 & 0xFF);
|
|
buffer[1] = (char)(x >> 2 & 0xFF);
|
|
buffer += 2, bufcur += 2;
|
|
}
|
|
|
|
while (frag.len && frag.str[0] == '=') {
|
|
frag.str++, frag.len--;
|
|
}
|
|
|
|
n = 0;
|
|
} else {
|
|
uint8_t b = unbase64one(frag.str[0]);
|
|
if (b == 0xFF) {
|
|
DEBUG_LOG("base64decode bad input %02X\n", (uint8_t)frag.str[0]);
|
|
|
|
state->tmp.selection.state = SELECTION_INVALID;
|
|
if (state->selection.callbacks->set) {
|
|
(*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){
|
|
.str = NULL,
|
|
.len = 0,
|
|
.initial = true,
|
|
.final = true,
|
|
}, state->selection.user);
|
|
}
|
|
break;
|
|
}
|
|
|
|
x = (x << 6) | b;
|
|
n++;
|
|
frag.str++, frag.len--;
|
|
|
|
if (n == 4) {
|
|
buffer[0] = (char)(x >> 16 & 0xFF);
|
|
buffer[1] = (char)(x >> 8 & 0xFF);
|
|
buffer[2] = (char)(x >> 0 & 0xFF);
|
|
|
|
buffer += 3, bufcur += 3;
|
|
x = 0;
|
|
n = 0;
|
|
}
|
|
}
|
|
|
|
if (!frag.len || (state->selection.buflen - bufcur) < 3) {
|
|
if (bufcur) {
|
|
(*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){
|
|
.str = state->selection.buffer,
|
|
.len = bufcur,
|
|
.initial = state->tmp.selection.state == SELECTION_SET_INITIAL,
|
|
.final = frag.final && !frag.len,
|
|
}, state->selection.user);
|
|
state->tmp.selection.state = SELECTION_SET;
|
|
}
|
|
|
|
buffer = state->selection.buffer;
|
|
bufcur = 0;
|
|
}
|
|
}
|
|
|
|
if (n) {
|
|
state->tmp.selection.recvpartial = (uint32_t)(n << 24) | x;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int on_osc(int command, VTermStringFragment frag, void *user)
|
|
{
|
|
VTermState *state = user;
|
|
|
|
switch (command) {
|
|
case 0:
|
|
settermprop_string(state, VTERM_PROP_ICONNAME, frag);
|
|
settermprop_string(state, VTERM_PROP_TITLE, frag);
|
|
break;
|
|
|
|
case 1:
|
|
settermprop_string(state, VTERM_PROP_ICONNAME, frag);
|
|
break;
|
|
|
|
case 2:
|
|
settermprop_string(state, VTERM_PROP_TITLE, frag);
|
|
break;
|
|
|
|
case 52:
|
|
if (state->selection.callbacks) {
|
|
osc_selection(state, frag);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (state->fallbacks && state->fallbacks->osc) {
|
|
if ((*state->fallbacks->osc)(command, frag, state->fbdata)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void request_status_string(VTermState *state, VTermStringFragment frag)
|
|
{
|
|
VTerm *vt = state->vt;
|
|
|
|
char *tmp = state->tmp.decrqss;
|
|
|
|
if (frag.initial) {
|
|
tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
|
|
}
|
|
|
|
size_t i = 0;
|
|
while (i < sizeof(state->tmp.decrqss) - 1 && tmp[i]) {
|
|
i++;
|
|
}
|
|
while (i < sizeof(state->tmp.decrqss) - 1 && frag.len--) {
|
|
tmp[i++] = (frag.str++)[0];
|
|
}
|
|
tmp[i] = 0;
|
|
|
|
if (!frag.final) {
|
|
return;
|
|
}
|
|
|
|
switch (tmp[0] | tmp[1] << 8 | tmp[2] << 16) {
|
|
case 'm': {
|
|
// Query SGR
|
|
long args[20];
|
|
int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
|
|
size_t cur = 0;
|
|
|
|
cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
|
|
vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ...
|
|
if (cur >= vt->tmpbuffer_len) {
|
|
return;
|
|
}
|
|
|
|
for (int argi = 0; argi < argc; argi++) {
|
|
cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
|
|
argi == argc - 1 ? "%ld"
|
|
: CSI_ARG_HAS_MORE(args[argi]) ? "%ld:"
|
|
: "%ld;",
|
|
CSI_ARG(args[argi]));
|
|
if (cur >= vt->tmpbuffer_len) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
|
|
vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST
|
|
if (cur >= vt->tmpbuffer_len) {
|
|
return;
|
|
}
|
|
|
|
vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
|
|
return;
|
|
}
|
|
|
|
case 'r':
|
|
// Query DECSTBM
|
|
vterm_push_output_sprintf_str(vt, C1_DCS, true,
|
|
"1$r%d;%dr", state->scrollregion_top + 1,
|
|
SCROLLREGION_BOTTOM(state));
|
|
return;
|
|
|
|
case 's':
|
|
// Query DECSLRM
|
|
vterm_push_output_sprintf_str(vt, C1_DCS, true,
|
|
"1$r%d;%ds", SCROLLREGION_LEFT(state) + 1,
|
|
SCROLLREGION_RIGHT(state));
|
|
return;
|
|
|
|
case ' '|('q' << 8): {
|
|
// Query DECSCUSR
|
|
int reply = 0;
|
|
switch (state->mode.cursor_shape) {
|
|
case VTERM_PROP_CURSORSHAPE_BLOCK:
|
|
reply = 2; break;
|
|
case VTERM_PROP_CURSORSHAPE_UNDERLINE:
|
|
reply = 4; break;
|
|
case VTERM_PROP_CURSORSHAPE_BAR_LEFT:
|
|
reply = 6; break;
|
|
}
|
|
if (state->mode.cursor_blink) {
|
|
reply--;
|
|
}
|
|
vterm_push_output_sprintf_str(vt, C1_DCS, true,
|
|
"1$r%d q", reply);
|
|
return;
|
|
}
|
|
|
|
case '\"'|('q' << 8):
|
|
// Query DECSCA
|
|
vterm_push_output_sprintf_str(vt, C1_DCS, true,
|
|
"1$r%d\"q", state->protected_cell ? 1 : 2);
|
|
return;
|
|
}
|
|
|
|
vterm_push_output_sprintf_str(state->vt, C1_DCS, true, "0$r");
|
|
}
|
|
|
|
static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
|
|
{
|
|
VTermState *state = user;
|
|
|
|
if (commandlen == 2 && strneq(command, "$q", 2)) {
|
|
request_status_string(state, frag);
|
|
return 1;
|
|
} else if (state->fallbacks && state->fallbacks->dcs) {
|
|
if ((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command);
|
|
return 0;
|
|
}
|
|
|
|
static int on_apc(VTermStringFragment frag, void *user)
|
|
{
|
|
VTermState *state = user;
|
|
|
|
if (state->fallbacks && state->fallbacks->apc) {
|
|
if ((*state->fallbacks->apc)(frag, state->fbdata)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// No DEBUG_LOG because all APCs are unhandled
|
|
return 0;
|
|
}
|
|
|
|
static int on_pm(VTermStringFragment frag, void *user)
|
|
{
|
|
VTermState *state = user;
|
|
|
|
if (state->fallbacks && state->fallbacks->pm) {
|
|
if ((*state->fallbacks->pm)(frag, state->fbdata)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// No DEBUG_LOG because all PMs are unhandled
|
|
return 0;
|
|
}
|
|
|
|
static int on_sos(VTermStringFragment frag, void *user)
|
|
{
|
|
VTermState *state = user;
|
|
|
|
if (state->fallbacks && state->fallbacks->sos) {
|
|
if ((*state->fallbacks->sos)(frag, state->fbdata)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// No DEBUG_LOG because all SOSs are unhandled
|
|
return 0;
|
|
}
|
|
|
|
static int on_resize(int rows, int cols, void *user)
|
|
{
|
|
VTermState *state = user;
|
|
VTermPos oldpos = state->pos;
|
|
|
|
if (cols != state->cols) {
|
|
uint8_t *newtabstops = vterm_allocator_malloc(state->vt, ((size_t)cols + 7) / 8);
|
|
|
|
// TODO(vterm): This can all be done much more efficiently bytewise
|
|
int col;
|
|
for (col = 0; col < state->cols && col < cols; col++) {
|
|
uint8_t mask = (uint8_t)(1 << (col & 7));
|
|
if (state->tabstops[col >> 3] & mask) {
|
|
newtabstops[col >> 3] |= mask;
|
|
} else {
|
|
newtabstops[col >> 3] &= ~mask;
|
|
}
|
|
}
|
|
|
|
for (; col < cols; col++) {
|
|
uint8_t mask = (uint8_t)(1 << (col & 7));
|
|
if (col % 8 == 0) {
|
|
newtabstops[col >> 3] |= mask;
|
|
} else {
|
|
newtabstops[col >> 3] &= ~mask;
|
|
}
|
|
}
|
|
|
|
vterm_allocator_free(state->vt, state->tabstops);
|
|
state->tabstops = newtabstops;
|
|
}
|
|
|
|
state->rows = rows;
|
|
state->cols = cols;
|
|
|
|
if (state->scrollregion_bottom > -1) {
|
|
UBOUND(state->scrollregion_bottom, state->rows);
|
|
}
|
|
if (state->scrollregion_right > -1) {
|
|
UBOUND(state->scrollregion_right, state->cols);
|
|
}
|
|
|
|
VTermStateFields fields = {
|
|
.pos = state->pos,
|
|
.lineinfos = {[0] = state->lineinfos[0], [1] = state->lineinfos[1] },
|
|
};
|
|
|
|
if (state->callbacks && state->callbacks->resize) {
|
|
(*state->callbacks->resize)(rows, cols, &fields, state->cbdata);
|
|
state->pos = fields.pos;
|
|
|
|
state->lineinfos[0] = fields.lineinfos[0];
|
|
state->lineinfos[1] = fields.lineinfos[1];
|
|
} else {
|
|
if (rows != state->rows) {
|
|
for (int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) {
|
|
VTermLineInfo *oldlineinfo = state->lineinfos[bufidx];
|
|
if (!oldlineinfo) {
|
|
continue;
|
|
}
|
|
|
|
VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt,
|
|
(size_t)rows * sizeof(VTermLineInfo));
|
|
|
|
int row;
|
|
for (row = 0; row < state->rows && row < rows; row++) {
|
|
newlineinfo[row] = oldlineinfo[row];
|
|
}
|
|
|
|
for (; row < rows; row++) {
|
|
newlineinfo[row] = (VTermLineInfo){
|
|
.doublewidth = 0,
|
|
};
|
|
}
|
|
|
|
vterm_allocator_free(state->vt, state->lineinfos[bufidx]);
|
|
state->lineinfos[bufidx] = newlineinfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
|
|
|
|
if (state->at_phantom && state->pos.col < cols - 1) {
|
|
state->at_phantom = 0;
|
|
state->pos.col++;
|
|
}
|
|
|
|
if (state->pos.row < 0) {
|
|
state->pos.row = 0;
|
|
}
|
|
if (state->pos.row >= rows) {
|
|
state->pos.row = rows - 1;
|
|
}
|
|
if (state->pos.col < 0) {
|
|
state->pos.col = 0;
|
|
}
|
|
if (state->pos.col >= cols) {
|
|
state->pos.col = cols - 1;
|
|
}
|
|
|
|
updatecursor(state, &oldpos, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static const VTermParserCallbacks parser_callbacks = {
|
|
.text = on_text,
|
|
.control = on_control,
|
|
.escape = on_escape,
|
|
.csi = on_csi,
|
|
.osc = on_osc,
|
|
.dcs = on_dcs,
|
|
.apc = on_apc,
|
|
.pm = on_pm,
|
|
.sos = on_sos,
|
|
.resize = on_resize,
|
|
};
|
|
|
|
VTermState *vterm_obtain_state(VTerm *vt)
|
|
{
|
|
if (vt->state) {
|
|
return vt->state;
|
|
}
|
|
|
|
VTermState *state = vterm_state_new(vt);
|
|
vt->state = state;
|
|
|
|
vterm_parser_set_callbacks(vt, &parser_callbacks, state);
|
|
|
|
return state;
|
|
}
|
|
|
|
void vterm_state_reset(VTermState *state, int hard)
|
|
{
|
|
state->scrollregion_top = 0;
|
|
state->scrollregion_bottom = -1;
|
|
state->scrollregion_left = 0;
|
|
state->scrollregion_right = -1;
|
|
|
|
state->mode.keypad = 0;
|
|
state->mode.cursor = 0;
|
|
state->mode.autowrap = 1;
|
|
state->mode.insert = 0;
|
|
state->mode.newline = 0;
|
|
state->mode.alt_screen = 0;
|
|
state->mode.origin = 0;
|
|
state->mode.leftrightmargin = 0;
|
|
state->mode.bracketpaste = 0;
|
|
state->mode.report_focus = 0;
|
|
|
|
state->mouse_flags = 0;
|
|
|
|
state->vt->mode.ctrl8bit = 0;
|
|
|
|
for (int col = 0; col < state->cols; col++) {
|
|
if (col % 8 == 0) {
|
|
set_col_tabstop(state, col);
|
|
} else {
|
|
clear_col_tabstop(state, col);
|
|
}
|
|
}
|
|
|
|
for (int row = 0; row < state->rows; row++) {
|
|
set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
|
|
}
|
|
|
|
if (state->callbacks && state->callbacks->initpen) {
|
|
(*state->callbacks->initpen)(state->cbdata);
|
|
}
|
|
|
|
vterm_state_resetpen(state);
|
|
|
|
VTermEncoding *default_enc = state->vt->mode.utf8
|
|
? vterm_lookup_encoding(ENC_UTF8, 'u')
|
|
: vterm_lookup_encoding(ENC_SINGLE_94, 'B');
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
state->encoding[i].enc = default_enc;
|
|
if (default_enc->init) {
|
|
(*default_enc->init)(default_enc, state->encoding[i].data);
|
|
}
|
|
}
|
|
|
|
state->gl_set = 0;
|
|
state->gr_set = 1;
|
|
state->gsingle_set = 0;
|
|
|
|
state->protected_cell = 0;
|
|
|
|
// Initialise the props
|
|
settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1);
|
|
settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
|
|
settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
|
|
|
|
if (hard) {
|
|
state->pos.row = 0;
|
|
state->pos.col = 0;
|
|
state->at_phantom = 0;
|
|
|
|
VTermRect rect = { 0, state->rows, 0, state->cols };
|
|
erase(state, rect, 0);
|
|
}
|
|
}
|
|
|
|
void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user)
|
|
{
|
|
if (callbacks) {
|
|
state->callbacks = callbacks;
|
|
state->cbdata = user;
|
|
|
|
if (state->callbacks && state->callbacks->initpen) {
|
|
(*state->callbacks->initpen)(state->cbdata);
|
|
}
|
|
} else {
|
|
state->callbacks = NULL;
|
|
state->cbdata = NULL;
|
|
}
|
|
}
|
|
|
|
void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks,
|
|
void *user)
|
|
{
|
|
if (fallbacks) {
|
|
state->fallbacks = fallbacks;
|
|
state->fbdata = user;
|
|
} else {
|
|
state->fallbacks = NULL;
|
|
state->fbdata = NULL;
|
|
}
|
|
}
|
|
|
|
int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val)
|
|
{
|
|
// Only store the new value of the property if usercode said it was happy. This is especially
|
|
// important for altscreen switching
|
|
if (state->callbacks && state->callbacks->settermprop) {
|
|
if (!(*state->callbacks->settermprop)(prop, val, state->cbdata)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
switch (prop) {
|
|
case VTERM_PROP_TITLE:
|
|
case VTERM_PROP_ICONNAME:
|
|
// we don't store these, just transparently pass through
|
|
return 1;
|
|
case VTERM_PROP_CURSORVISIBLE:
|
|
state->mode.cursor_visible = (unsigned)val->boolean;
|
|
return 1;
|
|
case VTERM_PROP_CURSORBLINK:
|
|
state->mode.cursor_blink = (unsigned)val->boolean;
|
|
return 1;
|
|
case VTERM_PROP_CURSORSHAPE:
|
|
state->mode.cursor_shape = (unsigned)val->number;
|
|
return 1;
|
|
case VTERM_PROP_REVERSE:
|
|
state->mode.screen = (unsigned)val->boolean;
|
|
return 1;
|
|
case VTERM_PROP_ALTSCREEN:
|
|
state->mode.alt_screen = (unsigned)val->boolean;
|
|
state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
|
|
if (state->mode.alt_screen) {
|
|
VTermRect rect = {
|
|
.start_row = 0,
|
|
.start_col = 0,
|
|
.end_row = state->rows,
|
|
.end_col = state->cols,
|
|
};
|
|
erase(state, rect, 0);
|
|
}
|
|
return 1;
|
|
case VTERM_PROP_MOUSE:
|
|
state->mouse_flags = 0;
|
|
if (val->number) {
|
|
state->mouse_flags |= MOUSE_WANT_CLICK;
|
|
}
|
|
if (val->number == VTERM_PROP_MOUSE_DRAG) {
|
|
state->mouse_flags |= MOUSE_WANT_DRAG;
|
|
}
|
|
if (val->number == VTERM_PROP_MOUSE_MOVE) {
|
|
state->mouse_flags |= MOUSE_WANT_MOVE;
|
|
}
|
|
return 1;
|
|
case VTERM_PROP_FOCUSREPORT:
|
|
state->mode.report_focus = (unsigned)val->boolean;
|
|
return 1;
|
|
case VTERM_PROP_THEMEUPDATES:
|
|
state->mode.theme_updates = (unsigned)val->boolean;
|
|
return 1;
|
|
|
|
case VTERM_N_PROPS:
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void vterm_state_focus_in(VTermState *state)
|
|
{
|
|
if (state->mode.report_focus) {
|
|
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I");
|
|
}
|
|
}
|
|
|
|
void vterm_state_focus_out(VTermState *state)
|
|
{
|
|
if (state->mode.report_focus) {
|
|
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O");
|
|
}
|
|
}
|
|
|
|
const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)
|
|
{
|
|
return state->lineinfo + row;
|
|
}
|
|
|
|
void vterm_state_set_selection_callbacks(VTermState *state,
|
|
const VTermSelectionCallbacks *callbacks, void *user,
|
|
char *buffer, size_t buflen)
|
|
{
|
|
if (buflen && !buffer) {
|
|
buffer = vterm_allocator_malloc(state->vt, buflen);
|
|
}
|
|
|
|
state->selection.callbacks = callbacks;
|
|
state->selection.user = user;
|
|
state->selection.buffer = buffer;
|
|
state->selection.buflen = buflen;
|
|
}
|