1 /*
2  * screen.c
3  * Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
4  *
5  * Distributed under terms of the GPL3 license.
6  */
7 
8 #define EXTRA_INIT { \
9     PyModule_AddIntMacro(module, SCROLL_LINE); PyModule_AddIntMacro(module, SCROLL_PAGE); PyModule_AddIntMacro(module, SCROLL_FULL); \
10     if (PyModule_AddFunctions(module, module_methods) != 0) return false; \
11 }
12 
13 #include "state.h"
14 #include "iqsort.h"
15 #include "fonts.h"
16 #include "lineops.h"
17 #include "hyperlink.h"
18 #include <structmember.h>
19 #include <limits.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 #include "unicode-data.h"
24 #include "modes.h"
25 #include "wcwidth-std.h"
26 #include "control-codes.h"
27 #include "charsets.h"
28 
29 static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true};
30 
31 #define CSI_REP_MAX_REPETITIONS 65535u
32 
33 // Constructor/destructor {{{
34 
35 static void
clear_selection(Selections * selections)36 clear_selection(Selections *selections) {
37     selections->in_progress = false;
38     selections->extend_mode = EXTEND_CELL;
39     selections->count = 0;
40 }
41 
42 static void
init_tabstops(bool * tabstops,index_type count)43 init_tabstops(bool *tabstops, index_type count) {
44     // In terminfo we specify the number of initial tabstops (it) as 8
45     for (unsigned int t=0; t < count; t++) {
46         tabstops[t] = t % 8 == 0 ? true : false;
47     }
48 }
49 
50 static bool
init_overlay_line(Screen * self,index_type columns)51 init_overlay_line(Screen *self, index_type columns) {
52     PyMem_Free(self->overlay_line.cpu_cells);
53     PyMem_Free(self->overlay_line.gpu_cells);
54     self->overlay_line.cpu_cells = PyMem_Calloc(columns, sizeof(CPUCell));
55     self->overlay_line.gpu_cells = PyMem_Calloc(columns, sizeof(GPUCell));
56     if (!self->overlay_line.cpu_cells || !self->overlay_line.gpu_cells) {
57         PyErr_NoMemory(); return false;
58     }
59     self->overlay_line.is_active = false;
60     self->overlay_line.xnum = 0;
61     self->overlay_line.ynum = 0;
62     self->overlay_line.xstart = 0;
63     return true;
64 }
65 
66 #define RESET_CHARSETS \
67         self->g0_charset = translation_table(0); \
68         self->g1_charset = self->g0_charset; \
69         self->g_charset = self->g0_charset; \
70         self->current_charset = 0; \
71         self->utf8_state = 0; \
72         self->utf8_codepoint = 0; \
73         self->use_latin1 = false;
74 #define CALLBACK(...) \
75     if (self->callbacks != Py_None) { \
76         PyObject *callback_ret = PyObject_CallMethod(self->callbacks, __VA_ARGS__); \
77         if (callback_ret == NULL) PyErr_Print(); else Py_DECREF(callback_ret); \
78     }
79 
80 static PyObject*
new(PyTypeObject * type,PyObject * args,PyObject UNUSED * kwds)81 new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
82     Screen *self;
83     int ret = 0;
84     PyObject *callbacks = Py_None, *test_child = Py_None;
85     unsigned int columns=80, lines=24, scrollback=0, cell_width=10, cell_height=20;
86     id_type window_id=0;
87     if (!PyArg_ParseTuple(args, "|OIIIIIKO", &callbacks, &lines, &columns, &scrollback, &cell_width, &cell_height, &window_id, &test_child)) return NULL;
88 
89     self = (Screen *)type->tp_alloc(type, 0);
90     if (self != NULL) {
91         if ((ret = pthread_mutex_init(&self->read_buf_lock, NULL)) != 0) {
92             Py_CLEAR(self); PyErr_Format(PyExc_RuntimeError, "Failed to create Screen read_buf_lock mutex: %s", strerror(ret));
93             return NULL;
94         }
95         if ((ret = pthread_mutex_init(&self->write_buf_lock, NULL)) != 0) {
96             Py_CLEAR(self); PyErr_Format(PyExc_RuntimeError, "Failed to create Screen write_buf_lock mutex: %s", strerror(ret));
97             return NULL;
98         }
99         self->reload_all_gpu_data = true;
100         self->cell_size.width = cell_width; self->cell_size.height = cell_height;
101         self->columns = columns; self->lines = lines;
102         self->write_buf = PyMem_RawMalloc(BUFSIZ);
103         self->window_id = window_id;
104         if (self->write_buf == NULL) { Py_CLEAR(self); return PyErr_NoMemory(); }
105         self->write_buf_sz = BUFSIZ;
106         self->modes = empty_modes;
107         self->is_dirty = true;
108         self->scroll_changed = false;
109         self->margin_top = 0; self->margin_bottom = self->lines - 1;
110         self->history_line_added_count = 0;
111         RESET_CHARSETS;
112         self->callbacks = callbacks; Py_INCREF(callbacks);
113         self->test_child = test_child; Py_INCREF(test_child);
114         self->cursor = alloc_cursor();
115         self->color_profile = alloc_color_profile();
116         self->main_linebuf = alloc_linebuf(lines, columns); self->alt_linebuf = alloc_linebuf(lines, columns);
117         self->linebuf = self->main_linebuf;
118         self->historybuf = alloc_historybuf(MAX(scrollback, lines), columns, OPT(scrollback_pager_history_size));
119         self->main_grman = grman_alloc();
120         self->alt_grman = grman_alloc();
121         self->active_hyperlink_id = 0;
122 
123         self->grman = self->main_grman;
124         self->pending_mode.wait_time = s_double_to_monotonic_t(2.0);
125         self->disable_ligatures = OPT(disable_ligatures);
126         self->main_tabstops = PyMem_Calloc(2 * self->columns, sizeof(bool));
127         if (
128             self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL ||
129             self->main_tabstops == NULL || self->historybuf == NULL || self->main_grman == NULL ||
130             self->alt_grman == NULL || self->color_profile == NULL
131         ) {
132             Py_CLEAR(self); return NULL;
133         }
134         self->main_grman->window_id = self->window_id; self->alt_grman->window_id = self->window_id;
135         self->alt_tabstops = self->main_tabstops + self->columns;
136         self->tabstops = self->main_tabstops;
137         init_tabstops(self->main_tabstops, self->columns);
138         init_tabstops(self->alt_tabstops, self->columns);
139         self->key_encoding_flags = self->main_key_encoding_flags;
140         if (!init_overlay_line(self, self->columns)) { Py_CLEAR(self); return NULL; }
141         self->hyperlink_pool = alloc_hyperlink_pool();
142         if (!self->hyperlink_pool) { Py_CLEAR(self); return PyErr_NoMemory(); }
143         self->as_ansi_buf.hyperlink_pool = self->hyperlink_pool;
144     }
145     return (PyObject*) self;
146 }
147 
148 static void deactivate_overlay_line(Screen *self);
149 static Line* range_line_(Screen *self, int y);
150 
151 void
screen_reset(Screen * self)152 screen_reset(Screen *self) {
153     if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true);
154     if (self->overlay_line.is_active) deactivate_overlay_line(self);
155     memset(self->main_key_encoding_flags, 0, sizeof(self->main_key_encoding_flags));
156     memset(self->alt_key_encoding_flags, 0, sizeof(self->alt_key_encoding_flags));
157     self->last_graphic_char = 0;
158     self->main_savepoint.is_valid = false;
159     self->alt_savepoint.is_valid = false;
160     linebuf_clear(self->linebuf, BLANK_CHAR);
161     historybuf_clear(self->historybuf);
162     clear_hyperlink_pool(self->hyperlink_pool);
163     grman_clear(self->grman, false, self->cell_size);
164     self->modes = empty_modes;
165     self->active_hyperlink_id = 0;
166 #define R(name) self->color_profile->overridden.name = 0
167     R(default_fg); R(default_bg); R(cursor_color); R(highlight_fg); R(highlight_bg);
168 #undef R
169     RESET_CHARSETS;
170     self->margin_top = 0; self->margin_bottom = self->lines - 1;
171     screen_normal_keypad_mode(self);
172     init_tabstops(self->main_tabstops, self->columns);
173     init_tabstops(self->alt_tabstops, self->columns);
174     cursor_reset(self->cursor);
175     self->is_dirty = true;
176     clear_selection(&self->selections);
177     clear_selection(&self->url_ranges);
178     screen_cursor_position(self, 1, 1);
179     set_dynamic_color(self, 110, NULL);
180     set_dynamic_color(self, 111, NULL);
181     set_color_table_color(self, 104, NULL);
182     self->parser_state = 0;
183     self->parser_text_start = 0;
184     self->parser_buf_pos = 0;
185     self->parser_has_pending_text = false;
186 }
187 
188 void
screen_dirty_sprite_positions(Screen * self)189 screen_dirty_sprite_positions(Screen *self) {
190     self->is_dirty = true;
191     for (index_type i = 0; i < self->lines; i++) {
192         linebuf_mark_line_dirty(self->main_linebuf, i);
193         linebuf_mark_line_dirty(self->alt_linebuf, i);
194     }
195     for (index_type i = 0; i < self->historybuf->count; i++) historybuf_mark_line_dirty(self->historybuf, i);
196 }
197 
198 static HistoryBuf*
realloc_hb(HistoryBuf * old,unsigned int lines,unsigned int columns,ANSIBuf * as_ansi_buf)199 realloc_hb(HistoryBuf *old, unsigned int lines, unsigned int columns, ANSIBuf *as_ansi_buf) {
200     HistoryBuf *ans = alloc_historybuf(lines, columns, 0);
201     if (ans == NULL) { PyErr_NoMemory(); return NULL; }
202     ans->pagerhist = old->pagerhist; old->pagerhist = NULL;
203     historybuf_rewrap(old, ans, as_ansi_buf);
204     return ans;
205 }
206 
207 
208 typedef struct CursorTrack {
209     index_type num_content_lines;
210     bool is_beyond_content;
211     struct { index_type x, y; } before;
212     struct { index_type x, y; } after;
213     struct { index_type x, y; } temp;
214 } CursorTrack;
215 
216 static LineBuf*
realloc_lb(LineBuf * old,unsigned int lines,unsigned int columns,index_type * nclb,index_type * ncla,HistoryBuf * hb,CursorTrack * a,CursorTrack * b,ANSIBuf * as_ansi_buf)217 realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *nclb, index_type *ncla, HistoryBuf *hb, CursorTrack *a, CursorTrack *b, ANSIBuf *as_ansi_buf) {
218     LineBuf *ans = alloc_linebuf(lines, columns);
219     if (ans == NULL) { PyErr_NoMemory(); return NULL; }
220     a->temp.x = a->before.x; a->temp.y = a->before.y;
221     b->temp.x = b->before.x; b->temp.y = b->before.y;
222     linebuf_rewrap(old, ans, nclb, ncla, hb, &a->temp.x, &a->temp.y, &b->temp.x, &b->temp.y, as_ansi_buf);
223     return ans;
224 }
225 
226 static bool
is_selection_empty(const Selection * s)227 is_selection_empty(const Selection *s) {
228     int start_y = (int)s->start.y - (int)s->start_scrolled_by, end_y = (int)s->end.y - (int)s->end_scrolled_by;
229     return s->start.x == s->end.x && s->start.in_left_half_of_cell == s->end.in_left_half_of_cell && start_y == end_y;
230 }
231 
232 static void
index_selection(const Screen * self,Selections * selections,bool up)233 index_selection(const Screen *self, Selections *selections, bool up) {
234     for (size_t i = 0; i < selections->count; i++) {
235         Selection *s = selections->items + i;
236         if (up) {
237             if (s->start.y == 0) s->start_scrolled_by += 1;
238             else {
239                 s->start.y--;
240                 if (s->input_start.y) s->input_start.y--;
241                 if (s->input_current.y) s->input_current.y--;
242                 if (s->initial_extent.start.y) s->initial_extent.start.y--;
243                 if (s->initial_extent.end.y) s->initial_extent.end.y--;
244             }
245             if (s->end.y == 0) s->end_scrolled_by += 1;
246             else s->end.y--;
247         } else {
248             if (s->start.y >= self->lines - 1) s->start_scrolled_by -= 1;
249             else {
250                 s->start.y++;
251                 if (s->input_start.y < self->lines - 1) s->input_start.y++;
252                 if (s->input_current.y < self->lines - 1) s->input_current.y++;
253             }
254             if (s->end.y >= self->lines - 1) s->end_scrolled_by -= 1;
255             else s->end.y++;
256         }
257     }
258 }
259 
260 
261 #define INDEX_GRAPHICS(amtv) { \
262     bool is_main = self->linebuf == self->main_linebuf; \
263     static ScrollData s; \
264     s.amt = amtv; s.limit = is_main ? -self->historybuf->ynum : 0; \
265     s.has_margins = self->margin_top != 0 || self->margin_bottom != self->lines - 1; \
266     s.margin_top = top; s.margin_bottom = bottom; \
267     grman_scroll_images(self->grman, &s, self->cell_size); \
268 }
269 
270 
271 #define INDEX_DOWN \
272     if (self->overlay_line.is_active) deactivate_overlay_line(self); \
273     linebuf_reverse_index(self->linebuf, top, bottom); \
274     linebuf_clear_line(self->linebuf, top); \
275     INDEX_GRAPHICS(1) \
276     self->is_dirty = true; \
277     index_selection(self, &self->selections, false);
278 
279 static bool
screen_resize(Screen * self,unsigned int lines,unsigned int columns)280 screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
281     if (self->overlay_line.is_active) deactivate_overlay_line(self);
282     lines = MAX(1u, lines); columns = MAX(1u, columns);
283 
284     bool is_main = self->linebuf == self->main_linebuf;
285     index_type num_content_lines_before, num_content_lines_after;
286     unsigned int lines_after_cursor_before_resize = self->lines - self->cursor->y;
287     CursorTrack cursor = {.before = {self->cursor->x, self->cursor->y}};
288     CursorTrack main_saved_cursor = {.before = {self->main_savepoint.cursor.x, self->main_savepoint.cursor.y}};
289     CursorTrack alt_saved_cursor = {.before = {self->alt_savepoint.cursor.x, self->alt_savepoint.cursor.y}};
290 #define setup_cursor(which) { \
291     which.after.x = which.temp.x; which.after.y = which.temp.y; \
292     which.is_beyond_content = num_content_lines_before > 0 && self->cursor->y >= num_content_lines_before; \
293     which.num_content_lines = num_content_lines_after; \
294 }
295     // Resize overlay line
296     if (!init_overlay_line(self, columns)) return false;
297 
298     // Resize main linebuf
299     HistoryBuf *nh = realloc_hb(self->historybuf, self->historybuf->ynum, columns, &self->as_ansi_buf);
300     if (nh == NULL) return false;
301     Py_CLEAR(self->historybuf); self->historybuf = nh;
302     LineBuf *n = realloc_lb(self->main_linebuf, lines, columns, &num_content_lines_before, &num_content_lines_after, self->historybuf, &cursor, &main_saved_cursor, &self->as_ansi_buf);
303     if (n == NULL) return false;
304     Py_CLEAR(self->main_linebuf); self->main_linebuf = n;
305     if (is_main) setup_cursor(cursor);
306     setup_cursor(main_saved_cursor);
307     grman_resize(self->main_grman, self->lines, lines, self->columns, columns);
308 
309     // Resize alt linebuf
310     n = realloc_lb(self->alt_linebuf, lines, columns, &num_content_lines_before, &num_content_lines_after, NULL, &cursor, &alt_saved_cursor, &self->as_ansi_buf);
311     if (n == NULL) return false;
312     Py_CLEAR(self->alt_linebuf); self->alt_linebuf = n;
313     if (!is_main) setup_cursor(cursor);
314     setup_cursor(alt_saved_cursor);
315     grman_resize(self->alt_grman, self->lines, lines, self->columns, columns);
316 #undef setup_cursor
317 
318     self->linebuf = is_main ? self->main_linebuf : self->alt_linebuf;
319     /* printf("\nold_size: (%u, %u) new_size: (%u, %u)\n", self->columns, self->lines, columns, lines); */
320     self->lines = lines; self->columns = columns;
321     self->margin_top = 0; self->margin_bottom = self->lines - 1;
322 
323     PyMem_Free(self->main_tabstops);
324     self->main_tabstops = PyMem_Calloc(2*self->columns, sizeof(bool));
325     if (self->main_tabstops == NULL) { PyErr_NoMemory(); return false; }
326     self->alt_tabstops = self->main_tabstops + self->columns;
327     self->tabstops = self->main_tabstops;
328     init_tabstops(self->main_tabstops, self->columns);
329     init_tabstops(self->alt_tabstops, self->columns);
330     self->is_dirty = true;
331     clear_selection(&self->selections);
332     clear_selection(&self->url_ranges);
333     /* printf("old_cursor: (%u, %u) new_cursor: (%u, %u) beyond_content: %d\n", self->cursor->x, self->cursor->y, cursor_x, cursor_y, cursor_is_beyond_content); */
334 #define S(c, w) c->x = MIN(w.after.x, self->columns - 1); c->y = MIN(w.after.y, self->lines - 1);
335     S(self->cursor, cursor);
336     S((&(self->main_savepoint.cursor)), main_saved_cursor);
337     S((&(self->alt_savepoint.cursor)), alt_saved_cursor);
338 #undef S
339     if (cursor.is_beyond_content) {
340         self->cursor->y = cursor.num_content_lines;
341         if (self->cursor->y >= self->lines) { self->cursor->y = self->lines - 1; screen_index(self); }
342     }
343     if (is_main && OPT(scrollback_fill_enlarged_window)) {
344         const unsigned int top = 0, bottom = self->lines-1;
345         Savepoint *sp = is_main ? &self->main_savepoint : &self->alt_savepoint;
346         while (self->cursor->y + 1 < self->lines && self->lines - self->cursor->y > lines_after_cursor_before_resize) {
347             if (!historybuf_pop_line(self->historybuf, self->alt_linebuf->line)) break;
348             INDEX_DOWN;
349             linebuf_copy_line_to(self->main_linebuf, self->alt_linebuf->line, 0);
350             self->cursor->y++;
351             sp->cursor.y = MIN(sp->cursor.y + 1, self->lines - 1);
352         }
353     }
354     return true;
355 }
356 
357 void
screen_rescale_images(Screen * self)358 screen_rescale_images(Screen *self) {
359     grman_rescale(self->main_grman, self->cell_size);
360     grman_rescale(self->alt_grman, self->cell_size);
361 }
362 
363 
364 static PyObject*
reset_callbacks(Screen * self,PyObject * a UNUSED)365 reset_callbacks(Screen *self, PyObject *a UNUSED) {
366     Py_CLEAR(self->callbacks);
367     self->callbacks = Py_None;
368     Py_INCREF(self->callbacks);
369     Py_RETURN_NONE;
370 }
371 
372 static void
dealloc(Screen * self)373 dealloc(Screen* self) {
374     pthread_mutex_destroy(&self->read_buf_lock);
375     pthread_mutex_destroy(&self->write_buf_lock);
376     Py_CLEAR(self->main_grman);
377     Py_CLEAR(self->alt_grman);
378     PyMem_RawFree(self->write_buf);
379     Py_CLEAR(self->callbacks);
380     Py_CLEAR(self->test_child);
381     Py_CLEAR(self->cursor);
382     Py_CLEAR(self->main_linebuf);
383     Py_CLEAR(self->alt_linebuf);
384     Py_CLEAR(self->historybuf);
385     Py_CLEAR(self->color_profile);
386     Py_CLEAR(self->marker);
387     PyMem_Free(self->overlay_line.cpu_cells);
388     PyMem_Free(self->overlay_line.gpu_cells);
389     PyMem_Free(self->main_tabstops);
390     free(self->pending_mode.buf);
391     free(self->selections.items);
392     free(self->url_ranges.items);
393     free_hyperlink_pool(self->hyperlink_pool);
394     free(self->as_ansi_buf.buf);
395     Py_TYPE(self)->tp_free((PyObject*)self);
396 } // }}}
397 
398 // Draw text {{{
399 
400 void
screen_change_charset(Screen * self,uint32_t which)401 screen_change_charset(Screen *self, uint32_t which) {
402     switch(which) {
403         case 0:
404             self->current_charset = 0;
405             self->g_charset = self->g0_charset;
406             break;
407         case 1:
408             self->current_charset = 1;
409             self->g_charset = self->g1_charset;
410             break;
411     }
412 }
413 
414 void
screen_designate_charset(Screen * self,uint32_t which,uint32_t as)415 screen_designate_charset(Screen *self, uint32_t which, uint32_t as) {
416     switch(which) {
417         case 0:
418             self->g0_charset = translation_table(as);
419             if (self->current_charset == 0) self->g_charset = self->g0_charset;
420             break;
421         case 1:
422             self->g1_charset = translation_table(as);
423             if (self->current_charset == 1) self->g_charset = self->g1_charset;
424             break;
425     }
426 }
427 
428 static void
move_widened_char(Screen * self,CPUCell * cpu_cell,GPUCell * gpu_cell,index_type xpos,index_type ypos)429 move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type xpos, index_type ypos) {
430     self->cursor->x = xpos; self->cursor->y = ypos;
431     CPUCell src_cpu = *cpu_cell, *dest_cpu;
432     GPUCell src_gpu = *gpu_cell, *dest_gpu;
433     line_clear_text(self->linebuf->line, xpos, 1, BLANK_CHAR);
434 
435     if (self->modes.mDECAWM) {  // overflow goes onto next line
436         screen_carriage_return(self);
437         screen_linefeed(self);
438         self->linebuf->line_attrs[self->cursor->y] |= CONTINUED_MASK;
439         linebuf_init_line(self->linebuf, self->cursor->y);
440         dest_cpu = self->linebuf->line->cpu_cells;
441         dest_gpu = self->linebuf->line->gpu_cells;
442         self->cursor->x = MIN(2u, self->columns);
443         linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
444     } else {
445         dest_cpu = cpu_cell - 1;
446         dest_gpu = gpu_cell - 1;
447         self->cursor->x = self->columns;
448     }
449     *dest_cpu = src_cpu;
450     *dest_gpu = src_gpu;
451 }
452 
453 static bool
selection_has_screen_line(const Selections * selections,const int y)454 selection_has_screen_line(const Selections *selections, const int y) {
455     for (size_t i = 0; i < selections->count; i++) {
456         const Selection *s = selections->items + i;
457         if (!is_selection_empty(s)) {
458             int start = (int)s->start.y - s->start_scrolled_by;
459             int end = (int)s->end.y - s->end_scrolled_by;
460             int top = MIN(start, end);
461             int bottom = MAX(start, end);
462             if (top <= y && y <= bottom) return true;
463         }
464     }
465     return false;
466 }
467 
468 void
set_active_hyperlink(Screen * self,char * id,char * url)469 set_active_hyperlink(Screen *self, char *id, char *url) {
470     if (OPT(allow_hyperlinks)) {
471         if (!url || !url[0]) {
472             self->active_hyperlink_id = 0;
473             return;
474         }
475         self->active_hyperlink_id = get_id_for_hyperlink(self, id, url);
476     }
477 }
478 
479 hyperlink_id_type
remap_hyperlink_ids(Screen * self,hyperlink_id_type * map)480 remap_hyperlink_ids(Screen *self, hyperlink_id_type *map) {
481 #define PROCESS_CELL(cell) { hid = (cell).hyperlink_id; if (hid) { if (!map[hid]) map[hid] = ++num; (cell).hyperlink_id = map[hid]; }}
482     hyperlink_id_type num = 0, hid;
483     if (self->historybuf->count) {
484         for (index_type y = self->historybuf->count; y-- > 0;) {
485             CPUCell *cells = historybuf_cpu_cells(self->historybuf, y);
486             for (index_type x = 0; x < self->historybuf->xnum; x++) {
487                 PROCESS_CELL(cells[x]);
488             }
489         }
490     }
491     LineBuf *second = self->linebuf, *first = second == self->main_linebuf ? self->alt_linebuf : self->main_linebuf;
492     for (index_type i = 0; i < self->lines * self->columns; i++) {
493         PROCESS_CELL(first->cpu_cell_buf[i]);
494     }
495     for (index_type i = 0; i < self->lines * self->columns; i++) {
496         PROCESS_CELL(second->cpu_cell_buf[i]);
497     }
498     return num;
499 #undef PROCESS_CELL
500 }
501 
502 
is_flag_pair(char_type a,char_type b)503 static bool is_flag_pair(char_type a, char_type b) {
504     return is_flag_codepoint(a) && is_flag_codepoint(b);
505 }
506 
507 static bool
draw_second_flag_codepoint(Screen * self,char_type ch)508 draw_second_flag_codepoint(Screen *self, char_type ch) {
509     index_type xpos = 0, ypos = 0;
510     if (self->cursor->x > 1) {
511         ypos = self->cursor->y;
512         xpos = self->cursor->x - 2;
513     } else if (self->cursor->y > 0 && self->columns > 1) {
514         ypos = self->cursor->y - 1;
515         xpos = self->columns - 2;
516     } else return false;
517 
518     linebuf_init_line(self->linebuf, ypos);
519     CPUCell *cell = self->linebuf->line->cpu_cells + xpos;
520     if (!is_flag_pair(cell->ch, ch) || cell->cc_idx[0]) return false;
521     line_add_combining_char(self->linebuf->line, ch, xpos);
522     self->is_dirty = true;
523     if (selection_has_screen_line(&self->selections, ypos)) clear_selection(&self->selections);
524     linebuf_mark_line_dirty(self->linebuf, ypos);
525     return true;
526 }
527 
528 static void
draw_combining_char(Screen * self,char_type ch)529 draw_combining_char(Screen *self, char_type ch) {
530     bool has_prev_char = false;
531     index_type xpos = 0, ypos = 0;
532     if (self->cursor->x > 0) {
533         ypos = self->cursor->y;
534         linebuf_init_line(self->linebuf, ypos);
535         xpos = self->cursor->x - 1;
536         has_prev_char = true;
537     } else if (self->cursor->y > 0) {
538         ypos = self->cursor->y - 1;
539         linebuf_init_line(self->linebuf, ypos);
540         xpos = self->columns - 1;
541         has_prev_char = true;
542     }
543     if (self->cursor->x > 0) {
544         ypos = self->cursor->y;
545         linebuf_init_line(self->linebuf, ypos);
546         xpos = self->cursor->x - 1;
547         has_prev_char = true;
548     } else if (self->cursor->y > 0) {
549         ypos = self->cursor->y - 1;
550         linebuf_init_line(self->linebuf, ypos);
551         xpos = self->columns - 1;
552         has_prev_char = true;
553     }
554     if (has_prev_char) {
555         line_add_combining_char(self->linebuf->line, ch, xpos);
556         self->is_dirty = true;
557         if (selection_has_screen_line(&self->selections, ypos)) clear_selection(&self->selections);
558         linebuf_mark_line_dirty(self->linebuf, ypos);
559         if (ch == 0xfe0f) {  // emoji presentation variation marker makes default text presentation emoji (narrow emoji) into wide emoji
560             CPUCell *cpu_cell = self->linebuf->line->cpu_cells + xpos;
561             GPUCell *gpu_cell = self->linebuf->line->gpu_cells + xpos;
562             if ((gpu_cell->attrs & WIDTH_MASK) != 2 && cpu_cell->cc_idx[0] == VS16 && is_emoji_presentation_base(cpu_cell->ch)) {
563                 if (self->cursor->x <= self->columns - 1) line_set_char(self->linebuf->line, self->cursor->x, 0, 0, self->cursor, self->active_hyperlink_id);
564                 gpu_cell->attrs = (gpu_cell->attrs & !WIDTH_MASK) | 2;
565                 if (xpos == self->columns - 1) move_widened_char(self, cpu_cell, gpu_cell, xpos, ypos);
566                 else self->cursor->x++;
567             }
568         } else if (ch == 0xfe0e) {
569             CPUCell *cpu_cell = self->linebuf->line->cpu_cells + xpos;
570             GPUCell *gpu_cell = self->linebuf->line->gpu_cells + xpos;
571             if ((gpu_cell->attrs & WIDTH_MASK) == 0 && cpu_cell->ch == 0 && xpos > 0) {
572                 xpos--;
573                 if (self->cursor->x > 0) self->cursor->x--;
574                 cpu_cell = self->linebuf->line->cpu_cells + xpos;
575                 gpu_cell = self->linebuf->line->gpu_cells + xpos;
576             }
577 
578             if ((gpu_cell->attrs & WIDTH_MASK) == 2 && cpu_cell->cc_idx[0] == VS15 && is_emoji_presentation_base(cpu_cell->ch)) {
579                 gpu_cell->attrs = (gpu_cell->attrs & !WIDTH_MASK) | 1;
580             }
581         }
582     }
583 }
584 
585 void
screen_draw(Screen * self,uint32_t och,bool from_input_stream)586 screen_draw(Screen *self, uint32_t och, bool from_input_stream) {
587     if (is_ignored_char(och)) return;
588     if (!self->has_activity_since_last_focus && !self->has_focus) {
589         self->has_activity_since_last_focus = true;
590         CALLBACK("on_activity_since_last_focus", NULL);
591     }
592     uint32_t ch = och < 256 ? self->g_charset[och] : och;
593     bool is_cc = is_combining_char(ch);
594     if (UNLIKELY(is_cc)) {
595         draw_combining_char(self, ch);
596         return;
597     }
598     bool is_flag = is_flag_codepoint(ch);
599     if (UNLIKELY(is_flag)) {
600         if (draw_second_flag_codepoint(self, ch)) return;
601     }
602     int char_width = wcwidth_std(ch);
603     if (UNLIKELY(char_width < 1)) {
604         if (char_width == 0) return;
605         char_width = 1;
606     }
607     if (from_input_stream) self->last_graphic_char = ch;
608     if (UNLIKELY(self->columns - self->cursor->x < (unsigned int)char_width)) {
609         if (self->modes.mDECAWM) {
610             screen_carriage_return(self);
611             screen_linefeed(self);
612             self->linebuf->line_attrs[self->cursor->y] |= CONTINUED_MASK;
613         } else {
614             self->cursor->x = self->columns - char_width;
615         }
616     }
617 
618     linebuf_init_line(self->linebuf, self->cursor->y);
619     if (self->modes.mIRM) {
620         line_right_shift(self->linebuf->line, self->cursor->x, char_width);
621     }
622     line_set_char(self->linebuf->line, self->cursor->x, ch, char_width, self->cursor, self->active_hyperlink_id);
623     self->cursor->x++;
624     if (char_width == 2) {
625         line_set_char(self->linebuf->line, self->cursor->x, 0, 0, self->cursor, self->active_hyperlink_id);
626         self->cursor->x++;
627     }
628     self->is_dirty = true;
629     if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections);
630     linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
631 }
632 
633 void
screen_draw_overlay_text(Screen * self,const char * utf8_text)634 screen_draw_overlay_text(Screen *self, const char *utf8_text) {
635     if (self->overlay_line.is_active) deactivate_overlay_line(self);
636     if (!utf8_text || !utf8_text[0]) return;
637     Line *line = range_line_(self, self->cursor->y);
638     if (!line) return;
639     line_save_cells(line, 0, self->columns, self->overlay_line.gpu_cells, self->overlay_line.cpu_cells);
640     self->overlay_line.is_active = true;
641     self->overlay_line.ynum = self->cursor->y;
642     self->overlay_line.xstart = self->cursor->x;
643     self->overlay_line.xnum = 0;
644     uint32_t codepoint = 0; UTF8State state = UTF8_ACCEPT;
645     bool orig_line_wrap_mode = self->modes.mDECAWM;
646     self->modes.mDECAWM = false;
647     self->cursor->reverse ^= true;
648     index_type before;
649     while (*utf8_text) {
650         switch(decode_utf8(&state, &codepoint, *(utf8_text++))) {
651             case UTF8_ACCEPT:
652                 before = self->cursor->x;
653                 screen_draw(self, codepoint, false);
654                 self->overlay_line.xnum += self->cursor->x - before;
655                 break;
656             case UTF8_REJECT:
657                 break;
658         }
659     }
660     self->cursor->reverse ^= true;
661     self->modes.mDECAWM = orig_line_wrap_mode;
662 }
663 
664 void
screen_align(Screen * self)665 screen_align(Screen *self) {
666     self->margin_top = 0; self->margin_bottom = self->lines - 1;
667     screen_cursor_position(self, 1, 1);
668     linebuf_clear(self->linebuf, 'E');
669 }
670 
671 // }}}
672 
673 // Graphics {{{
674 
675 void
screen_alignment_display(Screen * self)676 screen_alignment_display(Screen *self) {
677     // https://www.vt100.net/docs/vt510-rm/DECALN.html
678     screen_cursor_position(self, 1, 1);
679     self->margin_top = 0; self->margin_bottom = self->lines - 1;
680     for (unsigned int y = 0; y < self->linebuf->ynum; y++) {
681         linebuf_init_line(self->linebuf, y);
682         line_clear_text(self->linebuf->line, 0, self->linebuf->xnum, 'E');
683         linebuf_mark_line_dirty(self->linebuf, y);
684     }
685 }
686 
687 void
select_graphic_rendition(Screen * self,int * params,unsigned int count,Region * region_)688 select_graphic_rendition(Screen *self, int *params, unsigned int count, Region *region_) {
689     if (region_) {
690         Region region = *region_;
691         if (!region.top) region.top = 1;
692         if (!region.left) region.left = 1;
693         if (!region.bottom) region.bottom = self->lines;
694         if (!region.right) region.right = self->columns;
695         if (self->modes.mDECOM) {
696             region.top += self->margin_top; region.bottom += self->margin_top;
697         }
698         region.left -= 1; region.top -= 1; region.right -= 1; region.bottom -= 1;  // switch to zero based indexing
699         if (self->modes.mDECSACE) {
700             index_type x = MIN(region.left, self->columns - 1);
701             index_type num = region.right >= x ? region.right - x + 1 : 0;
702             num = MIN(num, self->columns - x);
703             for (index_type y = region.top; y < MIN(region.bottom + 1, self->lines); y++) {
704                 linebuf_init_line(self->linebuf, y);
705                 apply_sgr_to_cells(self->linebuf->line->gpu_cells + x, num, params, count);
706             }
707         } else {
708             index_type x, num;
709             for (index_type y = region.top; y < MIN(region.bottom + 1, self->lines); y++) {
710                 if (y == region.top) { x = MIN(region.left, self->columns - 1); num = self->columns - x; }
711                 else if (y == region.bottom) { x = 0; num = MIN(region.right + 1, self->columns); }
712                 else { x = 0; num = self->columns; }
713                 linebuf_init_line(self->linebuf, y);
714                 apply_sgr_to_cells(self->linebuf->line->gpu_cells + x, num, params, count);
715             }
716         }
717     } else cursor_from_sgr(self->cursor, params, count);
718 }
719 
720 static void
write_to_test_child(Screen * self,const char * data,size_t sz)721 write_to_test_child(Screen *self, const char *data, size_t sz) {
722     PyObject *r = PyObject_CallMethod(self->test_child, "write", "y#", data, sz); if (r == NULL) PyErr_Print(); Py_CLEAR(r);
723 }
724 
725 static void
write_to_child(Screen * self,const char * data,size_t sz)726 write_to_child(Screen *self, const char *data, size_t sz) {
727     if (self->window_id) schedule_write_to_child(self->window_id, 1, data, sz);
728     if (self->test_child != Py_None) { write_to_test_child(self, data, sz); }
729 }
730 
731 void
write_escape_code_to_child(Screen * self,unsigned char which,const char * data)732 write_escape_code_to_child(Screen *self, unsigned char which, const char *data) {
733     const char *prefix, *suffix = self->modes.eight_bit_controls ? "\x9c" : "\033\\";
734     switch(which) {
735         case DCS:
736             prefix = self->modes.eight_bit_controls ? "\x90" : "\033P";
737             break;
738         case CSI:
739             prefix = self->modes.eight_bit_controls ? "\x9b" : "\033["; suffix = "";
740             break;
741         case OSC:
742             prefix = self->modes.eight_bit_controls ? "\x9d" : "\033]";
743             break;
744         case PM:
745             prefix = self->modes.eight_bit_controls ? "\x9e" : "\033^";
746             break;
747         case APC:
748             prefix = self->modes.eight_bit_controls ? "\x9f" : "\033_";
749             break;
750         default:
751             fatal("Unknown escape code to write: %u", which);
752     }
753     if (self->window_id) {
754         if (suffix[0]) {
755             schedule_write_to_child(self->window_id, 3, prefix, strlen(prefix), data, strlen(data), suffix, strlen(suffix));
756         } else {
757             schedule_write_to_child(self->window_id, 2, prefix, strlen(prefix), data, strlen(data));
758         }
759     }
760     if (self->test_child != Py_None) {
761         write_to_test_child(self, prefix, strlen(prefix));
762         write_to_test_child(self, data, strlen(data));
763         if (suffix[0]) write_to_test_child(self, suffix, strlen(suffix));
764     }
765 }
766 
767 static bool
cursor_within_margins(Screen * self)768 cursor_within_margins(Screen *self) {
769     return self->margin_top <= self->cursor->y && self->cursor->y <= self->margin_bottom;
770 }
771 
772 void
screen_handle_graphics_command(Screen * self,const GraphicsCommand * cmd,const uint8_t * payload)773 screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const uint8_t *payload) {
774     unsigned int x = self->cursor->x, y = self->cursor->y;
775     const char *response = grman_handle_command(self->grman, cmd, payload, self->cursor, &self->is_dirty, self->cell_size);
776     if (response != NULL) write_escape_code_to_child(self, APC, response);
777     if (x != self->cursor->x || y != self->cursor->y) {
778         bool in_margins = cursor_within_margins(self);
779         if (self->cursor->x >= self->columns) { self->cursor->x = 0; self->cursor->y++; }
780         if (self->cursor->y > self->margin_bottom) screen_scroll(self, self->cursor->y - self->margin_bottom);
781         screen_ensure_bounds(self, false, in_margins);
782     }
783 }
784 // }}}
785 
786 // Modes {{{
787 
788 
789 void
screen_toggle_screen_buffer(Screen * self,bool save_cursor,bool clear_alt_screen)790 screen_toggle_screen_buffer(Screen *self, bool save_cursor, bool clear_alt_screen) {
791     bool to_alt = self->linebuf == self->main_linebuf;
792     self->active_hyperlink_id = 0;
793     if (to_alt) {
794         if (clear_alt_screen) {
795             linebuf_clear(self->alt_linebuf, BLANK_CHAR);
796             grman_clear(self->alt_grman, true, self->cell_size);
797         }
798         if (save_cursor) screen_save_cursor(self);
799         self->linebuf = self->alt_linebuf;
800         self->tabstops = self->alt_tabstops;
801         self->key_encoding_flags = self->alt_key_encoding_flags;
802         self->grman = self->alt_grman;
803         screen_cursor_position(self, 1, 1);
804         cursor_reset(self->cursor);
805     } else {
806         self->linebuf = self->main_linebuf;
807         self->tabstops = self->main_tabstops;
808         self->key_encoding_flags = self->main_key_encoding_flags;
809         if (save_cursor) screen_restore_cursor(self);
810         self->grman = self->main_grman;
811     }
812     screen_history_scroll(self, SCROLL_FULL, false);
813     self->is_dirty = true;
814     clear_selection(&self->selections);
815 }
816 
screen_normal_keypad_mode(Screen UNUSED * self)817 void screen_normal_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI
screen_alternate_keypad_mode(Screen UNUSED * self)818 void screen_alternate_keypad_mode(Screen UNUSED *self) {}  // Not implemented as this is handled by the GUI
819 
820 static void
set_mode_from_const(Screen * self,unsigned int mode,bool val)821 set_mode_from_const(Screen *self, unsigned int mode, bool val) {
822 #define SIMPLE_MODE(name) \
823     case name: \
824         self->modes.m##name = val; break;
825 
826 #define MOUSE_MODE(name, attr, value) \
827     case name: \
828         self->modes.attr = val ? value : 0; break;
829 
830     bool private;
831     switch(mode) {
832         SIMPLE_MODE(LNM)
833         SIMPLE_MODE(IRM)
834         SIMPLE_MODE(DECARM)
835         SIMPLE_MODE(BRACKETED_PASTE)
836         SIMPLE_MODE(FOCUS_TRACKING)
837         MOUSE_MODE(MOUSE_BUTTON_TRACKING, mouse_tracking_mode, BUTTON_MODE)
838         MOUSE_MODE(MOUSE_MOTION_TRACKING, mouse_tracking_mode, MOTION_MODE)
839         MOUSE_MODE(MOUSE_MOVE_TRACKING, mouse_tracking_mode, ANY_MODE)
840         MOUSE_MODE(MOUSE_UTF8_MODE, mouse_tracking_protocol, UTF8_PROTOCOL)
841         MOUSE_MODE(MOUSE_SGR_MODE, mouse_tracking_protocol, SGR_PROTOCOL)
842         MOUSE_MODE(MOUSE_URXVT_MODE, mouse_tracking_protocol, URXVT_PROTOCOL)
843 
844         case DECSCLM:
845         case DECNRCM:
846             break;  // we ignore these modes
847         case DECCKM:
848             self->modes.mDECCKM = val;
849             break;
850         case DECTCEM:
851             self->modes.mDECTCEM = val;
852             break;
853         case DECSCNM:
854             // Render screen in reverse video
855             if (self->modes.mDECSCNM != val) {
856                 self->modes.mDECSCNM = val;
857                 self->is_dirty = true;
858             }
859             break;
860         case DECOM:
861             self->modes.mDECOM = val;
862             // According to `vttest`, DECOM should also home the cursor, see
863             // vttest/main.c:303.
864             screen_cursor_position(self, 1, 1);
865             break;
866         case DECAWM:
867             self->modes.mDECAWM = val; break;
868         case DECCOLM:
869             self->modes.mDECCOLM = val;
870             if (val) {
871                 // When DECCOLM mode is set, the screen is erased and the cursor
872                 // moves to the home position.
873                 screen_erase_in_display(self, 2, false);
874                 screen_cursor_position(self, 1, 1);
875             }
876             break;
877         case CONTROL_CURSOR_BLINK:
878             self->cursor->non_blinking = !val;
879             break;
880         case SAVE_CURSOR:
881             screen_save_cursor(self);
882             break;
883         case TOGGLE_ALT_SCREEN_1:
884         case TOGGLE_ALT_SCREEN_2:
885         case ALTERNATE_SCREEN:
886             if (val && self->linebuf == self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN);
887             else if (!val && self->linebuf != self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN);
888             break;
889         case PENDING_UPDATE:
890             if (val) {
891                 self->pending_mode.activated_at = monotonic();
892             } else {
893                 if (!self->pending_mode.activated_at) log_error(
894                     "Pending mode stop command issued while not in pending mode, this can"
895                     " be either a bug in the terminal application or caused by a timeout with no data"
896                     " received for too long or by too much data in pending mode");
897                 else self->pending_mode.activated_at = 0;
898             }
899             break;
900         case 7727 << 5:
901             log_error("Application escape mode is not supported, the extended keyboard protocol should be used instead");
902             break;
903         default:
904             private = mode >= 1 << 5;
905             if (private) mode >>= 5;
906             log_error("%s %s %u %s", ERROR_PREFIX, "Unsupported screen mode: ", mode, private ? "(private)" : "");
907     }
908 #undef SIMPLE_MODE
909 #undef MOUSE_MODE
910 }
911 
912 void
screen_set_mode(Screen * self,unsigned int mode)913 screen_set_mode(Screen *self, unsigned int mode) {
914     set_mode_from_const(self, mode, true);
915 }
916 
917 void
screen_decsace(Screen * self,unsigned int val)918 screen_decsace(Screen *self, unsigned int val) {
919     self->modes.mDECSACE = val == 2 ? true : false;
920 }
921 
922 void
screen_reset_mode(Screen * self,unsigned int mode)923 screen_reset_mode(Screen *self, unsigned int mode) {
924     set_mode_from_const(self, mode, false);
925 }
926 
927 void
screen_set_8bit_controls(Screen * self,bool yes)928 screen_set_8bit_controls(Screen *self, bool yes) {
929     self->modes.eight_bit_controls = yes;
930 }
931 
932 uint8_t
screen_current_key_encoding_flags(Screen * self)933 screen_current_key_encoding_flags(Screen *self) {
934     for (unsigned i = arraysz(self->main_key_encoding_flags); i-- > 0; ) {
935         if (self->key_encoding_flags[i] & 0x80) return self->key_encoding_flags[i] & 0x7f;
936     }
937     return 0;
938 }
939 
940 void
screen_report_key_encoding_flags(Screen * self)941 screen_report_key_encoding_flags(Screen *self) {
942     char buf[16] = {0};
943     snprintf(buf, sizeof(buf), "?%uu", screen_current_key_encoding_flags(self));
944     write_escape_code_to_child(self, CSI, buf);
945 }
946 
947 void
screen_set_key_encoding_flags(Screen * self,uint32_t val,uint32_t how)948 screen_set_key_encoding_flags(Screen *self, uint32_t val, uint32_t how) {
949     unsigned idx = 0;
950     for (unsigned i = arraysz(self->main_key_encoding_flags); i-- > 0; ) {
951         if (self->key_encoding_flags[i] & 0x80) { idx = i; break; }
952     }
953     uint8_t q = val & 0x7f;
954     if (how == 1) self->key_encoding_flags[idx] = q;
955     else if (how == 2) self->key_encoding_flags[idx] |= q;
956     else if (how == 3) self->key_encoding_flags[idx] &= ~q;
957     self->key_encoding_flags[idx] |= 0x80;
958 }
959 
960 void
screen_push_key_encoding_flags(Screen * self,uint32_t val)961 screen_push_key_encoding_flags(Screen *self, uint32_t val) {
962     uint8_t q = val & 0x7f;
963     const unsigned sz = arraysz(self->main_key_encoding_flags);
964     unsigned current_idx = 0;
965     for (unsigned i = arraysz(self->main_key_encoding_flags); i-- > 0; ) {
966         if (self->key_encoding_flags[i] & 0x80) { current_idx = i; break; }
967     }
968     if (current_idx == sz - 1) memmove(self->key_encoding_flags, self->key_encoding_flags + 1, (sz - 1) * sizeof(self->main_key_encoding_flags[0]));
969     else self->key_encoding_flags[current_idx++] |= 0x80;
970     self->key_encoding_flags[current_idx] = 0x80 | q;
971 }
972 
973 void
screen_pop_key_encoding_flags(Screen * self,uint32_t num)974 screen_pop_key_encoding_flags(Screen *self, uint32_t num) {
975     for (unsigned i = arraysz(self->main_key_encoding_flags); num && i-- > 0; ) {
976         if (self->key_encoding_flags[i] & 0x80) { num--; self->key_encoding_flags[i] = 0; }
977     }
978 }
979 
980 void
screen_xtmodkeys(Screen * self,uint32_t p1,uint32_t p2)981 screen_xtmodkeys(Screen *self, uint32_t p1, uint32_t p2) {
982     // this is the legacy XTerm escape code for modify keys
983     // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-gt-Pp-m.1DB2
984     // we handle them as being equivalent to push and pop 1 onto the keyboard stack
985     if ((!p1 && !p2) || (p1 == 4 && p2 == 0)) screen_pop_key_encoding_flags(self, 1);
986     else if (p1 == 4 && p2 == 1) screen_push_key_encoding_flags(self, 1);
987 }
988 
989 
990 // }}}
991 
992 // Cursor {{{
993 
994 unsigned long
screen_current_char_width(Screen * self)995 screen_current_char_width(Screen *self) {
996     unsigned long ans = 1;
997     if (self->cursor->x < self->columns - 1 && self->cursor->y < self->lines) {
998         ans = linebuf_char_width_at(self->linebuf, self->cursor->x, self->cursor->y);
999     }
1000     return ans;
1001 }
1002 
1003 bool
screen_is_cursor_visible(Screen * self)1004 screen_is_cursor_visible(Screen *self) {
1005     return self->modes.mDECTCEM;
1006 }
1007 
1008 void
screen_backspace(Screen * self)1009 screen_backspace(Screen *self) {
1010     screen_cursor_back(self, 1, -1);
1011 }
1012 
1013 void
screen_tab(Screen * self)1014 screen_tab(Screen *self) {
1015     // Move to the next tab space, or the end of the screen if there aren't anymore left.
1016     unsigned int found = 0;
1017     for (unsigned int i = self->cursor->x + 1; i < self->columns; i++) {
1018         if (self->tabstops[i]) { found = i; break; }
1019     }
1020     if (!found) found = self->columns - 1;
1021     if (found != self->cursor->x) {
1022         if (self->cursor->x < self->columns) {
1023             linebuf_init_line(self->linebuf, self->cursor->y);
1024             combining_type diff = found - self->cursor->x;
1025             CPUCell *cpu_cell = self->linebuf->line->cpu_cells + self->cursor->x;
1026             bool ok = true;
1027             for (combining_type i = 0; i < diff; i++) {
1028                 CPUCell *c = cpu_cell + i;
1029                 if (c->ch != ' ' && c->ch != 0) { ok = false; break; }
1030             }
1031             if (ok) {
1032                 for (combining_type i = 0; i < diff; i++) {
1033                     CPUCell *c = cpu_cell + i;
1034                     c->ch = ' '; zero_at_ptr_count(c->cc_idx, arraysz(c->cc_idx));
1035                 }
1036                 cpu_cell->ch = '\t';
1037                 cpu_cell->cc_idx[0] = diff;
1038             }
1039         }
1040         self->cursor->x = found;
1041     }
1042 }
1043 
1044 void
screen_backtab(Screen * self,unsigned int count)1045 screen_backtab(Screen *self, unsigned int count) {
1046     // Move back count tabs
1047     if (!count) count = 1;
1048     int i;
1049     while (count > 0 && self->cursor->x > 0) {
1050         count--;
1051         for (i = self->cursor->x - 1; i >= 0; i--) {
1052             if (self->tabstops[i]) { self->cursor->x = i; break; }
1053         }
1054         if (i <= 0) self->cursor->x = 0;
1055     }
1056 }
1057 
1058 void
screen_clear_tab_stop(Screen * self,unsigned int how)1059 screen_clear_tab_stop(Screen *self, unsigned int how) {
1060     switch(how) {
1061         case 0:
1062             if (self->cursor->x < self->columns) self->tabstops[self->cursor->x] = false;
1063             break;
1064         case 2:
1065             break;  // no-op
1066         case 3:
1067             for (unsigned int i = 0; i < self->columns; i++) self->tabstops[i] = false;
1068             break;
1069         default:
1070             log_error("%s %s %u", ERROR_PREFIX, "Unsupported clear tab stop mode: ", how);
1071             break;
1072     }
1073 }
1074 
1075 void
screen_set_tab_stop(Screen * self)1076 screen_set_tab_stop(Screen *self) {
1077     if (self->cursor->x < self->columns)
1078         self->tabstops[self->cursor->x] = true;
1079 }
1080 
1081 void
screen_cursor_back(Screen * self,unsigned int count,int move_direction)1082 screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) {
1083     if (count == 0) count = 1;
1084     if (move_direction < 0 && count > self->cursor->x) self->cursor->x = 0;
1085     else self->cursor->x += move_direction * count;
1086     screen_ensure_bounds(self, false, cursor_within_margins(self));
1087 }
1088 
1089 void
screen_cursor_forward(Screen * self,unsigned int count)1090 screen_cursor_forward(Screen *self, unsigned int count/*=1*/) {
1091     screen_cursor_back(self, count, 1);
1092 }
1093 
1094 void
screen_cursor_up(Screen * self,unsigned int count,bool do_carriage_return,int move_direction)1095 screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/) {
1096     bool in_margins = cursor_within_margins(self);
1097     if (count == 0) count = 1;
1098     if (move_direction < 0 && count > self->cursor->y) self->cursor->y = 0;
1099     else self->cursor->y += move_direction * count;
1100     screen_ensure_bounds(self, true, in_margins);
1101     if (do_carriage_return) self->cursor->x = 0;
1102 }
1103 
1104 void
screen_cursor_up1(Screen * self,unsigned int count)1105 screen_cursor_up1(Screen *self, unsigned int count/*=1*/) {
1106     screen_cursor_up(self, count, true, -1);
1107 }
1108 
1109 void
screen_cursor_down(Screen * self,unsigned int count)1110 screen_cursor_down(Screen *self, unsigned int count/*=1*/) {
1111     screen_cursor_up(self, count, false, 1);
1112 }
1113 
1114 void
screen_cursor_down1(Screen * self,unsigned int count)1115 screen_cursor_down1(Screen *self, unsigned int count/*=1*/) {
1116     screen_cursor_up(self, count, true, 1);
1117 }
1118 
1119 void
screen_cursor_to_column(Screen * self,unsigned int column)1120 screen_cursor_to_column(Screen *self, unsigned int column) {
1121     unsigned int x = MAX(column, 1u) - 1;
1122     if (x != self->cursor->x) {
1123         self->cursor->x = x;
1124         screen_ensure_bounds(self, false, cursor_within_margins(self));
1125     }
1126 }
1127 
1128 #define INDEX_UP \
1129     if (self->overlay_line.is_active) deactivate_overlay_line(self); \
1130     linebuf_index(self->linebuf, top, bottom); \
1131     INDEX_GRAPHICS(-1) \
1132     if (self->linebuf == self->main_linebuf && self->margin_top == 0) { \
1133         /* Only add to history when no top margin has been set */ \
1134         linebuf_init_line(self->linebuf, bottom); \
1135         historybuf_add_line(self->historybuf, self->linebuf->line, &self->as_ansi_buf); \
1136         self->history_line_added_count++; \
1137     } \
1138     linebuf_clear_line(self->linebuf, bottom); \
1139     self->is_dirty = true; \
1140     index_selection(self, &self->selections, true);
1141 
1142 void
screen_index(Screen * self)1143 screen_index(Screen *self) {
1144     // Move cursor down one line, scrolling screen if needed
1145     unsigned int top = self->margin_top, bottom = self->margin_bottom;
1146     if (self->cursor->y == bottom) {
1147         INDEX_UP;
1148     } else screen_cursor_down(self, 1);
1149 }
1150 
1151 void
screen_scroll(Screen * self,unsigned int count)1152 screen_scroll(Screen *self, unsigned int count) {
1153     // Scroll the screen up by count lines, not moving the cursor
1154     unsigned int top = self->margin_top, bottom = self->margin_bottom;
1155     while (count > 0) {
1156         count--;
1157         INDEX_UP;
1158     }
1159 }
1160 
1161 void
screen_reverse_index(Screen * self)1162 screen_reverse_index(Screen *self) {
1163     // Move cursor up one line, scrolling screen if needed
1164     unsigned int top = self->margin_top, bottom = self->margin_bottom;
1165     if (self->cursor->y == top) {
1166         INDEX_DOWN;
1167     } else screen_cursor_up(self, 1, false, -1);
1168 }
1169 
1170 static void
_reverse_scroll(Screen * self,unsigned int count,bool fill_from_scrollback)1171 _reverse_scroll(Screen *self, unsigned int count, bool fill_from_scrollback) {
1172     // Scroll the screen down by count lines, not moving the cursor
1173     unsigned int top = self->margin_top, bottom = self->margin_bottom;
1174     fill_from_scrollback = fill_from_scrollback && self->linebuf == self->main_linebuf;
1175     if (fill_from_scrollback) {
1176         unsigned limit = MAX(self->lines, self->historybuf->count);
1177         count = MIN(limit, count);
1178     } else count = MIN(self->lines, count);
1179     while (count-- > 0) {
1180         bool copied = false;
1181         if (fill_from_scrollback) copied = historybuf_pop_line(self->historybuf, self->alt_linebuf->line);
1182         INDEX_DOWN;
1183         if (copied) linebuf_copy_line_to(self->main_linebuf, self->alt_linebuf->line, 0);
1184     }
1185 }
1186 
1187 void
screen_reverse_scroll(Screen * self,unsigned int count)1188 screen_reverse_scroll(Screen *self, unsigned int count) {
1189     _reverse_scroll(self, count, false);
1190 }
1191 
1192 void
screen_reverse_scroll_and_fill_from_scrollback(Screen * self,unsigned int count)1193 screen_reverse_scroll_and_fill_from_scrollback(Screen *self, unsigned int count) {
1194     _reverse_scroll(self, count, true);
1195 }
1196 
1197 
1198 void
screen_carriage_return(Screen * self)1199 screen_carriage_return(Screen *self) {
1200     if (self->cursor->x != 0) {
1201         self->cursor->x = 0;
1202     }
1203 }
1204 
1205 void
screen_linefeed(Screen * self)1206 screen_linefeed(Screen *self) {
1207     bool in_margins = cursor_within_margins(self);
1208     screen_index(self);
1209     if (self->modes.mLNM) screen_carriage_return(self);
1210     screen_ensure_bounds(self, false, in_margins);
1211 }
1212 
1213 #define buffer_push(self, ans) { \
1214     ans = (self)->buf + (((self)->start_of_data + (self)->count) % SAVEPOINTS_SZ); \
1215     if ((self)->count == SAVEPOINTS_SZ) (self)->start_of_data = ((self)->start_of_data + 1) % SAVEPOINTS_SZ; \
1216     else (self)->count++; \
1217 }
1218 
1219 #define buffer_pop(self, ans) { \
1220     if ((self)->count == 0) ans = NULL; \
1221     else { \
1222         (self)->count--; \
1223         ans = (self)->buf + (((self)->start_of_data + (self)->count) % SAVEPOINTS_SZ); \
1224     } \
1225 }
1226 
1227 #define COPY_CHARSETS(self, sp) \
1228     sp->utf8_state = self->utf8_state; \
1229     sp->utf8_codepoint = self->utf8_codepoint; \
1230     sp->g0_charset = self->g0_charset; \
1231     sp->g1_charset = self->g1_charset; \
1232     sp->current_charset = self->current_charset; \
1233     sp->use_latin1 = self->use_latin1;
1234 
1235 void
screen_save_cursor(Screen * self)1236 screen_save_cursor(Screen *self) {
1237     Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint;
1238     cursor_copy_to(self->cursor, &(sp->cursor));
1239     sp->mDECOM = self->modes.mDECOM;
1240     sp->mDECAWM = self->modes.mDECAWM;
1241     sp->mDECSCNM = self->modes.mDECSCNM;
1242     COPY_CHARSETS(self, sp);
1243     sp->is_valid = true;
1244 }
1245 
1246 void
screen_save_modes(Screen * self)1247 screen_save_modes(Screen *self) {
1248     ScreenModes *m;
1249     buffer_push(&self->modes_savepoints, m);
1250     *m = self->modes;
1251 }
1252 
1253 void
screen_restore_cursor(Screen * self)1254 screen_restore_cursor(Screen *self) {
1255     Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint;
1256     if (!sp->is_valid) {
1257         screen_cursor_position(self, 1, 1);
1258         screen_reset_mode(self, DECOM);
1259         RESET_CHARSETS;
1260         screen_reset_mode(self, DECSCNM);
1261     } else {
1262         COPY_CHARSETS(sp, self);
1263         self->g_charset = self->current_charset ? self->g1_charset : self->g0_charset;
1264         set_mode_from_const(self, DECOM, sp->mDECOM);
1265         set_mode_from_const(self, DECAWM, sp->mDECAWM);
1266         set_mode_from_const(self, DECSCNM, sp->mDECSCNM);
1267         cursor_copy_to(&(sp->cursor), self->cursor);
1268         screen_ensure_bounds(self, false, false);
1269     }
1270 }
1271 
1272 void
screen_restore_modes(Screen * self)1273 screen_restore_modes(Screen *self) {
1274     const ScreenModes *m;
1275     buffer_pop(&self->modes_savepoints, m);
1276     if (m == NULL) m = &empty_modes;
1277 #define S(name) set_mode_from_const(self, name, m->m##name)
1278     S(DECTCEM); S(DECSCNM); S(DECSCNM); S(DECOM); S(DECAWM); S(DECARM); S(DECCKM);
1279     S(BRACKETED_PASTE); S(FOCUS_TRACKING);
1280     self->modes.mouse_tracking_mode = m->mouse_tracking_mode;
1281     self->modes.mouse_tracking_protocol = m->mouse_tracking_protocol;
1282 #undef S
1283 }
1284 
1285 void
screen_ensure_bounds(Screen * self,bool force_use_margins,bool in_margins)1286 screen_ensure_bounds(Screen *self, bool force_use_margins/*=false*/, bool in_margins) {
1287     unsigned int top, bottom;
1288     if (in_margins && (force_use_margins || self->modes.mDECOM)) {
1289         top = self->margin_top; bottom = self->margin_bottom;
1290     } else {
1291         top = 0; bottom = self->lines - 1;
1292     }
1293     self->cursor->x = MIN(self->cursor->x, self->columns - 1);
1294     self->cursor->y = MAX(top, MIN(self->cursor->y, bottom));
1295 }
1296 
1297 void
screen_cursor_position(Screen * self,unsigned int line,unsigned int column)1298 screen_cursor_position(Screen *self, unsigned int line, unsigned int column) {
1299     bool in_margins = cursor_within_margins(self);
1300     line = (line == 0 ? 1 : line) - 1;
1301     column = (column == 0 ? 1: column) - 1;
1302     if (self->modes.mDECOM) {
1303         line += self->margin_top;
1304         line = MAX(self->margin_top, MIN(line, self->margin_bottom));
1305     }
1306     self->cursor->x = column; self->cursor->y = line;
1307     screen_ensure_bounds(self, false, in_margins);
1308 }
1309 
1310 void
screen_cursor_to_line(Screen * self,unsigned int line)1311 screen_cursor_to_line(Screen *self, unsigned int line) {
1312     screen_cursor_position(self, line, self->cursor->x + 1);
1313 }
1314 
1315 // }}}
1316 
1317 // Editing {{{
1318 
1319 void
screen_erase_in_line(Screen * self,unsigned int how,bool private)1320 screen_erase_in_line(Screen *self, unsigned int how, bool private) {
1321     /*Erases a line in a specific way.
1322 
1323         :param int how: defines the way the line should be erased in:
1324 
1325             * ``0`` -- Erases from cursor to end of line, including cursor
1326               position.
1327             * ``1`` -- Erases from beginning of line to cursor,
1328               including cursor position.
1329             * ``2`` -- Erases complete line.
1330         :param bool private: when ``True`` character attributes are left
1331                              unchanged.
1332         */
1333     unsigned int s = 0, n = 0;
1334     switch(how) {
1335         case 0:
1336             s = self->cursor->x;
1337             n = self->columns - self->cursor->x;
1338             break;
1339         case 1:
1340             n = self->cursor->x + 1;
1341             break;
1342         case 2:
1343             n = self->columns;
1344             break;
1345         default:
1346             break;
1347     }
1348     if (n > 0) {
1349         linebuf_init_line(self->linebuf, self->cursor->y);
1350         if (private) {
1351             line_clear_text(self->linebuf->line, s, n, BLANK_CHAR);
1352         } else {
1353             line_apply_cursor(self->linebuf->line, self->cursor, s, n, true);
1354         }
1355         self->is_dirty = true;
1356         if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections);
1357         linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
1358     }
1359 }
1360 
1361 void
screen_erase_in_display(Screen * self,unsigned int how,bool private)1362 screen_erase_in_display(Screen *self, unsigned int how, bool private) {
1363     /* Erases display in a specific way.
1364 
1365         :param int how: defines the way the screen should be erased:
1366 
1367             * ``0`` -- Erases from cursor to end of screen, including
1368               cursor position.
1369             * ``1`` -- Erases from beginning of screen to cursor,
1370               including cursor position.
1371             * ``2`` -- Erases complete display. All lines are erased
1372               and changed to single-width. Cursor does not move.
1373             * ``3`` -- Erase complete display and scrollback buffer as well.
1374         :param bool private: when ``True`` character attributes are left unchanged
1375     */
1376     unsigned int a, b;
1377     switch(how) {
1378         case 0:
1379             a = self->cursor->y + 1; b = self->lines; break;
1380         case 1:
1381             a = 0; b = self->cursor->y; break;
1382         case 2:
1383         case 3:
1384             grman_clear(self->grman, how == 3, self->cell_size);
1385             a = 0; b = self->lines; break;
1386         default:
1387             return;
1388     }
1389     if (b > a) {
1390         for (unsigned int i=a; i < b; i++) {
1391             linebuf_init_line(self->linebuf, i);
1392             if (private) {
1393                 line_clear_text(self->linebuf->line, 0, self->columns, BLANK_CHAR);
1394             } else {
1395                 line_apply_cursor(self->linebuf->line, self->cursor, 0, self->columns, true);
1396             }
1397             linebuf_mark_line_dirty(self->linebuf, i);
1398             linebuf_mark_line_as_not_continued(self->linebuf, i);
1399         }
1400         self->is_dirty = true;
1401         clear_selection(&self->selections);
1402     }
1403     if (how != 2) {
1404         screen_erase_in_line(self, how, private);
1405         if (how == 1) linebuf_mark_line_as_not_continued(self->linebuf, self->cursor->y);
1406     }
1407     if (how == 3 && self->linebuf == self->main_linebuf) {
1408         historybuf_clear(self->historybuf);
1409         if (self->scrolled_by != 0) {
1410             self->scrolled_by = 0;
1411             self->scroll_changed = true;
1412         }
1413     }
1414 }
1415 
1416 void
screen_insert_lines(Screen * self,unsigned int count)1417 screen_insert_lines(Screen *self, unsigned int count) {
1418     unsigned int top = self->margin_top, bottom = self->margin_bottom;
1419     if (count == 0) count = 1;
1420     if (top <= self->cursor->y && self->cursor->y <= bottom) {
1421         linebuf_insert_lines(self->linebuf, count, self->cursor->y, bottom);
1422         self->is_dirty = true;
1423         clear_selection(&self->selections);
1424         screen_carriage_return(self);
1425     }
1426 }
1427 
1428 void
screen_scroll_until_cursor(Screen * self)1429 screen_scroll_until_cursor(Screen *self) {
1430     unsigned int num_lines_to_scroll = MIN(self->margin_bottom, self->cursor->y + 1);
1431     index_type y = self->cursor->y;
1432     self->cursor->y = self->margin_bottom;
1433     while (num_lines_to_scroll--) screen_index(self);
1434     self->cursor->y = y;
1435 }
1436 
1437 void
screen_delete_lines(Screen * self,unsigned int count)1438 screen_delete_lines(Screen *self, unsigned int count) {
1439     unsigned int top = self->margin_top, bottom = self->margin_bottom;
1440     if (count == 0) count = 1;
1441     if (top <= self->cursor->y && self->cursor->y <= bottom) {
1442         linebuf_delete_lines(self->linebuf, count, self->cursor->y, bottom);
1443         self->is_dirty = true;
1444         clear_selection(&self->selections);
1445         screen_carriage_return(self);
1446     }
1447 }
1448 
1449 void
screen_insert_characters(Screen * self,unsigned int count)1450 screen_insert_characters(Screen *self, unsigned int count) {
1451     const unsigned int top = 0, bottom = self->lines ? self->lines - 1 : 0;
1452     if (count == 0) count = 1;
1453     if (top <= self->cursor->y && self->cursor->y <= bottom) {
1454         unsigned int x = self->cursor->x;
1455         unsigned int num = MIN(self->columns - x, count);
1456         linebuf_init_line(self->linebuf, self->cursor->y);
1457         line_right_shift(self->linebuf->line, x, num);
1458         line_apply_cursor(self->linebuf->line, self->cursor, x, num, true);
1459         linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
1460         self->is_dirty = true;
1461         if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections);
1462     }
1463 }
1464 
1465 void
screen_repeat_character(Screen * self,unsigned int count)1466 screen_repeat_character(Screen *self, unsigned int count) {
1467     if (self->last_graphic_char) {
1468         if (count == 0) count = 1;
1469         unsigned int num = MIN(count, CSI_REP_MAX_REPETITIONS);
1470         while (num-- > 0) screen_draw(self, self->last_graphic_char, false);
1471     }
1472 }
1473 
1474 void
screen_delete_characters(Screen * self,unsigned int count)1475 screen_delete_characters(Screen *self, unsigned int count) {
1476     // Delete characters, later characters are moved left
1477     const unsigned int top = 0, bottom = self->lines ? self->lines - 1 : 0;
1478     if (count == 0) count = 1;
1479     if (top <= self->cursor->y && self->cursor->y <= bottom) {
1480         unsigned int x = self->cursor->x;
1481         unsigned int num = MIN(self->columns - x, count);
1482         linebuf_init_line(self->linebuf, self->cursor->y);
1483         left_shift_line(self->linebuf->line, x, num);
1484         line_apply_cursor(self->linebuf->line, self->cursor, self->columns - num, num, true);
1485         linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
1486         self->is_dirty = true;
1487         if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections);
1488     }
1489 }
1490 
1491 void
screen_erase_characters(Screen * self,unsigned int count)1492 screen_erase_characters(Screen *self, unsigned int count) {
1493     // Delete characters replacing them by spaces
1494     if (count == 0) count = 1;
1495     unsigned int x = self->cursor->x;
1496     unsigned int num = MIN(self->columns - x, count);
1497     linebuf_init_line(self->linebuf, self->cursor->y);
1498     line_apply_cursor(self->linebuf->line, self->cursor, x, num, true);
1499     linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
1500     self->is_dirty = true;
1501     if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections);
1502 }
1503 
1504 // }}}
1505 
1506 // Device control {{{
1507 
1508 void
screen_use_latin1(Screen * self,bool on)1509 screen_use_latin1(Screen *self, bool on) {
1510     self->use_latin1 = on; self->utf8_state = 0; self->utf8_codepoint = 0;
1511     CALLBACK("use_utf8", "O", on ? Py_False : Py_True);
1512 }
1513 
1514 bool
screen_invert_colors(Screen * self)1515 screen_invert_colors(Screen *self) {
1516     bool inverted = false;
1517     if (self->modes.mDECSCNM) inverted = true;
1518     return inverted;
1519 }
1520 
1521 void
screen_bell(Screen * self)1522 screen_bell(Screen *self) {
1523     request_window_attention(self->window_id, OPT(enable_audio_bell));
1524     if (OPT(visual_bell_duration) > 0.0f) self->start_visual_bell_at = monotonic();
1525     CALLBACK("on_bell", NULL);
1526 }
1527 
1528 void
report_device_attributes(Screen * self,unsigned int mode,char start_modifier)1529 report_device_attributes(Screen *self, unsigned int mode, char start_modifier) {
1530     if (mode == 0) {
1531         switch(start_modifier) {
1532             case 0:
1533                 write_escape_code_to_child(self, CSI, "?62;c");
1534                 break;
1535             case '>':
1536                 write_escape_code_to_child(self, CSI, ">1;" xstr(PRIMARY_VERSION) ";" xstr(SECONDARY_VERSION) "c");  // VT-220 + primary version + secondary version
1537                 break;
1538         }
1539     }
1540 }
1541 
1542 void
screen_xtversion(Screen * self,unsigned int mode)1543 screen_xtversion(Screen *self, unsigned int mode) {
1544     if (mode == 0) {
1545         write_escape_code_to_child(self, DCS, ">|kitty(" XT_VERSION ")");
1546     }
1547 }
1548 
1549 void
screen_report_size(Screen * self,unsigned int which)1550 screen_report_size(Screen *self, unsigned int which) {
1551     char buf[32] = {0};
1552     unsigned int code = 0;
1553     unsigned int width = 0, height = 0;
1554     switch(which) {
1555         case 14:
1556             code = 4;
1557             width = self->cell_size.width * self->columns;
1558             height = self->cell_size.height * self->lines;
1559             break;
1560         case 16:
1561             code = 6;
1562             width = self->cell_size.width;
1563             height = self->cell_size.height;
1564             break;
1565         case 18:
1566             code = 8;
1567             width = self->columns;
1568             height = self->lines;
1569             break;
1570     }
1571     if (code) {
1572         snprintf(buf, sizeof(buf), "%u;%u;%ut", code, height, width);
1573         write_escape_code_to_child(self, CSI, buf);
1574     }
1575 }
1576 
1577 void
screen_manipulate_title_stack(Screen * self,unsigned int op,unsigned int which)1578 screen_manipulate_title_stack(Screen *self, unsigned int op, unsigned int which) {
1579     CALLBACK("manipulate_title_stack", "OOO",
1580         op == 23 ? Py_True : Py_False,
1581         which == 0 || which == 2 ? Py_True : Py_False,
1582         which == 0 || which == 1 ? Py_True : Py_False
1583     );
1584 }
1585 
1586 void
report_device_status(Screen * self,unsigned int which,bool private)1587 report_device_status(Screen *self, unsigned int which, bool private) {
1588     // We don't implement the private device status codes, since I haven't come
1589     // across any programs that use them
1590     unsigned int x, y;
1591     static char buf[64];
1592     switch(which) {
1593         case 5:  // device status
1594             write_escape_code_to_child(self, CSI, "0n");
1595             break;
1596         case 6:  // cursor position
1597             x = self->cursor->x; y = self->cursor->y;
1598             if (x >= self->columns) {
1599                 if (y < self->lines - 1) { x = 0; y++; }
1600                 else x--;
1601             }
1602             if (self->modes.mDECOM) y -= MAX(y, self->margin_top);
1603             // 1-based indexing
1604             int sz = snprintf(buf, sizeof(buf) - 1, "%s%u;%uR", (private ? "?": ""), y + 1, x + 1);
1605             if (sz > 0) write_escape_code_to_child(self, CSI, buf);
1606             break;
1607     }
1608 }
1609 
1610 void
report_mode_status(Screen * self,unsigned int which,bool private)1611 report_mode_status(Screen *self, unsigned int which, bool private) {
1612     unsigned int q = private ? which << 5 : which;
1613     unsigned int ans = 0;
1614     char buf[50] = {0};
1615     switch(q) {
1616 #define KNOWN_MODE(x) \
1617         case x: \
1618             ans = self->modes.m##x ? 1 : 2; break;
1619         KNOWN_MODE(LNM);
1620         KNOWN_MODE(IRM);
1621         KNOWN_MODE(DECTCEM);
1622         KNOWN_MODE(DECSCNM);
1623         KNOWN_MODE(DECOM);
1624         KNOWN_MODE(DECAWM);
1625         KNOWN_MODE(DECCOLM);
1626         KNOWN_MODE(DECARM);
1627         KNOWN_MODE(DECCKM);
1628         KNOWN_MODE(BRACKETED_PASTE);
1629         KNOWN_MODE(FOCUS_TRACKING);
1630 #undef KNOWN_MODE
1631         case ALTERNATE_SCREEN:
1632             ans = self->linebuf == self->alt_linebuf ? 1 : 2; break;
1633         case MOUSE_BUTTON_TRACKING:
1634             ans = self->modes.mouse_tracking_mode == BUTTON_MODE ? 1 : 2; break;
1635         case MOUSE_MOTION_TRACKING:
1636             ans = self->modes.mouse_tracking_mode == MOTION_MODE ? 1 : 2; break;
1637         case MOUSE_MOVE_TRACKING:
1638             ans = self->modes.mouse_tracking_mode == ANY_MODE ? 1 : 2; break;
1639         case MOUSE_SGR_MODE:
1640             ans = self->modes.mouse_tracking_protocol == SGR_PROTOCOL ? 1 : 2; break;
1641         case PENDING_UPDATE:
1642             ans = self->pending_mode.activated_at ? 1 : 2; break;
1643     }
1644     int sz = snprintf(buf, sizeof(buf) - 1, "%s%u;%u$y", (private ? "?" : ""), which, ans);
1645     if (sz > 0) write_escape_code_to_child(self, CSI, buf);
1646 }
1647 
1648 void
screen_set_margins(Screen * self,unsigned int top,unsigned int bottom)1649 screen_set_margins(Screen *self, unsigned int top, unsigned int bottom) {
1650     if (!top) top = 1;
1651     if (!bottom) bottom = self->lines;
1652     top = MIN(self->lines, top);
1653     bottom = MIN(self->lines, bottom);
1654     top--; bottom--;  // 1 based indexing
1655     if (bottom > top) {
1656         // Even though VT102 and VT220 require DECSTBM to ignore regions
1657         // of width less than 2, some programs (like aptitude for example)
1658         // rely on it. Practicality beats purity.
1659         self->margin_top = top; self->margin_bottom = bottom;
1660         // The cursor moves to the home position when the top and
1661         // bottom margins of the scrolling region (DECSTBM) changes.
1662         screen_cursor_position(self, 1, 1);
1663     }
1664 }
1665 
1666 void
screen_set_cursor(Screen * self,unsigned int mode,uint8_t secondary)1667 screen_set_cursor(Screen *self, unsigned int mode, uint8_t secondary) {
1668     uint8_t shape; bool blink;
1669     switch(secondary) {
1670         case 0: // DECLL
1671             break;
1672         case '"':  // DECCSA
1673             break;
1674         case ' ': // DECSCUSR
1675             shape = 0; blink = false;
1676             if (mode > 0) {
1677                 blink = mode % 2;
1678                 shape = (mode < 3) ? CURSOR_BLOCK : (mode < 5) ? CURSOR_UNDERLINE : (mode < 7) ? CURSOR_BEAM : NO_CURSOR_SHAPE;
1679             }
1680             if (shape != self->cursor->shape || blink != !self->cursor->non_blinking) {
1681                 self->cursor->shape = shape; self->cursor->non_blinking = !blink;
1682             }
1683             break;
1684     }
1685 }
1686 
1687 void
set_title(Screen * self,PyObject * title)1688 set_title(Screen *self, PyObject *title) {
1689     CALLBACK("title_changed", "O", title);
1690 }
1691 
1692 void
desktop_notify(Screen * self,unsigned int osc_code,PyObject * data)1693 desktop_notify(Screen *self, unsigned int osc_code, PyObject *data) {
1694     CALLBACK("desktop_notify", "IO", osc_code, data);
1695 }
1696 
1697 void
set_icon(Screen * self,PyObject * icon)1698 set_icon(Screen *self, PyObject *icon) {
1699     CALLBACK("icon_changed", "O", icon);
1700 }
1701 
1702 void
set_dynamic_color(Screen * self,unsigned int code,PyObject * color)1703 set_dynamic_color(Screen *self, unsigned int code, PyObject *color) {
1704     if (color == NULL) { CALLBACK("set_dynamic_color", "Is", code, ""); }
1705     else { CALLBACK("set_dynamic_color", "IO", code, color); }
1706 }
1707 
1708 void
clipboard_control(Screen * self,int code,PyObject * data)1709 clipboard_control(Screen *self, int code, PyObject *data) {
1710     CALLBACK("clipboard_control", "OO", data, code == -52 ? Py_True: Py_False);
1711 }
1712 
1713 void
set_color_table_color(Screen * self,unsigned int code,PyObject * color)1714 set_color_table_color(Screen *self, unsigned int code, PyObject *color) {
1715     if (color == NULL) { CALLBACK("set_color_table_color", "Is", code, ""); }
1716     else { CALLBACK("set_color_table_color", "IO", code, color); }
1717 }
1718 
1719 void
process_cwd_notification(Screen * self,unsigned int code,PyObject * cwd)1720 process_cwd_notification(Screen *self, unsigned int code, PyObject *cwd) {
1721     (void)self; (void)code; (void)cwd;
1722     // we ignore this as we dont need the stupid OSC 7 cwd reporting protocol,
1723     // since, being moderately intelligent, we can get CWD directly.
1724 }
1725 
1726 void
screen_handle_cmd(Screen * self,PyObject * cmd)1727 screen_handle_cmd(Screen *self, PyObject *cmd) {
1728     CALLBACK("handle_remote_cmd", "O", cmd);
1729 }
1730 
1731 void
screen_push_colors(Screen * self,unsigned int idx)1732 screen_push_colors(Screen *self, unsigned int idx) {
1733     colorprofile_push_colors(self->color_profile, idx);
1734 }
1735 
1736 void
screen_pop_colors(Screen * self,unsigned int idx)1737 screen_pop_colors(Screen *self, unsigned int idx) {
1738     colorprofile_pop_colors(self->color_profile, idx);
1739 }
1740 
1741 void
screen_report_color_stack(Screen * self)1742 screen_report_color_stack(Screen *self) {
1743     unsigned int idx, count;
1744     colorprofile_report_stack(self->color_profile, &idx, &count);
1745     char buf[128] = {0};
1746     snprintf(buf, arraysz(buf), "%u;%u#Q", idx, count);
1747     write_escape_code_to_child(self, CSI, buf);
1748 }
1749 
1750 void
screen_handle_print(Screen * self,PyObject * msg)1751 screen_handle_print(Screen *self, PyObject *msg) {
1752     CALLBACK("handle_remote_print", "O", msg);
1753 }
1754 
1755 void
screen_request_capabilities(Screen * self,char c,PyObject * q)1756 screen_request_capabilities(Screen *self, char c, PyObject *q) {
1757     static char buf[128];
1758     int shape = 0;
1759     const char *query;
1760     switch(c) {
1761         case '+':
1762             CALLBACK("request_capabilities", "O", q);
1763             break;
1764         case '$':
1765             // report status
1766             query = PyUnicode_AsUTF8(q);
1767             if (strcmp(" q", query) == 0) {
1768                 // cursor shape
1769                 switch(self->cursor->shape) {
1770                     case NO_CURSOR_SHAPE:
1771                     case NUM_OF_CURSOR_SHAPES:
1772                         shape = 1; break;
1773                     case CURSOR_BLOCK:
1774                         shape = self->cursor->non_blinking ? 2 : 0; break;
1775                     case CURSOR_UNDERLINE:
1776                         shape = self->cursor->non_blinking ? 4 : 3; break;
1777                     case CURSOR_BEAM:
1778                         shape = self->cursor->non_blinking ? 6 : 5; break;
1779                 }
1780                 shape = snprintf(buf, sizeof(buf), "1$r%d q", shape);
1781             } else if (strcmp("m", query) == 0) {
1782                 // SGR
1783                 shape = snprintf(buf, sizeof(buf), "1$r%sm", cursor_as_sgr(self->cursor));
1784             } else if (strcmp("r", query) == 0) {
1785                 shape = snprintf(buf, sizeof(buf), "1$r%u;%ur", self->margin_top + 1, self->margin_bottom + 1);
1786             } else {
1787                 shape = snprintf(buf, sizeof(buf), "0$r%s", query);
1788             }
1789             if (shape > 0) write_escape_code_to_child(self, DCS, buf);
1790             break;
1791     }
1792 }
1793 
1794 // }}}
1795 
1796 // Rendering {{{
1797 static void
update_line_data(Line * line,unsigned int dest_y,uint8_t * data)1798 update_line_data(Line *line, unsigned int dest_y, uint8_t *data) {
1799     size_t base = sizeof(GPUCell) * dest_y * line->xnum;
1800     memcpy(data + base, line->gpu_cells, line->xnum * sizeof(GPUCell));
1801 }
1802 
1803 
1804 static void
screen_reset_dirty(Screen * self)1805 screen_reset_dirty(Screen *self) {
1806     self->is_dirty = false;
1807     self->history_line_added_count = 0;
1808 }
1809 
1810 static bool
screen_has_marker(Screen * self)1811 screen_has_marker(Screen *self) {
1812     return self->marker != NULL;
1813 }
1814 
1815 
1816 void
screen_update_cell_data(Screen * self,void * address,FONTS_DATA_HANDLE fonts_data,bool cursor_has_moved)1817 screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_data, bool cursor_has_moved) {
1818     unsigned int history_line_added_count = self->history_line_added_count;
1819     index_type lnum;
1820     bool was_dirty = self->is_dirty;
1821     if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count);
1822     screen_reset_dirty(self);
1823     self->scroll_changed = false;
1824     for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) {
1825         lnum = self->scrolled_by - 1 - y;
1826         historybuf_init_line(self->historybuf, lnum, self->historybuf->line);
1827         if (self->historybuf->line->has_dirty_text) {
1828             render_line(fonts_data, self->historybuf->line, lnum, self->cursor, self->disable_ligatures);
1829             if (screen_has_marker(self)) mark_text_in_line(self->marker, self->historybuf->line);
1830             historybuf_mark_line_clean(self->historybuf, lnum);
1831         }
1832         update_line_data(self->historybuf->line, y, address);
1833     }
1834     for (index_type y = self->scrolled_by; y < self->lines; y++) {
1835         lnum = y - self->scrolled_by;
1836         linebuf_init_line(self->linebuf, lnum);
1837         if (self->linebuf->line->has_dirty_text ||
1838             (cursor_has_moved && (self->cursor->y == lnum || self->last_rendered.cursor_y == lnum))) {
1839             render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures);
1840             if (self->linebuf->line->has_dirty_text && screen_has_marker(self)) mark_text_in_line(self->marker, self->linebuf->line);
1841 
1842             linebuf_mark_line_clean(self->linebuf, lnum);
1843         }
1844         update_line_data(self->linebuf->line, y, address);
1845     }
1846     if (was_dirty) clear_selection(&self->url_ranges);
1847 }
1848 
1849 
1850 static bool
selection_boundary_less_than(const SelectionBoundary * a,const SelectionBoundary * b)1851 selection_boundary_less_than(const SelectionBoundary *a, const SelectionBoundary *b) {
1852     // y -values must be absolutized (aka adjusted with scrolled_by)
1853     // this means the oldest line has the highest value and is thus the least
1854     if (a->y > b->y) return true;
1855     if (a->y < b->y) return false;
1856     if (a->x < b->x) return true;
1857     if (a->x > b->x) return false;
1858     if (a->in_left_half_of_cell && !b->in_left_half_of_cell) return true;
1859     return false;
1860 }
1861 
1862 static index_type
num_cells_between_selection_boundaries(const Screen * self,const SelectionBoundary * a,const SelectionBoundary * b)1863 num_cells_between_selection_boundaries(const Screen *self, const SelectionBoundary *a, const SelectionBoundary *b) {
1864     const SelectionBoundary *before, *after;
1865     if (selection_boundary_less_than(a, b)) { before = a; after = b; }
1866     else { before = b; after = a; }
1867     index_type ans = 0;
1868     if (before->y + 1 < after->y) ans += self->columns * (after->y - before->y - 1);
1869     if (before->y == after->y) ans += after->x - before->x;
1870     else ans += (self->columns - before->x) + after->x;
1871     return ans;
1872 }
1873 
1874 static index_type
num_lines_between_selection_boundaries(const SelectionBoundary * a,const SelectionBoundary * b)1875 num_lines_between_selection_boundaries(const SelectionBoundary *a, const SelectionBoundary *b) {
1876     const SelectionBoundary *before, *after;
1877     if (selection_boundary_less_than(a, b)) { before = a; after = b; }
1878     else { before = b; after = a; }
1879     return before->y - after->y;
1880 }
1881 
1882 typedef Line*(linefunc_t)(Screen*, int);
1883 
1884 static Line*
visual_line_(Screen * self,int y_)1885 visual_line_(Screen *self, int y_) {
1886     index_type y = MAX(0, y_);
1887     if (self->scrolled_by) {
1888         if (y < self->scrolled_by) {
1889             historybuf_init_line(self->historybuf, self->scrolled_by - 1 - y, self->historybuf->line);
1890             return self->historybuf->line;
1891         }
1892         y -= self->scrolled_by;
1893     }
1894     linebuf_init_line(self->linebuf, y);
1895     return self->linebuf->line;
1896 }
1897 
1898 static Line*
range_line_(Screen * self,int y)1899 range_line_(Screen *self, int y) {
1900     if (y < 0) {
1901         historybuf_init_line(self->historybuf, -(y + 1), self->historybuf->line);
1902         return self->historybuf->line;
1903     }
1904     linebuf_init_line(self->linebuf, y);
1905     return self->linebuf->line;
1906 }
1907 
1908 static bool
selection_is_left_to_right(const Selection * self)1909 selection_is_left_to_right(const Selection *self) {
1910     return self->input_start.x < self->input_current.x || (self->input_start.x == self->input_current.x && self->input_start.in_left_half_of_cell);
1911 }
1912 
1913 static void
iteration_data(const Screen * self,const Selection * sel,IterationData * ans,int min_y,bool add_scrolled_by)1914 iteration_data(const Screen *self, const Selection *sel, IterationData *ans, int min_y, bool add_scrolled_by) {
1915     memset(ans, 0, sizeof(IterationData));
1916     const SelectionBoundary *start = &sel->start, *end = &sel->end;
1917     int start_y = (int)start->y - sel->start_scrolled_by, end_y = (int)end->y - sel->end_scrolled_by;
1918     // empty selection
1919     if (start->x == end->x && start_y == end_y && start->in_left_half_of_cell == end->in_left_half_of_cell) return;
1920 
1921     if (sel->rectangle_select) {
1922         // empty selection
1923         if (start->x == end->x && (!start->in_left_half_of_cell || end->in_left_half_of_cell)) return;
1924 
1925         ans->y = MIN(start_y, end_y); ans->y_limit = MAX(start_y, end_y) + 1;
1926         index_type x, x_limit;
1927         bool left_to_right = selection_is_left_to_right(sel);
1928 
1929         if (start->x == end->x) {
1930             x = start->x; x_limit = start->x + 1;
1931         } else {
1932             if (left_to_right) {
1933                 x = start->x + (start->in_left_half_of_cell ? 0 : 1);
1934                 x_limit = 1 + end->x + (end->in_left_half_of_cell ? -1: 0);
1935             } else {
1936                 x = end->x + (end->in_left_half_of_cell ? 0 : 1);
1937                 x_limit = 1 + start->x + (start->in_left_half_of_cell ? -1 : 0);
1938             }
1939         }
1940         ans->first.x = x; ans->body.x = x; ans->last.x = x;
1941         ans->first.x_limit = x_limit; ans->body.x_limit = x_limit; ans->last.x_limit = x_limit;
1942     } else {
1943         index_type line_limit = self->columns;
1944 
1945         if (start_y == end_y) {
1946             if (start->x == end->x) {
1947                 if (start->in_left_half_of_cell && !end->in_left_half_of_cell) {
1948                     // single cell selection
1949                     ans->first.x = start->x; ans->body.x = start->x; ans->last.x = start->x;
1950                     ans->first.x_limit = start->x + 1; ans->body.x_limit = start->x + 1; ans->last.x_limit = start->x + 1;
1951                 } else return; // empty selection
1952             }
1953             // single line selection
1954             else if (start->x <= end->x) {
1955                 ans->first.x = start->x + (start->in_left_half_of_cell ? 0 : 1);
1956                 ans->first.x_limit = 1 + end->x + (end->in_left_half_of_cell ? -1 : 0);
1957             } else {
1958                 ans->first.x = end->x + (end->in_left_half_of_cell ? 0 : 1);
1959                 ans->first.x_limit = 1 + start->x + (start->in_left_half_of_cell ? -1 : 0);
1960             }
1961         } else if (start_y < end_y) { // downwards
1962             ans->body.x_limit = line_limit;
1963             ans->first.x_limit = line_limit;
1964             ans->first.x = start->x + (start->in_left_half_of_cell ? 0 : 1);
1965             ans->last.x_limit = 1 + end->x + (end->in_left_half_of_cell ? -1 : 0);
1966         } else { // upwards
1967             ans->body.x_limit = line_limit;
1968             ans->first.x_limit = line_limit;
1969             ans->first.x = end->x + (end->in_left_half_of_cell ? 0 : 1);
1970             ans->last.x_limit = 1 + start->x + (start->in_left_half_of_cell ? -1 : 0);
1971         }
1972         ans->y = MIN(start_y, end_y); ans->y_limit = MAX(start_y, end_y) + 1;
1973 
1974     }
1975     if (add_scrolled_by) {
1976         ans->y += self->scrolled_by; ans->y_limit += self->scrolled_by;
1977     }
1978     ans->y = MAX(ans->y, min_y);
1979 }
1980 
1981 static XRange
xrange_for_iteration(const IterationData * idata,const int y,const Line * line)1982 xrange_for_iteration(const IterationData *idata, const int y, const Line *line) {
1983     XRange ans = {.x_limit=xlimit_for_line(line)};
1984     if (y == idata->y) {
1985         ans.x_limit = MIN(idata->first.x_limit, ans.x_limit);
1986         ans.x = idata->first.x;
1987     } else if (y == idata->y_limit - 1) {
1988         ans.x_limit = MIN(idata->last.x_limit, ans.x_limit);
1989         ans.x = idata->last.x;
1990     } else {
1991         ans.x_limit = MIN(idata->body.x_limit, ans.x_limit);
1992         ans.x = idata->body.x;
1993     }
1994     return ans;
1995 }
1996 
1997 static bool
iteration_data_is_empty(const Screen * self,const IterationData * idata)1998 iteration_data_is_empty(const Screen *self, const IterationData *idata) {
1999     if (idata->y >= idata->y_limit) return true;
2000     index_type xl = MIN(idata->first.x_limit, self->columns);
2001     if (idata->first.x < xl) return false;
2002     xl = MIN(idata->body.x_limit, self->columns);
2003     if (idata->body.x < xl) return false;
2004     xl = MIN(idata->last.x_limit, self->columns);
2005     if (idata->last.x < xl) return false;
2006     return true;
2007 }
2008 
2009 static void
apply_selection(Screen * self,uint8_t * data,Selection * s,uint8_t set_mask)2010 apply_selection(Screen *self, uint8_t *data, Selection *s, uint8_t set_mask) {
2011     iteration_data(self, s, &s->last_rendered, -self->historybuf->count, true);
2012 
2013     for (int y = MAX(0, s->last_rendered.y); y < s->last_rendered.y_limit && y < (int)self->lines; y++) {
2014         Line *line = visual_line_(self, y);
2015         uint8_t *line_start = data + self->columns * y;
2016         XRange xr = xrange_for_iteration(&s->last_rendered, y, line);
2017         for (index_type x = xr.x; x < xr.x_limit; x++) line_start[x] |= set_mask;
2018     }
2019     s->last_rendered.y = MAX(0, s->last_rendered.y);
2020 }
2021 
2022 bool
screen_has_selection(Screen * self)2023 screen_has_selection(Screen *self) {
2024     IterationData idata;
2025     for (size_t i = 0; i < self->selections.count; i++) {
2026         Selection *s = self->selections.items + i;
2027         if (!is_selection_empty(s)) {
2028             iteration_data(self, s, &idata, -self->historybuf->count, true);
2029             if (!iteration_data_is_empty(self, &idata)) return true;
2030         }
2031     }
2032     return false;
2033 }
2034 
2035 void
screen_apply_selection(Screen * self,void * address,size_t size)2036 screen_apply_selection(Screen *self, void *address, size_t size) {
2037     memset(address, 0, size);
2038     for (size_t i = 0; i < self->selections.count; i++) {
2039         apply_selection(self, address, self->selections.items + i, 1);
2040     }
2041     self->selections.last_rendered_count = self->selections.count;
2042     for (size_t i = 0; i < self->url_ranges.count; i++) {
2043         apply_selection(self, address, self->url_ranges.items + i, 2);
2044     }
2045     self->url_ranges.last_rendered_count = self->url_ranges.count;
2046 }
2047 
2048 static PyObject*
text_for_range(Screen * self,const Selection * sel,bool insert_newlines)2049 text_for_range(Screen *self, const Selection *sel, bool insert_newlines) {
2050     IterationData idata;
2051     iteration_data(self, sel, &idata, -self->historybuf->count, false);
2052     int limit = MIN((int)self->lines, idata.y_limit);
2053     PyObject *ans = PyTuple_New(limit - idata.y);
2054     if (!ans) return NULL;
2055     for (int i = 0, y = idata.y; y < limit; y++, i++) {
2056         Line *line = range_line_(self, y);
2057         XRange xr = xrange_for_iteration(&idata, y, line);
2058         char leading_char = (i > 0 && insert_newlines && !line->continued) ? '\n' : 0;
2059         PyObject *text = unicode_in_range(line, xr.x, xr.x_limit, true, leading_char, false);
2060         if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); }
2061         PyTuple_SET_ITEM(ans, i, text);
2062     }
2063     return ans;
2064 }
2065 
2066 static hyperlink_id_type
hyperlink_id_for_range(Screen * self,const Selection * sel)2067 hyperlink_id_for_range(Screen *self, const Selection *sel) {
2068     IterationData idata;
2069     iteration_data(self, sel, &idata, -self->historybuf->count, false);
2070     for (int i = 0, y = idata.y; y < idata.y_limit && y < (int)self->lines; y++, i++) {
2071         Line *line = range_line_(self, y);
2072         XRange xr = xrange_for_iteration(&idata, y, line);
2073         for (index_type x = xr.x; x < xr.x_limit; x++) {
2074             if (line->cpu_cells[x].hyperlink_id) return line->cpu_cells[x].hyperlink_id;
2075         }
2076     }
2077     return 0;
2078 }
2079 
2080 static PyObject*
extend_tuple(PyObject * a,PyObject * b)2081 extend_tuple(PyObject *a, PyObject *b) {
2082     Py_ssize_t bs = PyBytes_GET_SIZE(b);
2083     if (bs < 1) return a;
2084     Py_ssize_t off = PyTuple_GET_SIZE(a);
2085     if (_PyTuple_Resize(&a, off + bs) != 0) return NULL;
2086     for (Py_ssize_t y = 0; y < bs; y++) {
2087         PyObject *t = PyTuple_GET_ITEM(b, y);
2088         Py_INCREF(t);
2089         PyTuple_SET_ITEM(a, off + y, t);
2090     }
2091     return a;
2092 }
2093 
2094 static PyObject*
current_url_text(Screen * self,PyObject * args UNUSED)2095 current_url_text(Screen *self, PyObject *args UNUSED) {
2096     PyObject *empty_string = PyUnicode_FromString(""), *ans = NULL;
2097     if (!empty_string) return NULL;
2098     for (size_t i = 0; i < self->url_ranges.count; i++) {
2099         Selection *s = self->url_ranges.items + i;
2100         if (!is_selection_empty(s)) {
2101             PyObject *temp = text_for_range(self, s, false);
2102             if (!temp) goto error;
2103             PyObject *text = PyUnicode_Join(empty_string, temp);
2104             Py_CLEAR(temp);
2105             if (!text) goto error;
2106             if (ans) {
2107                 PyObject *t = ans;
2108                 ans = PyUnicode_Concat(ans, text);
2109                 Py_CLEAR(text); Py_CLEAR(t);
2110                 if (!ans) goto error;
2111             } else ans = text;
2112         }
2113     }
2114     Py_CLEAR(empty_string);
2115     if (!ans) Py_RETURN_NONE;
2116     return ans;
2117 error:
2118     Py_CLEAR(empty_string); Py_CLEAR(ans);
2119     return NULL;
2120 }
2121 
2122 
2123 bool
screen_open_url(Screen * self)2124 screen_open_url(Screen *self) {
2125     if (!self->url_ranges.count) return false;
2126     hyperlink_id_type hid = hyperlink_id_for_range(self, self->url_ranges.items);
2127     if (hid) {
2128         const char *url = get_hyperlink_for_id(self->hyperlink_pool, hid, true);
2129         if (url) {
2130             CALLBACK("open_url", "sH", url, hid);
2131             return true;
2132         }
2133     }
2134     PyObject *text = current_url_text(self, NULL);
2135     if (!text) {
2136         if (PyErr_Occurred()) PyErr_Print();
2137         return false;
2138     }
2139     bool found = false;
2140     if (PyUnicode_Check(text)) {
2141         CALLBACK("open_url", "OH", text, 0);
2142         found = true;
2143     }
2144     Py_CLEAR(text);
2145     return found;
2146 }
2147 
2148 static void
deactivate_overlay_line(Screen * self)2149 deactivate_overlay_line(Screen *self) {
2150     if (self->overlay_line.is_active && self->overlay_line.xnum && self->overlay_line.ynum < self->lines) {
2151         Line *line = range_line_(self, self->overlay_line.ynum);
2152         line_reset_cells(line, self->overlay_line.xstart, self->overlay_line.xnum, self->overlay_line.gpu_cells, self->overlay_line.cpu_cells);
2153         if (self->cursor->y == self->overlay_line.ynum) self->cursor->x = self->overlay_line.xstart;
2154         self->is_dirty = true;
2155         linebuf_mark_line_dirty(self->linebuf, self->overlay_line.ynum);
2156     }
2157     self->overlay_line.is_active = false;
2158     self->overlay_line.ynum = 0;
2159     self->overlay_line.xnum = 0;
2160     self->overlay_line.xstart = 0;
2161 }
2162 
2163 
2164 // }}}
2165 
2166 // URLs {{{
2167 static void
extend_url(Screen * screen,Line * line,index_type * x,index_type * y,char_type sentinel)2168 extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type sentinel) {
2169     unsigned int count = 0;
2170     while(count++ < 10) {
2171         if (*x != line->xnum - 1) break;
2172         bool next_line_starts_with_url_chars = false;
2173         line = screen_visual_line(screen, *y + 2);
2174         if (line) next_line_starts_with_url_chars = line_startswith_url_chars(line);
2175         line = screen_visual_line(screen, *y + 1);
2176         if (!line) break;
2177         // we deliberately allow non-continued lines as some programs, like
2178         // mutt split URLs with newlines at line boundaries
2179         index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars);
2180         if (!new_x && !line_startswith_url_chars(line)) break;
2181         *y += 1; *x = new_x;
2182     }
2183 }
2184 
2185 static char_type
get_url_sentinel(Line * line,index_type url_start)2186 get_url_sentinel(Line *line, index_type url_start) {
2187     char_type before = 0, sentinel;
2188     if (url_start > 0 && url_start < line->xnum) before = line->cpu_cells[url_start - 1].ch;
2189     switch(before) {
2190         case '"':
2191         case '\'':
2192         case '*':
2193             sentinel = before; break;
2194         case '(':
2195             sentinel = ')'; break;
2196         case '[':
2197             sentinel = ']'; break;
2198         case '{':
2199             sentinel = '}'; break;
2200         case '<':
2201             sentinel = '>'; break;
2202         default:
2203             sentinel = 0; break;
2204     }
2205     return sentinel;
2206 }
2207 
2208 bool
screen_detect_url(Screen * screen,unsigned int x,unsigned int y)2209 screen_detect_url(Screen *screen, unsigned int x, unsigned int y) {
2210     bool has_url = false;
2211     index_type url_start, url_end = 0;
2212     Line *line = screen_visual_line(screen, y);
2213     if (!line || x >= screen->columns) return false;
2214     if (line->cpu_cells[x].hyperlink_id) {
2215         screen_mark_hyperlink(screen, x, y);
2216         return true;
2217     }
2218     char_type sentinel = 0;
2219     if (line) {
2220         url_start = line_url_start_at(line, x);
2221         if (url_start < line->xnum) {
2222             bool next_line_starts_with_url_chars = false;
2223             if (y < screen->lines - 1) {
2224                 line = screen_visual_line(screen, y+1);
2225                 next_line_starts_with_url_chars = line_startswith_url_chars(line);
2226                 line = screen_visual_line(screen, y);
2227             }
2228             sentinel = get_url_sentinel(line, url_start);
2229             url_end = line_url_end_at(line, x, true, sentinel, next_line_starts_with_url_chars);
2230         }
2231         has_url = url_end > url_start;
2232     }
2233     if (has_url) {
2234         index_type y_extended = y;
2235         extend_url(screen, line, &url_end, &y_extended, sentinel);
2236         screen_mark_url(screen, url_start, y, url_end, y_extended);
2237     } else {
2238         screen_mark_url(screen, 0, 0, 0, 0);
2239     }
2240     return has_url;
2241 }
2242 
2243 
2244 
2245 // }}}
2246 
2247 // Python interface {{{
2248 #define WRAP0(name) static PyObject* name(Screen *self, PyObject *a UNUSED) { screen_##name(self); Py_RETURN_NONE; }
2249 #define WRAP0x(name) static PyObject* xxx_##name(Screen *self, PyObject *a UNUSED) { screen_##name(self); Py_RETURN_NONE; }
2250 #define WRAP1(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v); Py_RETURN_NONE; }
2251 #define WRAP1B(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; int b=false; if(!PyArg_ParseTuple(args, "|Ip", &v, &b)) return NULL; screen_##name(self, v, b); Py_RETURN_NONE; }
2252 #define WRAP1E(name, defval, ...) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v, __VA_ARGS__); Py_RETURN_NONE; }
2253 #define WRAP2(name, defval1, defval2) static PyObject* name(Screen *self, PyObject *args) { unsigned int a=defval1, b=defval2; if(!PyArg_ParseTuple(args, "|II", &a, &b)) return NULL; screen_##name(self, a, b); Py_RETURN_NONE; }
2254 #define WRAP2B(name) static PyObject* name(Screen *self, PyObject *args) { unsigned int a, b; int p; if(!PyArg_ParseTuple(args, "IIp", &a, &b, &p)) return NULL; screen_##name(self, a, b, (bool)p); Py_RETURN_NONE; }
2255 
2256 WRAP0(garbage_collect_hyperlink_pool)
WRAP0x(has_selection)2257 WRAP0x(has_selection)
2258 
2259 static PyObject*
2260 hyperlinks_as_list(Screen *self, PyObject *args UNUSED) {
2261     return screen_hyperlinks_as_list(self);
2262 }
2263 
2264 static PyObject*
hyperlink_for_id(Screen * self,PyObject * val)2265 hyperlink_for_id(Screen *self, PyObject *val) {
2266     unsigned long id = PyLong_AsUnsignedLong(val);
2267     if (id > HYPERLINK_MAX_NUMBER) { PyErr_SetString(PyExc_IndexError, "Out of bounds"); return NULL; }
2268     return Py_BuildValue("s", get_hyperlink_for_id(self->hyperlink_pool, id, true));
2269 }
2270 
2271 static PyObject*
set_pending_timeout(Screen * self,PyObject * val)2272 set_pending_timeout(Screen *self, PyObject *val) {
2273     if (!PyFloat_Check(val)) { PyErr_SetString(PyExc_TypeError, "timeout must be a float"); return NULL; }
2274     PyObject *ans = PyFloat_FromDouble(self->pending_mode.wait_time);
2275     self->pending_mode.wait_time = s_double_to_monotonic_t(PyFloat_AS_DOUBLE(val));
2276     return ans;
2277 }
2278 
get_visual_line(void * x,int y)2279 static Line* get_visual_line(void *x, int y) { return visual_line_(x, y); }
get_range_line(void * x,int y)2280 static Line* get_range_line(void *x, int y) { return range_line_(x, y); }
2281 
2282 static PyObject*
as_text(Screen * self,PyObject * args)2283 as_text(Screen *self, PyObject *args) {
2284     return as_text_generic(args, self, get_visual_line, self->lines, &self->as_ansi_buf);
2285 }
2286 
2287 static PyObject*
as_text_non_visual(Screen * self,PyObject * args)2288 as_text_non_visual(Screen *self, PyObject *args) {
2289     return as_text_generic(args, self, get_range_line, self->lines, &self->as_ansi_buf);
2290 }
2291 
2292 static PyObject*
as_text_generic_wrapper(Screen * self,PyObject * args,get_line_func get_line)2293 as_text_generic_wrapper(Screen *self, PyObject *args, get_line_func get_line) {
2294     return as_text_generic(args, self, get_line, self->lines, &self->as_ansi_buf);
2295 }
2296 
2297 static PyObject*
as_text_alternate(Screen * self,PyObject * args)2298 as_text_alternate(Screen *self, PyObject *args) {
2299     LineBuf *original = self->linebuf;
2300     self->linebuf = original == self->main_linebuf ? self->alt_linebuf : self->main_linebuf;
2301     PyObject *ans = as_text_generic_wrapper(self, args, get_range_line);
2302     self->linebuf = original;
2303     return ans;
2304 }
2305 
2306 
2307 
2308 static PyObject*
screen_truncate_point_for_length(PyObject UNUSED * self,PyObject * args)2309 screen_truncate_point_for_length(PyObject UNUSED *self, PyObject *args) {
2310     PyObject *str; unsigned int num_cells, start_pos = 0;
2311     if (!PyArg_ParseTuple(args, "UI|I", &str, &num_cells, &start_pos)) return NULL;
2312     if (PyUnicode_READY(str) != 0) return NULL;
2313     int kind = PyUnicode_KIND(str);
2314     void *data = PyUnicode_DATA(str);
2315     Py_ssize_t len = PyUnicode_GET_LENGTH(str), i;
2316     char_type prev_ch = 0;
2317     int prev_width = 0;
2318     bool in_sgr = false;
2319     unsigned long width_so_far = 0;
2320     for (i = start_pos; i < len && width_so_far < num_cells; i++) {
2321         char_type ch = PyUnicode_READ(kind, data, i);
2322         if (in_sgr) {
2323             if (ch == 'm') in_sgr = false;
2324             continue;
2325         }
2326         if (ch == 0x1b && i + 1 < len && PyUnicode_READ(kind, data, i + 1) == '[') { in_sgr = true; continue; }
2327         if (ch == 0xfe0f) {
2328             if (is_emoji_presentation_base(prev_ch) && prev_width == 1) {
2329                 width_so_far += 1;
2330                 prev_width = 2;
2331             } else prev_width = 0;
2332         } else {
2333             int w = wcwidth_std(ch);
2334             switch(w) {
2335                 case -1:
2336                 case 0:
2337                     prev_width = 0; break;
2338                 case 2:
2339                     prev_width = 2; break;
2340                 default:
2341                     prev_width = 1; break;
2342             }
2343             if (width_so_far + prev_width > num_cells) { break; }
2344             width_so_far += prev_width;
2345         }
2346         prev_ch = ch;
2347 
2348     }
2349     return PyLong_FromUnsignedLong(i);
2350 }
2351 
2352 
2353 static PyObject*
line(Screen * self,PyObject * val)2354 line(Screen *self, PyObject *val) {
2355     unsigned long y = PyLong_AsUnsignedLong(val);
2356     if (y >= self->lines) { PyErr_SetString(PyExc_IndexError, "Out of bounds"); return NULL; }
2357     linebuf_init_line(self->linebuf, y);
2358     Py_INCREF(self->linebuf->line);
2359     return (PyObject*) self->linebuf->line;
2360 }
2361 
2362 Line*
screen_visual_line(Screen * self,index_type y)2363 screen_visual_line(Screen *self, index_type y) {
2364     if (y >= self->lines) return NULL;
2365     return visual_line_(self, y);
2366 }
2367 
2368 static PyObject*
visual_line(Screen * self,PyObject * args)2369 visual_line(Screen *self, PyObject *args) {
2370     // The line corresponding to the yth visual line, taking into account scrolling
2371     unsigned int y;
2372     if (!PyArg_ParseTuple(args, "I", &y)) return NULL;
2373     if (y >= self->lines) { Py_RETURN_NONE; }
2374     return Py_BuildValue("O", visual_line_(self, y));
2375 }
2376 
2377 static PyObject*
draw(Screen * self,PyObject * src)2378 draw(Screen *self, PyObject *src) {
2379     if (!PyUnicode_Check(src)) { PyErr_SetString(PyExc_TypeError, "A unicode string is required"); return NULL; }
2380     if (PyUnicode_READY(src) != 0) { return PyErr_NoMemory(); }
2381     int kind = PyUnicode_KIND(src);
2382     void *buf = PyUnicode_DATA(src);
2383     Py_ssize_t sz = PyUnicode_GET_LENGTH(src);
2384     for (Py_ssize_t i = 0; i < sz; i++) screen_draw(self, PyUnicode_READ(kind, buf, i), true);
2385     Py_RETURN_NONE;
2386 }
2387 
2388 extern void
2389 parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, unsigned int *params, PyObject *dump_callback, const char *report_name, Region *region);
2390 
2391 static PyObject*
apply_sgr(Screen * self,PyObject * src)2392 apply_sgr(Screen *self, PyObject *src) {
2393     if (!PyUnicode_Check(src)) { PyErr_SetString(PyExc_TypeError, "A unicode string is required"); return NULL; }
2394     if (PyUnicode_READY(src) != 0) { return PyErr_NoMemory(); }
2395     Py_UCS4 *buf = PyUnicode_AsUCS4Copy(src);
2396     if (!buf) return NULL;
2397     unsigned int params[MAX_PARAMS] = {0};
2398     parse_sgr(self, buf, PyUnicode_GET_LENGTH(src), params, NULL, "parse_sgr", NULL);
2399     Py_RETURN_NONE;
2400 }
2401 
2402 static PyObject*
reset_mode(Screen * self,PyObject * args)2403 reset_mode(Screen *self, PyObject *args) {
2404     int private = false;
2405     unsigned int mode;
2406     if (!PyArg_ParseTuple(args, "I|p", &mode, &private)) return NULL;
2407     if (private) mode <<= 5;
2408     screen_reset_mode(self, mode);
2409     Py_RETURN_NONE;
2410 }
2411 
2412 static PyObject*
_select_graphic_rendition(Screen * self,PyObject * args)2413 _select_graphic_rendition(Screen *self, PyObject *args) {
2414     int params[256] = {0};
2415     for (int i = 0; i < PyTuple_GET_SIZE(args); i++) { params[i] = PyLong_AsLong(PyTuple_GET_ITEM(args, i)); }
2416     select_graphic_rendition(self, params, PyTuple_GET_SIZE(args), NULL);
2417     Py_RETURN_NONE;
2418 }
2419 
2420 static PyObject*
set_mode(Screen * self,PyObject * args)2421 set_mode(Screen *self, PyObject *args) {
2422     int private = false;
2423     unsigned int mode;
2424     if (!PyArg_ParseTuple(args, "I|p", &mode, &private)) return NULL;
2425     if (private) mode <<= 5;
2426     screen_set_mode(self, mode);
2427     Py_RETURN_NONE;
2428 }
2429 
2430 static PyObject*
reset_dirty(Screen * self,PyObject * a UNUSED)2431 reset_dirty(Screen *self, PyObject *a UNUSED) {
2432     screen_reset_dirty(self);
2433     Py_RETURN_NONE;
2434 }
2435 
2436 static PyObject*
is_using_alternate_linebuf(Screen * self,PyObject * a UNUSED)2437 is_using_alternate_linebuf(Screen *self, PyObject *a UNUSED) {
2438     if (self->linebuf == self->alt_linebuf) Py_RETURN_TRUE;
2439     Py_RETURN_FALSE;
2440 }
2441 
2442 WRAP1E(cursor_back, 1, -1)
2443 WRAP1B(erase_in_line, 0)
2444 WRAP1B(erase_in_display, 0)
WRAP0(scroll_until_cursor)2445 WRAP0(scroll_until_cursor)
2446 
2447 #define MODE_GETSET(name, uname) \
2448     static PyObject* name##_get(Screen *self, void UNUSED *closure) { PyObject *ans = self->modes.m##uname ? Py_True : Py_False; Py_INCREF(ans); return ans; } \
2449     static int name##_set(Screen *self, PyObject *val, void UNUSED *closure) { if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } set_mode_from_const(self, uname, PyObject_IsTrue(val) ? true : false); return 0; }
2450 
2451 MODE_GETSET(in_bracketed_paste_mode, BRACKETED_PASTE)
2452 MODE_GETSET(focus_tracking_enabled, FOCUS_TRACKING)
2453 MODE_GETSET(auto_repeat_enabled, DECARM)
2454 MODE_GETSET(cursor_visible, DECTCEM)
2455 MODE_GETSET(cursor_key_mode, DECCKM)
2456 
2457 static PyObject* disable_ligatures_get(Screen *self, void UNUSED *closure) {
2458     const char *ans = NULL;
2459     switch(self->disable_ligatures) {
2460         case DISABLE_LIGATURES_NEVER:
2461             ans = "never";
2462             break;
2463         case DISABLE_LIGATURES_CURSOR:
2464             ans = "cursor";
2465             break;
2466         case DISABLE_LIGATURES_ALWAYS:
2467             ans = "always";
2468             break;
2469     }
2470     return PyUnicode_FromString(ans);
2471 }
2472 
disable_ligatures_set(Screen * self,PyObject * val,void UNUSED * closure)2473 static int disable_ligatures_set(Screen *self, PyObject *val, void UNUSED *closure) {
2474     if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; }
2475     if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "unicode string expected"); return -1; }
2476     if (PyUnicode_READY(val) != 0) return -1;
2477     const char *q = PyUnicode_AsUTF8(val);
2478     DisableLigature dl = DISABLE_LIGATURES_NEVER;
2479     if (strcmp(q, "always") == 0) dl = DISABLE_LIGATURES_ALWAYS;
2480     else if (strcmp(q, "cursor") == 0) dl = DISABLE_LIGATURES_CURSOR;
2481     if (dl != self->disable_ligatures) {
2482         self->disable_ligatures = dl;
2483         screen_dirty_sprite_positions(self);
2484     }
2485     return 0;
2486 }
2487 
2488 static PyObject*
cursor_up(Screen * self,PyObject * args)2489 cursor_up(Screen *self, PyObject *args) {
2490     unsigned int count = 1;
2491     int do_carriage_return = false, move_direction = -1;
2492     if (!PyArg_ParseTuple(args, "|Ipi", &count, &do_carriage_return, &move_direction)) return NULL;
2493     screen_cursor_up(self, count, do_carriage_return, move_direction);
2494     Py_RETURN_NONE;
2495 }
2496 
2497 static PyObject*
update_selection(Screen * self,PyObject * args)2498 update_selection(Screen *self, PyObject *args) {
2499     unsigned int x, y;
2500     int in_left_half_of_cell = 0, ended = 1, nearest = 0;
2501     if (!PyArg_ParseTuple(args, "II|ppp", &x, &y, &in_left_half_of_cell, &ended, &nearest)) return NULL;
2502     screen_update_selection(self, x, y, in_left_half_of_cell, (SelectionUpdate){.ended = ended, .set_as_nearest_extend=nearest});
2503     Py_RETURN_NONE;
2504 }
2505 
2506 static PyObject*
clear_selection_(Screen * s,PyObject * args UNUSED)2507 clear_selection_(Screen *s, PyObject *args UNUSED) {
2508     clear_selection(&s->selections);
2509     Py_RETURN_NONE;
2510 }
2511 
2512 static PyObject*
resize(Screen * self,PyObject * args)2513 resize(Screen *self, PyObject *args) {
2514     unsigned int a=1, b=1;
2515     if(!PyArg_ParseTuple(args, "|II", &a, &b)) return NULL;
2516     screen_resize(self, a, b);
2517     if (PyErr_Occurred()) return NULL;
2518     Py_RETURN_NONE;
2519 }
2520 
2521 WRAP0x(index)
WRAP0(reverse_index)2522 WRAP0(reverse_index)
2523 WRAP0(reset)
2524 WRAP0(set_tab_stop)
2525 WRAP1(clear_tab_stop, 0)
2526 WRAP0(backspace)
2527 WRAP0(tab)
2528 WRAP0(linefeed)
2529 WRAP0(carriage_return)
2530 WRAP2(set_margins, 1, 1)
2531 WRAP2(detect_url, 0, 0)
2532 WRAP0(rescale_images)
2533 
2534 static PyObject*
2535 current_key_encoding_flags(Screen *self, PyObject *args UNUSED) {
2536     unsigned long ans = screen_current_key_encoding_flags(self);
2537     return PyLong_FromUnsignedLong(ans);
2538 }
2539 
2540 static PyObject*
start_selection(Screen * self,PyObject * args)2541 start_selection(Screen *self, PyObject *args) {
2542     unsigned int x, y;
2543     int rectangle_select = 0, extend_mode = EXTEND_CELL, in_left_half_of_cell = 1;
2544     if (!PyArg_ParseTuple(args, "II|pip", &x, &y, &rectangle_select, &extend_mode, &in_left_half_of_cell)) return NULL;
2545     screen_start_selection(self, x, y, in_left_half_of_cell, rectangle_select, extend_mode);
2546     Py_RETURN_NONE;
2547 }
2548 
2549 static PyObject*
is_rectangle_select(Screen * self,PyObject * a UNUSED)2550 is_rectangle_select(Screen *self, PyObject *a UNUSED) {
2551     if (self->selections.count && self->selections.items[0].rectangle_select) Py_RETURN_TRUE;
2552     Py_RETURN_FALSE;
2553 }
2554 
2555 static PyObject*
copy_colors_from(Screen * self,Screen * other)2556 copy_colors_from(Screen *self, Screen *other) {
2557     copy_color_profile(self->color_profile, other->color_profile);
2558     Py_RETURN_NONE;
2559 }
2560 
2561 static PyObject*
text_for_selections(Screen * self,Selections * selections)2562 text_for_selections(Screen *self, Selections *selections) {
2563     PyObject *lines = NULL;
2564     for (size_t i = 0; i < selections->count; i++) {
2565         PyObject *temp = text_for_range(self, selections->items + i, true);
2566         if (temp) {
2567             if (lines) {
2568                 lines = extend_tuple(lines, temp);
2569                 Py_DECREF(temp);
2570             } else lines = temp;
2571         } else break;
2572     }
2573     if (PyErr_Occurred()) { Py_CLEAR(lines); return NULL; }
2574     if (!lines) lines = PyTuple_New(0);
2575     return lines;
2576 }
2577 
2578 static PyObject*
text_for_selection(Screen * self,PyObject * a UNUSED)2579 text_for_selection(Screen *self, PyObject *a UNUSED) {
2580     return text_for_selections(self, &self->selections);
2581 }
2582 
2583 static PyObject*
text_for_marked_url(Screen * self,PyObject * a UNUSED)2584 text_for_marked_url(Screen *self, PyObject *a UNUSED) {
2585     return text_for_selections(self, &self->url_ranges);
2586 }
2587 
2588 
2589 bool
screen_selection_range_for_line(Screen * self,index_type y,index_type * start,index_type * end)2590 screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end) {
2591     if (y >= self->lines) { return false; }
2592     Line *line = visual_line_(self, y);
2593     index_type xlimit = line->xnum, xstart = 0;
2594     while (xlimit > 0 && CHAR_IS_BLANK(line->cpu_cells[xlimit - 1].ch)) xlimit--;
2595     while (xstart < xlimit && CHAR_IS_BLANK(line->cpu_cells[xstart].ch)) xstart++;
2596     *start = xstart; *end = xlimit > 0 ? xlimit - 1 : 0;
2597     return true;
2598 }
2599 
2600 static bool
is_opt_word_char(char_type ch)2601 is_opt_word_char(char_type ch) {
2602     if (OPT(select_by_word_characters)) {
2603         for (const char_type *p = OPT(select_by_word_characters); *p; p++) {
2604             if (ch == *p) return true;
2605         }
2606     }
2607     return false;
2608 }
2609 
2610 static bool
is_char_ok_for_word_extension(Line * line,index_type x)2611 is_char_ok_for_word_extension(Line* line, index_type x) {
2612     char_type ch = line->cpu_cells[x].ch;
2613     if (is_word_char(ch) || is_opt_word_char(ch)) return true;
2614     // pass : from :// so that common URLs are matched
2615     if (ch == ':' && x + 2 < line->xnum && line->cpu_cells[x+1].ch == '/' && line->cpu_cells[x+2].ch == '/') return true;
2616     return false;
2617 }
2618 
2619 bool
screen_selection_range_for_word(Screen * self,const index_type x,const index_type y,index_type * y1,index_type * y2,index_type * s,index_type * e,bool initial_selection)2620 screen_selection_range_for_word(Screen *self, const index_type x, const index_type y, index_type *y1, index_type *y2, index_type *s, index_type *e, bool initial_selection) {
2621     if (y >= self->lines || x >= self->columns) return false;
2622     index_type start, end;
2623     Line *line = visual_line_(self, y);
2624     *y1 = y;
2625     *y2 = y;
2626 #define is_ok(x) is_char_ok_for_word_extension(line, x)
2627     if (!is_ok(x)) {
2628         if (initial_selection) return false;
2629         *s = x; *e = x;
2630         return true;
2631     }
2632     start = x; end = x;
2633     while(true) {
2634         while(start > 0 && is_ok(start - 1)) start--;
2635         if (start > 0 || !line->continued || *y1 == 0) break;
2636         line = visual_line_(self, *y1 - 1);
2637         if (!is_ok(self->columns - 1)) break;
2638         (*y1)--; start = self->columns - 1;
2639     }
2640     line = visual_line_(self, *y2);
2641     while(true) {
2642         while(end < self->columns - 1 && is_ok(end + 1)) end++;
2643         if (end < self->columns - 1 || *y2 >= self->lines - 1) break;
2644         line = visual_line_(self, *y2 + 1);
2645         if (!line->continued || !is_ok(0)) break;
2646         (*y2)++; end = 0;
2647     }
2648     *s = start; *e = end;
2649     return true;
2650 #undef is_ok
2651 }
2652 
2653 bool
screen_history_scroll(Screen * self,int amt,bool upwards)2654 screen_history_scroll(Screen *self, int amt, bool upwards) {
2655     switch(amt) {
2656         case SCROLL_LINE:
2657             amt = 1;
2658             break;
2659         case SCROLL_PAGE:
2660             amt = self->lines - 1;
2661             break;
2662         case SCROLL_FULL:
2663             amt = self->historybuf->count;
2664             break;
2665         default:
2666             amt = MAX(0, amt);
2667             break;
2668     }
2669     if (!upwards) {
2670         amt = MIN((unsigned int)amt, self->scrolled_by);
2671         amt *= -1;
2672     }
2673     if (amt == 0) return false;
2674     unsigned int new_scroll = MIN(self->scrolled_by + amt, self->historybuf->count);
2675     if (new_scroll != self->scrolled_by) {
2676         self->scrolled_by = new_scroll;
2677         self->scroll_changed = true;
2678         return true;
2679     }
2680     return false;
2681 }
2682 
2683 static PyObject*
scroll(Screen * self,PyObject * args)2684 scroll(Screen *self, PyObject *args) {
2685     int amt, upwards;
2686     if (!PyArg_ParseTuple(args, "ip", &amt, &upwards)) return NULL;
2687     if (screen_history_scroll(self, amt, upwards)) { Py_RETURN_TRUE; }
2688     Py_RETURN_FALSE;
2689 }
2690 
2691 bool
screen_is_selection_dirty(Screen * self)2692 screen_is_selection_dirty(Screen *self) {
2693     IterationData q;
2694     if (self->scrolled_by != self->last_rendered.scrolled_by) return true;
2695     if (self->selections.last_rendered_count != self->selections.count || self->url_ranges.last_rendered_count != self->url_ranges.count) return true;
2696     for (size_t i = 0; i < self->selections.count; i++) {
2697         iteration_data(self, self->selections.items + i, &q, 0, true);
2698         if (memcmp(&q, &self->selections.items[i].last_rendered, sizeof(IterationData)) != 0) return true;
2699     }
2700     for (size_t i = 0; i < self->url_ranges.count; i++) {
2701         iteration_data(self, self->url_ranges.items + i, &q, 0, true);
2702         if (memcmp(&q, &self->url_ranges.items[i].last_rendered, sizeof(IterationData)) != 0) return true;
2703     }
2704     return false;
2705 }
2706 
2707 void
screen_start_selection(Screen * self,index_type x,index_type y,bool in_left_half_of_cell,bool rectangle_select,SelectionExtendMode extend_mode)2708 screen_start_selection(Screen *self, index_type x, index_type y, bool in_left_half_of_cell, bool rectangle_select, SelectionExtendMode extend_mode) {
2709 #define A(attr, val) self->selections.items->attr = val;
2710     ensure_space_for(&self->selections, items, Selection, self->selections.count + 1, capacity, 1, false);
2711     memset(self->selections.items, 0, sizeof(Selection));
2712     self->selections.count = 1;
2713     self->selections.in_progress = true;
2714     self->selections.extend_mode = extend_mode;
2715     self->selections.items[0].last_rendered.y = INT_MAX;
2716     A(start.x, x); A(end.x, x); A(start.y, y); A(end.y, y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by);
2717     A(rectangle_select, rectangle_select); A(start.in_left_half_of_cell, in_left_half_of_cell); A(end.in_left_half_of_cell, in_left_half_of_cell);
2718     A(input_start.x, x); A(input_start.y, y); A(input_start.in_left_half_of_cell, in_left_half_of_cell);
2719     A(input_current.x, x); A(input_current.y, y); A(input_current.in_left_half_of_cell, in_left_half_of_cell);
2720 #undef A
2721 }
2722 
2723 static void
add_url_range(Screen * self,index_type start_x,index_type start_y,index_type end_x,index_type end_y)2724 add_url_range(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y) {
2725 #define A(attr, val) r->attr = val;
2726     ensure_space_for(&self->url_ranges, items, Selection, self->url_ranges.count + 8, capacity, 8, false);
2727     Selection *r = self->url_ranges.items + self->url_ranges.count++;
2728     memset(r, 0, sizeof(Selection));
2729     r->last_rendered.y = INT_MAX;
2730     A(start.x, start_x); A(end.x, end_x); A(start.y, start_y); A(end.y, end_y);
2731     A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by);
2732     A(start.in_left_half_of_cell, true);
2733 #undef A
2734 }
2735 
2736 void
screen_mark_url(Screen * self,index_type start_x,index_type start_y,index_type end_x,index_type end_y)2737 screen_mark_url(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y) {
2738     self->url_ranges.count = 0;
2739     if (start_x || start_y || end_x || end_y) add_url_range(self, start_x, start_y, end_x, end_y);
2740 }
2741 
2742 static bool
mark_hyperlinks_in_line(Screen * self,Line * line,hyperlink_id_type id,index_type y)2743 mark_hyperlinks_in_line(Screen *self, Line *line, hyperlink_id_type id, index_type y) {
2744     index_type start = 0;
2745     bool found = false;
2746     bool in_range = false;
2747     for (index_type x = 0; x < line->xnum; x++) {
2748         bool has_hyperlink = line->cpu_cells[x].hyperlink_id == id;
2749         if (in_range) {
2750             if (!has_hyperlink) {
2751                 add_url_range(self, start, y, x - 1, y);
2752                 in_range = false;
2753                 start = 0;
2754             }
2755         } else {
2756             if (has_hyperlink) {
2757                 start = x; in_range = true;
2758                 found = true;
2759             }
2760         }
2761     }
2762     if (in_range) add_url_range(self, start, y, self->columns - 1, y);
2763     return found;
2764 }
2765 
2766 static void
sort_ranges(const Screen * self,Selections * s)2767 sort_ranges(const Screen *self, Selections *s) {
2768     IterationData a;
2769     for (size_t i = 0; i < s->count; i++) {
2770         iteration_data(self, s->items + i, &a, 0, false);
2771         s->items[i].sort_x = a.first.x;
2772         s->items[i].sort_y = a.y;
2773     }
2774 #define range_lt(a, b) ((a)->sort_y < (b)->sort_y || ((a)->sort_y == (b)->sort_y && (a)->sort_x < (b)->sort_x))
2775     QSORT(Selection, s->items, s->count, range_lt);
2776 #undef range_lt
2777 }
2778 
2779 hyperlink_id_type
screen_mark_hyperlink(Screen * self,index_type x,index_type y)2780 screen_mark_hyperlink(Screen *self, index_type x, index_type y) {
2781     self->url_ranges.count = 0;
2782     Line *line = screen_visual_line(self, y);
2783     hyperlink_id_type id = line->cpu_cells[x].hyperlink_id;
2784     if (!id) return 0;
2785     index_type ypos = y, last_marked_line = y;
2786     do {
2787         if (mark_hyperlinks_in_line(self, line, id, ypos)) last_marked_line = ypos;
2788         if (ypos == 0) break;
2789         ypos--;
2790         line = screen_visual_line(self, ypos);
2791     } while (last_marked_line - ypos < 5);
2792     ypos = y + 1; last_marked_line = y;
2793     while (ypos < self->lines - 1 && ypos - last_marked_line < 5) {
2794         line = screen_visual_line(self, ypos);
2795         if (mark_hyperlinks_in_line(self, line, id, ypos)) last_marked_line = ypos;
2796         ypos++;
2797     }
2798     if (self->url_ranges.count > 1) sort_ranges(self, &self->url_ranges);
2799     return id;
2800 }
2801 
2802 static index_type
continue_line_upwards(Screen * self,index_type top_line,SelectionBoundary * start,SelectionBoundary * end)2803 continue_line_upwards(Screen *self, index_type top_line, SelectionBoundary *start, SelectionBoundary *end) {
2804     while (top_line > 0 && visual_line_(self, top_line)->continued) {
2805         if (!screen_selection_range_for_line(self, top_line - 1, &start->x, &end->x)) break;
2806         top_line--;
2807     }
2808     return top_line;
2809 }
2810 
2811 static index_type
continue_line_downwards(Screen * self,index_type bottom_line,SelectionBoundary * start,SelectionBoundary * end)2812 continue_line_downwards(Screen *self, index_type bottom_line, SelectionBoundary *start, SelectionBoundary *end) {
2813     while (bottom_line < self->lines - 1 && visual_line_(self, bottom_line + 1)->continued) {
2814         if (!screen_selection_range_for_line(self, bottom_line + 1, &start->x, &end->x)) break;
2815         bottom_line++;
2816     }
2817     return bottom_line;
2818 }
2819 
2820 void
screen_update_selection(Screen * self,index_type x,index_type y,bool in_left_half_of_cell,SelectionUpdate upd)2821 screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_half_of_cell, SelectionUpdate upd) {
2822     if (!self->selections.count) return;
2823     self->selections.in_progress = !upd.ended;
2824     Selection *s = self->selections.items;
2825     s->input_current.x = x; s->input_current.y = y;
2826     s->input_current.in_left_half_of_cell = in_left_half_of_cell;
2827     SelectionBoundary start, end, *a = &s->start, *b = &s->end, abs_start, abs_end, abs_current_input;
2828 #define set_abs(which, initializer, scrolled_by) which = initializer; which.y = scrolled_by + self->lines - 1 - which.y;
2829     set_abs(abs_start, s->start, s->start_scrolled_by);
2830     set_abs(abs_end, s->end, s->end_scrolled_by);
2831     set_abs(abs_current_input, s->input_current, self->scrolled_by);
2832     if (upd.set_as_nearest_extend || self->selections.extension_in_progress) {
2833         self->selections.extension_in_progress = true;
2834         bool start_is_nearer = false;
2835         if (self->selections.extend_mode == EXTEND_LINE || self->selections.extend_mode == EXTEND_LINE_FROM_POINT) {
2836             if (abs_start.y == abs_end.y) {
2837                 if (abs_current_input.y == abs_start.y) start_is_nearer = selection_boundary_less_than(&abs_start, &abs_end) ? (abs_current_input.x <= abs_start.x) : (abs_current_input.x <= abs_end.x);
2838                 else start_is_nearer = selection_boundary_less_than(&abs_start, &abs_end) ? (abs_current_input.y > abs_start.y) : (abs_current_input.y < abs_end.y);
2839             } else {
2840                 start_is_nearer = num_lines_between_selection_boundaries(&abs_start, &abs_current_input) < num_lines_between_selection_boundaries(&abs_end, &abs_current_input);
2841             }
2842         } else start_is_nearer = num_cells_between_selection_boundaries(self, &abs_start, &abs_current_input) < num_cells_between_selection_boundaries(self, &abs_end, &abs_current_input);
2843         if (start_is_nearer) s->adjusting_start = true;
2844     } else if (!upd.start_extended_selection && self->selections.extend_mode != EXTEND_CELL) {
2845         SelectionBoundary abs_initial_start, abs_initial_end;
2846         set_abs(abs_initial_start, s->initial_extent.start, s->initial_extent.scrolled_by);
2847         set_abs(abs_initial_end, s->initial_extent.end, s->initial_extent.scrolled_by);
2848         if (self->selections.extend_mode == EXTEND_WORD) {
2849             s->adjusting_start = selection_boundary_less_than(&abs_current_input, &abs_initial_end);
2850         } else {
2851             const unsigned int initial_line = abs_initial_start.y;
2852             if (initial_line == abs_current_input.y) {
2853                 s->adjusting_start = false;
2854                 s->start = s->initial_extent.start; s->start_scrolled_by = s->initial_extent.scrolled_by;
2855                 s->end = s->initial_extent.end; s->end_scrolled_by = s->initial_extent.scrolled_by;
2856             }
2857             else {
2858                 s->adjusting_start = abs_current_input.y > initial_line;
2859             }
2860         }
2861     }
2862 #undef set_abs
2863     bool adjusted_boundary_is_before;
2864     if (s->adjusting_start) adjusted_boundary_is_before = selection_boundary_less_than(&abs_start, &abs_end);
2865     else { adjusted_boundary_is_before = selection_boundary_less_than(&abs_end, &abs_start); }
2866 
2867     switch(self->selections.extend_mode) {
2868         case EXTEND_WORD: {
2869             if (!s->adjusting_start) { a = &s->end; b = &s->start; }
2870             const bool word_found_at_cursor = screen_selection_range_for_word(self, s->input_current.x, s->input_current.y, &start.y, &end.y, &start.x, &end.x, true);
2871             bool adjust_both_ends = is_selection_empty(s);
2872             if (word_found_at_cursor) {
2873                 if (adjusted_boundary_is_before) {
2874                     *a = start; a->in_left_half_of_cell = true;
2875                     if (adjust_both_ends) { *b = end; b->in_left_half_of_cell = false; }
2876                 } else {
2877                     *a = end; a->in_left_half_of_cell = false;
2878                     if (adjust_both_ends) { *b = start; b->in_left_half_of_cell = true; }
2879                 }
2880                 if (s->adjusting_start || adjust_both_ends) s->start_scrolled_by = self->scrolled_by;
2881                 if (!s->adjusting_start || adjust_both_ends) s->end_scrolled_by = self->scrolled_by;
2882             } else {
2883                 *a = s->input_current;
2884                 if (s->adjusting_start) s->start_scrolled_by = self->scrolled_by; else s->end_scrolled_by = self->scrolled_by;
2885             }
2886             break;
2887         }
2888         case EXTEND_LINE_FROM_POINT:
2889         case EXTEND_LINE: {
2890             bool adjust_both_ends = is_selection_empty(s);
2891             if (s->adjusting_start || adjust_both_ends) s->start_scrolled_by = self->scrolled_by;
2892             if (!s->adjusting_start || adjust_both_ends) s->end_scrolled_by = self->scrolled_by;
2893             index_type top_line, bottom_line;
2894             SelectionBoundary up_start, up_end, down_start, down_end;
2895             if (adjust_both_ends) {
2896                 // empty initial selection
2897                 top_line = s->input_current.y; bottom_line = s->input_current.y;
2898                 if (screen_selection_range_for_line(self, top_line, &up_start.x, &up_end.x)) {
2899 #define S \
2900     s->start.y = top_line; s->end.y = bottom_line; \
2901     s->start.in_left_half_of_cell = true; s->end.in_left_half_of_cell = false; \
2902     s->start.x = up_start.x; s->end.x = bottom_line == top_line ? up_end.x : down_end.x;
2903                     down_start = up_start; down_end = up_end;
2904                     bottom_line = continue_line_downwards(self, bottom_line, &down_start, &down_end);
2905                     if (self->selections.extend_mode == EXTEND_LINE_FROM_POINT) {
2906                         if (x <= up_end.x) {
2907                             S; s->start.x = MAX(x, up_start.x);
2908                         }
2909                     } else {
2910                         top_line = continue_line_upwards(self, top_line, &up_start, &up_end);
2911                         S;
2912                     }
2913                 }
2914 #undef S
2915             } else {
2916                 // extending an existing selection
2917                 top_line = s->input_current.y; bottom_line = s->input_current.y;
2918                 if (screen_selection_range_for_line(self, top_line, &up_start.x, &up_end.x)) {
2919                     down_start = up_start; down_end = up_end;
2920                     top_line = continue_line_upwards(self, top_line, &up_start, &up_end);
2921                     bottom_line = continue_line_downwards(self, bottom_line, &down_start, &down_end);
2922                     if (!s->adjusting_start) { a = &s->end; b = &s->start; }
2923                     if (adjusted_boundary_is_before) {
2924                         a->in_left_half_of_cell = true; a->x = up_start.x; a->y = top_line;
2925                     } else {
2926                         a->in_left_half_of_cell = false; a->x = down_end.x; a->y = bottom_line;
2927                     }
2928                     // allow selecting whitespace at the start of the top line
2929                     if (a->y == top_line && s->input_current.y == top_line && s->input_current.x < a->x && adjusted_boundary_is_before) a->x = s->input_current.x;
2930                 }
2931             }
2932         }
2933         break;
2934         case EXTEND_CELL:
2935             if (s->adjusting_start) b = &s->start;
2936             b->x = x; b->y = y; b->in_left_half_of_cell = in_left_half_of_cell;
2937             if (s->adjusting_start) s->start_scrolled_by = self->scrolled_by; else s->end_scrolled_by = self->scrolled_by;
2938             break;
2939     }
2940     if (!self->selections.in_progress) {
2941         s->adjusting_start = false;
2942         self->selections.extension_in_progress = false;
2943         call_boss(set_primary_selection, NULL);
2944     } else {
2945         if (upd.start_extended_selection && self->selections.extend_mode != EXTEND_CELL) {
2946             s->initial_extent.start = s->start; s->initial_extent.end = s->end;
2947             s->initial_extent.scrolled_by = s->start_scrolled_by;
2948         }
2949     }
2950 }
2951 
2952 static PyObject*
mark_as_dirty(Screen * self,PyObject * a UNUSED)2953 mark_as_dirty(Screen *self, PyObject *a UNUSED) {
2954     self->is_dirty = true;
2955     Py_RETURN_NONE;
2956 }
2957 
2958 static PyObject*
current_char_width(Screen * self,PyObject * a UNUSED)2959 current_char_width(Screen *self, PyObject *a UNUSED) {
2960 #define current_char_width_doc "The width of the character under the cursor"
2961     return PyLong_FromUnsignedLong(screen_current_char_width(self));
2962 }
2963 
2964 static PyObject*
is_main_linebuf(Screen * self,PyObject * a UNUSED)2965 is_main_linebuf(Screen *self, PyObject *a UNUSED) {
2966     PyObject *ans = (self->linebuf == self->main_linebuf) ? Py_True : Py_False;
2967     Py_INCREF(ans);
2968     return ans;
2969 }
2970 
2971 static PyObject*
toggle_alt_screen(Screen * self,PyObject * a UNUSED)2972 toggle_alt_screen(Screen *self, PyObject *a UNUSED) {
2973     screen_toggle_screen_buffer(self, true, true);
2974     Py_RETURN_NONE;
2975 }
2976 
2977 static PyObject*
send_escape_code_to_child(Screen * self,PyObject * args)2978 send_escape_code_to_child(Screen *self, PyObject *args) {
2979     int code;
2980     char *text;
2981     if (!PyArg_ParseTuple(args, "is", &code, &text)) return NULL;
2982     write_escape_code_to_child(self, code, text);
2983     Py_RETURN_NONE;
2984 }
2985 
2986 static void
screen_mark_all(Screen * self)2987 screen_mark_all(Screen *self) {
2988     for (index_type y = 0; y < self->main_linebuf->ynum; y++) {
2989         linebuf_init_line(self->main_linebuf, y);
2990         mark_text_in_line(self->marker, self->main_linebuf->line);
2991     }
2992     for (index_type y = 0; y < self->alt_linebuf->ynum; y++) {
2993         linebuf_init_line(self->alt_linebuf, y);
2994         mark_text_in_line(self->marker, self->alt_linebuf->line);
2995     }
2996     for (index_type y = 0; y < self->historybuf->count; y++) {
2997         historybuf_init_line(self->historybuf, y, self->historybuf->line);
2998         mark_text_in_line(self->marker, self->historybuf->line);
2999     }
3000     self->is_dirty = true;
3001 }
3002 
3003 static PyObject*
set_marker(Screen * self,PyObject * args)3004 set_marker(Screen *self, PyObject *args) {
3005     PyObject *marker = NULL;
3006     if (!PyArg_ParseTuple(args, "|O", &marker)) return NULL;
3007     if (!marker) {
3008         if (self->marker) {
3009             Py_CLEAR(self->marker);
3010             screen_mark_all(self);
3011         }
3012         Py_RETURN_NONE;
3013     }
3014     if (!PyCallable_Check(marker)) {
3015         PyErr_SetString(PyExc_TypeError, "marker must be a callable");
3016         return NULL;
3017     }
3018     self->marker = marker;
3019     Py_INCREF(marker);
3020     screen_mark_all(self);
3021     Py_RETURN_NONE;
3022 }
3023 
3024 
3025 static PyObject*
scroll_to_next_mark(Screen * self,PyObject * args)3026 scroll_to_next_mark(Screen *self, PyObject *args) {
3027     int backwards = 1;
3028     unsigned int mark = 0;
3029     if (!PyArg_ParseTuple(args, "|Ip", &mark, &backwards)) return NULL;
3030     if (!screen_has_marker(self) || self->linebuf == self->alt_linebuf) Py_RETURN_FALSE;
3031     if (backwards) {
3032         for (unsigned int y = self->scrolled_by; y < self->historybuf->count; y++) {
3033             historybuf_init_line(self->historybuf, y, self->historybuf->line);
3034             if (line_has_mark(self->historybuf->line, mark)) {
3035                 screen_history_scroll(self, y - self->scrolled_by + 1, true);
3036                 Py_RETURN_TRUE;
3037             }
3038         }
3039     } else {
3040         Line *line;
3041         for (unsigned int y = self->scrolled_by; y > 0; y--) {
3042             if (y > self->lines) {
3043                 historybuf_init_line(self->historybuf, y - self->lines, self->historybuf->line);
3044                 line = self->historybuf->line;
3045             } else {
3046                 linebuf_init_line(self->linebuf, self->lines - y);
3047                 line = self->linebuf->line;
3048             }
3049             if (line_has_mark(line, mark)) {
3050                 screen_history_scroll(self, self->scrolled_by - y + 1, false);
3051                 Py_RETURN_TRUE;
3052             }
3053         }
3054     }
3055     Py_RETURN_FALSE;
3056 }
3057 
3058 static PyObject*
marked_cells(Screen * self,PyObject * o UNUSED)3059 marked_cells(Screen *self, PyObject *o UNUSED) {
3060     PyObject *ans = PyList_New(0);
3061     if (!ans) return ans;
3062     for (index_type y = 0; y < self->lines; y++) {
3063         linebuf_init_line(self->linebuf, y);
3064         for (index_type x = 0; x < self->columns; x++) {
3065             GPUCell *gpu_cell = self->linebuf->line->gpu_cells + x;
3066             unsigned int mark = (gpu_cell->attrs >> MARK_SHIFT) & MARK_MASK;
3067             if (mark) {
3068                 PyObject *t = Py_BuildValue("III", x, y, mark);
3069                 if (!t) { Py_DECREF(ans); return NULL; }
3070                 if (PyList_Append(ans, t) != 0) { Py_DECREF(t); Py_DECREF(ans); return NULL; }
3071                 Py_DECREF(t);
3072             }
3073         }
3074     }
3075     return ans;
3076 }
3077 
3078 static PyObject*
paste(Screen * self,PyObject * bytes)3079 paste(Screen *self, PyObject *bytes) {
3080     if (!PyBytes_Check(bytes)) { PyErr_SetString(PyExc_TypeError, "Must paste() bytes"); return NULL; }
3081     if (self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, CSI, BRACKETED_PASTE_START);
3082     write_to_child(self, PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
3083     if (self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, CSI, BRACKETED_PASTE_END);
3084     Py_RETURN_NONE;
3085 }
3086 
3087 static PyObject*
paste_bytes(Screen * self,PyObject * bytes)3088 paste_bytes(Screen *self, PyObject *bytes) {
3089     if (!PyBytes_Check(bytes)) { PyErr_SetString(PyExc_TypeError, "Must paste() bytes"); return NULL; }
3090     write_to_child(self, PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
3091     Py_RETURN_NONE;
3092 }
3093 
3094 static PyObject*
focus_changed(Screen * self,PyObject * has_focus_)3095 focus_changed(Screen *self, PyObject *has_focus_) {
3096     bool previous = self->has_focus;
3097     bool has_focus = PyObject_IsTrue(has_focus_) ? true : false;
3098     if (has_focus != previous) {
3099         self->has_focus = has_focus;
3100         if (has_focus) self->has_activity_since_last_focus = false;
3101         if (self->modes.mFOCUS_TRACKING) write_escape_code_to_child(self, CSI, has_focus ? "I" : "O");
3102         Py_RETURN_TRUE;
3103     }
3104     Py_RETURN_FALSE;
3105 }
3106 
3107 static PyObject*
has_focus(Screen * self,PyObject * args UNUSED)3108 has_focus(Screen *self, PyObject *args UNUSED) {
3109     if (self->has_focus) Py_RETURN_TRUE;
3110     Py_RETURN_FALSE;
3111 }
3112 
3113 static PyObject*
has_activity_since_last_focus(Screen * self,PyObject * args UNUSED)3114 has_activity_since_last_focus(Screen *self, PyObject *args UNUSED) {
3115     if (self->has_activity_since_last_focus) Py_RETURN_TRUE;
3116     Py_RETURN_FALSE;
3117 }
3118 
3119 WRAP2(cursor_position, 1, 1)
3120 
3121 #define COUNT_WRAP(name) WRAP1(name, 1)
COUNT_WRAP(insert_lines)3122 COUNT_WRAP(insert_lines)
3123 COUNT_WRAP(delete_lines)
3124 COUNT_WRAP(insert_characters)
3125 COUNT_WRAP(delete_characters)
3126 COUNT_WRAP(erase_characters)
3127 COUNT_WRAP(cursor_up1)
3128 COUNT_WRAP(cursor_down)
3129 COUNT_WRAP(cursor_down1)
3130 COUNT_WRAP(cursor_forward)
3131 
3132 static PyObject*
3133 screen_is_emoji_presentation_base(PyObject UNUSED *self, PyObject *code_) {
3134     unsigned long code = PyLong_AsUnsignedLong(code_);
3135     if (is_emoji_presentation_base(code)) Py_RETURN_TRUE;
3136     Py_RETURN_FALSE;
3137 }
3138 
3139 static PyObject*
hyperlink_at(Screen * self,PyObject * args)3140 hyperlink_at(Screen *self, PyObject *args) {
3141     unsigned int x, y;
3142     if (!PyArg_ParseTuple(args, "II", &x, &y)) return NULL;
3143     screen_mark_hyperlink(self, x, y);
3144     if (!self->url_ranges.count) Py_RETURN_NONE;
3145     hyperlink_id_type hid = hyperlink_id_for_range(self, self->url_ranges.items);
3146     if (!hid) Py_RETURN_NONE;
3147     const char *url = get_hyperlink_for_id(self->hyperlink_pool, hid, true);
3148     return Py_BuildValue("s", url);
3149 }
3150 
3151 static PyObject*
reverse_scroll(Screen * self,PyObject * args)3152 reverse_scroll(Screen *self, PyObject *args) {
3153     int fill_from_scrollback = 0;
3154     unsigned int amt;
3155     if (!PyArg_ParseTuple(args, "I|p", &amt, &fill_from_scrollback)) return NULL;
3156     _reverse_scroll(self, amt, fill_from_scrollback);
3157     Py_RETURN_NONE;
3158 }
3159 
3160 #define MND(name, args) {#name, (PyCFunction)name, args, #name},
3161 #define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O)
3162 
3163 static PyMethodDef methods[] = {
3164     MND(line, METH_O)
3165     MND(visual_line, METH_VARARGS)
3166     MND(current_url_text, METH_NOARGS)
3167     MND(draw, METH_O)
3168     MND(apply_sgr, METH_O)
3169     MND(cursor_position, METH_VARARGS)
3170     MND(set_mode, METH_VARARGS)
3171     MND(reset_mode, METH_VARARGS)
3172     MND(reset, METH_NOARGS)
3173     MND(reset_dirty, METH_NOARGS)
3174     MND(is_using_alternate_linebuf, METH_NOARGS)
3175     MND(is_main_linebuf, METH_NOARGS)
3176     MND(cursor_back, METH_VARARGS)
3177     MND(erase_in_line, METH_VARARGS)
3178     MND(erase_in_display, METH_VARARGS)
3179     MND(scroll_until_cursor, METH_NOARGS)
3180     MND(hyperlinks_as_list, METH_NOARGS)
3181     MND(garbage_collect_hyperlink_pool, METH_NOARGS)
3182     MND(hyperlink_for_id, METH_O)
3183     MND(reverse_scroll, METH_VARARGS)
3184     METHOD(current_char_width, METH_NOARGS)
3185     MND(insert_lines, METH_VARARGS)
3186     MND(delete_lines, METH_VARARGS)
3187     MND(insert_characters, METH_VARARGS)
3188     MND(delete_characters, METH_VARARGS)
3189     MND(erase_characters, METH_VARARGS)
3190     MND(cursor_up, METH_VARARGS)
3191     MND(cursor_up1, METH_VARARGS)
3192     MND(cursor_down, METH_VARARGS)
3193     MND(cursor_down1, METH_VARARGS)
3194     MND(cursor_forward, METH_VARARGS)
3195     {"index", (PyCFunction)xxx_index, METH_VARARGS, ""},
3196     {"has_selection", (PyCFunction)xxx_has_selection, METH_VARARGS, ""},
3197     MND(set_pending_timeout, METH_O)
3198     MND(as_text, METH_VARARGS)
3199     MND(as_text_non_visual, METH_VARARGS)
3200     MND(as_text_alternate, METH_VARARGS)
3201     MND(tab, METH_NOARGS)
3202     MND(backspace, METH_NOARGS)
3203     MND(linefeed, METH_NOARGS)
3204     MND(carriage_return, METH_NOARGS)
3205     MND(set_tab_stop, METH_NOARGS)
3206     MND(clear_tab_stop, METH_VARARGS)
3207     MND(start_selection, METH_VARARGS)
3208     MND(update_selection, METH_VARARGS)
3209     {"clear_selection", (PyCFunction)clear_selection_, METH_NOARGS, ""},
3210     MND(reverse_index, METH_NOARGS)
3211     MND(mark_as_dirty, METH_NOARGS)
3212     MND(resize, METH_VARARGS)
3213     MND(set_margins, METH_VARARGS)
3214     MND(detect_url, METH_VARARGS)
3215     MND(rescale_images, METH_NOARGS)
3216     MND(current_key_encoding_flags, METH_NOARGS)
3217     MND(text_for_selection, METH_NOARGS)
3218     MND(text_for_marked_url, METH_NOARGS)
3219     MND(is_rectangle_select, METH_NOARGS)
3220     MND(scroll, METH_VARARGS)
3221     MND(send_escape_code_to_child, METH_VARARGS)
3222     MND(hyperlink_at, METH_VARARGS)
3223     MND(toggle_alt_screen, METH_NOARGS)
3224     MND(reset_callbacks, METH_NOARGS)
3225     MND(paste, METH_O)
3226     MND(paste_bytes, METH_O)
3227     MND(focus_changed, METH_O)
3228     MND(has_focus, METH_NOARGS)
3229     MND(has_activity_since_last_focus, METH_NOARGS)
3230     MND(copy_colors_from, METH_O)
3231     MND(set_marker, METH_VARARGS)
3232     MND(marked_cells, METH_NOARGS)
3233     MND(scroll_to_next_mark, METH_VARARGS)
3234     {"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""},
3235 
3236     {NULL}  /* Sentinel */
3237 };
3238 
3239 static PyGetSetDef getsetters[] = {
3240     GETSET(in_bracketed_paste_mode)
3241     GETSET(auto_repeat_enabled)
3242     GETSET(focus_tracking_enabled)
3243     GETSET(cursor_visible)
3244     GETSET(cursor_key_mode)
3245     GETSET(disable_ligatures)
3246     {NULL}  /* Sentinel */
3247 };
3248 
3249 #if UINT_MAX == UINT32_MAX
3250 #define T_COL T_UINT
3251 #elif ULONG_MAX == UINT32_MAX
3252 #define T_COL T_ULONG
3253 #else
3254 #error Neither int nor long is 4-bytes in size
3255 #endif
3256 
3257 static PyMemberDef members[] = {
3258     {"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"},
3259     {"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"},
3260     {"grman", T_OBJECT_EX, offsetof(Screen, grman), READONLY, "grman"},
3261     {"color_profile", T_OBJECT_EX, offsetof(Screen, color_profile), READONLY, "color_profile"},
3262     {"linebuf", T_OBJECT_EX, offsetof(Screen, linebuf), READONLY, "linebuf"},
3263     {"main_linebuf", T_OBJECT_EX, offsetof(Screen, main_linebuf), READONLY, "main_linebuf"},
3264     {"historybuf", T_OBJECT_EX, offsetof(Screen, historybuf), READONLY, "historybuf"},
3265     {"scrolled_by", T_UINT, offsetof(Screen, scrolled_by), READONLY, "scrolled_by"},
3266     {"lines", T_UINT, offsetof(Screen, lines), READONLY, "lines"},
3267     {"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"},
3268     {"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"},
3269     {"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"},
3270     {"history_line_added_count", T_UINT, offsetof(Screen, history_line_added_count), 0, "history_line_added_count"},
3271     {NULL}
3272 };
3273 
3274 PyTypeObject Screen_Type = {
3275     PyVarObject_HEAD_INIT(NULL, 0)
3276     .tp_name = "fast_data_types.Screen",
3277     .tp_basicsize = sizeof(Screen),
3278     .tp_dealloc = (destructor)dealloc,
3279     .tp_flags = Py_TPFLAGS_DEFAULT,
3280     .tp_doc = "Screen",
3281     .tp_methods = methods,
3282     .tp_members = members,
3283     .tp_new = new,
3284     .tp_getset = getsetters,
3285 };
3286 
3287 static PyMethodDef module_methods[] = {
3288     {"is_emoji_presentation_base", (PyCFunction)screen_is_emoji_presentation_base, METH_O, ""},
3289     {"truncate_point_for_length", (PyCFunction)screen_truncate_point_for_length, METH_VARARGS, ""},
3290     {NULL}  /* Sentinel */
3291 };
3292 
3293 INIT_TYPE(Screen)
3294 // }}}
3295