More work on the screen replacement

The first set of tests now pass
This commit is contained in:
Kovid Goyal 2016-11-13 14:10:57 +05:30
parent 5dc0b9af13
commit 9a7b4263e0
11 changed files with 394 additions and 141 deletions

View File

@ -51,6 +51,8 @@ PyInit_fast_data_types(void) {
PyModule_AddIntMacro(m, CURSOR_BLOCK);
PyModule_AddIntMacro(m, CURSOR_BEAM);
PyModule_AddIntMacro(m, CURSOR_UNDERLINE);
PyModule_AddIntMacro(m, DECAWM);
PyModule_AddIntMacro(m, IRM);
}
return m;

View File

@ -43,6 +43,8 @@ typedef unsigned int index_type;
#define CURSOR_BLOCK 1
#define CURSOR_BEAM 2
#define CURSOR_UNDERLINE 3
#define FG 1
#define BG 2
#define CURSOR_TO_ATTRS(c, w) \
((w) | (((c->decoration & 3) << DECORATION_SHIFT) | ((c->bold & 1) << BOLD_SHIFT) | \
@ -233,6 +235,7 @@ typedef struct {
Cursor *cursor;
PyObject *savepoints, *main_savepoints, *alt_savepoints, *callbacks;
LineBuf *linebuf, *main_linebuf, *alt_linebuf;
bool *tabstops;
ChangeTracker *change_tracker;
ScreenModes modes;
@ -243,12 +246,6 @@ typedef struct {
} Screen;
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) \
for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \
COPY_CELL(line, __i__ + (num), line, __i__) \
@ -257,6 +254,11 @@ Savepoint* alloc_savepoint();
// 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_Cursor(PyObject *);
int init_Line(PyObject *);
@ -279,14 +281,23 @@ Cursor* cursor_copy(Cursor*);
void linebuf_clear(LineBuf *);
bool screen_restore_cursor(Screen *);
bool screen_save_cursor(Screen *);
bool screen_cursor_position(Screen*, unsigned int, unsigned int);
bool screen_erase_in_display(Screen *, unsigned int, bool);
bool screen_draw(Screen *screen, uint8_t *buf, unsigned int buflen);
void screen_cursor_position(Screen*, unsigned int, unsigned int);
void screen_erase_in_display(Screen *, unsigned int, bool);
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 *);
uint32_t to_color(ColorProfile *, uint32_t, uint32_t);
PyObject* line_text_at(char_type, combining_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(backspace)
DECLARE_CH_SCREEN_HANDLER(tab)

View File

@ -33,7 +33,7 @@ clear(LineBuf *self) {
static PyObject *
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
LineBuf *self;
index_type xnum, ynum;
unsigned int xnum = 1, ynum = 1;
if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) return NULL;
@ -202,19 +202,23 @@ copy_line_to(LineBuf *self, PyObject *args) {
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*
clear_line(LineBuf *self, PyObject *val) {
#define clear_line_doc "clear_line(y) -> Clear the specified line"
index_type y = (index_type)PyLong_AsUnsignedLong(val);
Line l;
if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; }
INIT_LINE(self, &l, self->line_map[y]);
CLEAR_LINE(&l, 0, self->xnum);
self->continued_map[y] = 0;
linebuf_clear_line(self, y);
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];
bool old_cont = self->continued_map[top];
for (index_type i = top; i < bottom; i++) {
@ -231,7 +235,7 @@ index(LineBuf *self, PyObject *args) {
unsigned int top, bottom;
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; }
index_inner(self, top, bottom);
linebuf_index(self, top, bottom);
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) {\
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); \
if (l == NULL) 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);
}
LineBuf *alloc_linebuf() {
return (LineBuf*)new(&LineBuf_Type, NULL, NULL);
LineBuf *alloc_linebuf(unsigned int lines, unsigned int columns) {
return (LineBuf*)new(&LineBuf_Type, Py_BuildValue("II", lines, columns), NULL);
}

View File

@ -185,27 +185,25 @@ cursor_from(Line* self, PyObject *args) {
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*
clear_text(Line* self, PyObject *args) {
#define clear_text_doc "clear_text(at, num, ch=' ') -> Clear characters in the specified range, preserving formatting."
unsigned int at, num;
int ch = 32;
if (!PyArg_ParseTuple(args, "II|C", &at, &num, &ch)) return NULL;
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));
line_clear_text(self, at, num, ch);
Py_RETURN_NONE;
}
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;
void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char) {
char_type attrs = CURSOR_TO_ATTRS(cursor, 1);
color_type col = (cursor->fg & COL_MASK) | ((color_type)(cursor->bg & COL_MASK) << COL_SHIFT);
decoration_type dfg = cursor->decoration_fg & COL_MASK;
@ -218,7 +216,16 @@ apply_cursor(Line* self, PyObject *args) {
self->colors[i] = col;
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;
}

View File

@ -17,7 +17,6 @@
// Parse text {{{
static inline bool
read_text(Screen *screen, uint8_t *buf, unsigned int buflen, unsigned int *pos) {
bool ret;
uint8_t ch;
while(*pos < buflen) {
@ -25,14 +24,13 @@ read_text(Screen *screen, uint8_t *buf, unsigned int buflen, unsigned int *pos)
#define DRAW_TEXT \
if (screen->parser_has_pending_text) { \
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; \
if (!ret) return false; \
}
#define CALL_SCREEN_HANDLER(name) \
DRAW_TEXT; \
if (!screen_##name(screen, ch)) return false;
screen_##name(screen, ch);
#define CHANGE_PARSER_STATE(state) screen->parser_state = state; return true;
switch(ch) {

View File

@ -32,64 +32,76 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
self->margin_top = 0; self->margin_bottom = self->lines - 1;
self->callbacks = callbacks; Py_INCREF(callbacks);
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->main_savepoints = PyList_New(0); self->alt_savepoints = PyList_New(0);
self->savepoints = self->main_savepoints;
self->change_tracker = alloc_change_tracker();
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->change_tracker = alloc_change_tracker(lines, columns);
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;
}
}
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
dealloc(Screen* self) {
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);
PyMem_Free(self->tabstops);
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");
if (f != NULL) {
fwrite(&ch, 1, 1, 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 {{{
static inline int safe_wcwidth(uint32_t ch) {
static inline unsigned int safe_wcwidth(uint32_t ch) {
int ans = wcwidth(ch);
if (ans < 0) ans = 1;
return MIN(2, ans);
}
static inline bool
static inline void
draw_codepoint(Screen UNUSED *self, uint32_t ch) {
if (is_ignored_char(ch)) return true;
int char_width = safe_wcwidth(ch);
int space_left_in_line = self->columns - self->cursor->x;
if (space_left_in_line < char_width) {
if (is_ignored_char(ch)) return;
unsigned int char_width = safe_wcwidth(ch);
if (self->columns - (unsigned int)self->cursor->x < char_width) {
if (self->modes.mDECAWM) {
if (!screen_carriage_return(self, 13)) return false;
if (!screen_linefeed(self, 10)) return false;
screen_carriage_return(self, 13);
screen_linefeed(self, 10);
self->linebuf->continued_map[self->cursor->y] = true;
} else {
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);
}
}
return true;
}
static inline bool
static inline void
screen_draw_utf8(Screen *self, uint8_t *buf, unsigned int buflen) {
uint32_t prev = UTF8_ACCEPT, codepoint = 0;
for (unsigned int i = 0; i < buflen; i++, prev = self->utf8_state) {
switch (decode_utf8(&self->utf8_state, &codepoint, buf[i])) {
case UTF8_ACCEPT:
if (!draw_codepoint(self, codepoint)) return false;
draw_codepoint(self, codepoint);
break;
case UTF8_REJECT:
self->utf8_state = UTF8_ACCEPT;
@ -137,49 +148,41 @@ screen_draw_utf8(Screen *self, uint8_t *buf, unsigned int buflen) {
break;
}
}
return true;
}
static inline bool
static inline void
screen_draw_charset(Screen *self, unsigned short *table, uint8_t *buf, unsigned int buflen) {
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) {
case 0:
return screen_draw_charset(self, self->g0_charset, buf, buflen);
break;
screen_draw_charset(self, self->g0_charset, buf, buflen); break;
case 1:
return screen_draw_charset(self, self->g1_charset, buf, buflen);
break;
screen_draw_charset(self, self->g1_charset, buf, buflen); break;
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
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
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
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
return true;
}
bool screen_toggle_screen_buffer(Screen *self) {
@ -196,9 +199,22 @@ bool screen_toggle_screen_buffer(Screen *self) {
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 {{{
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) {
switch(mode) {
case LNM:
@ -226,12 +242,12 @@ bool screen_set_mode(Screen *self, int mode) {
if (mode == DECCOLM) {
// When DECCOLM mode is set, the screen is erased and the cursor
// moves to the home position.
if (!screen_erase_in_display(self, 2, false)) return false;
if (!screen_cursor_position(self, 1, 1)) return false;
screen_erase_in_display(self, 2, false);
screen_cursor_position(self, 1, 1);
}
// According to `vttest`, DECOM should also home the cursor, see
// 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) {
// Mark all displayed characters as reverse.
@ -273,12 +289,12 @@ bool screen_reset_mode(Screen *self, int mode) {
if (mode == DECCOLM) {
// When DECCOLM mode is set, the screen is erased and the cursor
// moves to the home position.
if (!screen_erase_in_display(self, 2, false)) return false;
if (!screen_cursor_position(self, 1, 1)) return false;
screen_erase_in_display(self, 2, false);
screen_cursor_position(self, 1, 1);
}
// According to `vttest`, DECOM should also home the cursor, see
// 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) {
// Mark all displayed characters as reverse.
@ -303,6 +319,68 @@ bool screen_reset_mode(Screen *self, int mode) {
// }}}
// 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) {
Savepoint *sp = alloc_savepoint();
if (sp == NULL) return false;
@ -333,30 +411,120 @@ bool screen_restore_cursor(Screen *self) {
} else {
screen_cursor_position(self, 1, 1);
tracker_cursor_changed(self->change_tracker);
screen_reset_mode(self, DECOM);
if (!screen_reset_mode(self, DECOM)) return false;
}
return true;
}
bool screen_cursor_position(Screen UNUSED *self, unsigned int UNUSED line, unsigned int UNUSED column) {
return true; // TODO: Implement this
void screen_ensure_bounds(Screen *self, bool use_margins/*=false*/) {
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 {{{
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) {
if (self->linebuf == self->alt_linebuf) {if (!screen_toggle_screen_buffer(self)) return false; }
linebuf_clear(self->linebuf);
// TODO: Implement this
return true;
}
// Python interface {{{
static PyObject*
line(Screen *self, PyObject *val) {
#define line_doc ""
@ -372,17 +540,72 @@ draw(Screen *self, PyObject *args) {
#define draw_doc ""
Py_buffer pybuf;
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;
}
// 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[] = {
METHOD(line, METH_O)
METHOD(draw, METH_VARARGS)
METHOD(set_mode, METH_VARARGS)
METHOD(reset_mode, METH_VARARGS)
METHOD(enable_focus_tracking, 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 */
};
@ -408,4 +631,3 @@ PyTypeObject Screen_Type = {
INIT_TYPE(Screen)
// }}}

View File

@ -162,6 +162,7 @@ class Screen:
"""
self.lines, self.columns = lines, 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
self.tophistorybuf.clear()
is_main = self.linebuf is self.main_linebuf

View File

@ -8,26 +8,27 @@
#include "data-types.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*
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;
bool tracker_resize(ChangeTracker *self, unsigned int ynum, unsigned int xnum) {
#define ALLOC_VAR(name, sz) \
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;
self->ynum = ynum; self->xnum = xnum;
ALLOC_VAR(changed_lines, self->ynum);
ALLOC_VAR(changed_cells, self->xnum * self->ynum);
ALLOC_VAR(lines_with_changed_cells, self->ynum);
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;
}
@ -49,19 +50,10 @@ dealloc(ChangeTracker* 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*
reset(ChangeTracker *self) {
#define reset_doc "Reset all changes"
reset_inner(self);
tracker_reset(self);
Py_RETURN_NONE;
}
@ -156,7 +148,7 @@ update_cell_data(ChangeTracker *self, PyObject *args) {
}
PyObject *cursor_changed = self->cursor_changed ? Py_True : Py_False;
reset_inner(self);
tracker_reset(self);
Py_INCREF(cursor_changed);
return cursor_changed;
}
@ -192,9 +184,8 @@ get_ranges(bool *line, unsigned int xnum) {
return ans;
}
static PyObject*
consolidate_changes(ChangeTracker *self) {
#define consolidate_changes_doc ""
PyObject*
tracker_consolidate_changes(ChangeTracker *self) {
PyObject *ans = PyDict_New();
if (ans == NULL) return PyErr_NoMemory();
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; }
Py_CLEAR(t);
reset_inner(self);
tracker_reset(self);
return ans;
}
@ -259,7 +250,7 @@ static PyMethodDef methods[] = {
METHOD(update_cell_data, METH_VARARGS)
METHOD(reset, 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(update_screen, METH_NOARGS)
METHOD(update_line_range, METH_VARARGS)
@ -283,6 +274,8 @@ PyTypeObject ChangeTracker_Type = {
INIT_TYPE(ChangeTracker)
// }}}
ChangeTracker* alloc_change_tracker() {
return (ChangeTracker*)new(&ChangeTracker_Type, NULL, NULL);
ChangeTracker* alloc_change_tracker(unsigned int ynum, unsigned int xnum) {
ChangeTracker *self = (ChangeTracker *)(&ChangeTracker_Type)->tp_alloc((&ChangeTracker_Type), 0);
if (!tracker_resize(self, ynum, xnum)) { Py_CLEAR(self); return NULL; }
return self;
}

View File

@ -37,3 +37,17 @@ static inline void tracker_update_cell_range(ChangeTracker *self, unsigned int l
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);

View File

@ -33,8 +33,8 @@ class BaseTest(TestCase):
def create_screen(self, cols=5, lines=5, history_size=5):
return S(history_size, columns=cols, lines=lines)
def create_screen2(self, cols=5, lines=5, history_size=5):
return Screen(history_size, None, lines, cols)
def create_screen2(self, cols=5, lines=5):
return Screen(None, lines, cols)
def assertEqualAttributes(self, c1, c2):
x1, y1, c1.x, c1.y = c1.x, c1.y, 0, 0

View File

@ -4,14 +4,15 @@
from . import BaseTest
from kitty.screen import mo
from kitty.fast_data_types import DECAWM, IRM
class TestScreen(BaseTest):
def test_draw_fast(self):
s = self.create_screen2()
# Test in line-wrap, non-insert mode
s = self.create_screen()
s.draw(b'a' * 5)
self.ae(str(s.line(0)), 'a' * 5)
self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0)
@ -30,7 +31,7 @@ class TestScreen(BaseTest):
# Now test without line-wrap
s.reset(), s.reset_dirty()
s.reset_mode(mo.DECAWM)
s.reset_mode(DECAWM)
s.draw(b'0123456789')
self.ae(str(s.line(0)), '01239')
self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0)
@ -42,7 +43,7 @@ class TestScreen(BaseTest):
# Now test in insert mode
s.reset(), s.reset_dirty()
s.set_mode(mo.IRM)
s.set_mode(IRM)
s.draw(b'12345' * 5)
s.cursor_back(5)
self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 4)
@ -79,7 +80,7 @@ class TestScreen(BaseTest):
# Now test without line-wrap
s.reset(), s.reset_dirty()
s.reset_mode(mo.DECAWM)
s.reset_mode(DECAWM)
s.draw('0\u030612345\u03066789\u0306'.encode('utf-8'))
self.ae(str(s.line(0)), '0\u03061239\u0306')
self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0)
@ -91,7 +92,7 @@ class TestScreen(BaseTest):
# Now test in insert mode
s.reset(), s.reset_dirty()
s.set_mode(mo.IRM)
s.set_mode(IRM)
s.draw('1\u03062345'.encode('utf-8') * 5)
s.cursor_back(5)
self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 4)