More work on the screen replacement
The first set of tests now pass
This commit is contained in:
parent
5dc0b9af13
commit
9a7b4263e0
@ -51,6 +51,8 @@ PyInit_fast_data_types(void) {
|
|||||||
PyModule_AddIntMacro(m, CURSOR_BLOCK);
|
PyModule_AddIntMacro(m, CURSOR_BLOCK);
|
||||||
PyModule_AddIntMacro(m, CURSOR_BEAM);
|
PyModule_AddIntMacro(m, CURSOR_BEAM);
|
||||||
PyModule_AddIntMacro(m, CURSOR_UNDERLINE);
|
PyModule_AddIntMacro(m, CURSOR_UNDERLINE);
|
||||||
|
PyModule_AddIntMacro(m, DECAWM);
|
||||||
|
PyModule_AddIntMacro(m, IRM);
|
||||||
}
|
}
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
|
|||||||
@ -43,6 +43,8 @@ typedef unsigned int index_type;
|
|||||||
#define CURSOR_BLOCK 1
|
#define CURSOR_BLOCK 1
|
||||||
#define CURSOR_BEAM 2
|
#define CURSOR_BEAM 2
|
||||||
#define CURSOR_UNDERLINE 3
|
#define CURSOR_UNDERLINE 3
|
||||||
|
#define FG 1
|
||||||
|
#define BG 2
|
||||||
|
|
||||||
#define CURSOR_TO_ATTRS(c, w) \
|
#define CURSOR_TO_ATTRS(c, w) \
|
||||||
((w) | (((c->decoration & 3) << DECORATION_SHIFT) | ((c->bold & 1) << BOLD_SHIFT) | \
|
((w) | (((c->decoration & 3) << DECORATION_SHIFT) | ((c->bold & 1) << BOLD_SHIFT) | \
|
||||||
@ -233,6 +235,7 @@ typedef struct {
|
|||||||
Cursor *cursor;
|
Cursor *cursor;
|
||||||
PyObject *savepoints, *main_savepoints, *alt_savepoints, *callbacks;
|
PyObject *savepoints, *main_savepoints, *alt_savepoints, *callbacks;
|
||||||
LineBuf *linebuf, *main_linebuf, *alt_linebuf;
|
LineBuf *linebuf, *main_linebuf, *alt_linebuf;
|
||||||
|
bool *tabstops;
|
||||||
ChangeTracker *change_tracker;
|
ChangeTracker *change_tracker;
|
||||||
ScreenModes modes;
|
ScreenModes modes;
|
||||||
|
|
||||||
@ -243,12 +246,6 @@ typedef struct {
|
|||||||
} Screen;
|
} Screen;
|
||||||
PyTypeObject Screen_Type;
|
PyTypeObject Screen_Type;
|
||||||
|
|
||||||
Line* alloc_line();
|
|
||||||
Cursor* alloc_cursor();
|
|
||||||
LineBuf* alloc_linebuf();
|
|
||||||
ChangeTracker* alloc_change_tracker();
|
|
||||||
Savepoint* alloc_savepoint();
|
|
||||||
|
|
||||||
#define left_shift_line(line, at, num) \
|
#define left_shift_line(line, at, num) \
|
||||||
for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \
|
for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \
|
||||||
COPY_CELL(line, __i__ + (num), line, __i__) \
|
COPY_CELL(line, __i__ + (num), line, __i__) \
|
||||||
@ -257,6 +254,11 @@ Savepoint* alloc_savepoint();
|
|||||||
|
|
||||||
|
|
||||||
// Global functions
|
// Global functions
|
||||||
|
Line* alloc_line();
|
||||||
|
Cursor* alloc_cursor();
|
||||||
|
LineBuf* alloc_linebuf(unsigned int, unsigned int);
|
||||||
|
ChangeTracker* alloc_change_tracker(unsigned int, unsigned int);
|
||||||
|
Savepoint* alloc_savepoint();
|
||||||
int init_LineBuf(PyObject *);
|
int init_LineBuf(PyObject *);
|
||||||
int init_Cursor(PyObject *);
|
int init_Cursor(PyObject *);
|
||||||
int init_Line(PyObject *);
|
int init_Line(PyObject *);
|
||||||
@ -279,14 +281,23 @@ Cursor* cursor_copy(Cursor*);
|
|||||||
void linebuf_clear(LineBuf *);
|
void linebuf_clear(LineBuf *);
|
||||||
bool screen_restore_cursor(Screen *);
|
bool screen_restore_cursor(Screen *);
|
||||||
bool screen_save_cursor(Screen *);
|
bool screen_save_cursor(Screen *);
|
||||||
bool screen_cursor_position(Screen*, unsigned int, unsigned int);
|
void screen_cursor_position(Screen*, unsigned int, unsigned int);
|
||||||
bool screen_erase_in_display(Screen *, unsigned int, bool);
|
void screen_erase_in_display(Screen *, unsigned int, bool);
|
||||||
bool screen_draw(Screen *screen, uint8_t *buf, unsigned int buflen);
|
void screen_draw(Screen *screen, uint8_t *buf, unsigned int buflen);
|
||||||
bool update_cell_range_data(SpriteMap *, Line *, unsigned int, unsigned int, ColorProfile *, const uint32_t, const uint32_t, unsigned int *);
|
bool update_cell_range_data(SpriteMap *, Line *, unsigned int, unsigned int, ColorProfile *, const uint32_t, const uint32_t, unsigned int *);
|
||||||
uint32_t to_color(ColorProfile *, uint32_t, uint32_t);
|
uint32_t to_color(ColorProfile *, uint32_t, uint32_t);
|
||||||
PyObject* line_text_at(char_type, combining_type);
|
PyObject* line_text_at(char_type, combining_type);
|
||||||
void linebuf_init_line(LineBuf *, index_type);
|
void linebuf_init_line(LineBuf *, index_type);
|
||||||
#define DECLARE_CH_SCREEN_HANDLER(name) bool screen_##name(Screen *screen, uint8_t ch);
|
void linebuf_index(LineBuf* self, index_type top, index_type bottom);
|
||||||
|
void linebuf_clear_line(LineBuf *self, index_type y);
|
||||||
|
void screen_ensure_bounds(Screen *self, bool use_margins);
|
||||||
|
void line_clear_text(Line *self, unsigned int at, unsigned int num, int ch);
|
||||||
|
void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char);
|
||||||
|
bool screen_toggle_screen_buffer(Screen *self);
|
||||||
|
void screen_normal_keypad_mode(Screen *self);
|
||||||
|
void screen_alternate_keypad_mode(Screen *self);
|
||||||
|
void screen_change_default_color(Screen *self, unsigned int which, uint32_t col);
|
||||||
|
#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen, uint8_t ch);
|
||||||
DECLARE_CH_SCREEN_HANDLER(bell)
|
DECLARE_CH_SCREEN_HANDLER(bell)
|
||||||
DECLARE_CH_SCREEN_HANDLER(backspace)
|
DECLARE_CH_SCREEN_HANDLER(backspace)
|
||||||
DECLARE_CH_SCREEN_HANDLER(tab)
|
DECLARE_CH_SCREEN_HANDLER(tab)
|
||||||
|
|||||||
@ -33,7 +33,7 @@ clear(LineBuf *self) {
|
|||||||
static PyObject *
|
static PyObject *
|
||||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||||
LineBuf *self;
|
LineBuf *self;
|
||||||
index_type xnum, ynum;
|
unsigned int xnum = 1, ynum = 1;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) return NULL;
|
if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) return NULL;
|
||||||
|
|
||||||
@ -202,19 +202,23 @@ copy_line_to(LineBuf *self, PyObject *args) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void linebuf_clear_line(LineBuf *self, index_type y) {
|
||||||
|
Line l;
|
||||||
|
INIT_LINE(self, &l, self->line_map[y]);
|
||||||
|
CLEAR_LINE(&l, 0, self->xnum);
|
||||||
|
self->continued_map[y] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
clear_line(LineBuf *self, PyObject *val) {
|
clear_line(LineBuf *self, PyObject *val) {
|
||||||
#define clear_line_doc "clear_line(y) -> Clear the specified line"
|
#define clear_line_doc "clear_line(y) -> Clear the specified line"
|
||||||
index_type y = (index_type)PyLong_AsUnsignedLong(val);
|
index_type y = (index_type)PyLong_AsUnsignedLong(val);
|
||||||
Line l;
|
|
||||||
if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; }
|
if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; }
|
||||||
INIT_LINE(self, &l, self->line_map[y]);
|
linebuf_clear_line(self, y);
|
||||||
CLEAR_LINE(&l, 0, self->xnum);
|
|
||||||
self->continued_map[y] = 0;
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void index_inner(LineBuf* self, index_type top, index_type bottom) {
|
void linebuf_index(LineBuf* self, index_type top, index_type bottom) {
|
||||||
index_type old_top = self->line_map[top];
|
index_type old_top = self->line_map[top];
|
||||||
bool old_cont = self->continued_map[top];
|
bool old_cont = self->continued_map[top];
|
||||||
for (index_type i = top; i < bottom; i++) {
|
for (index_type i = top; i < bottom; i++) {
|
||||||
@ -231,7 +235,7 @@ index(LineBuf *self, PyObject *args) {
|
|||||||
unsigned int top, bottom;
|
unsigned int top, bottom;
|
||||||
if (!PyArg_ParseTuple(args, "II", &top, &bottom)) return NULL;
|
if (!PyArg_ParseTuple(args, "II", &top, &bottom)) return NULL;
|
||||||
if (top >= self->ynum - 1 || bottom >= self->ynum || bottom <= top) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; }
|
if (top >= self->ynum - 1 || bottom >= self->ynum || bottom <= top) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; }
|
||||||
index_inner(self, top, bottom);
|
linebuf_index(self, top, bottom);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,7 +408,7 @@ static inline void copy_range(Line *src, index_type src_at, Line* dest, index_ty
|
|||||||
|
|
||||||
#define next_dest_line(continued) {\
|
#define next_dest_line(continued) {\
|
||||||
if (dest_y >= dest->ynum - 1) { \
|
if (dest_y >= dest->ynum - 1) { \
|
||||||
index_inner(dest, 0, dest->ynum - 1); \
|
linebuf_index(dest, 0, dest->ynum - 1); \
|
||||||
PyObject *l = create_line_copy_inner(dest, dest_y); \
|
PyObject *l = create_line_copy_inner(dest, dest_y); \
|
||||||
if (l == NULL) return false; \
|
if (l == NULL) return false; \
|
||||||
if (PyList_Append(extra_lines, l) != 0) { Py_CLEAR(l); return false; } \
|
if (PyList_Append(extra_lines, l) != 0) { Py_CLEAR(l); return false; } \
|
||||||
@ -488,6 +492,6 @@ end:
|
|||||||
return Py_BuildValue("Ni", ret, cursor_y);
|
return Py_BuildValue("Ni", ret, cursor_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
LineBuf *alloc_linebuf() {
|
LineBuf *alloc_linebuf(unsigned int lines, unsigned int columns) {
|
||||||
return (LineBuf*)new(&LineBuf_Type, NULL, NULL);
|
return (LineBuf*)new(&LineBuf_Type, Py_BuildValue("II", lines, columns), NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
31
kitty/line.c
31
kitty/line.c
@ -185,27 +185,25 @@ cursor_from(Line* self, PyObject *args) {
|
|||||||
return (PyObject*)ans;
|
return (PyObject*)ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void line_clear_text(Line *self, unsigned int at, unsigned int num, int ch) {
|
||||||
|
const char_type repl = ((char_type)ch & CHAR_MASK) | (1 << ATTRS_SHIFT);
|
||||||
|
for (index_type i = at; i < MIN(self->xnum, at + num); i++) {
|
||||||
|
self->chars[i] = (self->chars[i] & ATTRS_MASK_WITHOUT_WIDTH) | repl;
|
||||||
|
}
|
||||||
|
memset(self->combining_chars + at, 0, MIN(num, self->xnum - at) * sizeof(combining_type));
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
clear_text(Line* self, PyObject *args) {
|
clear_text(Line* self, PyObject *args) {
|
||||||
#define clear_text_doc "clear_text(at, num, ch=' ') -> Clear characters in the specified range, preserving formatting."
|
#define clear_text_doc "clear_text(at, num, ch=' ') -> Clear characters in the specified range, preserving formatting."
|
||||||
unsigned int at, num;
|
unsigned int at, num;
|
||||||
int ch = 32;
|
int ch = 32;
|
||||||
if (!PyArg_ParseTuple(args, "II|C", &at, &num, &ch)) return NULL;
|
if (!PyArg_ParseTuple(args, "II|C", &at, &num, &ch)) return NULL;
|
||||||
const char_type repl = ((char_type)ch & CHAR_MASK) | (1 << ATTRS_SHIFT);
|
line_clear_text(self, at, num, ch);
|
||||||
for (index_type i = at; i < MIN(self->xnum, at + num); i++) {
|
|
||||||
self->chars[i] = (self->chars[i] & ATTRS_MASK_WITHOUT_WIDTH) | repl;
|
|
||||||
}
|
|
||||||
memset(self->combining_chars + at, 0, MIN(num, self->xnum - at) * sizeof(combining_type));
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char) {
|
||||||
apply_cursor(Line* self, PyObject *args) {
|
|
||||||
#define apply_cursor_doc "apply_cursor(cursor, at=0, num=1, clear_char=False) -> Apply the formatting attributes from cursor to the specified characters in this line."
|
|
||||||
Cursor* cursor;
|
|
||||||
unsigned int at=0, num=1;
|
|
||||||
int clear_char = 0;
|
|
||||||
if (!PyArg_ParseTuple(args, "O!|IIp", &Cursor_Type, &cursor, &at, &num, &clear_char)) return NULL;
|
|
||||||
char_type attrs = CURSOR_TO_ATTRS(cursor, 1);
|
char_type attrs = CURSOR_TO_ATTRS(cursor, 1);
|
||||||
color_type col = (cursor->fg & COL_MASK) | ((color_type)(cursor->bg & COL_MASK) << COL_SHIFT);
|
color_type col = (cursor->fg & COL_MASK) | ((color_type)(cursor->bg & COL_MASK) << COL_SHIFT);
|
||||||
decoration_type dfg = cursor->decoration_fg & COL_MASK;
|
decoration_type dfg = cursor->decoration_fg & COL_MASK;
|
||||||
@ -218,7 +216,16 @@ apply_cursor(Line* self, PyObject *args) {
|
|||||||
self->colors[i] = col;
|
self->colors[i] = col;
|
||||||
self->decoration_fg[i] = dfg;
|
self->decoration_fg[i] = dfg;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
apply_cursor(Line* self, PyObject *args) {
|
||||||
|
#define apply_cursor_doc "apply_cursor(cursor, at=0, num=1, clear_char=False) -> Apply the formatting attributes from cursor to the specified characters in this line."
|
||||||
|
Cursor* cursor;
|
||||||
|
unsigned int at=0, num=1;
|
||||||
|
int clear_char = 0;
|
||||||
|
if (!PyArg_ParseTuple(args, "O!|IIp", &Cursor_Type, &cursor, &at, &num, &clear_char)) return NULL;
|
||||||
|
line_apply_cursor(self, cursor, at, num, clear_char & 1);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,6 @@
|
|||||||
// Parse text {{{
|
// Parse text {{{
|
||||||
static inline bool
|
static inline bool
|
||||||
read_text(Screen *screen, uint8_t *buf, unsigned int buflen, unsigned int *pos) {
|
read_text(Screen *screen, uint8_t *buf, unsigned int buflen, unsigned int *pos) {
|
||||||
bool ret;
|
|
||||||
uint8_t ch;
|
uint8_t ch;
|
||||||
|
|
||||||
while(*pos < buflen) {
|
while(*pos < buflen) {
|
||||||
@ -25,14 +24,13 @@ read_text(Screen *screen, uint8_t *buf, unsigned int buflen, unsigned int *pos)
|
|||||||
#define DRAW_TEXT \
|
#define DRAW_TEXT \
|
||||||
if (screen->parser_has_pending_text) { \
|
if (screen->parser_has_pending_text) { \
|
||||||
screen->parser_has_pending_text = false; \
|
screen->parser_has_pending_text = false; \
|
||||||
ret = screen_draw(screen, buf + screen->parser_text_start, (*pos) - screen->parser_text_start); \
|
screen_draw(screen, buf + screen->parser_text_start, (*pos) - screen->parser_text_start); \
|
||||||
screen->parser_text_start = 0; \
|
screen->parser_text_start = 0; \
|
||||||
if (!ret) return false; \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CALL_SCREEN_HANDLER(name) \
|
#define CALL_SCREEN_HANDLER(name) \
|
||||||
DRAW_TEXT; \
|
DRAW_TEXT; \
|
||||||
if (!screen_##name(screen, ch)) return false;
|
screen_##name(screen, ch);
|
||||||
|
|
||||||
#define CHANGE_PARSER_STATE(state) screen->parser_state = state; return true;
|
#define CHANGE_PARSER_STATE(state) screen->parser_state = state; return true;
|
||||||
switch(ch) {
|
switch(ch) {
|
||||||
|
|||||||
358
kitty/screen.c
358
kitty/screen.c
@ -32,64 +32,76 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
|||||||
self->margin_top = 0; self->margin_bottom = self->lines - 1;
|
self->margin_top = 0; self->margin_bottom = self->lines - 1;
|
||||||
self->callbacks = callbacks; Py_INCREF(callbacks);
|
self->callbacks = callbacks; Py_INCREF(callbacks);
|
||||||
self->cursor = alloc_cursor();
|
self->cursor = alloc_cursor();
|
||||||
self->main_linebuf = alloc_linebuf(); self->alt_linebuf = alloc_linebuf();
|
self->main_linebuf = alloc_linebuf(lines, columns); self->alt_linebuf = alloc_linebuf(lines, columns);
|
||||||
self->linebuf = self->main_linebuf;
|
self->linebuf = self->main_linebuf;
|
||||||
self->main_savepoints = PyList_New(0); self->alt_savepoints = PyList_New(0);
|
self->main_savepoints = PyList_New(0); self->alt_savepoints = PyList_New(0);
|
||||||
self->savepoints = self->main_savepoints;
|
self->savepoints = self->main_savepoints;
|
||||||
self->change_tracker = alloc_change_tracker();
|
self->change_tracker = alloc_change_tracker(lines, columns);
|
||||||
if (self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL || self->main_savepoints == NULL || self->alt_savepoints == NULL || self->change_tracker == NULL) {
|
self->tabstops = PyMem_Calloc(self->columns, sizeof(bool));
|
||||||
|
if (self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL || self->main_savepoints == NULL || self->alt_savepoints == NULL || self->change_tracker == NULL || self->tabstops == NULL) {
|
||||||
Py_CLEAR(self); return NULL;
|
Py_CLEAR(self); return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (PyObject*) self;
|
return (PyObject*) self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool screen_reset(Screen *self) {
|
||||||
|
if (self->linebuf == self->alt_linebuf) {if (!screen_toggle_screen_buffer(self)) return false; }
|
||||||
|
linebuf_clear(self->linebuf);
|
||||||
|
self->current_charset = 2;
|
||||||
|
self->g0_charset = translation_table('B');
|
||||||
|
self->g1_charset = translation_table('0');
|
||||||
|
self->modes = empty_modes;
|
||||||
|
self->utf8_state = 0;
|
||||||
|
self->margin_top = 0; self->margin_bottom = self->lines - 1;
|
||||||
|
// In terminfo we specify the number of initial tabstops (it) as 8
|
||||||
|
for (unsigned int t=0; t < self->columns; t++) self->tabstops[t] = t > 0 && (t+1) % 8 == 0;
|
||||||
|
screen_normal_keypad_mode(self);
|
||||||
|
cursor_reset(self->cursor);
|
||||||
|
tracker_cursor_changed(self->change_tracker);
|
||||||
|
screen_cursor_position(self, 1, 1);
|
||||||
|
screen_change_default_color(self, FG, 0);
|
||||||
|
screen_change_default_color(self, BG, 0);
|
||||||
|
tracker_update_screen(self->change_tracker);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dealloc(Screen* self) {
|
dealloc(Screen* self) {
|
||||||
Py_CLEAR(self->callbacks);
|
Py_CLEAR(self->callbacks);
|
||||||
Py_CLEAR(self->cursor); Py_CLEAR(self->main_linebuf); Py_CLEAR(self->alt_linebuf);
|
Py_CLEAR(self->cursor);
|
||||||
|
Py_CLEAR(self->main_linebuf);
|
||||||
|
Py_CLEAR(self->alt_linebuf);
|
||||||
Py_CLEAR(self->main_savepoints); Py_CLEAR(self->alt_savepoints); Py_CLEAR(self->change_tracker);
|
Py_CLEAR(self->main_savepoints); Py_CLEAR(self->alt_savepoints); Py_CLEAR(self->change_tracker);
|
||||||
|
PyMem_Free(self->tabstops);
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
bool screen_bell(Screen UNUSED *self, uint8_t ch) { // {{{
|
void screen_bell(Screen UNUSED *self, uint8_t ch) { // {{{
|
||||||
FILE *f = fopen("/dev/tty", "w");
|
FILE *f = fopen("/dev/tty", "w");
|
||||||
if (f != NULL) {
|
if (f != NULL) {
|
||||||
fwrite(&ch, 1, 1, f);
|
fwrite(&ch, 1, 1, f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
|
|
||||||
bool screen_linefeed(Screen UNUSED *self, uint8_t UNUSED ch) {
|
|
||||||
// TODO: Implement this
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool screen_carriage_return(Screen UNUSED *self, uint8_t UNUSED ch) {
|
|
||||||
// TODO: Implement this
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Draw text {{{
|
// Draw text {{{
|
||||||
|
|
||||||
static inline int safe_wcwidth(uint32_t ch) {
|
static inline unsigned int safe_wcwidth(uint32_t ch) {
|
||||||
int ans = wcwidth(ch);
|
int ans = wcwidth(ch);
|
||||||
if (ans < 0) ans = 1;
|
if (ans < 0) ans = 1;
|
||||||
return MIN(2, ans);
|
return MIN(2, ans);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline void
|
||||||
draw_codepoint(Screen UNUSED *self, uint32_t ch) {
|
draw_codepoint(Screen UNUSED *self, uint32_t ch) {
|
||||||
if (is_ignored_char(ch)) return true;
|
if (is_ignored_char(ch)) return;
|
||||||
int char_width = safe_wcwidth(ch);
|
unsigned int char_width = safe_wcwidth(ch);
|
||||||
int space_left_in_line = self->columns - self->cursor->x;
|
if (self->columns - (unsigned int)self->cursor->x < char_width) {
|
||||||
if (space_left_in_line < char_width) {
|
|
||||||
if (self->modes.mDECAWM) {
|
if (self->modes.mDECAWM) {
|
||||||
if (!screen_carriage_return(self, 13)) return false;
|
screen_carriage_return(self, 13);
|
||||||
if (!screen_linefeed(self, 10)) return false;
|
screen_linefeed(self, 10);
|
||||||
self->linebuf->continued_map[self->cursor->y] = true;
|
self->linebuf->continued_map[self->cursor->y] = true;
|
||||||
} else {
|
} else {
|
||||||
self->cursor->x = self->columns - char_width;
|
self->cursor->x = self->columns - char_width;
|
||||||
@ -120,16 +132,15 @@ draw_codepoint(Screen UNUSED *self, uint32_t ch) {
|
|||||||
tracker_update_cell_range(self->change_tracker, self->cursor->y - 1, self->columns - 1, self->columns - 1);
|
tracker_update_cell_range(self->change_tracker, self->cursor->y - 1, self->columns - 1, self->columns - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline void
|
||||||
screen_draw_utf8(Screen *self, uint8_t *buf, unsigned int buflen) {
|
screen_draw_utf8(Screen *self, uint8_t *buf, unsigned int buflen) {
|
||||||
uint32_t prev = UTF8_ACCEPT, codepoint = 0;
|
uint32_t prev = UTF8_ACCEPT, codepoint = 0;
|
||||||
for (unsigned int i = 0; i < buflen; i++, prev = self->utf8_state) {
|
for (unsigned int i = 0; i < buflen; i++, prev = self->utf8_state) {
|
||||||
switch (decode_utf8(&self->utf8_state, &codepoint, buf[i])) {
|
switch (decode_utf8(&self->utf8_state, &codepoint, buf[i])) {
|
||||||
case UTF8_ACCEPT:
|
case UTF8_ACCEPT:
|
||||||
if (!draw_codepoint(self, codepoint)) return false;
|
draw_codepoint(self, codepoint);
|
||||||
break;
|
break;
|
||||||
case UTF8_REJECT:
|
case UTF8_REJECT:
|
||||||
self->utf8_state = UTF8_ACCEPT;
|
self->utf8_state = UTF8_ACCEPT;
|
||||||
@ -137,49 +148,41 @@ screen_draw_utf8(Screen *self, uint8_t *buf, unsigned int buflen) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline void
|
||||||
screen_draw_charset(Screen *self, unsigned short *table, uint8_t *buf, unsigned int buflen) {
|
screen_draw_charset(Screen *self, unsigned short *table, uint8_t *buf, unsigned int buflen) {
|
||||||
for (unsigned int i = 0; i < buflen; i++) {
|
for (unsigned int i = 0; i < buflen; i++) {
|
||||||
if (!draw_codepoint(self, table[buf[i]])) return false;
|
draw_codepoint(self, table[buf[i]]);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool screen_draw(Screen *self, uint8_t *buf, unsigned int buflen) {
|
void screen_draw(Screen *self, uint8_t *buf, unsigned int buflen) {
|
||||||
switch(self->current_charset) {
|
switch(self->current_charset) {
|
||||||
case 0:
|
case 0:
|
||||||
return screen_draw_charset(self, self->g0_charset, buf, buflen);
|
screen_draw_charset(self, self->g0_charset, buf, buflen); break;
|
||||||
break;
|
|
||||||
case 1:
|
case 1:
|
||||||
return screen_draw_charset(self, self->g1_charset, buf, buflen);
|
screen_draw_charset(self, self->g1_charset, buf, buflen); break;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return screen_draw_utf8(self, buf, buflen); break;
|
screen_draw_utf8(self, buf, buflen); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
bool screen_backspace(Screen UNUSED *self, uint8_t UNUSED ch) {
|
void screen_backspace(Screen UNUSED *self, uint8_t UNUSED ch) {
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool screen_tab(Screen UNUSED *self, uint8_t UNUSED ch) {
|
void screen_tab(Screen UNUSED *self, uint8_t UNUSED ch) {
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool screen_shift_out(Screen UNUSED *self, uint8_t UNUSED ch) {
|
void screen_shift_out(Screen UNUSED *self, uint8_t UNUSED ch) {
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool screen_shift_in(Screen UNUSED *self, uint8_t UNUSED ch) {
|
void screen_shift_in(Screen UNUSED *self, uint8_t UNUSED ch) {
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool screen_toggle_screen_buffer(Screen *self) {
|
bool screen_toggle_screen_buffer(Screen *self) {
|
||||||
@ -196,9 +199,22 @@ bool screen_toggle_screen_buffer(Screen *self) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Graphics {{{
|
||||||
|
void screen_change_default_color(Screen *self, unsigned int which, uint32_t col) {
|
||||||
|
if (self->callbacks == Py_None) return;
|
||||||
|
if (col & 0xFF) PyObject_CallMethod(self->callbacks, "change_default_color", "s(III)", which == FG ? "fg" : "bg",
|
||||||
|
(col >> 24) & 0xFF, (col >> 16) & 0xFF, (col >> 8) & 0xFF);
|
||||||
|
else PyObject_CallMethod(self->callbacks, "change_default_color", "sO", which == FG ? "fg" : "bg", Py_None);
|
||||||
|
if (PyErr_Occurred()) PyErr_Print();
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
// Modes {{{
|
// Modes {{{
|
||||||
|
|
||||||
|
void screen_normal_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI
|
||||||
|
void screen_alternate_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI
|
||||||
|
|
||||||
static inline void set_mode_from_const(Screen *self, int mode, bool val) {
|
static inline void set_mode_from_const(Screen *self, int mode, bool val) {
|
||||||
switch(mode) {
|
switch(mode) {
|
||||||
case LNM:
|
case LNM:
|
||||||
@ -226,12 +242,12 @@ bool screen_set_mode(Screen *self, int mode) {
|
|||||||
if (mode == DECCOLM) {
|
if (mode == DECCOLM) {
|
||||||
// When DECCOLM mode is set, the screen is erased and the cursor
|
// When DECCOLM mode is set, the screen is erased and the cursor
|
||||||
// moves to the home position.
|
// moves to the home position.
|
||||||
if (!screen_erase_in_display(self, 2, false)) return false;
|
screen_erase_in_display(self, 2, false);
|
||||||
if (!screen_cursor_position(self, 1, 1)) return false;
|
screen_cursor_position(self, 1, 1);
|
||||||
}
|
}
|
||||||
// According to `vttest`, DECOM should also home the cursor, see
|
// According to `vttest`, DECOM should also home the cursor, see
|
||||||
// vttest/main.c:303.
|
// vttest/main.c:303.
|
||||||
if (mode == DECOM) { if (!screen_cursor_position(self, 1, 1)) return false; }
|
if (mode == DECOM) screen_cursor_position(self, 1, 1);
|
||||||
|
|
||||||
if (mode == DECSCNM) {
|
if (mode == DECSCNM) {
|
||||||
// Mark all displayed characters as reverse.
|
// Mark all displayed characters as reverse.
|
||||||
@ -273,12 +289,12 @@ bool screen_reset_mode(Screen *self, int mode) {
|
|||||||
if (mode == DECCOLM) {
|
if (mode == DECCOLM) {
|
||||||
// When DECCOLM mode is set, the screen is erased and the cursor
|
// When DECCOLM mode is set, the screen is erased and the cursor
|
||||||
// moves to the home position.
|
// moves to the home position.
|
||||||
if (!screen_erase_in_display(self, 2, false)) return false;
|
screen_erase_in_display(self, 2, false);
|
||||||
if (!screen_cursor_position(self, 1, 1)) return false;
|
screen_cursor_position(self, 1, 1);
|
||||||
}
|
}
|
||||||
// According to `vttest`, DECOM should also home the cursor, see
|
// According to `vttest`, DECOM should also home the cursor, see
|
||||||
// vttest/main.c:303.
|
// vttest/main.c:303.
|
||||||
if (mode == DECOM) { if (!screen_cursor_position(self, 1, 1)) return false; }
|
if (mode == DECOM) screen_cursor_position(self, 1, 1);
|
||||||
|
|
||||||
if (mode == DECSCNM) {
|
if (mode == DECSCNM) {
|
||||||
// Mark all displayed characters as reverse.
|
// Mark all displayed characters as reverse.
|
||||||
@ -303,6 +319,68 @@ bool screen_reset_mode(Screen *self, int mode) {
|
|||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
// Cursor {{{
|
// Cursor {{{
|
||||||
|
|
||||||
|
void screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) {
|
||||||
|
int x = self->cursor->x;
|
||||||
|
if (count == 0) count = 1;
|
||||||
|
self->cursor->x += move_direction * count;
|
||||||
|
screen_ensure_bounds(self, false);
|
||||||
|
if (x != self->cursor->x) tracker_cursor_changed(self->change_tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_cursor_forward(Screen *self, unsigned int count/*=1*/) {
|
||||||
|
screen_cursor_back(self, count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/) {
|
||||||
|
int x = self->cursor->x, y = self->cursor->y;
|
||||||
|
if (count == 0) count = 1;
|
||||||
|
self->cursor->y += move_direction * count;
|
||||||
|
screen_ensure_bounds(self, true);
|
||||||
|
if (do_carriage_return) self->cursor->x = 0;
|
||||||
|
if (x != self->cursor->x || y != self->cursor->y) tracker_cursor_changed(self->change_tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_cursor_up1(Screen *self, unsigned int count/*=1*/) {
|
||||||
|
screen_cursor_up(self, count, true, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_cursor_down(Screen *self, unsigned int count/*=1*/) {
|
||||||
|
screen_cursor_up(self, count, false, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_cursor_down1(Screen *self, unsigned int count/*=1*/) {
|
||||||
|
screen_cursor_up(self, count, true, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_index(Screen *self) {
|
||||||
|
// Move cursor down one line, scrolling screen if needed
|
||||||
|
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
||||||
|
if ((unsigned int)self->cursor->y == self->margin_bottom) {
|
||||||
|
linebuf_index(self->linebuf, top, bottom);
|
||||||
|
if (self->linebuf == self->main_linebuf) {
|
||||||
|
// TODO: Add line to tophistorybuf
|
||||||
|
tracker_line_added_to_history(self->change_tracker);
|
||||||
|
}
|
||||||
|
linebuf_clear_line(self->linebuf, bottom);
|
||||||
|
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker);
|
||||||
|
else tracker_update_line_range(self->change_tracker, top, bottom);
|
||||||
|
} else screen_cursor_down(self, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_carriage_return(Screen *self, uint8_t UNUSED ch) {
|
||||||
|
if (self->cursor->x != 0) {
|
||||||
|
self->cursor->x = 0;
|
||||||
|
tracker_cursor_changed(self->change_tracker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_linefeed(Screen *self, uint8_t UNUSED ch) {
|
||||||
|
screen_index(self);
|
||||||
|
if (self->modes.mLNM) screen_carriage_return(self, 13);
|
||||||
|
screen_ensure_bounds(self, false);
|
||||||
|
}
|
||||||
|
|
||||||
bool screen_save_cursor(Screen *self) {
|
bool screen_save_cursor(Screen *self) {
|
||||||
Savepoint *sp = alloc_savepoint();
|
Savepoint *sp = alloc_savepoint();
|
||||||
if (sp == NULL) return false;
|
if (sp == NULL) return false;
|
||||||
@ -333,30 +411,120 @@ bool screen_restore_cursor(Screen *self) {
|
|||||||
} else {
|
} else {
|
||||||
screen_cursor_position(self, 1, 1);
|
screen_cursor_position(self, 1, 1);
|
||||||
tracker_cursor_changed(self->change_tracker);
|
tracker_cursor_changed(self->change_tracker);
|
||||||
screen_reset_mode(self, DECOM);
|
if (!screen_reset_mode(self, DECOM)) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool screen_cursor_position(Screen UNUSED *self, unsigned int UNUSED line, unsigned int UNUSED column) {
|
void screen_ensure_bounds(Screen *self, bool use_margins/*=false*/) {
|
||||||
return true; // TODO: Implement this
|
unsigned int top, bottom;
|
||||||
|
if (use_margins || self->modes.mDECOM) {
|
||||||
|
top = self->margin_top; bottom = self->margin_bottom;
|
||||||
|
} else {
|
||||||
|
top = 0; bottom = self->lines - 1;
|
||||||
|
}
|
||||||
|
self->cursor->x = MIN((unsigned int)MAX(0, self->cursor->x), self->columns - 1);
|
||||||
|
self->cursor->y = MAX(top, MIN((unsigned int)MAX(0, self->cursor->y), bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_cursor_position(Screen *self, unsigned int line, unsigned int column) {
|
||||||
|
line = (line || 1) - 1;
|
||||||
|
column = (column || 1) - 1;
|
||||||
|
if (self->modes.mDECOM) {
|
||||||
|
line += self->margin_top;
|
||||||
|
if (line < self->margin_bottom || line > self->margin_top) return;
|
||||||
|
}
|
||||||
|
int x = self->cursor->x, y = self->cursor->y;
|
||||||
|
self->cursor->x = column; self->cursor->y = line;
|
||||||
|
screen_ensure_bounds(self, false);
|
||||||
|
if (x != self->cursor->x || y != self->cursor->y) tracker_cursor_changed(self->change_tracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
// Editing {{{
|
// Editing {{{
|
||||||
bool screen_erase_in_display(Screen UNUSED *self, unsigned int UNUSED how, bool UNUSED private) {
|
|
||||||
return true; // TODO: Implement this
|
void screen_erase_in_line(Screen *self, unsigned int how, bool private) {
|
||||||
|
/*Erases a line in a specific way.
|
||||||
|
|
||||||
|
:param int how: defines the way the line should be erased in:
|
||||||
|
|
||||||
|
* ``0`` -- Erases from cursor to end of line, including cursor
|
||||||
|
position.
|
||||||
|
* ``1`` -- Erases from beginning of line to cursor,
|
||||||
|
including cursor position.
|
||||||
|
* ``2`` -- Erases complete line.
|
||||||
|
:param bool private: when ``True`` character attributes are left
|
||||||
|
unchanged.
|
||||||
|
*/
|
||||||
|
unsigned int s, n;
|
||||||
|
switch(how) {
|
||||||
|
case 0:
|
||||||
|
s = self->cursor->x;
|
||||||
|
n = self->columns - self->cursor->x;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
s = 0; n = self->cursor->x + 1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
s = 0; n = self->columns;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (n > s) {
|
||||||
|
linebuf_init_line(self->linebuf, self->cursor->y);
|
||||||
|
if (private) {
|
||||||
|
line_clear_text(self->linebuf->line, s, n, ' ');
|
||||||
|
} else {
|
||||||
|
line_apply_cursor(self->linebuf->line, self->cursor, s, n, true);
|
||||||
|
}
|
||||||
|
tracker_update_cell_range(self->change_tracker, self->cursor->y, s, MIN(s+n, self->columns) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_erase_in_display(Screen *self, unsigned int how, bool private) {
|
||||||
|
/* Erases display in a specific way.
|
||||||
|
|
||||||
|
:param int how: defines the way the line should be erased in:
|
||||||
|
|
||||||
|
* ``0`` -- Erases from cursor to end of screen, including
|
||||||
|
cursor position.
|
||||||
|
* ``1`` -- Erases from beginning of screen to cursor,
|
||||||
|
including cursor position.
|
||||||
|
* ``2`` -- Erases complete display. All lines are erased
|
||||||
|
and changed to single-width. Cursor does not move.
|
||||||
|
:param bool private: when ``True`` character attributes are left unchanged
|
||||||
|
*/
|
||||||
|
unsigned int a, b;
|
||||||
|
switch(how) {
|
||||||
|
case 0:
|
||||||
|
a = self->cursor->y + 1; b = self->lines; break;
|
||||||
|
case 1:
|
||||||
|
a = 0; b = self->cursor->y; break;
|
||||||
|
case 2:
|
||||||
|
a = 0; b = self->lines; break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (b > a) {
|
||||||
|
for (unsigned int i=a; i < b; i++) {
|
||||||
|
linebuf_init_line(self->linebuf, i);
|
||||||
|
if (private) {
|
||||||
|
line_clear_text(self->linebuf->line, 0, self->columns, ' ');
|
||||||
|
} else {
|
||||||
|
line_apply_cursor(self->linebuf->line, self->cursor, 0, self->columns, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tracker_update_line_range(self->change_tracker, a, b-1);
|
||||||
|
}
|
||||||
|
if (how != 2) {
|
||||||
|
screen_erase_in_line(self, how, private);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
bool screen_reset(Screen *self) {
|
// Python interface {{{
|
||||||
if (self->linebuf == self->alt_linebuf) {if (!screen_toggle_screen_buffer(self)) return false; }
|
|
||||||
linebuf_clear(self->linebuf);
|
|
||||||
// TODO: Implement this
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
line(Screen *self, PyObject *val) {
|
line(Screen *self, PyObject *val) {
|
||||||
#define line_doc ""
|
#define line_doc ""
|
||||||
@ -372,17 +540,72 @@ draw(Screen *self, PyObject *args) {
|
|||||||
#define draw_doc ""
|
#define draw_doc ""
|
||||||
Py_buffer pybuf;
|
Py_buffer pybuf;
|
||||||
if(!PyArg_ParseTuple(args, "y*", &pybuf)) return NULL;
|
if(!PyArg_ParseTuple(args, "y*", &pybuf)) return NULL;
|
||||||
if (!screen_draw(self, pybuf.buf, pybuf.len)) return NULL;
|
screen_draw(self, pybuf.buf, pybuf.len);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
reset(Screen *self) {
|
||||||
|
#define reset_doc ""
|
||||||
|
if(!screen_reset(self)) return NULL;
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
reset_mode(Screen *self, PyObject *args) {
|
||||||
|
#define reset_mode_doc ""
|
||||||
|
bool private = false;
|
||||||
|
unsigned int mode;
|
||||||
|
if (!PyArg_ParseTuple(args, "I|p", &mode, &private)) return NULL;
|
||||||
|
if (private) mode <<= 5;
|
||||||
|
if (!screen_reset_mode(self, mode)) return NULL;
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boilerplate {{{
|
static PyObject*
|
||||||
|
set_mode(Screen *self, PyObject *args) {
|
||||||
|
#define set_mode_doc ""
|
||||||
|
bool private = false;
|
||||||
|
unsigned int mode;
|
||||||
|
if (!PyArg_ParseTuple(args, "I|p", &mode, &private)) return NULL;
|
||||||
|
if (private) mode <<= 5;
|
||||||
|
if (!screen_set_mode(self, mode)) return NULL;
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
reset_dirty(Screen *self) {
|
||||||
|
#define reset_dirty_doc ""
|
||||||
|
tracker_reset(self->change_tracker);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
consolidate_changes(Screen *self) {
|
||||||
|
#define consolidate_changes_doc ""
|
||||||
|
return tracker_consolidate_changes(self->change_tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
cursor_back(Screen *self, PyObject *args) {
|
||||||
|
#define cursor_back_doc ""
|
||||||
|
unsigned int count = 1;
|
||||||
|
if (!PyArg_ParseTuple(args, "|I", &count)) return NULL;
|
||||||
|
screen_cursor_back(self, count, -1);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
static PyMethodDef methods[] = {
|
static PyMethodDef methods[] = {
|
||||||
METHOD(line, METH_O)
|
METHOD(line, METH_O)
|
||||||
METHOD(draw, METH_VARARGS)
|
METHOD(draw, METH_VARARGS)
|
||||||
|
METHOD(set_mode, METH_VARARGS)
|
||||||
|
METHOD(reset_mode, METH_VARARGS)
|
||||||
METHOD(enable_focus_tracking, METH_NOARGS)
|
METHOD(enable_focus_tracking, METH_NOARGS)
|
||||||
METHOD(in_bracketed_paste_mode, METH_NOARGS)
|
METHOD(in_bracketed_paste_mode, METH_NOARGS)
|
||||||
|
METHOD(reset, METH_NOARGS)
|
||||||
|
METHOD(reset_dirty, METH_NOARGS)
|
||||||
|
METHOD(consolidate_changes, METH_NOARGS)
|
||||||
|
METHOD(cursor_back, METH_VARARGS)
|
||||||
|
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
@ -408,4 +631,3 @@ PyTypeObject Screen_Type = {
|
|||||||
INIT_TYPE(Screen)
|
INIT_TYPE(Screen)
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -162,6 +162,7 @@ class Screen:
|
|||||||
"""
|
"""
|
||||||
self.lines, self.columns = lines, columns
|
self.lines, self.columns = lines, columns
|
||||||
self.tracker.resize(self.lines, self.columns)
|
self.tracker.resize(self.lines, self.columns)
|
||||||
|
self.tabstops = {x for x in self.tabstops if x < self.columns}
|
||||||
# TODO: Implement rewrap for history buf
|
# TODO: Implement rewrap for history buf
|
||||||
self.tophistorybuf.clear()
|
self.tophistorybuf.clear()
|
||||||
is_main = self.linebuf is self.main_linebuf
|
is_main = self.linebuf is self.main_linebuf
|
||||||
|
|||||||
@ -8,26 +8,27 @@
|
|||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
#include "tracker.h"
|
#include "tracker.h"
|
||||||
|
|
||||||
#define RESET_STATE_VARS(self) \
|
|
||||||
self->screen_changed = false; self->cursor_changed = false; self->dirty = false; self->history_line_added_count = 0;
|
|
||||||
|
|
||||||
static PyObject*
|
bool tracker_resize(ChangeTracker *self, unsigned int ynum, unsigned int xnum) {
|
||||||
resize(ChangeTracker *self, PyObject *args) {
|
|
||||||
#define resize_doc "Resize theis change tracker must be called when the screen it is tracking for is resized"
|
|
||||||
unsigned long ynum=1, xnum=1;
|
|
||||||
if (args) {
|
|
||||||
if (!PyArg_ParseTuple(args, "kk", &ynum, &xnum)) return NULL;
|
|
||||||
}
|
|
||||||
self->ynum = ynum; self->xnum = xnum;
|
|
||||||
#define ALLOC_VAR(name, sz) \
|
#define ALLOC_VAR(name, sz) \
|
||||||
bool *name = PyMem_Calloc(sz, sizeof(bool)); \
|
bool *name = PyMem_Calloc(sz, sizeof(bool)); \
|
||||||
if (name == NULL) return PyErr_NoMemory(); \
|
if (name == NULL) { PyErr_NoMemory(); return false; } \
|
||||||
PyMem_Free(self->name); self->name = name;
|
PyMem_Free(self->name); self->name = name;
|
||||||
|
|
||||||
|
self->ynum = ynum; self->xnum = xnum;
|
||||||
ALLOC_VAR(changed_lines, self->ynum);
|
ALLOC_VAR(changed_lines, self->ynum);
|
||||||
ALLOC_VAR(changed_cells, self->xnum * self->ynum);
|
ALLOC_VAR(changed_cells, self->xnum * self->ynum);
|
||||||
ALLOC_VAR(lines_with_changed_cells, self->ynum);
|
ALLOC_VAR(lines_with_changed_cells, self->ynum);
|
||||||
RESET_STATE_VARS(self);
|
RESET_STATE_VARS(self);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
resize(ChangeTracker *self, PyObject *args) {
|
||||||
|
#define resize_doc "Resize this change tracker must be called when the screen it is tracking for is resized"
|
||||||
|
unsigned int ynum=1, xnum=1;
|
||||||
|
if (!PyArg_ParseTuple(args, "|II", &ynum, &xnum)) return NULL;
|
||||||
|
if (!tracker_resize(self, ynum, xnum)) return NULL;
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,19 +50,10 @@ dealloc(ChangeTracker* self) {
|
|||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void reset_inner(ChangeTracker *self) {
|
|
||||||
self->screen_changed = false; self->cursor_changed = false; self->dirty = false;
|
|
||||||
self->history_line_added_count = 0;
|
|
||||||
memset(self->changed_lines, 0, self->ynum * sizeof(bool));
|
|
||||||
memset(self->changed_cells, 0, self->ynum * self->xnum * sizeof(bool));
|
|
||||||
memset(self->lines_with_changed_cells, 0, self->ynum * sizeof(bool));
|
|
||||||
RESET_STATE_VARS(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
reset(ChangeTracker *self) {
|
reset(ChangeTracker *self) {
|
||||||
#define reset_doc "Reset all changes"
|
#define reset_doc "Reset all changes"
|
||||||
reset_inner(self);
|
tracker_reset(self);
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
@ -156,7 +148,7 @@ update_cell_data(ChangeTracker *self, PyObject *args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PyObject *cursor_changed = self->cursor_changed ? Py_True : Py_False;
|
PyObject *cursor_changed = self->cursor_changed ? Py_True : Py_False;
|
||||||
reset_inner(self);
|
tracker_reset(self);
|
||||||
Py_INCREF(cursor_changed);
|
Py_INCREF(cursor_changed);
|
||||||
return cursor_changed;
|
return cursor_changed;
|
||||||
}
|
}
|
||||||
@ -192,9 +184,8 @@ get_ranges(bool *line, unsigned int xnum) {
|
|||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
PyObject*
|
||||||
consolidate_changes(ChangeTracker *self) {
|
tracker_consolidate_changes(ChangeTracker *self) {
|
||||||
#define consolidate_changes_doc ""
|
|
||||||
PyObject *ans = PyDict_New();
|
PyObject *ans = PyDict_New();
|
||||||
if (ans == NULL) return PyErr_NoMemory();
|
if (ans == NULL) return PyErr_NoMemory();
|
||||||
if (PyDict_SetItemString(ans, "screen", self->screen_changed ? Py_True : Py_False) != 0) { Py_CLEAR(ans); return NULL; }
|
if (PyDict_SetItemString(ans, "screen", self->screen_changed ? Py_True : Py_False) != 0) { Py_CLEAR(ans); return NULL; }
|
||||||
@ -242,7 +233,7 @@ consolidate_changes(ChangeTracker *self) {
|
|||||||
if (PyDict_SetItemString(ans, "cells", t) != 0) { Py_CLEAR(t); Py_CLEAR(ans); return NULL; }
|
if (PyDict_SetItemString(ans, "cells", t) != 0) { Py_CLEAR(t); Py_CLEAR(ans); return NULL; }
|
||||||
Py_CLEAR(t);
|
Py_CLEAR(t);
|
||||||
|
|
||||||
reset_inner(self);
|
tracker_reset(self);
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +250,7 @@ static PyMethodDef methods[] = {
|
|||||||
METHOD(update_cell_data, METH_VARARGS)
|
METHOD(update_cell_data, METH_VARARGS)
|
||||||
METHOD(reset, METH_NOARGS)
|
METHOD(reset, METH_NOARGS)
|
||||||
METHOD(cursor_changed, METH_NOARGS)
|
METHOD(cursor_changed, METH_NOARGS)
|
||||||
METHOD(consolidate_changes, METH_NOARGS)
|
{"consolidate_changes", (PyCFunction)tracker_consolidate_changes, METH_NOARGS, ""},
|
||||||
METHOD(line_added_to_history, METH_NOARGS)
|
METHOD(line_added_to_history, METH_NOARGS)
|
||||||
METHOD(update_screen, METH_NOARGS)
|
METHOD(update_screen, METH_NOARGS)
|
||||||
METHOD(update_line_range, METH_VARARGS)
|
METHOD(update_line_range, METH_VARARGS)
|
||||||
@ -283,6 +274,8 @@ PyTypeObject ChangeTracker_Type = {
|
|||||||
INIT_TYPE(ChangeTracker)
|
INIT_TYPE(ChangeTracker)
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
ChangeTracker* alloc_change_tracker() {
|
ChangeTracker* alloc_change_tracker(unsigned int ynum, unsigned int xnum) {
|
||||||
return (ChangeTracker*)new(&ChangeTracker_Type, NULL, NULL);
|
ChangeTracker *self = (ChangeTracker *)(&ChangeTracker_Type)->tp_alloc((&ChangeTracker_Type), 0);
|
||||||
|
if (!tracker_resize(self, ynum, xnum)) { Py_CLEAR(self); return NULL; }
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,3 +37,17 @@ static inline void tracker_update_cell_range(ChangeTracker *self, unsigned int l
|
|||||||
self->dirty = true;
|
self->dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define RESET_STATE_VARS(self) \
|
||||||
|
self->screen_changed = false; self->cursor_changed = false; self->dirty = false; self->history_line_added_count = 0;
|
||||||
|
|
||||||
|
static inline void tracker_reset(ChangeTracker *self) {
|
||||||
|
self->screen_changed = false; self->cursor_changed = false; self->dirty = false;
|
||||||
|
self->history_line_added_count = 0;
|
||||||
|
memset(self->changed_lines, 0, self->ynum * sizeof(bool));
|
||||||
|
memset(self->changed_cells, 0, self->ynum * self->xnum * sizeof(bool));
|
||||||
|
memset(self->lines_with_changed_cells, 0, self->ynum * sizeof(bool));
|
||||||
|
RESET_STATE_VARS(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* tracker_consolidate_changes(ChangeTracker *self);
|
||||||
|
|||||||
@ -33,8 +33,8 @@ class BaseTest(TestCase):
|
|||||||
def create_screen(self, cols=5, lines=5, history_size=5):
|
def create_screen(self, cols=5, lines=5, history_size=5):
|
||||||
return S(history_size, columns=cols, lines=lines)
|
return S(history_size, columns=cols, lines=lines)
|
||||||
|
|
||||||
def create_screen2(self, cols=5, lines=5, history_size=5):
|
def create_screen2(self, cols=5, lines=5):
|
||||||
return Screen(history_size, None, lines, cols)
|
return Screen(None, lines, cols)
|
||||||
|
|
||||||
def assertEqualAttributes(self, c1, c2):
|
def assertEqualAttributes(self, c1, c2):
|
||||||
x1, y1, c1.x, c1.y = c1.x, c1.y, 0, 0
|
x1, y1, c1.x, c1.y = c1.x, c1.y, 0, 0
|
||||||
|
|||||||
@ -4,14 +4,15 @@
|
|||||||
|
|
||||||
from . import BaseTest
|
from . import BaseTest
|
||||||
|
|
||||||
from kitty.screen import mo
|
from kitty.fast_data_types import DECAWM, IRM
|
||||||
|
|
||||||
|
|
||||||
class TestScreen(BaseTest):
|
class TestScreen(BaseTest):
|
||||||
|
|
||||||
def test_draw_fast(self):
|
def test_draw_fast(self):
|
||||||
|
s = self.create_screen2()
|
||||||
|
|
||||||
# Test in line-wrap, non-insert mode
|
# Test in line-wrap, non-insert mode
|
||||||
s = self.create_screen()
|
|
||||||
s.draw(b'a' * 5)
|
s.draw(b'a' * 5)
|
||||||
self.ae(str(s.line(0)), 'a' * 5)
|
self.ae(str(s.line(0)), 'a' * 5)
|
||||||
self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0)
|
self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0)
|
||||||
@ -30,7 +31,7 @@ class TestScreen(BaseTest):
|
|||||||
|
|
||||||
# Now test without line-wrap
|
# Now test without line-wrap
|
||||||
s.reset(), s.reset_dirty()
|
s.reset(), s.reset_dirty()
|
||||||
s.reset_mode(mo.DECAWM)
|
s.reset_mode(DECAWM)
|
||||||
s.draw(b'0123456789')
|
s.draw(b'0123456789')
|
||||||
self.ae(str(s.line(0)), '01239')
|
self.ae(str(s.line(0)), '01239')
|
||||||
self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0)
|
self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0)
|
||||||
@ -42,7 +43,7 @@ class TestScreen(BaseTest):
|
|||||||
|
|
||||||
# Now test in insert mode
|
# Now test in insert mode
|
||||||
s.reset(), s.reset_dirty()
|
s.reset(), s.reset_dirty()
|
||||||
s.set_mode(mo.IRM)
|
s.set_mode(IRM)
|
||||||
s.draw(b'12345' * 5)
|
s.draw(b'12345' * 5)
|
||||||
s.cursor_back(5)
|
s.cursor_back(5)
|
||||||
self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 4)
|
self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 4)
|
||||||
@ -79,7 +80,7 @@ class TestScreen(BaseTest):
|
|||||||
|
|
||||||
# Now test without line-wrap
|
# Now test without line-wrap
|
||||||
s.reset(), s.reset_dirty()
|
s.reset(), s.reset_dirty()
|
||||||
s.reset_mode(mo.DECAWM)
|
s.reset_mode(DECAWM)
|
||||||
s.draw('0\u030612345\u03066789\u0306'.encode('utf-8'))
|
s.draw('0\u030612345\u03066789\u0306'.encode('utf-8'))
|
||||||
self.ae(str(s.line(0)), '0\u03061239\u0306')
|
self.ae(str(s.line(0)), '0\u03061239\u0306')
|
||||||
self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0)
|
self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0)
|
||||||
@ -91,7 +92,7 @@ class TestScreen(BaseTest):
|
|||||||
|
|
||||||
# Now test in insert mode
|
# Now test in insert mode
|
||||||
s.reset(), s.reset_dirty()
|
s.reset(), s.reset_dirty()
|
||||||
s.set_mode(mo.IRM)
|
s.set_mode(IRM)
|
||||||
s.draw('1\u03062345'.encode('utf-8') * 5)
|
s.draw('1\u03062345'.encode('utf-8') * 5)
|
||||||
s.cursor_back(5)
|
s.cursor_back(5)
|
||||||
self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 4)
|
self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 4)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user