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.
29 #include <sys/ioctl.h>
30 #include <sys/types.h>
33 #if defined(__linux__) || defined(__CYGWIN__)
35 #elif defined(__FreeBSD__)
37 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
40 #if defined(__CYGWIN__) || defined(_AIX)
47 # include "forkpty-aix.c"
50 #ifndef NCURSES_ATTR_SHIFT
51 # define NCURSES_ATTR_SHIFT 8
56 # define NCURSES_ACS(c) (acs_map[(unsigned char)(c)])
57 # else /* BSD curses */
58 # define NCURSES_ACS(c) (_acs_map[(unsigned char)(c)])
62 #ifdef NCURSES_VERSION
63 # ifndef NCURSES_EXT_COLORS
64 # define NCURSES_EXT_COLORS 0
66 # if !NCURSES_EXT_COLORS
67 # define MAX_COLOR_PAIRS 256
70 #ifndef MAX_COLOR_PAIRS
71 # define MAX_COLOR_PAIRS COLOR_PAIRS
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
;
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
,
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
,
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
133 int rows
, cols
, maxcols
;
134 unsigned curattrs
, savattrs
;
135 int curs_col
, curs_srow
, curs_scol
;
136 short curfg
, curbg
, savfg
, savbg
;
140 Buffer buffer_normal
;
141 Buffer buffer_alternate
;
149 unsigned seen_input
:1;
153 unsigned curskeymode
:1;
155 unsigned copymode_selecting
:1;
157 unsigned relposmode
:1;
158 unsigned mousetrack
:1;
159 unsigned graphmode
:1;
161 char copymode_searching
;
163 int copymode_curs_srow
, copymode_curs_scol
;
164 Row
*copymode_sel_start_row
;
165 int copymode_sel_start_col
;
167 mbstate_t searchbuf_ps
;
168 int searchbuf_curs
, searchbuf_size
;
169 int copymode_cmd_multiplier
;
170 /* buffers and parsing state */
174 unsigned int rlen
, elen
;
176 /* xterm style window title */
179 vt_event_handler_t event_handler
;
181 /* custom escape sequence handler */
182 vt_escseq_handler_t escseq_handler
;
186 static char const * const keytable
[KEY_MAX
+1] = {
188 /* for the arrow keys the CSI / SS3 sequences are not stored here
189 * because they depend on the current cursor terminal mode
196 [KEY_SUP
] = "\e[1;2A",
199 [KEY_SDOWN
] = "\e[1;2B",
201 [KEY_SRIGHT
] = "\e[1;2C",
202 [KEY_SLEFT
] = "\e[1;2D",
203 [KEY_BACKSPACE
] = "\177",
206 [KEY_PPAGE
] = "\e[5~",
207 [KEY_NPAGE
] = "\e[6~",
208 [KEY_HOME
] = "\e[7~",
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~",
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
)
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
;
258 static void row_roll(Row
*start
, Row
*end
, int 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
++)
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
)
285 if (t
->curs_row
>= lines
+ rows
)
286 t
->curs_row
= lines
+ rows
- 1;
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
;
316 static void restore_attrs(Vt
*vt
)
318 Buffer
*t
= vt
->buffer
;
319 t
->curattrs
= t
->savattrs
;
324 static void fill_scroll_buf(Buffer
*t
, int s
)
326 /* work in screenfuls */
327 int ssz
= t
->scroll_bot
- t
->scroll_top
;
329 fill_scroll_buf(t
, ssz
);
330 fill_scroll_buf(t
, s
- ssz
);
334 fill_scroll_buf(t
, -ssz
);
335 fill_scroll_buf(t
, s
+ ssz
);
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
;
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
--) {
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
);
374 if (t
->curs_row
< t
->scroll_bot
)
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
)
391 static void cancel_escape_sequence(Vt
*t
)
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
;
410 /* special case: reset attributes */
411 t
->curattrs
= A_NORMAL
;
412 t
->curfg
= t
->curbg
= -1;
416 for (int i
= 0; i
< pcount
; i
++) {
419 t
->curattrs
= A_NORMAL
;
420 t
->curfg
= t
->curbg
= -1;
423 t
->curattrs
|= A_BOLD
;
426 t
->curattrs
|= A_UNDERLINE
;
429 t
->curattrs
|= A_BLINK
;
432 t
->curattrs
|= A_REVERSE
;
435 t
->curattrs
|= A_INVIS
;
438 t
->curattrs
&= ~A_BOLD
;
441 t
->curattrs
&= ~A_UNDERLINE
;
444 t
->curattrs
&= ~A_BLINK
;
447 t
->curattrs
&= ~A_REVERSE
;
450 t
->curattrs
&= ~A_INVIS
;
452 case 30 ... 37: /* fg */
453 t
->curfg
= param
[i
] - 30;
456 if ((i
+ 2) < pcount
&& param
[i
+ 1] == 5) {
457 t
->curfg
= param
[i
+ 2];
464 case 40 ... 47: /* bg */
465 t
->curbg
= param
[i
] - 40;
468 if ((i
+ 2) < pcount
&& param
[i
+ 1] == 5) {
469 t
->curbg
= param
[i
+ 2];
476 case 90 ... 97: /* hi fg */
477 t
->curfg
= param
[i
] - 82;
479 case 100 ... 107: /* hi bg */
480 t
->curbg
= param
[i
] - 92;
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
;
495 t
->curattrs
= A_NORMAL
;
496 t
->curfg
= t
->curbg
= -1;
498 if (pcount
&& param
[0] == 2) {
500 end
= t
->lines
+ t
->rows
;
501 } else if (pcount
&& param
[0] == 1) {
504 row_set(t
->curs_row
, 0, t
->curs_col
+ 1, t
);
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
);
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
;
526 } else if (pcount
== 1) {
527 t
->curs_row
= lines
+ param
[0] - 1;
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;
569 t
->curs_col
= param
[0] - 1;
572 t
->curs_row
= t
->lines
+ param
[0] - 1;
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) {
585 row_set(t
->curs_row
, 0, t
->curs_col
+ 1, t
);
588 row_set(t
->curs_row
, 0, t
->cols
, t
);
591 row_set(t
->curs_row
, t
->curs_col
, t
->cols
- t
->curs_col
, t
);
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
);
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
);
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
;
680 t
->scroll_top
= t
->lines
;
681 t
->scroll_bot
= t
->lines
+ t
->rows
;
684 new_top
= param
[0] - 1;
687 /* clamp to bounds */
690 if (new_top
>= t
->rows
)
691 new_top
= t
->rows
- 1;
694 if (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
;
704 return; /* malformed */
708 static void interpret_csi(Vt
*t
)
710 static int csiparam
[BUFSIZ
];
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;
737 case 6: /* set origin to relative (DECOM) */
738 t
->relposmode
= true;
740 case 25: /* make cursor visible (DECCM) */
743 case 47: /* use alternate screen buffer */
744 t
->buffer
= &t
->buffer_alternate
;
747 case 1000: /* enable normal mouse tracking */
748 t
->mousetrack
= true;
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;
756 case 6: /* set origin to absolute (DECOM) */
757 t
->relposmode
= false;
759 case 25: /* make cursor visible (DECCM) */
762 case 47: /* use normal screen buffer */
763 t
->buffer
= &t
->buffer_normal
;
766 case 1000: /* disable normal mouse tracking */
767 t
->mousetrack
= false;
773 /* delegate handling depending on command character (verb) */
776 if (param_count
== 1 && csiparam
[0] == 4) /* insert mode */
780 if (param_count
== 1 && csiparam
[0] == 4) /* replace mode */
783 case 'm': /* it's a 'set attribute' sequence */
784 interpret_csi_sgr(t
, csiparam
, param_count
);
786 case 'J': /* it's an 'erase display' sequence */
787 interpret_csi_ed(t
, csiparam
, param_count
);
790 case 'f': /* it's a 'move cursor' sequence */
791 interpret_csi_cup(t
, csiparam
, param_count
);
804 /* it is a 'relative move' */
805 interpret_csi_c(t
, verb
, csiparam
, param_count
);
807 case 'K': /* erase line */
808 interpret_csi_el(t
, csiparam
, param_count
);
810 case '@': /* insert characters */
811 interpret_csi_ich(t
, csiparam
, param_count
);
813 case 'P': /* delete characters */
814 interpret_csi_dch(t
, csiparam
, param_count
);
816 case 'L': /* insert lines */
817 interpret_csi_il(t
, csiparam
, param_count
);
819 case 'M': /* delete lines */
820 interpret_csi_dl(t
, csiparam
, param_count
);
822 case 'X': /* erase chars */
823 interpret_csi_ech(t
, csiparam
, param_count
);
825 case 'r': /* set scrolling region */
826 interpret_csi_decstbm(t
, csiparam
, param_count
);
828 case 's': /* save cursor location */
831 case 'u': /* restore cursor location */
834 case 'n': /* query cursor location */
835 if (param_count
== 1 && csiparam
[0] == 6)
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)
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
)
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) {
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 */
887 switch (t
->ebuf
[1]) {
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];
906 if (t
->escseq_handler
) {
907 switch ((*(t
->escseq_handler
)) (t
, t
->ebuf
)) {
908 case VT_ESCSEQ_HANDLER_OK
:
909 cancel_escape_sequence(t
);
911 case VT_ESCSEQ_HANDLER_NOTYET
:
912 if (t
->elen
+ 1 >= sizeof(t
->ebuf
))
919 case '#': /* ignore DECDHL, DECSWL, DECDWL, DECHCP, DECALN, DECFPP */
926 interpret_csi_scs(t
);
930 case ']': /* xterm thing */
931 if (lastchar
== '\a' ||
932 (lastchar
== '\\' && t
->elen
>= 2 && t
->ebuf
[t
->elen
- 2] == '\e')) {
933 interpret_esc_xterm(t
);
938 if (is_valid_csi_ender(lastchar
)) {
943 case '7': /* DECSC: save cursor and attributes */
947 case '8': /* DECRC: restore cursor and attributes */
951 case 'D': /* IND: index */
952 interpret_csi_ind(t
);
954 case 'M': /* RI: reverse index */
957 case 'E': /* NEL: next line */
958 interpret_csi_nel(t
);
964 if (t
->elen
+ 1 >= sizeof(t
->ebuf
)) {
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
);
972 fprintf(stderr
, "\\%03o", t
->ebuf
[i
]);
978 cancel_escape_sequence(t
);
982 static void process_nonprinting(Vt
*vt
, wchar_t wc
)
984 Buffer
*t
= vt
->buffer
;
987 new_escape_sequence(vt
);
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;
1008 cursor_line_down(vt
);
1010 case C0_SO
: /* shift out, invoke the G1 character set */
1011 vt
->graphmode
= vt
->charsets
[1];
1013 case C0_SI
: /* shift in, invoke the G0 character set */
1014 vt
->graphmode
= vt
->charsets
[0];
1019 static void is_utf8_locale(void)
1021 const char *cset
= nl_langinfo(CODESET
);
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
1047 return vt100_utf8
[c
- 0x41];
1048 else if (strchr(vt100_acs
, c
))
1049 return NCURSES_ACS(c
);
1053 static void put_wc(Vt
*t
, wchar_t wc
)
1057 if (!t
->seen_input
) {
1059 kill(-t
->childpid
, SIGWINCH
);
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
);
1068 cancel_escape_sequence(t
);
1070 } else if (IS_CONTROL(wc
)) {
1071 process_nonprinting(t
, wc
);
1074 if (wc
>= 0x41 && wc
<= 0x7e) {
1075 wchar_t gc
= get_vt100_graphic(wc
);
1080 } else if ((width
= wcwidth(wc
)) < 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
) {
1092 cursor_line_down(t
);
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;
1106 b
->curs_row
->cells
[b
->curs_col
++] = blank_cell
;
1110 int vt_process(Vt
*t
)
1113 unsigned int pos
= 0;
1120 res
= read(t
->pty
, t
->rbuf
+ t
->rlen
, sizeof(t
->rbuf
) - t
->rlen
);
1125 while (pos
< t
->rlen
) {
1129 len
= (ssize_t
)mbrtowc(&wc
, t
->rbuf
+ pos
, t
->rlen
- pos
, &t
->ps
);
1132 memmove(t
->rbuf
, t
->rbuf
+ pos
, t
->rlen
);
1141 pos
+= len
? len
: 1;
1146 memmove(t
->rbuf
, t
->rbuf
+ pos
, t
->rlen
);
1150 void vt_set_default_colors(Vt
*t
, unsigned attrs
, short fg
, short bg
)
1152 t
->defattrs
= attrs
;
1157 static void buffer_free(Buffer
*t
)
1159 for (int i
= 0; i
< t
->rows
; i
++)
1160 free(t
->lines
[i
].cells
);
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
));
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
));
1178 t
->rows
= row
- lines
;
1181 row_set(row
, 0, cols
, NULL
);
1184 if (scroll_buf_sz
< 0)
1186 t
->scroll_buf
= scroll_buf
= calloc(scroll_buf_sz
, sizeof(Row
));
1189 for (Row
*row
= scroll_buf
, *end
= scroll_buf
+ scroll_buf_sz
; row
< end
; row
++) {
1190 row
->cells
= calloc(cols
, sizeof(Cell
));
1192 t
->scroll_buf_sz
= row
- scroll_buf
;
1196 t
->curs_row
= lines
;
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
;
1210 Vt
*vt_create(int rows
, int cols
, int scroll_buf_sz
)
1214 if (rows
<= 0 || cols
<= 0)
1217 t
= calloc(1, sizeof(Vt
));
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)) {
1228 t
->buffer
= &t
->buffer_normal
;
1229 t
->copymode_cmd_multiplier
= 0;
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
);
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
);
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
);
1261 row_set(sbuf
+ row
, t
->cols
, cols
- t
->cols
, NULL
);
1265 } else if (t
->cols
!= cols
) {
1266 for (int row
= 0; row
< t
->rows
; row
++)
1267 lines
[row
].dirty
= true;
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
);
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
;
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)
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
)
1320 buffer_free(&t
->buffer_normal
);
1321 buffer_free(&t
->buffer_alternate
);
1326 void vt_dirty(Vt
*vt
)
1328 Buffer
*t
= vt
->buffer
;
1329 for (Row
*row
= t
->lines
, *end
= row
+ t
->rows
; row
< end
; row
++)
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
;
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
;
1353 /* part of the scrollback buffer is also selected */
1354 if (vt
->copymode_sel_start_row
< t
->lines
) {
1355 /* above the current page */
1357 *start_row
= t
->lines
;
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
;
1368 /* below the current page */
1369 *start_row
= t
->curs_row
;
1370 *start_col
= t
->curs_col
;
1372 *end_row
= t
->lines
+ t
->rows
;
1373 *end_col
= t
->cols
- 1;
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
;
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);
1394 for (int i
= 0; i
< t
->rows
; i
++) {
1395 Row
*row
= t
->lines
+ i
;
1400 wmove(win
, srow
+ i
, scol
);
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
;
1411 cell
->fg
= vt
->deffg
;
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
));
1424 wattrset(win
, (attr_t
) cell
->attr
<< NCURSES_ATTR_SHIFT
);
1425 wcolor_set(win
, vt_color_get(vt
, cell
->fg
, cell
->bg
), NULL
);
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)
1436 waddch(win
, cell
->text
> ' ' ? cell
->text
: ' ');
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
)
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
;
1476 vt_scroll(t
, scroll_amount
);
1479 void vt_bell(Vt
*t
, bool bell
)
1484 void vt_togglebell(Vt
*t
)
1489 pid_t
vt_forkpty(Vt
*t
, const char *p
, const char *argv
[], const char *env
[], int *pty
)
1493 const char **envp
= env
;
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
);
1507 maxfd
= sysconf(_SC_OPEN_MAX
);
1508 for (fd
= 3; fd
< maxfd
; fd
++)
1509 if (close(fd
) == -1 && errno
== EBADF
)
1512 while (envp
&& envp
[0]) {
1513 setenv(envp
[0], envp
[1], 1);
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]);
1524 return t
->childpid
= pid
;
1527 int vt_getpty(Vt
*t
)
1532 int vt_write(Vt
*t
, const char *buf
, int len
)
1537 int res
= write(t
->pty
, buf
, len
);
1538 if (res
< 0 && errno
!= EAGAIN
&& errno
!= EINTR
)
1548 static void send_curs(Vt
*vt
)
1550 Buffer
*t
= vt
->buffer
;
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
;
1562 if (keycode
>= 0 && keycode
< KEY_MAX
&& keytable
[keycode
]) {
1568 char keyseq
[3] = { '\e', (t
->curskeymode
? 'O' : '['), keytable
[keycode
][0] };
1569 vt_write(t
, keyseq
, sizeof keyseq
);
1573 vt_write(t
, keytable
[keycode
], strlen(keytable
[keycode
]));
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;
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
)
1595 if (vt
->searchbuf
[s
] == row
->cells
[col
].text
) {
1597 matched_row_offset
= offset
;
1601 t
->curs_col
= matched_col
;
1602 return matched_row_offset
;
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
];
1632 static void copymode_search(Vt
*vt
, int direction
)
1634 if (!vt
->searchbuf
|| vt
->searchbuf
[0] == '\0')
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
) {
1647 } else if (start_col
< 0) {
1648 start_col
= t
->cols
- 1;
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
);
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;
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
;
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
;
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
;
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;
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
;
1736 if (vt
->copymode_searching
) {
1739 if (--vt
->searchbuf_curs
< 0)
1740 vt
->searchbuf_curs
= 0;
1741 vt
->searchbuf
[vt
->searchbuf_curs
] = '\0';
1744 copymode_search(vt
, vt
->copymode_searching
);
1746 vt
->copymode_searching
= 0;
1747 t
->lines
[t
->rows
- 1].dirty
= true;
1750 len
= (ssize_t
)mbrtowc(&wc
, &keychar
, 1, &vt
->searchbuf_ps
);
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));
1761 vt
->searchbuf
= buf
;
1763 vt
->searchbuf
[vt
->searchbuf_curs
++] = wc
;
1764 vt
->searchbuf
[vt
->searchbuf_curs
] = '\0';
1770 vt
->copymode_cmd_multiplier
= (vt
->copymode_cmd_multiplier
* 10) + (keychar
- '0');
1773 delta
= t
->curs_row
- t
->lines
;
1774 if (delta
> scroll_page
)
1775 t
->curs_row
-= scroll_page
;
1777 t
->curs_row
= t
->lines
;
1778 vt_scroll(vt
, delta
- scroll_page
);
1782 delta
= t
->rows
- (t
->curs_row
- t
->lines
);
1783 if (delta
> scroll_page
)
1784 t
->curs_row
+= scroll_page
;
1786 t
->curs_row
= t
->lines
+ t
->rows
- 1;
1787 vt_scroll(vt
, scroll_page
- delta
);
1791 if (t
->scroll_buf_len
)
1792 vt_scroll(vt
, -t
->scroll_buf_len
);
1795 t
->curs_row
= t
->lines
;
1798 t
->curs_row
= t
->lines
+ (t
->rows
/ 2);
1804 t
->curs_row
= t
->lines
+ t
->rows
- 1;
1812 start_col
= t
->cols
- 1;
1813 for (int i
= 0; i
< t
->cols
; i
++)
1814 if (t
->curs_row
->cells
[i
].text
)
1816 t
->curs_col
= start_col
;
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));
1827 vt
->searchbuf
[0] = L
'\0';
1828 vt
->searchbuf_curs
= 0;
1829 vt
->copymode_searching
= keycode
== '/' ? 1 : -1;
1833 copymode_search(vt
, keycode
== 'n' ? 1 : -1);
1836 vt
->copymode_selecting
= true;
1837 vt
->copymode_sel_start_row
= t
->curs_row
;
1838 vt
->copymode_sel_start_col
= t
->curs_col
;
1841 if (!vt
->copymode_selecting
) {
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);
1857 char *s
= copybuf
, *last_non_space
= s
;
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
];
1864 int j
= (row
== start_row
) ? start_col
: 0;
1865 int col
= (row
== end_row
) ? end_col
: t
->cols
- 1;
1867 if (row
->cells
[j
].text
) {
1868 size_t len
= wcrtomb(s
, row
->cells
[j
].text
, &ps
);
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
)
1888 else if (row
== t
->lines
+ t
->rows
- 1)
1889 row
= &t
->scroll_buf
[t
->scroll_buf_ptr
];
1894 if (vt
->event_handler
)
1895 vt
->event_handler(vt
, VT_EVENT_COPY_TEXT
, copybuf
);
1900 vt_copymode_leave(vt
);
1903 for (int c
= 0; c
< (vt
->copymode_cmd_multiplier
? vt
->copymode_cmd_multiplier
: 1); c
++) {
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;
1916 if (t
->curs_row
->cells
[col
].text
== ' ') {
1927 while (t
->curs_row
->cells
[col
].text
== ' ') {
1928 if (col
== end_col
) {
1929 t
->curs_row
+= direction
;
1936 t
->curs_row
+= direction
;
1939 if (t
->curs_row
< t
->lines
) {
1940 t
->curs_row
= t
->lines
;
1941 if (t
->scroll_buf_len
)
1947 if (t
->curs_row
>= t
->lines
+ t
->rows
) {
1948 t
->curs_row
= t
->lines
+ t
->rows
- 1;
1949 if (t
->scroll_amount
)
1961 if (t
->curs_row
== t
->lines
)
1968 if (t
->curs_row
== t
->lines
+ t
->rows
- 1)
1976 if (t
->curs_col
>= t
->cols
) {
1977 t
->curs_col
= t
->cols
- 1;
1978 vt
->copymode_cmd_multiplier
= 0;
1984 if (t
->curs_col
< 0) {
1986 vt
->copymode_cmd_multiplier
= 0;
1994 if (vt
->copymode_selecting
)
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;
2007 if (mask
& (BUTTON1_PRESSED
| BUTTON1_CLICKED
))
2009 else if (mask
& (BUTTON2_PRESSED
| BUTTON2_CLICKED
))
2011 else if (mask
& (BUTTON3_PRESSED
| BUTTON3_CLICKED
))
2013 else if (mask
& (BUTTON1_RELEASED
| BUTTON2_RELEASED
| BUTTON3_RELEASED
))
2016 if (mask
& BUTTON_SHIFT
)
2018 if (mask
& BUTTON_ALT
)
2020 if (mask
& BUTTON_CTRL
)
2023 seq
[3] = 32 + button
+ state
;
2027 vt_write(t
, seq
, sizeof seq
);
2029 if (mask
& (BUTTON1_CLICKED
| BUTTON2_CLICKED
| BUTTON3_CLICKED
)) {
2030 /* send a button release event */
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
)
2044 return fg
* (COLORS
+ 2) + bg
;
2047 short vt_color_get(Vt
*t
, short fg
, short bg
)
2050 fg
= (t
? t
->deffg
: default_fg
);
2052 bg
= (t
? t
->defbg
: default_bg
);
2054 if (!has_default_colors
) {
2056 fg
= (t
&& t
->deffg
!= -1 ? t
->deffg
: default_fg
);
2058 bg
= (t
&& t
->defbg
!= -1 ? t
->defbg
: default_bg
);
2061 if (!color2palette
|| (fg
== -1 && bg
== -1))
2063 unsigned int index
= color_hash(fg
, bg
);
2064 if (color2palette
[index
] == 0) {
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
;
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
)
2089 if (!has_default_colors
&& fg
== -1)
2091 if (!has_default_colors
&& bg
== -1)
2093 if (fg
== -1 && bg
== -1)
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
);
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
)
2143 void *vt_get_data(Vt
*t
)
2148 unsigned vt_cursor(Vt
*t
)
2152 return t
->buffer
->scroll_amount
? 0 : !t
->curshid
;
2155 unsigned vt_copymode(Vt
*t
)
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
;