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