Allow multiple digit command multiplier
[dvtm.git] / vt.c
blobbde215f4113a4a3e55a4fec4aaa4e745302f8358
1 /*
2 * Copyright © 2004 Bruno T. C. de Oliveira
3 * Copyright © 2006 Pierre Habouzit
4 * Copyright © 2008-2012 Marc Andre Tanner
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #define _GNU_SOURCE
20 #include <ctype.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <langinfo.h>
24 #include <limits.h>
25 #include <signal.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/ioctl.h>
30 #include <sys/types.h>
31 #include <termios.h>
32 #include <wchar.h>
33 #if defined(__linux__) || defined(__CYGWIN__)
34 # include <pty.h>
35 #elif defined(__FreeBSD__)
36 # include <libutil.h>
37 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
38 # include <util.h>
39 #endif
40 #if defined(__CYGWIN__) || defined(_AIX)
41 # include <alloca.h>
42 #endif
44 #include "vt.h"
46 #ifdef _AIX
47 # include "forkpty-aix.c"
48 #endif
50 #ifndef NCURSES_ATTR_SHIFT
51 # define NCURSES_ATTR_SHIFT 8
52 #endif
54 #ifndef NCURSES_ACS
55 # ifdef PDCURSES
56 # define NCURSES_ACS(c) (acs_map[(unsigned char)(c)])
57 # else /* BSD curses */
58 # define NCURSES_ACS(c) (_acs_map[(unsigned char)(c)])
59 # endif
60 #endif
62 #ifdef NCURSES_VERSION
63 # ifndef NCURSES_EXT_COLORS
64 # define NCURSES_EXT_COLORS 0
65 # endif
66 # if !NCURSES_EXT_COLORS
67 # define MAX_COLOR_PAIRS 256
68 # endif
69 #endif
70 #ifndef MAX_COLOR_PAIRS
71 # define MAX_COLOR_PAIRS COLOR_PAIRS
72 #endif
74 #define IS_CONTROL(ch) !((ch) & 0xffffff60UL)
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define sstrlen(str) (sizeof(str) - 1)
78 #define COPYMODE_ATTR A_REVERSE
79 static bool is_utf8, has_default_colors;
80 static short color_pairs_reserved, color_pairs_max, color_pair_current;
81 static short *color2palette, default_fg, default_bg;
83 enum {
84 C0_NUL = 0x00,
85 C0_SOH, C0_STX, C0_ETX, C0_EOT, C0_ENQ, C0_ACK, C0_BEL,
86 C0_BS, C0_HT, C0_LF, C0_VT, C0_FF, C0_CR, C0_SO, C0_SI,
87 C0_DLE, C0_DC1, C0_DC2, D0_DC3, C0_DC4, C0_NAK, C0_SYN, C0_ETB,
88 C0_CAN, C0_EM, C0_SUB, C0_ESC, C0_IS4, C0_IS3, C0_IS2, C0_IS1,
91 enum {
92 C1_40 = 0x40,
93 C1_41, C1_BPH, C1_NBH, C1_44, C1_NEL, C1_SSA, C1_ESA,
94 C1_HTS, C1_HTJ, C1_VTS, C1_PLD, C1_PLU, C1_RI, C1_SS2, C1_SS3,
95 C1_DCS, C1_PU1, C1_PU2, C1_STS, C1_CCH, C1_MW, C1_SPA, C1_EPA,
96 C1_SOS, C1_59, C1_SCI, C1_CSI, CS_ST, C1_OSC, C1_PM, C1_APC,
99 enum {
100 CSI_ICH = 0x40,
101 CSI_CUU, CSI_CUD, CSI_CUF, CSI_CUB, CSI_CNL, CSI_CPL, CSI_CHA,
102 CSI_CUP, CSI_CHT, CSI_ED, CSI_EL, CSI_IL, CSI_DL, CSI_EF, CSI_EA,
103 CSI_DCH, CSI_SEE, CSI_CPR, CSI_SU, CSI_SD, CSI_NP, CSI_PP, CSI_CTC,
104 CSI_ECH, CSI_CVT, CSI_CBT, CSI_SRS, CSI_PTX, CSI_SDS, CSI_SIMD, CSI_5F,
105 CSI_HPA, CSI_HPR, CSI_REP, CSI_DA, CSI_VPA, CSI_VPR, CSI_HVP, CSI_TBC,
106 CSI_SM, CSI_MC, CSI_HPB, CSI_VPB, CSI_RM, CSI_SGR, CSI_DSR, CSI_DAQ,
107 CSI_70, CSI_71, CSI_72, CSI_73, CSI_74, CSI_75, CSI_76, CSI_77,
108 CSI_78, CSI_79, CSI_7A, CSI_7B, CSI_7C, CSI_7D, CSI_7E, CSI_7F
111 typedef struct {
112 wchar_t text;
113 uint16_t attr;
114 short fg;
115 short bg;
116 } Cell;
118 typedef struct {
119 Cell *cells;
120 unsigned dirty:1;
121 } Row;
123 typedef struct {
124 Row *lines;
125 Row *curs_row;
126 Row *scroll_buf;
127 Row *scroll_top;
128 Row *scroll_bot;
129 int scroll_buf_sz;
130 int scroll_buf_ptr;
131 int scroll_buf_len;
132 int scroll_amount;
133 int rows, cols, maxcols;
134 unsigned curattrs, savattrs;
135 int curs_col, curs_srow, curs_scol;
136 short curfg, curbg, savfg, savbg;
137 } Buffer;
139 struct Vt {
140 Buffer buffer_normal;
141 Buffer buffer_alternate;
142 Buffer *buffer;
143 unsigned defattrs;
144 short deffg, defbg;
145 int pty;
146 pid_t childpid;
148 /* flags */
149 unsigned seen_input:1;
150 unsigned insert:1;
151 unsigned escaped:1;
152 unsigned curshid:1;
153 unsigned curskeymode:1;
154 unsigned copymode:1;
155 unsigned copymode_selecting:1;
156 unsigned bell:1;
157 unsigned relposmode:1;
158 unsigned mousetrack:1;
159 unsigned graphmode:1;
160 bool charsets[2];
161 char copymode_searching;
162 /* copymode */
163 int copymode_curs_srow, copymode_curs_scol;
164 Row *copymode_sel_start_row;
165 int copymode_sel_start_col;
166 wchar_t *searchbuf;
167 mbstate_t searchbuf_ps;
168 int searchbuf_curs, searchbuf_size;
169 int copymode_cmd_multiplier;
170 /* buffers and parsing state */
171 mbstate_t ps;
172 char rbuf[BUFSIZ];
173 char ebuf[BUFSIZ];
174 unsigned int rlen, elen;
176 /* xterm style window title */
177 char title[256];
179 vt_event_handler_t event_handler;
181 /* custom escape sequence handler */
182 vt_escseq_handler_t escseq_handler;
183 void *data;
186 static char const * const keytable[KEY_MAX+1] = {
187 ['\n'] = "\r",
188 /* for the arrow keys the CSI / SS3 sequences are not stored here
189 * because they depend on the current cursor terminal mode
191 [KEY_UP] = "A",
192 [KEY_DOWN] = "B",
193 [KEY_RIGHT] = "C",
194 [KEY_LEFT] = "D",
195 #ifdef KEY_SUP
196 [KEY_SUP] = "\e[1;2A",
197 #endif
198 #ifdef KEY_SDOWN
199 [KEY_SDOWN] = "\e[1;2B",
200 #endif
201 [KEY_SRIGHT] = "\e[1;2C",
202 [KEY_SLEFT] = "\e[1;2D",
203 [KEY_BACKSPACE] = "\177",
204 [KEY_IC] = "\e[2~",
205 [KEY_DC] = "\e[3~",
206 [KEY_PPAGE] = "\e[5~",
207 [KEY_NPAGE] = "\e[6~",
208 [KEY_HOME] = "\e[7~",
209 [KEY_END] = "\e[8~",
210 [KEY_SUSPEND] = "\x1A", /* Ctrl+Z gets mapped to this */
211 [KEY_F(1)] = "\e[11~",
212 [KEY_F(2)] = "\e[12~",
213 [KEY_F(3)] = "\e[13~",
214 [KEY_F(4)] = "\e[14~",
215 [KEY_F(5)] = "\e[15~",
216 [KEY_F(6)] = "\e[17~",
217 [KEY_F(7)] = "\e[18~",
218 [KEY_F(8)] = "\e[19~",
219 [KEY_F(9)] = "\e[20~",
220 [KEY_F(10)] = "\e[21~",
221 [KEY_F(11)] = "\e[23~",
222 [KEY_F(12)] = "\e[24~",
223 [KEY_F(13)] = "\e[25~",
224 [KEY_F(14)] = "\e[26~",
225 [KEY_F(15)] = "\e[28~",
226 [KEY_F(16)] = "\e[29~",
227 [KEY_F(17)] = "\e[31~",
228 [KEY_F(18)] = "\e[32~",
229 [KEY_F(19)] = "\e[33~",
230 [KEY_F(20)] = "\e[34~",
231 [KEY_RESIZE] = "",
234 static void process_nonprinting(Vt *t, wchar_t wc);
235 static void send_curs(Vt *t);
237 __attribute__ ((const))
238 static uint16_t build_attrs(unsigned curattrs)
240 return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff))
241 >> NCURSES_ATTR_SHIFT;
244 static void row_set(Row *row, int start, int len, Buffer *t)
246 Cell cell = {
247 .text = L'\0',
248 .attr = t ? build_attrs(t->curattrs) : 0,
249 .fg = t ? t->curfg : -1,
250 .bg = t ? t->curbg : -1,
253 for (int i = start; i < len + start; i++)
254 row->cells[i] = cell;
255 row->dirty = true;
258 static void row_roll(Row *start, Row *end, int count)
260 int n = end - start;
262 count %= n;
263 if (count < 0)
264 count += n;
266 if (count) {
267 Row *buf = alloca(count * sizeof(Row));
269 memcpy(buf, start, count * sizeof(Row));
270 memmove(start, start + count, (n - count) * sizeof(Row));
271 memcpy(end - count, buf, count * sizeof(Row));
272 for (Row *row = start; row < end; row++)
273 row->dirty = true;
277 static void clamp_cursor_to_bounds(Vt *vt)
279 Buffer *t = vt->buffer;
280 Row *lines = vt->relposmode ? t->scroll_top : t->lines;
281 int rows = vt->relposmode ? t->scroll_bot - t->scroll_top : t->rows;
283 if (t->curs_row < lines)
284 t->curs_row = lines;
285 if (t->curs_row >= lines + rows)
286 t->curs_row = lines + rows - 1;
287 if (t->curs_col < 0)
288 t->curs_col = 0;
289 if (t->curs_col >= t->cols)
290 t->curs_col = t->cols - 1;
293 static void save_curs(Vt *vt)
295 Buffer *t = vt->buffer;
296 t->curs_srow = t->curs_row - t->lines;
297 t->curs_scol = t->curs_col;
300 static void restore_curs(Vt *vt)
302 Buffer *t = vt->buffer;
303 t->curs_row = t->lines + t->curs_srow;
304 t->curs_col = t->curs_scol;
305 clamp_cursor_to_bounds(vt);
308 static void save_attrs(Vt *vt)
310 Buffer *t = vt->buffer;
311 t->savattrs = t->curattrs;
312 t->savfg = t->curfg;
313 t->savbg = t->curbg;
316 static void restore_attrs(Vt *vt)
318 Buffer *t = vt->buffer;
319 t->curattrs = t->savattrs;
320 t->curfg = t->savfg;
321 t->curbg = t->savbg;
324 static void fill_scroll_buf(Buffer *t, int s)
326 /* work in screenfuls */
327 int ssz = t->scroll_bot - t->scroll_top;
328 if (s > ssz) {
329 fill_scroll_buf(t, ssz);
330 fill_scroll_buf(t, s - ssz);
331 return;
333 if (s < -ssz) {
334 fill_scroll_buf(t, -ssz);
335 fill_scroll_buf(t, s + ssz);
336 return;
339 t->scroll_buf_len += s;
340 if (t->scroll_buf_len >= t->scroll_buf_sz)
341 t->scroll_buf_len = t->scroll_buf_sz;
343 if (s > 0 && t->scroll_buf_sz) {
344 for (int i = 0; i < s; i++) {
345 Row tmp = t->scroll_top[i];
346 t->scroll_top[i] = t->scroll_buf[t->scroll_buf_ptr];
347 t->scroll_buf[t->scroll_buf_ptr] = tmp;
349 t->scroll_buf_ptr++;
350 if (t->scroll_buf_ptr == t->scroll_buf_sz)
351 t->scroll_buf_ptr = 0;
354 row_roll(t->scroll_top, t->scroll_bot, s);
355 if (s < 0 && t->scroll_buf_sz) {
356 for (int i = (-s) - 1; i >= 0; i--) {
357 t->scroll_buf_ptr--;
358 if (t->scroll_buf_ptr == -1)
359 t->scroll_buf_ptr = t->scroll_buf_sz - 1;
361 Row tmp = t->scroll_top[i];
362 t->scroll_top[i] = t->scroll_buf[t->scroll_buf_ptr];
363 t->scroll_buf[t->scroll_buf_ptr] = tmp;
364 t->scroll_top[i].dirty = true;
369 static void cursor_line_down(Vt *vt)
371 Buffer *t = vt->buffer;
372 row_set(t->curs_row, t->cols, t->maxcols - t->cols, NULL);
373 t->curs_row++;
374 if (t->curs_row < t->scroll_bot)
375 return;
377 vt_noscroll(vt);
379 t->curs_row = t->scroll_bot - 1;
380 fill_scroll_buf(t, 1);
381 row_set(t->curs_row, 0, t->cols, t);
384 static void new_escape_sequence(Vt *t)
386 t->escaped = true;
387 t->elen = 0;
388 t->ebuf[0] = '\0';
391 static void cancel_escape_sequence(Vt *t)
393 t->escaped = false;
394 t->elen = 0;
395 t->ebuf[0] = '\0';
398 static bool is_valid_csi_ender(int c)
400 return (c >= 'a' && c <= 'z')
401 || (c >= 'A' && c <= 'Z')
402 || (c == '@' || c == '`');
405 /* interprets a 'set attribute' (SGR) CSI escape sequence */
406 static void interpret_csi_sgr(Vt *vt, int param[], int pcount)
408 Buffer *t = vt->buffer;
409 if (pcount == 0) {
410 /* special case: reset attributes */
411 t->curattrs = A_NORMAL;
412 t->curfg = t->curbg = -1;
413 return;
416 for (int i = 0; i < pcount; i++) {
417 switch (param[i]) {
418 case 0:
419 t->curattrs = A_NORMAL;
420 t->curfg = t->curbg = -1;
421 break;
422 case 1:
423 t->curattrs |= A_BOLD;
424 break;
425 case 4:
426 t->curattrs |= A_UNDERLINE;
427 break;
428 case 5:
429 t->curattrs |= A_BLINK;
430 break;
431 case 7:
432 t->curattrs |= A_REVERSE;
433 break;
434 case 8:
435 t->curattrs |= A_INVIS;
436 break;
437 case 22:
438 t->curattrs &= ~A_BOLD;
439 break;
440 case 24:
441 t->curattrs &= ~A_UNDERLINE;
442 break;
443 case 25:
444 t->curattrs &= ~A_BLINK;
445 break;
446 case 27:
447 t->curattrs &= ~A_REVERSE;
448 break;
449 case 28:
450 t->curattrs &= ~A_INVIS;
451 break;
452 case 30 ... 37: /* fg */
453 t->curfg = param[i] - 30;
454 break;
455 case 38:
456 if ((i + 2) < pcount && param[i + 1] == 5) {
457 t->curfg = param[i + 2];
458 i += 2;
460 break;
461 case 39:
462 t->curfg = -1;
463 break;
464 case 40 ... 47: /* bg */
465 t->curbg = param[i] - 40;
466 break;
467 case 48:
468 if ((i + 2) < pcount && param[i + 1] == 5) {
469 t->curbg = param[i + 2];
470 i += 2;
472 break;
473 case 49:
474 t->curbg = -1;
475 break;
476 case 90 ... 97: /* hi fg */
477 t->curfg = param[i] - 82;
478 break;
479 case 100 ... 107: /* hi bg */
480 t->curbg = param[i] - 92;
481 break;
482 default:
483 break;
488 /* interprets an 'erase display' (ED) escape sequence */
489 static void interpret_csi_ed(Vt *vt, int param[], int pcount)
491 Row *row, *start, *end;
492 Buffer *t = vt->buffer;
494 save_attrs(vt);
495 t->curattrs = A_NORMAL;
496 t->curfg = t->curbg = -1;
498 if (pcount && param[0] == 2) {
499 start = t->lines;
500 end = t->lines + t->rows;
501 } else if (pcount && param[0] == 1) {
502 start = t->lines;
503 end = t->curs_row;
504 row_set(t->curs_row, 0, t->curs_col + 1, t);
505 } else {
506 row_set(t->curs_row, t->curs_col, t->cols - t->curs_col, t);
507 start = t->curs_row + 1;
508 end = t->lines + t->rows;
511 for (row = start; row < end; row++)
512 row_set(row, 0, t->cols, t);
514 restore_attrs(vt);
517 /* interprets a 'move cursor' (CUP) escape sequence */
518 static void interpret_csi_cup(Vt *vt, int param[], int pcount)
520 Buffer *t = vt->buffer;
521 Row *lines = vt->relposmode ? t->scroll_top : t->lines;
523 if (pcount == 0) {
524 t->curs_row = lines;
525 t->curs_col = 0;
526 } else if (pcount == 1) {
527 t->curs_row = lines + param[0] - 1;
528 t->curs_col = 0;
529 } else {
530 t->curs_row = lines + param[0] - 1;
531 t->curs_col = param[1] - 1;
534 clamp_cursor_to_bounds(vt);
537 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
538 * CPL, CHA, HPR, VPA, VPR, HPA */
539 static void interpret_csi_c(Vt *vt, char verb, int param[], int pcount)
541 Buffer *t = vt->buffer;
542 int n = (pcount && param[0] > 0) ? param[0] : 1;
544 switch (verb) {
545 case 'A':
546 t->curs_row -= n;
547 break;
548 case 'B':
549 case 'e':
550 t->curs_row += n;
551 break;
552 case 'C':
553 case 'a':
554 t->curs_col += n;
555 break;
556 case 'D':
557 t->curs_col -= n;
558 break;
559 case 'E':
560 t->curs_row += n;
561 t->curs_col = 0;
562 break;
563 case 'F':
564 t->curs_row -= n;
565 t->curs_col = 0;
566 break;
567 case 'G':
568 case '`':
569 t->curs_col = param[0] - 1;
570 break;
571 case 'd':
572 t->curs_row = t->lines + param[0] - 1;
573 break;
576 clamp_cursor_to_bounds(vt);
579 /* Interpret the 'erase line' escape sequence */
580 static void interpret_csi_el(Vt *vt, int param[], int pcount)
582 Buffer *t = vt->buffer;
583 switch (pcount ? param[0] : 0) {
584 case 1:
585 row_set(t->curs_row, 0, t->curs_col + 1, t);
586 break;
587 case 2:
588 row_set(t->curs_row, 0, t->cols, t);
589 break;
590 default:
591 row_set(t->curs_row, t->curs_col, t->cols - t->curs_col, t);
592 break;
596 /* Interpret the 'insert blanks' sequence (ICH) */
597 static void interpret_csi_ich(Vt *vt, int param[], int pcount)
599 Buffer *t = vt->buffer;
600 Row *row = t->curs_row;
601 int n = (pcount && param[0] > 0) ? param[0] : 1;
603 if (t->curs_col + n > t->cols)
604 n = t->cols - t->curs_col;
606 for (int i = t->cols - 1; i >= t->curs_col + n; i--)
607 row->cells[i] = row->cells[i - n];
609 row_set(row, t->curs_col, n, t);
612 /* Interpret the 'delete chars' sequence (DCH) */
613 static void interpret_csi_dch(Vt *vt, int param[], int pcount)
615 Buffer *t = vt->buffer;
616 Row *row = t->curs_row;
617 int n = (pcount && param[0] > 0) ? param[0] : 1;
619 if (t->curs_col + n > t->cols)
620 n = t->cols - t->curs_col;
622 for (int i = t->curs_col; i < t->cols - n; i++)
623 row->cells[i] = row->cells[i + n];
625 row_set(row, t->cols - n, n, t);
628 /* Interpret an 'insert line' sequence (IL) */
629 static void interpret_csi_il(Vt *vt, int param[], int pcount)
631 Buffer *t = vt->buffer;
632 int n = (pcount && param[0] > 0) ? param[0] : 1;
634 if (t->curs_row + n >= t->scroll_bot) {
635 for (Row *row = t->curs_row; row < t->scroll_bot; row++)
636 row_set(row, 0, t->cols, t);
637 } else {
638 row_roll(t->curs_row, t->scroll_bot, -n);
639 for (Row *row = t->curs_row; row < t->curs_row + n; row++)
640 row_set(row, 0, t->cols, t);
644 /* Interpret a 'delete line' sequence (DL) */
645 static void interpret_csi_dl(Vt *vt, int param[], int pcount)
647 Buffer *t = vt->buffer;
648 int n = (pcount && param[0] > 0) ? param[0] : 1;
650 if (t->curs_row + n >= t->scroll_bot) {
651 for (Row *row = t->curs_row; row < t->scroll_bot; row++)
652 row_set(row, 0, t->cols, t);
653 } else {
654 row_roll(t->curs_row, t->scroll_bot, n);
655 for (Row *row = t->scroll_bot - n; row < t->scroll_bot; row++)
656 row_set(row, 0, t->cols, t);
660 /* Interpret an 'erase characters' (ECH) sequence */
661 static void interpret_csi_ech(Vt *vt, int param[], int pcount)
663 Buffer *t = vt->buffer;
664 int n = (pcount && param[0] > 0) ? param[0] : 1;
666 if (t->curs_col + n > t->cols)
667 n = t->cols - t->curs_col;
669 row_set(t->curs_row, t->curs_col, n, t);
672 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
673 static void interpret_csi_decstbm(Vt *vt, int param[], int pcount)
675 Buffer *t = vt->buffer;
676 int new_top, new_bot;
678 switch (pcount) {
679 case 0:
680 t->scroll_top = t->lines;
681 t->scroll_bot = t->lines + t->rows;
682 break;
683 case 2:
684 new_top = param[0] - 1;
685 new_bot = param[1];
687 /* clamp to bounds */
688 if (new_top < 0)
689 new_top = 0;
690 if (new_top >= t->rows)
691 new_top = t->rows - 1;
692 if (new_bot < 0)
693 new_bot = 0;
694 if (new_bot >= t->rows)
695 new_bot = t->rows;
697 /* check for range validity */
698 if (new_top < new_bot) {
699 t->scroll_top = t->lines + new_top;
700 t->scroll_bot = t->lines + new_bot;
702 break;
703 default:
704 return; /* malformed */
708 static void interpret_csi(Vt *t)
710 static int csiparam[BUFSIZ];
711 int param_count = 0;
712 const char *p = t->ebuf + 1;
713 char verb = t->ebuf[t->elen - 1];
715 /* parse numeric parameters */
716 for (p += (t->ebuf[1] == '?'); *p; p++) {
717 if (IS_CONTROL(*p)) {
718 process_nonprinting(t, *p);
719 } else if (*p == ';') {
720 if (param_count >= (int)sizeof(csiparam))
721 return; /* too long! */
722 csiparam[param_count++] = 0;
723 } else if (isdigit((unsigned char)*p)) {
724 if (param_count == 0)
725 csiparam[param_count++] = 0;
726 csiparam[param_count - 1] *= 10;
727 csiparam[param_count - 1] += *p - '0';
731 if (t->ebuf[1] == '?') {
732 if (verb == 'h') { /* DEC Private Mode Set (DECSET) */
733 switch (csiparam[0]) {
734 case 1: /* set ANSI cursor (application) key mode (DECCKM) */
735 t->curskeymode = true;
736 break;
737 case 6: /* set origin to relative (DECOM) */
738 t->relposmode = true;
739 break;
740 case 25: /* make cursor visible (DECCM) */
741 t->curshid = false;
742 break;
743 case 47: /* use alternate screen buffer */
744 t->buffer = &t->buffer_alternate;
745 vt_dirty(t);
746 break;
747 case 1000: /* enable normal mouse tracking */
748 t->mousetrack = true;
749 break;
751 } else if (verb == 'l') { /* DEC Private Mode Reset (DECRST) */
752 switch (csiparam[0]) {
753 case 1: /* reset ANSI cursor (normal) key mode (DECCKM) */
754 t->curskeymode = false;
755 break;
756 case 6: /* set origin to absolute (DECOM) */
757 t->relposmode = false;
758 break;
759 case 25: /* make cursor visible (DECCM) */
760 t->curshid = true;
761 break;
762 case 47: /* use normal screen buffer */
763 t->buffer = &t->buffer_normal;
764 vt_dirty(t);
765 break;
766 case 1000: /* disable normal mouse tracking */
767 t->mousetrack = false;
768 break;
773 /* delegate handling depending on command character (verb) */
774 switch (verb) {
775 case 'h':
776 if (param_count == 1 && csiparam[0] == 4) /* insert mode */
777 t->insert = true;
778 break;
779 case 'l':
780 if (param_count == 1 && csiparam[0] == 4) /* replace mode */
781 t->insert = false;
782 break;
783 case 'm': /* it's a 'set attribute' sequence */
784 interpret_csi_sgr(t, csiparam, param_count);
785 break;
786 case 'J': /* it's an 'erase display' sequence */
787 interpret_csi_ed(t, csiparam, param_count);
788 break;
789 case 'H':
790 case 'f': /* it's a 'move cursor' sequence */
791 interpret_csi_cup(t, csiparam, param_count);
792 break;
793 case 'A':
794 case 'B':
795 case 'C':
796 case 'D':
797 case 'E':
798 case 'F':
799 case 'G':
800 case 'e':
801 case 'a':
802 case 'd':
803 case '`':
804 /* it is a 'relative move' */
805 interpret_csi_c(t, verb, csiparam, param_count);
806 break;
807 case 'K': /* erase line */
808 interpret_csi_el(t, csiparam, param_count);
809 break;
810 case '@': /* insert characters */
811 interpret_csi_ich(t, csiparam, param_count);
812 break;
813 case 'P': /* delete characters */
814 interpret_csi_dch(t, csiparam, param_count);
815 break;
816 case 'L': /* insert lines */
817 interpret_csi_il(t, csiparam, param_count);
818 break;
819 case 'M': /* delete lines */
820 interpret_csi_dl(t, csiparam, param_count);
821 break;
822 case 'X': /* erase chars */
823 interpret_csi_ech(t, csiparam, param_count);
824 break;
825 case 'r': /* set scrolling region */
826 interpret_csi_decstbm(t, csiparam, param_count);
827 break;
828 case 's': /* save cursor location */
829 save_curs(t);
830 break;
831 case 'u': /* restore cursor location */
832 restore_curs(t);
833 break;
834 case 'n': /* query cursor location */
835 if (param_count == 1 && csiparam[0] == 6)
836 send_curs(t);
837 break;
838 default:
839 break;
843 /* Interpret an 'index' (IND) sequence */
844 static void interpret_csi_ind(Vt *vt)
846 Buffer *t = vt->buffer;
847 if (t->curs_row < t->lines + t->rows - 1)
848 t->curs_row++;
851 /* Interpret a 'reverse index' (RI) sequence */
852 static void interpret_csi_ri(Vt *vt)
854 Buffer *t = vt->buffer;
855 if (t->curs_row > t->lines)
856 t->curs_row--;
857 else {
858 row_roll(t->scroll_top, t->scroll_bot, -1);
859 row_set(t->scroll_top, 0, t->cols, t);
863 /* Interpret a 'next line' (NEL) sequence */
864 static void interpret_csi_nel(Vt *vt)
866 Buffer *t = vt->buffer;
867 if (t->curs_row < t->lines + t->rows - 1) {
868 t->curs_row++;
869 t->curs_col = 0;
873 /* Interpret a 'select character set' (SCS) sequence */
874 static void interpret_csi_scs(Vt *t)
876 /* ESC ( sets G0, ESC ) sets G1 */
877 t->charsets[!!(t->ebuf[0] == ')')] = (t->ebuf[1] == '0');
878 t->graphmode = t->charsets[0];
881 /* Interpret xterm specific escape sequences */
882 static void interpret_esc_xterm(Vt *t)
884 /* ESC]n;dataBEL -- the ESC is not part of t->ebuf */
885 char *title = NULL;
887 switch (t->ebuf[1]) {
888 case '0':
889 case '2':
890 t->ebuf[t->elen - 1] = '\0';
891 if (t->elen > sstrlen("]n;\a"))
892 title = t->ebuf + sstrlen("]n;");
894 if (t->event_handler)
895 t->event_handler(t, VT_EVENT_TITLE, title);
899 static void try_interpret_escape_seq(Vt *t)
901 char lastchar = t->ebuf[t->elen - 1];
903 if (!*t->ebuf)
904 return;
906 if (t->escseq_handler) {
907 switch ((*(t->escseq_handler)) (t, t->ebuf)) {
908 case VT_ESCSEQ_HANDLER_OK:
909 cancel_escape_sequence(t);
910 return;
911 case VT_ESCSEQ_HANDLER_NOTYET:
912 if (t->elen + 1 >= sizeof(t->ebuf))
913 goto cancel;
914 return;
918 switch (*t->ebuf) {
919 case '#': /* ignore DECDHL, DECSWL, DECDWL, DECHCP, DECALN, DECFPP */
920 if (t->elen == 2)
921 goto cancel;
922 break;
923 case '(':
924 case ')':
925 if (t->elen == 2) {
926 interpret_csi_scs(t);
927 goto handled;
929 break;
930 case ']': /* xterm thing */
931 if (lastchar == '\a' ||
932 (lastchar == '\\' && t->elen >= 2 && t->ebuf[t->elen - 2] == '\e')) {
933 interpret_esc_xterm(t);
934 goto handled;
936 break;
937 case '[':
938 if (is_valid_csi_ender(lastchar)) {
939 interpret_csi(t);
940 goto handled;
942 break;
943 case '7': /* DECSC: save cursor and attributes */
944 save_attrs(t);
945 save_curs(t);
946 goto handled;
947 case '8': /* DECRC: restore cursor and attributes */
948 restore_attrs(t);
949 restore_curs(t);
950 goto handled;
951 case 'D': /* IND: index */
952 interpret_csi_ind(t);
953 goto handled;
954 case 'M': /* RI: reverse index */
955 interpret_csi_ri(t);
956 goto handled;
957 case 'E': /* NEL: next line */
958 interpret_csi_nel(t);
959 goto handled;
960 default:
961 goto cancel;
964 if (t->elen + 1 >= sizeof(t->ebuf)) {
965 cancel:
966 #ifndef NDEBUG
967 fprintf(stderr, "cancelled: \\033");
968 for (unsigned int i = 0; i < t->elen; i++) {
969 if (isprint(t->ebuf[i])) {
970 fputc(t->ebuf[i], stderr);
971 } else {
972 fprintf(stderr, "\\%03o", t->ebuf[i]);
975 fputc('\n', stderr);
976 #endif
977 handled:
978 cancel_escape_sequence(t);
982 static void process_nonprinting(Vt *vt, wchar_t wc)
984 Buffer *t = vt->buffer;
985 switch (wc) {
986 case C0_ESC:
987 new_escape_sequence(vt);
988 break;
989 case C0_BEL:
990 if (vt->bell)
991 beep();
992 break;
993 case C0_BS:
994 if (t->curs_col > 0)
995 t->curs_col--;
996 break;
997 case C0_HT: /* tab */
998 t->curs_col = (t->curs_col + 8) & ~7;
999 if (t->curs_col >= t->cols)
1000 t->curs_col = t->cols - 1;
1001 break;
1002 case C0_CR:
1003 t->curs_col = 0;
1004 break;
1005 case C0_VT:
1006 case C0_FF:
1007 case C0_LF:
1008 cursor_line_down(vt);
1009 break;
1010 case C0_SO: /* shift out, invoke the G1 character set */
1011 vt->graphmode = vt->charsets[1];
1012 break;
1013 case C0_SI: /* shift in, invoke the G0 character set */
1014 vt->graphmode = vt->charsets[0];
1015 break;
1019 static void is_utf8_locale(void)
1021 const char *cset = nl_langinfo(CODESET);
1022 if (!cset)
1023 cset = "ANSI_X3.4-1968";
1024 is_utf8 = !strcmp(cset, "UTF-8");
1027 static wchar_t get_vt100_graphic(char c)
1029 static char vt100_acs[] = "`afgjklmnopqrstuvwxyz{|}~";
1032 * 5f-7e standard vt100
1033 * 40-5e rxvt extension for extra curses acs chars
1035 static uint16_t const vt100_utf8[62] = {
1036 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47
1037 0, 0, 0, 0, 0, 0, 0, 0, // 48-4f
1038 0, 0, 0, 0, 0, 0, 0, 0, // 50-57
1039 0, 0, 0, 0, 0, 0, 0, 0x0020, // 58-5f
1040 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67
1041 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f
1042 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77
1043 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, // 78-7e
1046 if (is_utf8)
1047 return vt100_utf8[c - 0x41];
1048 else if (strchr(vt100_acs, c))
1049 return NCURSES_ACS(c);
1050 return '\0';
1053 static void put_wc(Vt *t, wchar_t wc)
1055 int width = 0;
1057 if (!t->seen_input) {
1058 t->seen_input = 1;
1059 kill(-t->childpid, SIGWINCH);
1062 if (t->escaped) {
1063 if (t->elen + 1 < sizeof(t->ebuf)) {
1064 t->ebuf[t->elen] = wc;
1065 t->ebuf[++t->elen] = '\0';
1066 try_interpret_escape_seq(t);
1067 } else {
1068 cancel_escape_sequence(t);
1070 } else if (IS_CONTROL(wc)) {
1071 process_nonprinting(t, wc);
1072 } else {
1073 if (t->graphmode) {
1074 if (wc >= 0x41 && wc <= 0x7e) {
1075 wchar_t gc = get_vt100_graphic(wc);
1076 if (gc)
1077 wc = gc;
1079 width = 1;
1080 } else if ((width = wcwidth(wc)) < 1) {
1081 width = 1;
1083 Buffer *b = t->buffer;
1084 Cell blank_cell = { L'\0', build_attrs(b->curattrs), b->curfg, b->curbg };
1085 if (width == 2 && b->curs_col == b->cols - 1) {
1086 b->curs_row->cells[b->curs_col++] = blank_cell;
1087 b->curs_row->dirty = true;
1090 if (b->curs_col >= b->cols) {
1091 b->curs_col = 0;
1092 cursor_line_down(t);
1095 if (t->insert) {
1096 Cell *src = b->curs_row->cells + b->curs_col;
1097 Cell *dest = src + width;
1098 size_t len = b->cols - b->curs_col - width;
1099 memmove(dest, src, len);
1102 b->curs_row->cells[b->curs_col] = blank_cell;
1103 b->curs_row->cells[b->curs_col++].text = wc;
1104 b->curs_row->dirty = true;
1105 if (width == 2)
1106 b->curs_row->cells[b->curs_col++] = blank_cell;
1110 int vt_process(Vt *t)
1112 int res;
1113 unsigned int pos = 0;
1115 if (t->pty < 0) {
1116 errno = EINVAL;
1117 return -1;
1120 res = read(t->pty, t->rbuf + t->rlen, sizeof(t->rbuf) - t->rlen);
1121 if (res < 0)
1122 return -1;
1124 t->rlen += res;
1125 while (pos < t->rlen) {
1126 wchar_t wc;
1127 ssize_t len;
1129 len = (ssize_t)mbrtowc(&wc, t->rbuf + pos, t->rlen - pos, &t->ps);
1130 if (len == -2) {
1131 t->rlen -= pos;
1132 memmove(t->rbuf, t->rbuf + pos, t->rlen);
1133 return 0;
1136 if (len == -1) {
1137 len = 1;
1138 wc = t->rbuf[pos];
1141 pos += len ? len : 1;
1142 put_wc(t, wc);
1145 t->rlen -= pos;
1146 memmove(t->rbuf, t->rbuf + pos, t->rlen);
1147 return 0;
1150 void vt_set_default_colors(Vt *t, unsigned attrs, short fg, short bg)
1152 t->defattrs = attrs;
1153 t->deffg = fg;
1154 t->defbg = bg;
1157 static void buffer_free(Buffer *t)
1159 for (int i = 0; i < t->rows; i++)
1160 free(t->lines[i].cells);
1161 free(t->lines);
1162 for (int i = 0; i < t->scroll_buf_sz; i++)
1163 free(t->scroll_buf[i].cells);
1164 free(t->scroll_buf);
1167 static bool buffer_init(Buffer *t, int rows, int cols, int scroll_buf_sz)
1169 Row *lines, *scroll_buf;
1170 t->lines = lines = calloc(rows, sizeof(Row));
1171 if (!lines)
1172 return false;
1173 t->curattrs = A_NORMAL; /* white text over black background */
1174 t->curfg = t->curbg = -1;
1175 for (Row *row = lines, *end = lines + rows; row < end; row++) {
1176 row->cells = malloc(cols * sizeof(Cell));
1177 if (!row->cells) {
1178 t->rows = row - lines;
1179 goto fail;
1181 row_set(row, 0, cols, NULL);
1183 t->rows = rows;
1184 if (scroll_buf_sz < 0)
1185 scroll_buf_sz = 0;
1186 t->scroll_buf = scroll_buf = calloc(scroll_buf_sz, sizeof(Row));
1187 if (!scroll_buf)
1188 goto fail;
1189 for (Row *row = scroll_buf, *end = scroll_buf + scroll_buf_sz; row < end; row++) {
1190 row->cells = calloc(cols, sizeof(Cell));
1191 if (!row->cells) {
1192 t->scroll_buf_sz = row - scroll_buf;
1193 goto fail;
1196 t->curs_row = lines;
1197 t->curs_col = 0;
1198 /* initial scrolling area is the whole window */
1199 t->scroll_top = lines;
1200 t->scroll_bot = lines + rows;
1201 t->scroll_buf_sz = scroll_buf_sz;
1202 t->maxcols = t->cols = cols;
1203 return true;
1205 fail:
1206 buffer_free(t);
1207 return false;
1210 Vt *vt_create(int rows, int cols, int scroll_buf_sz)
1212 Vt *t;
1214 if (rows <= 0 || cols <= 0)
1215 return NULL;
1217 t = calloc(1, sizeof(Vt));
1218 if (!t)
1219 return NULL;
1221 t->pty = -1;
1222 t->deffg = t->defbg = -1;
1223 if (!buffer_init(&t->buffer_normal, rows, cols, scroll_buf_sz) ||
1224 !buffer_init(&t->buffer_alternate, rows, cols, 0)) {
1225 free(t);
1226 return NULL;
1228 t->buffer = &t->buffer_normal;
1229 t->copymode_cmd_multiplier = 0;
1230 return t;
1233 static void buffer_resize(Buffer *t, int rows, int cols)
1235 Row *lines = t->lines;
1237 if (t->rows != rows) {
1238 if (t->curs_row > lines + rows) {
1239 /* scroll up instead of simply chopping off bottom */
1240 fill_scroll_buf(t, (t->curs_row - t->lines) - rows + 1);
1242 while (t->rows > rows) {
1243 free(lines[t->rows - 1].cells);
1244 t->rows--;
1247 lines = realloc(lines, sizeof(Row) * rows);
1250 if (t->maxcols < cols) {
1251 for (int row = 0; row < t->rows; row++) {
1252 lines[row].cells = realloc(lines[row].cells, sizeof(Cell) * cols);
1253 if (t->cols < cols)
1254 row_set(lines + row, t->cols, cols - t->cols, NULL);
1255 lines[row].dirty = true;
1257 Row *sbuf = t->scroll_buf;
1258 for (int row = 0; row < t->scroll_buf_sz; row++) {
1259 sbuf[row].cells = realloc(sbuf[row].cells, sizeof(Cell) * cols);
1260 if (t->cols < cols)
1261 row_set(sbuf + row, t->cols, cols - t->cols, NULL);
1263 t->maxcols = cols;
1264 t->cols = cols;
1265 } else if (t->cols != cols) {
1266 for (int row = 0; row < t->rows; row++)
1267 lines[row].dirty = true;
1268 t->cols = cols;
1271 int deltarows = 0;
1272 if (t->rows < rows) {
1273 while (t->rows < rows) {
1274 lines[t->rows].cells = calloc(t->maxcols, sizeof(Cell));
1275 row_set(lines + t->rows, 0, t->maxcols, t);
1276 t->rows++;
1279 /* prepare for backfill */
1280 if (t->curs_row >= t->scroll_bot - 1) {
1281 deltarows = t->lines + rows - t->curs_row - 1;
1282 if (deltarows > t->scroll_buf_len)
1283 deltarows = t->scroll_buf_len;
1287 t->curs_row += lines - t->lines;
1288 t->scroll_top = lines;
1289 t->scroll_bot = lines + rows;
1290 t->lines = lines;
1292 /* perform backfill */
1293 if (deltarows > 0) {
1294 fill_scroll_buf(t, -deltarows);
1295 t->curs_row += deltarows;
1299 void vt_resize(Vt *t, int rows, int cols)
1301 struct winsize ws = { .ws_row = rows, .ws_col = cols };
1303 if (rows <= 0 || cols <= 0)
1304 return;
1306 vt_noscroll(t);
1307 if (t->copymode)
1308 vt_copymode_leave(t);
1309 buffer_resize(&t->buffer_normal, rows, cols);
1310 buffer_resize(&t->buffer_alternate, rows, cols);
1311 clamp_cursor_to_bounds(t);
1312 ioctl(t->pty, TIOCSWINSZ, &ws);
1313 kill(-t->childpid, SIGWINCH);
1316 void vt_destroy(Vt *t)
1318 if (!t)
1319 return;
1320 buffer_free(&t->buffer_normal);
1321 buffer_free(&t->buffer_alternate);
1322 free(t->searchbuf);
1323 free(t);
1326 void vt_dirty(Vt *vt)
1328 Buffer *t = vt->buffer;
1329 for (Row *row = t->lines, *end = row + t->rows; row < end; row++)
1330 row->dirty = true;
1333 static void copymode_get_selection_boundry(Vt *vt, Row **start_row, int *start_col, Row **end_row, int *end_col, bool clip) {
1334 Buffer *t = vt->buffer;
1335 if (vt->copymode_sel_start_row >= t->lines && vt->copymode_sel_start_row < t->lines + t->rows) {
1336 /* within the current page */
1337 if (t->curs_row >= vt->copymode_sel_start_row) {
1338 *start_row = vt->copymode_sel_start_row;
1339 *end_row = t->curs_row;
1340 *start_col = vt->copymode_sel_start_col;
1341 *end_col = t->curs_col;
1342 } else {
1343 *start_row = t->curs_row;
1344 *end_row = vt->copymode_sel_start_row;
1345 *start_col = t->curs_col;
1346 *end_col = vt->copymode_sel_start_col;
1348 if (t->curs_col < *start_col && *start_row == *end_row) {
1349 *start_col = t->curs_col;
1350 *end_col = vt->copymode_sel_start_col;
1352 } else {
1353 /* part of the scrollback buffer is also selected */
1354 if (vt->copymode_sel_start_row < t->lines) {
1355 /* above the current page */
1356 if (clip) {
1357 *start_row = t->lines;
1358 *start_col = 0;
1359 } else {
1360 int copied_lines = t->lines - vt->copymode_sel_start_row;
1361 *start_row = &t->scroll_buf
1362 [(t->scroll_buf_ptr - copied_lines + t->scroll_buf_sz) % t->scroll_buf_sz];
1363 *start_col = vt->copymode_sel_start_col;
1365 *end_row = t->curs_row;
1366 *end_col = t->curs_col;
1367 } else {
1368 /* below the current page */
1369 *start_row = t->curs_row;
1370 *start_col = t->curs_col;
1371 if (clip) {
1372 *end_row = t->lines + t->rows;
1373 *end_col = t->cols - 1;
1374 } else {
1375 int copied_lines = vt->copymode_sel_start_row -(t->lines + t->rows);
1376 *end_row = &t->scroll_buf
1377 [(t->scroll_buf_ptr + copied_lines) % t->scroll_buf_sz];
1378 *end_col = vt->copymode_sel_start_col;
1384 void vt_draw(Vt *vt, WINDOW * win, int srow, int scol)
1386 Buffer *t = vt->buffer;
1387 bool sel = false;
1388 Row *sel_row_start, *sel_row_end;
1389 int sel_col_start, sel_col_end;
1391 copymode_get_selection_boundry(vt, &sel_row_start, &sel_col_start, &sel_row_end, &sel_col_end, true);
1392 curs_set(0);
1394 for (int i = 0; i < t->rows; i++) {
1395 Row *row = t->lines + i;
1397 if (!row->dirty)
1398 continue;
1400 wmove(win, srow + i, scol);
1401 Cell *cell = NULL;
1402 for (int j = 0; j < t->cols; j++) {
1403 Cell *prev_cell = cell;
1404 cell = row->cells + j;
1405 if (!prev_cell || cell->attr != prev_cell->attr
1406 || cell->fg != prev_cell->fg
1407 || cell->bg != prev_cell->bg) {
1408 if (cell->attr == A_NORMAL)
1409 cell->attr = vt->defattrs;
1410 if (cell->fg == -1)
1411 cell->fg = vt->deffg;
1412 if (cell->bg == -1)
1413 cell->bg = vt->defbg;
1414 wattrset(win, (attr_t) cell->attr << NCURSES_ATTR_SHIFT);
1415 wcolor_set(win, vt_color_get(vt, cell->fg, cell->bg), NULL);
1418 if (vt->copymode_selecting && ((row > sel_row_start && row < sel_row_end) ||
1419 (row == sel_row_start && j >= sel_col_start && (row != sel_row_end || j <= sel_col_end)) ||
1420 (row == sel_row_end && j <= sel_col_end && (row != sel_row_start || j >= sel_col_start)))) {
1421 wattrset(win, (attr_t) ((cell->attr << NCURSES_ATTR_SHIFT)|COPYMODE_ATTR));
1422 sel = true;
1423 } else if (sel) {
1424 wattrset(win, (attr_t) cell->attr << NCURSES_ATTR_SHIFT);
1425 wcolor_set(win, vt_color_get(vt, cell->fg, cell->bg), NULL);
1426 sel = false;
1429 if (is_utf8 && cell->text >= 128) {
1430 char buf[MB_CUR_MAX + 1];
1431 int len = wcrtomb(buf, cell->text, NULL);
1432 waddnstr(win, buf, len);
1433 if (wcwidth(cell->text) > 1)
1434 j++;
1435 } else {
1436 waddch(win, cell->text > ' ' ? cell->text : ' ');
1439 row->dirty = false;
1442 wmove(win, srow + t->curs_row - t->lines, scol + t->curs_col);
1444 if (vt->copymode_searching) {
1445 wattrset(win, vt->defattrs << NCURSES_ATTR_SHIFT);
1446 mvwaddch(win, srow + t->rows - 1, 0, vt->copymode_searching == 1 ? '/' : '?');
1447 int len = waddnwstr(win, vt->searchbuf, t->cols - 1);
1448 whline(win, ' ', t->cols - len - 1);
1451 curs_set(vt_cursor(vt));
1454 void vt_scroll(Vt *vt, int rows)
1456 Buffer *t = vt->buffer;
1457 if (!t->scroll_buf_sz)
1458 return;
1459 if (rows < 0) { /* scroll back */
1460 if (rows < -t->scroll_buf_len)
1461 rows = -t->scroll_buf_len;
1462 } else { /* scroll forward */
1463 if (rows > t->scroll_amount)
1464 rows = t->scroll_amount;
1466 fill_scroll_buf(t, rows);
1467 t->scroll_amount -= rows;
1468 if (vt->copymode_selecting)
1469 vt->copymode_sel_start_row -= rows;
1472 void vt_noscroll(Vt *t)
1474 int scroll_amount = t->buffer->scroll_amount;
1475 if (scroll_amount)
1476 vt_scroll(t, scroll_amount);
1479 void vt_bell(Vt *t, bool bell)
1481 t->bell = bell;
1484 void vt_togglebell(Vt *t)
1486 t->bell = !t->bell;
1489 pid_t vt_forkpty(Vt *t, const char *p, const char *argv[], const char *env[], int *pty)
1491 struct winsize ws;
1492 pid_t pid;
1493 const char **envp = env;
1494 int fd, maxfd;
1496 ws.ws_row = t->buffer->rows;
1497 ws.ws_col = t->buffer->cols;
1498 ws.ws_xpixel = ws.ws_ypixel = 0;
1500 pid = forkpty(&t->pty, NULL, NULL, &ws);
1501 if (pid < 0)
1502 return -1;
1504 if (pid == 0) {
1505 setsid();
1507 maxfd = sysconf(_SC_OPEN_MAX);
1508 for (fd = 3; fd < maxfd; fd++)
1509 if (close(fd) == -1 && errno == EBADF)
1510 break;
1512 while (envp && envp[0]) {
1513 setenv(envp[0], envp[1], 1);
1514 envp += 2;
1516 setenv("TERM", COLORS >= 256 ? "rxvt-256color" : "rxvt", 1);
1517 execv(p, (char *const *)argv);
1518 fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
1519 exit(1);
1522 if (pty)
1523 *pty = t->pty;
1524 return t->childpid = pid;
1527 int vt_getpty(Vt *t)
1529 return t->pty;
1532 int vt_write(Vt *t, const char *buf, int len)
1534 int ret = len;
1536 while (len > 0) {
1537 int res = write(t->pty, buf, len);
1538 if (res < 0 && errno != EAGAIN && errno != EINTR)
1539 return -1;
1541 buf += res;
1542 len -= res;
1545 return ret;
1548 static void send_curs(Vt *vt)
1550 Buffer *t = vt->buffer;
1551 char keyseq[16];
1552 sprintf(keyseq, "\e[%d;%dR", (int)(t->curs_row - t->lines), t->curs_col);
1553 vt_write(vt, keyseq, strlen(keyseq));
1556 void vt_keypress(Vt *t, int keycode)
1558 char c = (char)keycode;
1560 vt_noscroll(t);
1562 if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode]) {
1563 switch (keycode) {
1564 case KEY_UP:
1565 case KEY_DOWN:
1566 case KEY_RIGHT:
1567 case KEY_LEFT: {
1568 char keyseq[3] = { '\e', (t->curskeymode ? 'O' : '['), keytable[keycode][0] };
1569 vt_write(t, keyseq, sizeof keyseq);
1570 break;
1572 default:
1573 vt_write(t, keytable[keycode], strlen(keytable[keycode]));
1575 } else {
1576 vt_write(t, &c, 1);
1580 static int copymode_search_range(Vt *vt, Row *start_row, Row *end_row, int start_col, int direction)
1582 Buffer *t = vt->buffer;
1583 int matched_row_offset = 0, matched_col = 0;
1584 int s_start = direction > 0 ? 0 : vt->searchbuf_curs - 1;
1585 int s_end = direction > 0 ? vt->searchbuf_curs - 1 : 0;
1586 int s = s_start;
1587 int end_col = direction > 0 ? t->cols - 1 : 0;
1588 Row *row = start_row;
1590 for (int offset = 0; ; offset++) {
1591 int col = direction > 0 ? 0 : t->cols - 1;
1592 if (row == start_row)
1593 col = start_col;
1594 for (;;) {
1595 if (vt->searchbuf[s] == row->cells[col].text) {
1596 if (s == s_start) {
1597 matched_row_offset = offset;
1598 matched_col = col;
1600 if (s == s_end) {
1601 t->curs_col = matched_col;
1602 return matched_row_offset;
1604 s += direction;
1605 } else
1606 s = s_start;
1608 if (col == end_col)
1609 break;
1610 col += direction;
1613 if (row == end_row)
1614 break;
1616 if (direction > 0 && row == &t->scroll_buf[t->scroll_buf_sz - 1])
1617 row = t->scroll_buf;
1618 else if (direction < 0 && row == t->scroll_buf) {
1619 int index = t->scroll_buf_len;
1620 if (t->scroll_amount)
1621 index += t->scroll_amount - 1;
1622 if (index >= t->scroll_buf_sz)
1623 index = t->scroll_buf_sz - 1;
1624 row = &t->scroll_buf[index];
1625 } else
1626 row += direction;
1629 return INT_MAX;
1632 static void copymode_search(Vt *vt, int direction)
1634 if (!vt->searchbuf || vt->searchbuf[0] == '\0')
1635 return;
1637 int match_offset;
1638 Buffer *t = vt->buffer;
1639 Row *start_row = t->curs_row;
1640 Row *end_row = direction > 0 ? t->lines + t->rows - 1 : t->lines;
1642 /* avoid match at current cursor position */
1643 int start_col = t->curs_col + direction;
1644 if (start_col >= t->cols) {
1645 start_col = 0;
1646 start_row++;
1647 } else if (start_col < 0) {
1648 start_col = t->cols - 1;
1649 start_row--;
1652 if (start_row >= t->lines && start_row < t->lines + t->rows &&
1653 end_row >= t->lines && end_row < t->lines + t->rows) {
1654 match_offset = copymode_search_range(vt, start_row, end_row, start_col, direction);
1655 if (match_offset != INT_MAX) {
1656 t->curs_row = start_row + (direction > 0 ? match_offset : -match_offset);
1657 return;
1661 Row *before_start_row, *before_end_row, *after_start_row, *after_end_row;
1663 if (t->scroll_buf_sz) {
1664 before_start_row = &t->scroll_buf
1665 [(t->scroll_buf_ptr - 1 + t->scroll_buf_sz) % t->scroll_buf_sz];
1666 before_end_row = &t->scroll_buf
1667 [(t->scroll_buf_ptr - t->scroll_buf_len + t->scroll_buf_sz) % t->scroll_buf_sz];
1668 after_start_row = &t->scroll_buf[t->scroll_buf_ptr];
1669 after_end_row = &t->scroll_buf
1670 [(t->scroll_buf_ptr + t->scroll_amount - 1) % t->scroll_buf_sz];
1673 if (direction > 0) {
1674 if (t->scroll_buf_sz && t->scroll_amount) { /* end of page => end of scrollbuffer */
1675 match_offset = copymode_search_range(vt, after_start_row, after_end_row, 0, 1);
1676 if (match_offset != INT_MAX) {
1677 vt_scroll(vt, match_offset + 1);
1678 t->curs_row = t->lines + t->rows - 1;
1679 return;
1683 if (t->scroll_buf_sz && t->scroll_buf_len) { /* begin of scrollbuffer => begin of page */
1684 match_offset = copymode_search_range(vt, before_end_row, before_start_row, 0, 1);
1685 if (match_offset != INT_MAX) {
1686 vt_scroll(vt, -(t->scroll_buf_len - match_offset));
1687 t->curs_row = t->lines;
1688 return;
1692 /* begin of page => cursor position */
1693 end_row = t->curs_row + (t->curs_row == t->lines + t->rows - 1 ? 0 : 1);
1694 match_offset = copymode_search_range(vt, t->lines, end_row, 0, 1);
1695 if (match_offset != INT_MAX)
1696 t->curs_row = t->lines + match_offset;
1697 } else {
1698 if (t->scroll_buf_sz && t->scroll_buf_len) { /* begin of page => begin of scrollbuffer */
1699 match_offset = copymode_search_range(vt, before_start_row, before_end_row, t->cols - 1, -1);
1700 if (match_offset != INT_MAX) {
1701 vt_scroll(vt, -(match_offset + 1));
1702 t->curs_row = t->lines;
1703 return;
1707 if (t->scroll_buf_sz && t->scroll_amount) { /* end of scrollbuffer => end of page */
1708 match_offset = copymode_search_range(vt, after_end_row, after_start_row, t->cols - 1, -1);
1709 if (match_offset != INT_MAX) {
1710 vt_scroll(vt, t->scroll_amount - match_offset);
1711 t->curs_row = t->lines + t->rows - 1;
1712 return;
1715 /* end of page => cursor position */
1716 end_row = t->curs_row - (t->curs_row == t->lines ? 0 : 1);
1717 match_offset = copymode_search_range(vt, t->lines + t->rows - 1, end_row, t->cols - 1, -1);
1718 if (match_offset != INT_MAX)
1719 t->curs_row = t->lines + t->rows - 1 - match_offset;
1723 void vt_copymode_keypress(Vt *vt, int keycode)
1725 Buffer *t = vt->buffer;
1726 Row *start_row, *end_row;
1727 int direction, col, start_col, end_col, delta, scroll_page = t->rows / 2;
1728 char *copybuf, keychar = (char)keycode;
1729 wchar_t wc;
1730 ssize_t len;
1731 bool found;
1733 if (!vt->copymode)
1734 return;
1736 if (vt->copymode_searching) {
1737 switch (keycode) {
1738 case KEY_BACKSPACE:
1739 if (--vt->searchbuf_curs < 0)
1740 vt->searchbuf_curs = 0;
1741 vt->searchbuf[vt->searchbuf_curs] = '\0';
1742 break;
1743 case '\n':
1744 copymode_search(vt, vt->copymode_searching);
1745 case '\e':
1746 vt->copymode_searching = 0;
1747 t->lines[t->rows - 1].dirty = true;
1748 break;
1749 default:
1750 len = (ssize_t)mbrtowc(&wc, &keychar, 1, &vt->searchbuf_ps);
1752 if (len == -2)
1753 return;
1754 if (len == -1)
1755 wc = keycode;
1756 if (vt->searchbuf_curs >= vt->searchbuf_size - 2) {
1757 vt->searchbuf_size *= 2;
1758 wchar_t *buf = realloc(vt->searchbuf, vt->searchbuf_size * sizeof(wchar_t));
1759 if (!buf)
1760 return;
1761 vt->searchbuf = buf;
1763 vt->searchbuf[vt->searchbuf_curs++] = wc;
1764 vt->searchbuf[vt->searchbuf_curs] = '\0';
1765 break;
1767 } else {
1768 switch (keycode) {
1769 case '0' ... '9':
1770 vt->copymode_cmd_multiplier = (vt->copymode_cmd_multiplier * 10) + (keychar - '0');
1771 return;
1772 case KEY_PPAGE:
1773 delta = t->curs_row - t->lines;
1774 if (delta > scroll_page)
1775 t->curs_row -= scroll_page;
1776 else {
1777 t->curs_row = t->lines;
1778 vt_scroll(vt, delta - scroll_page);
1780 break;
1781 case KEY_NPAGE:
1782 delta = t->rows - (t->curs_row - t->lines);
1783 if (delta > scroll_page)
1784 t->curs_row += scroll_page;
1785 else {
1786 t->curs_row = t->lines + t->rows - 1;
1787 vt_scroll(vt, scroll_page - delta);
1789 break;
1790 case 'g':
1791 if (t->scroll_buf_len)
1792 vt_scroll(vt, -t->scroll_buf_len);
1793 /* fall through */
1794 case 'H':
1795 t->curs_row = t->lines;
1796 break;
1797 case 'M':
1798 t->curs_row = t->lines + (t->rows / 2);
1799 break;
1800 case 'G':
1801 vt_noscroll(vt);
1802 /* fall through */
1803 case 'L':
1804 t->curs_row = t->lines + t->rows - 1;
1805 break;
1806 case KEY_HOME:
1807 case '^':
1808 t->curs_col = 0;
1809 break;
1810 case KEY_END:
1811 case '$':
1812 start_col = t->cols - 1;
1813 for (int i = 0; i < t->cols; i++)
1814 if (t->curs_row->cells[i].text)
1815 start_col = i;
1816 t->curs_col = start_col;
1817 break;
1818 case '/':
1819 case '?':
1820 memset(&vt->searchbuf_ps, 0, sizeof(mbstate_t));
1821 if (!vt->searchbuf) {
1822 vt->searchbuf_size = t->cols+1;
1823 vt->searchbuf = malloc(vt->searchbuf_size * sizeof(wchar_t));
1825 if (!vt->searchbuf)
1826 return;
1827 vt->searchbuf[0] = L'\0';
1828 vt->searchbuf_curs = 0;
1829 vt->copymode_searching = keycode == '/' ? 1 : -1;
1830 break;
1831 case 'n':
1832 case 'N':
1833 copymode_search(vt, keycode == 'n' ? 1 : -1);
1834 break;
1835 case 'v':
1836 vt->copymode_selecting = true;
1837 vt->copymode_sel_start_row = t->curs_row;
1838 vt->copymode_sel_start_col = t->curs_col;
1839 break;
1840 case 'y':
1841 if (!vt->copymode_selecting) {
1842 t->curs_col = 0;
1843 vt->copymode_sel_start_row = t->curs_row +
1844 (vt->copymode_cmd_multiplier ? vt->copymode_cmd_multiplier - 1 : 0);
1845 if (vt->copymode_sel_start_row >= t->lines + t->rows)
1846 vt->copymode_sel_start_row = t->lines + t->rows - 1;
1847 vt->copymode_sel_start_col = t->cols - 1;
1850 copymode_get_selection_boundry(vt, &start_row, &start_col, &end_row, &end_col, false);
1851 int line_count = vt->copymode_sel_start_row > t->curs_row ?
1852 vt->copymode_sel_start_row - t->curs_row :
1853 t->curs_row - vt->copymode_sel_start_row;
1854 copybuf = calloc(1, (line_count + 1) * t->cols * MB_CUR_MAX + 1);
1856 if (copybuf) {
1857 char *s = copybuf, *last_non_space = s;
1858 mbstate_t ps;
1859 memset(&ps, 0, sizeof(mbstate_t));
1860 Row *row = start_row;
1861 Row *scroll_data_end = &t->scroll_buf
1862 [(t->scroll_buf_ptr - 1 + t->scroll_buf_sz) % t->scroll_buf_sz];
1863 for (;;) {
1864 int j = (row == start_row) ? start_col : 0;
1865 int col = (row == end_row) ? end_col : t->cols - 1;
1866 while (j <= col) {
1867 if (row->cells[j].text) {
1868 size_t len = wcrtomb(s, row->cells[j].text, &ps);
1869 if (len > 0)
1870 s += len;
1871 last_non_space = s;
1872 } else
1873 *s++ = ' ';
1874 j++;
1877 s = last_non_space;
1879 if (row == end_row)
1880 break;
1881 else
1882 *s++ = '\n';
1884 if (t->scroll_buf_len && row == &t->scroll_buf[t->scroll_buf_sz - 1])
1885 row = t->scroll_buf;
1886 else if (t->scroll_buf_len && row == scroll_data_end)
1887 row = t->lines;
1888 else if (row == t->lines + t->rows - 1)
1889 row = &t->scroll_buf[t->scroll_buf_ptr];
1890 else
1891 row++;
1893 *s = '\0';
1894 if (vt->event_handler)
1895 vt->event_handler(vt, VT_EVENT_COPY_TEXT, copybuf);
1897 /* fall through */
1898 case '\e':
1899 case 'q':
1900 vt_copymode_leave(vt);
1901 return;
1902 default:
1903 for (int c = 0; c < (vt->copymode_cmd_multiplier ? vt->copymode_cmd_multiplier : 1); c++) {
1904 switch (keycode) {
1905 case 'w':
1906 case 'W':
1907 case 'b':
1908 case 'B':
1909 direction = (keycode == 'w' || keycode == 'W') ? 1 : -1;
1910 start_col = (direction > 0) ? 0 : t->cols - 1;
1911 end_col = (direction > 0) ? t->cols - 1 : 0;
1912 col = t->curs_col;
1913 found = false;
1914 do {
1915 for (;;) {
1916 if (t->curs_row->cells[col].text == ' ') {
1917 found = true;
1918 break;
1921 if (col == end_col)
1922 break;
1923 col += direction;
1926 if (found) {
1927 while (t->curs_row->cells[col].text == ' ') {
1928 if (col == end_col) {
1929 t->curs_row += direction;
1930 break;
1932 col += direction;
1934 } else {
1935 col = start_col;
1936 t->curs_row += direction;
1939 if (t->curs_row < t->lines) {
1940 t->curs_row = t->lines;
1941 if (t->scroll_buf_len)
1942 vt_scroll(vt, -1);
1943 else
1944 break;
1947 if (t->curs_row >= t->lines + t->rows) {
1948 t->curs_row = t->lines + t->rows - 1;
1949 if (t->scroll_amount)
1950 vt_scroll(vt, 1);
1951 else
1952 break;
1954 } while (!found);
1956 if (found)
1957 t->curs_col = col;
1958 break;
1959 case KEY_UP:
1960 case 'k':
1961 if (t->curs_row == t->lines)
1962 vt_scroll(vt, -1);
1963 else
1964 t->curs_row--;
1965 break;
1966 case KEY_DOWN:
1967 case 'j':
1968 if (t->curs_row == t->lines + t->rows - 1)
1969 vt_scroll(vt, 1);
1970 else
1971 t->curs_row++;
1972 break;
1973 case KEY_RIGHT:
1974 case 'l':
1975 t->curs_col++;
1976 if (t->curs_col >= t->cols) {
1977 t->curs_col = t->cols - 1;
1978 vt->copymode_cmd_multiplier = 0;
1980 break;
1981 case KEY_LEFT:
1982 case 'h':
1983 t->curs_col--;
1984 if (t->curs_col < 0) {
1985 t->curs_col = 0;
1986 vt->copymode_cmd_multiplier = 0;
1988 break;
1991 break;
1994 if (vt->copymode_selecting)
1995 vt_dirty(vt);
1996 vt->copymode_cmd_multiplier = 0;
1999 void vt_mouse(Vt *t, int x, int y, mmask_t mask)
2001 #ifdef NCURSES_MOUSE_VERSION
2002 char seq[6] = { '\e', '[', 'M' }, state = 0, button = 0;
2004 if (!t->mousetrack)
2005 return;
2007 if (mask & (BUTTON1_PRESSED | BUTTON1_CLICKED))
2008 button = 0;
2009 else if (mask & (BUTTON2_PRESSED | BUTTON2_CLICKED))
2010 button = 1;
2011 else if (mask & (BUTTON3_PRESSED | BUTTON3_CLICKED))
2012 button = 2;
2013 else if (mask & (BUTTON1_RELEASED | BUTTON2_RELEASED | BUTTON3_RELEASED))
2014 button = 3;
2016 if (mask & BUTTON_SHIFT)
2017 state |= 4;
2018 if (mask & BUTTON_ALT)
2019 state |= 8;
2020 if (mask & BUTTON_CTRL)
2021 state |= 16;
2023 seq[3] = 32 + button + state;
2024 seq[4] = 32 + x;
2025 seq[5] = 32 + y;
2027 vt_write(t, seq, sizeof seq);
2029 if (mask & (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED)) {
2030 /* send a button release event */
2031 button = 3;
2032 seq[3] = 32 + button + state;
2033 vt_write(t, seq, sizeof seq);
2035 #endif /* NCURSES_MOUSE_VERSION */
2038 static unsigned int color_hash(short fg, short bg)
2040 if (fg == -1)
2041 fg = COLORS;
2042 if (bg == -1)
2043 bg = COLORS + 1;
2044 return fg * (COLORS + 2) + bg;
2047 short vt_color_get(Vt *t, short fg, short bg)
2049 if (fg >= COLORS)
2050 fg = (t ? t->deffg : default_fg);
2051 if (bg >= COLORS)
2052 bg = (t ? t->defbg : default_bg);
2054 if (!has_default_colors) {
2055 if (fg == -1)
2056 fg = (t && t->deffg != -1 ? t->deffg : default_fg);
2057 if (bg == -1)
2058 bg = (t && t->defbg != -1 ? t->defbg : default_bg);
2061 if (!color2palette || (fg == -1 && bg == -1))
2062 return 0;
2063 unsigned int index = color_hash(fg, bg);
2064 if (color2palette[index] == 0) {
2065 short oldfg, oldbg;
2066 for (;;) {
2067 if (++color_pair_current >= color_pairs_max)
2068 color_pair_current = color_pairs_reserved + 1;
2069 pair_content(color_pair_current, &oldfg, &oldbg);
2070 unsigned int old_index = color_hash(oldfg, oldbg);
2071 if (color2palette[old_index] >= 0) {
2072 if (init_pair(color_pair_current, fg, bg) == OK) {
2073 color2palette[old_index] = 0;
2074 color2palette[index] = color_pair_current;
2076 break;
2081 short color_pair = color2palette[index];
2082 return color_pair >= 0 ? color_pair : -color_pair;
2085 short vt_color_reserve(short fg, short bg)
2087 if (!color2palette || fg >= COLORS || bg >= COLORS)
2088 return 0;
2089 if (!has_default_colors && fg == -1)
2090 fg = default_fg;
2091 if (!has_default_colors && bg == -1)
2092 bg = default_bg;
2093 if (fg == -1 && bg == -1)
2094 return 0;
2095 unsigned int index = color_hash(fg, bg);
2096 if (color2palette[index] >= 0) {
2097 if (init_pair(++color_pairs_reserved, fg, bg) == OK)
2098 color2palette[index] = -color_pairs_reserved;
2100 short color_pair = color2palette[index];
2101 return color_pair >= 0 ? color_pair : -color_pair;
2104 static void init_colors(void)
2106 pair_content(0, &default_fg, &default_bg);
2107 if (default_fg == -1)
2108 default_fg = COLOR_WHITE;
2109 if (default_bg == -1)
2110 default_bg = COLOR_BLACK;
2111 has_default_colors = (use_default_colors() == OK);
2112 color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS);
2113 color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short));
2114 vt_color_reserve(COLOR_WHITE, COLOR_BLACK);
2117 void vt_init(void)
2119 init_colors();
2120 is_utf8_locale();
2123 void vt_shutdown(void)
2125 free(color2palette);
2128 void vt_set_escseq_handler(Vt *t, vt_escseq_handler_t handler)
2130 t->escseq_handler = handler;
2133 void vt_set_event_handler(Vt *t, vt_event_handler_t handler)
2135 t->event_handler = handler;
2138 void vt_set_data(Vt *t, void *data)
2140 t->data = data;
2143 void *vt_get_data(Vt *t)
2145 return t->data;
2148 unsigned vt_cursor(Vt *t)
2150 if (t->copymode)
2151 return 1;
2152 return t->buffer->scroll_amount ? 0 : !t->curshid;
2155 unsigned vt_copymode(Vt *t)
2157 return t->copymode;
2160 void vt_copymode_enter(Vt *vt)
2162 Buffer *t = vt->buffer;
2163 if (!vt->copymode) {
2164 vt->copymode_curs_srow = t->curs_row - t->lines;
2165 vt->copymode_curs_scol = t->curs_col;
2167 vt->copymode = true;
2170 void vt_copymode_leave(Vt *vt)
2172 Buffer *t = vt->buffer;
2173 vt->copymode = false;
2174 vt->copymode_selecting = false;
2175 vt->copymode_searching = false;
2176 vt->copymode_sel_start_row = t->lines;
2177 vt->copymode_sel_start_col = 0;
2178 vt->copymode_cmd_multiplier = 0;
2179 t->curs_row = t->lines + vt->copymode_curs_srow;
2180 t->curs_col = vt->copymode_curs_scol;
2181 vt_noscroll(vt);
2182 vt_dirty(vt);