-
-
Notifications
You must be signed in to change notification settings - Fork 6.1k
/
Copy pathvterm_spec.lua
3811 lines (3263 loc) · 105 KB
/
vterm_spec.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
local t = require('test.unit.testutil')
local itp = t.gen_itp(it)
local bit = require('bit')
--- @class vterm
--- @field ENC_UTF8 integer
--- @field VTERM_ATTR_BLINK integer
--- @field VTERM_ATTR_BOLD integer
--- @field VTERM_ATTR_FONT integer
--- @field VTERM_ATTR_ITALIC integer
--- @field VTERM_ATTR_REVERSE integer
--- @field VTERM_ATTR_UNDERLINE integer
--- @field VTERM_BASELINE_RAISE integer
--- @field VTERM_KEY_ENTER integer
--- @field VTERM_KEY_FUNCTION_0 integer
--- @field VTERM_KEY_KP_0 integer
--- @field VTERM_KEY_NONE integer
--- @field VTERM_KEY_TAB integer
--- @field VTERM_KEY_UP integer
--- @field VTERM_KEY_BACKSPACE integer
--- @field VTERM_KEY_ESCAPE integer
--- @field VTERM_KEY_DEL integer
--- @field VTERM_MOD_ALT integer
--- @field VTERM_MOD_CTRL integer
--- @field VTERM_MOD_SHIFT integer
--- @field parser_apc function
--- @field parser_csi function
--- @field parser_dcs function
--- @field parser_osc function
--- @field parser_pm function
--- @field parser_sos function
--- @field parser_text function
--- @field print_color function
--- @field schar_get fun(any, any):integer
--- @field screen_sb_clear function
--- @field screen_sb_popline function
--- @field screen_sb_pushline function
--- @field selection_query function
--- @field selection_set function
--- @field state_erase function
--- @field state_movecursor function
--- @field state_moverect function
--- @field state_pos function
--- @field state_putglyph function
--- @field state_sb_clear function
--- @field state_scrollrect function
--- @field state_setpenattr function
--- @field state_settermprop function
--- @field term_output function
--- @field utf_ptr2char fun(any):integer
--- @field utf_ptr2len fun(any):integer
--- @field vterm_input_write function
--- @field vterm_keyboard_end_paste function
--- @field vterm_keyboard_key function
--- @field vterm_keyboard_start_paste function
--- @field vterm_keyboard_unichar function
--- @field vterm_lookup_encoding fun(any, any):any
--- @field vterm_mouse_button function
--- @field vterm_mouse_move function
--- @field vterm_new fun(any, any):any
--- @field vterm_obtain_screen fun(any):any
--- @field vterm_obtain_state fun(any): any
--- @field vterm_output_set_callback function
--- @field vterm_parser_set_callbacks fun(any, any, any):any
--- @field vterm_screen_convert_color_to_rgb function
--- @field vterm_screen_enable_altscreen function
--- @field vterm_screen_enable_reflow function
--- @field vterm_screen_get_attrs_extent function
--- @field vterm_screen_get_cell function
--- @field vterm_screen_get_text fun(any, any, any, any):any
--- @field vterm_screen_is_eol fun(any, any):any
--- @field vterm_screen_reset function
--- @field vterm_screen_set_callbacks function
--- @field vterm_set_size function
--- @field vterm_set_utf8 fun(any, any, any):any
--- @field vterm_state_focus_in function
--- @field vterm_state_focus_out function
--- @field vterm_state_get_cursorpos fun(any, any)
--- @field vterm_state_get_lineinfo fun(any, any):any
--- @field vterm_state_get_penattr function
--- @field vterm_state_reset function
--- @field vterm_state_set_bold_highbright function
--- @field vterm_state_set_callbacks function
--- @field vterm_state_set_selection_callbacks function
--- @field vterm_state_set_unrecognised_fallbacks function
local vterm = t.cimport(
'./src/nvim/grid.h',
'./src/nvim/mbyte.h',
'./src/nvim/vterm/encoding.h',
'./src/nvim/vterm/keyboard.h',
'./src/nvim/vterm/mouse.h',
'./src/nvim/vterm/parser.h',
'./src/nvim/vterm/pen.h',
'./src/nvim/vterm/screen.h',
'./src/nvim/vterm/state.h',
'./src/nvim/vterm/vterm.h',
'./src/nvim/vterm/vterm_internal.h',
'./test/unit/fixtures/vterm_test.h'
)
--- @return string
local function read_rm()
local f = assert(io.open(t.paths.vterm_test_file, 'rb'))
local text = f:read('*a')
f:close()
vim.fs.rm(t.paths.vterm_test_file, { force = true })
return text
end
local function append(str)
local f = assert(io.open(t.paths.vterm_test_file, 'a'))
f:write(str)
f:close()
return 1
end
local function parser_control(control)
return append(string.format('control %02x\n', control))
end
local function parser_escape(bytes)
return append(string.format('escape %s\n', t.ffi.string(bytes)))
end
local function wantparser(vt)
assert(vt)
local parser_cbs = t.ffi.new('VTermParserCallbacks')
parser_cbs['text'] = vterm.parser_text
parser_cbs['control'] = parser_control
parser_cbs['escape'] = parser_escape
parser_cbs['csi'] = vterm.parser_csi
parser_cbs['osc'] = vterm.parser_osc
parser_cbs['dcs'] = vterm.parser_dcs
parser_cbs['apc'] = vterm.parser_apc
parser_cbs['pm'] = vterm.parser_pm
parser_cbs['sos'] = vterm.parser_sos
vterm.vterm_parser_set_callbacks(vt, parser_cbs, nil)
end
--- @return any
local function init()
local vt = vterm.vterm_new(25, 80)
vterm.vterm_output_set_callback(vt, vterm.term_output, nil)
vterm.vterm_set_utf8(vt, true)
return vt
end
local function state_setlineinfo()
return 1
end
--- @return any
local function wantstate(vt, opts)
opts = opts or {}
assert(vt)
local state = vterm.vterm_obtain_state(vt)
local state_cbs = t.ffi.new('VTermStateCallbacks')
state_cbs['putglyph'] = vterm.state_putglyph
state_cbs['movecursor'] = vterm.state_movecursor
state_cbs['scrollrect'] = vterm.state_scrollrect
state_cbs['moverect'] = vterm.state_moverect
state_cbs['erase'] = vterm.state_erase
state_cbs['setpenattr'] = vterm.state_setpenattr
state_cbs['settermprop'] = vterm.state_settermprop
state_cbs['setlineinfo'] = state_setlineinfo
state_cbs['sb_clear'] = vterm.state_sb_clear
local selection_cbs = t.ffi.new('VTermSelectionCallbacks')
selection_cbs['set'] = vterm.selection_set
selection_cbs['query'] = vterm.selection_query
vterm.vterm_state_set_callbacks(state, state_cbs, nil)
-- In some tests we want to check the behaviour of overflowing the buffer, so make it nicely small
vterm.vterm_state_set_selection_callbacks(state, selection_cbs, nil, nil, 16)
vterm.vterm_state_set_bold_highbright(state, 1)
vterm.vterm_state_reset(state, 1)
local fallbacks = t.ffi.new('VTermStateFallbacks')
fallbacks['control'] = parser_control
fallbacks['csi'] = vterm.parser_csi
fallbacks['osc'] = vterm.parser_osc
fallbacks['dcs'] = vterm.parser_dcs
fallbacks['apc'] = vterm.parser_apc
fallbacks['pm'] = vterm.parser_pm
fallbacks['sos'] = vterm.parser_sos
vterm.want_state_scrollback = opts.b or false
vterm.want_state_erase = opts.e or false
vterm.vterm_state_set_unrecognised_fallbacks(state, opts.f and fallbacks or nil, nil)
vterm.want_state_putglyph = opts.g or false
vterm.want_state_moverect = opts.m or false
vterm.want_state_settermprop = opts.p or false
vterm.want_state_scrollrect = opts.s or false
return state
end
--- @return any
local function wantscreen(vt, opts)
opts = opts or {}
local screen = vterm.vterm_obtain_screen(vt)
local screen_cbs = t.ffi.new('VTermScreenCallbacks')
-- TODO(dundargoc): fix
-- screen_cbs['damage'] = vterm.screen_damage
screen_cbs['moverect'] = vterm.state_moverect
screen_cbs['movecursor'] = vterm.state_movecursor
screen_cbs['settermprop'] = vterm.state_settermprop
screen_cbs['sb_pushline'] = vterm.screen_sb_pushline
screen_cbs['sb_popline'] = vterm.screen_sb_popline
screen_cbs['sb_clear'] = vterm.screen_sb_clear
vterm.vterm_screen_set_callbacks(screen, screen_cbs, nil)
if opts.a then
vterm.vterm_screen_enable_altscreen(screen, 1)
end
vterm.want_screen_scrollback = opts.b or false
vterm.want_state_movecursor = opts.c or false
-- TODO(dundargoc): fix
-- vterm.want_screen_damage = opts.d or opts.D or false
-- vterm.want_screen_cells = opts.D or false
vterm.want_state_moverect = opts.m or false
vterm.want_state_settermprop = opts.p or false
if opts.r then
vterm.vterm_screen_enable_reflow(screen, true)
end
return screen
end
local function reset(state, screen)
if state then
vterm.vterm_state_reset(state, 1)
vterm.vterm_state_get_cursorpos(state, vterm.state_pos)
end
if screen then
vterm.vterm_screen_reset(screen, 1)
end
end
local function push(input, vt)
vterm.vterm_input_write(vt, input, string.len(input))
end
local function expect(expected)
local actual = read_rm()
t.eq(expected .. '\n', actual)
end
local function expect_output(expected_preformat)
local actual = read_rm()
local expected = 'output '
for c in string.gmatch(expected_preformat, '.') do
if expected ~= 'output ' then
expected = expected .. ','
end
expected = string.format('%s%x', expected, string.byte(c))
end
t.eq(expected .. '\n', actual)
end
local function cursor(row, col, state)
local pos = t.ffi.new('VTermPos') --- @type {row: integer, col: integer}
vterm.vterm_state_get_cursorpos(state, pos)
t.eq(row, pos.row)
t.eq(col, pos.col)
end
local function lineinfo(row, expected, state)
local info = vterm.vterm_state_get_lineinfo(state, row)
local dwl = info.doublewidth == 1
local dhl = info.doubleheight == 1
local cont = info.continuation == 1
t.eq(dwl, expected.dwl or false)
t.eq(dhl, expected.dhl or false)
t.eq(cont, expected.cont or false)
end
local function pen(attribute, expected, state)
local is_bool = { bold = true, italic = true, blink = true, reverse = true }
local vterm_attribute = {
bold = vterm.VTERM_ATTR_BOLD,
underline = vterm.VTERM_ATTR_UNDERLINE,
italic = vterm.VTERM_ATTR_ITALIC,
blink = vterm.VTERM_ATTR_BLINK,
reverse = vterm.VTERM_ATTR_REVERSE,
font = vterm.VTERM_ATTR_FONT,
}
local val = t.ffi.new('VTermValue') --- @type {boolean: integer}
vterm.vterm_state_get_penattr(state, vterm_attribute[attribute], val)
local actual = val.boolean --- @type integer|boolean
if is_bool[attribute] then
actual = val.boolean == 1
end
t.eq(expected, actual)
end
local function resize(rows, cols, vt)
vterm.vterm_set_size(vt, rows, cols)
end
local function screen_chars(start_row, start_col, end_row, end_col, expected, screen)
local rect = t.ffi.new('VTermRect')
rect['start_row'] = start_row
rect['start_col'] = start_col
rect['end_row'] = end_row
rect['end_col'] = end_col
local len = vterm.vterm_screen_get_text(screen, nil, 0, rect)
local text = t.ffi.new('unsigned char[?]', len)
vterm.vterm_screen_get_text(screen, text, len, rect)
local actual = t.ffi.string(text, len)
t.eq(expected, actual)
end
local function screen_text(start_row, start_col, end_row, end_col, expected, screen)
local rect = t.ffi.new('VTermRect')
rect['start_row'] = start_row
rect['start_col'] = start_col
rect['end_row'] = end_row
rect['end_col'] = end_col
local len = vterm.vterm_screen_get_text(screen, nil, 0, rect)
local text = t.ffi.new('unsigned char[?]', len)
vterm.vterm_screen_get_text(screen, text, len, rect)
local actual = ''
for i = 0, tonumber(len) - 1 do
actual = string.format('%s%02x,', actual, text[i])
end
actual = actual:sub(1, -2)
t.eq(expected, actual)
end
--- @param row integer
local function screen_row(row, expected, screen, end_col)
local rect = t.ffi.new('VTermRect')
rect['start_row'] = row
rect['start_col'] = 0
rect['end_row'] = row + 1
rect['end_col'] = end_col or 80
local len = vterm.vterm_screen_get_text(screen, nil, 0, rect)
local text = t.ffi.new('unsigned char[?]', len)
vterm.vterm_screen_get_text(screen, text, len, rect)
t.eq(expected, t.ffi.string(text, len))
end
local function screen_cell(row, col, expected, screen)
local pos = t.ffi.new('VTermPos')
pos['row'] = row
pos['col'] = col
local cell = t.ffi.new('VTermScreenCell') ---@type any
vterm.vterm_screen_get_cell(screen, pos, cell)
local buf = t.ffi.new('unsigned char[32]')
vterm.schar_get(buf, cell.schar)
local actual = '{'
local i = 0
while buf[i] > 0 do
local char = vterm.utf_ptr2char(buf + i)
local charlen = vterm.utf_ptr2len(buf + i)
if i > 0 then
actual = actual .. ','
end
local invalid = char >= 128 and charlen == 1
actual = string.format('%s%s%02x', actual, invalid and '?' or '', char)
i = i + charlen
end
actual = string.format('%s} width=%d attrs={', actual, cell['width'])
actual = actual .. (cell['attrs'].bold ~= 0 and 'B' or '')
actual = actual
.. (cell['attrs'].underline ~= 0 and string.format('U%d', cell['attrs'].underline) or '')
actual = actual .. (cell['attrs'].italic ~= 0 and 'I' or '')
actual = actual .. (cell['attrs'].blink ~= 0 and 'K' or '')
actual = actual .. (cell['attrs'].reverse ~= 0 and 'R' or '')
actual = actual .. (cell['attrs'].font ~= 0 and string.format('F%d', cell['attrs'].font) or '')
actual = actual .. (cell['attrs'].small ~= 0 and 'S' or '')
if cell['attrs'].baseline ~= 0 then
actual = actual .. (cell['attrs'].baseline == vterm.VTERM_BASELINE_RAISE and '^' or '_')
end
actual = actual .. '} '
actual = actual .. (cell['attrs'].dwl ~= 0 and 'dwl ' or '')
if cell['attrs'].dhl ~= 0 then
actual = actual .. string.format('dhl-%s ', cell['attrs'].dhl == 2 and 'bottom' or 'top')
end
actual = string.format('%sfg=', actual)
vterm.vterm_screen_convert_color_to_rgb(screen, cell['fg'])
vterm.print_color(cell['fg'])
actual = actual .. read_rm()
actual = actual .. ' bg='
vterm.vterm_screen_convert_color_to_rgb(screen, cell['bg'])
vterm.print_color(cell['bg'])
actual = actual .. read_rm()
t.eq(expected, actual)
end
local function screen_eol(row, col, expected, screen)
local pos = t.ffi.new('VTermPos')
pos['row'] = row
pos['col'] = col
local is_eol = vterm.vterm_screen_is_eol(screen, pos)
t.eq(expected, is_eol)
end
local function screen_attrs_extent(row, col, expected, screen)
local pos = t.ffi.new('VTermPos')
pos['row'] = row
pos['col'] = col
local rect = t.ffi.new('VTermRect')
rect['start_col'] = 0
rect['end_col'] = -1
vterm.vterm_screen_get_attrs_extent(screen, rect, pos, 1)
local actual = string.format(
'%d,%d-%d,%d',
rect['start_row'],
rect['start_col'],
rect['end_row'],
rect['end_col']
)
t.eq(expected, actual)
end
local function wantencoding()
local encoding = t.ffi.new('VTermEncodingInstance')
encoding['enc'] = vterm.vterm_lookup_encoding(vterm.ENC_UTF8, string.byte('u'))
if encoding.enc.init then
encoding.enc.init(encoding.enc, encoding['data'])
end
return encoding
end
local function encin(input, encoding)
local len = string.len(input)
local cp = t.ffi.new('uint32_t[?]', len)
local cpi = t.ffi.new('int[1]')
local pos = t.ffi.new('size_t[1]', 0)
encoding.enc.decode(encoding.enc, encoding.data, cp, cpi, len, input, pos, len)
local f = assert(io.open(t.paths.vterm_test_file, 'w'))
if tonumber(cpi[0]) > 0 then
f:write('encout ')
for i = 0, cpi[0] - 1 do
if i == 0 then
f:write(string.format('%x', cp[i]))
else
f:write(string.format(',%x', cp[i]))
end
end
f:write('\n')
end
f:close()
end
local function strpe_modifiers(input_mod)
local mod = t.ffi.new('VTermModifier') ---@type any
if input_mod.C then
mod = bit.bor(mod, vterm.VTERM_MOD_CTRL)
end
if input_mod.S then
mod = bit.bor(mod, vterm.VTERM_MOD_SHIFT)
end
if input_mod.A then
mod = bit.bor(mod, vterm.VTERM_MOD_ALT)
end
return mod
end
local function strp_key(input_key)
if input_key == 'up' then
return vterm.VTERM_KEY_UP
end
if input_key == 'tab' then
return vterm.VTERM_KEY_TAB
end
if input_key == 'enter' then
return vterm.VTERM_KEY_ENTER
end
if input_key == 'bs' then
return vterm.VTERM_KEY_BACKSPACE
end
if input_key == 'del' then
return vterm.VTERM_KEY_DEL
end
if input_key == 'esc' then
return vterm.VTERM_KEY_ESCAPE
end
if input_key == 'f1' then
return vterm.VTERM_KEY_FUNCTION_0 + 1
end
if input_key == 'kp0' then
return vterm.VTERM_KEY_KP_0
end
return vterm.VTERM_KEY_NONE
end
local function mousemove(row, col, vt, input_mod)
input_mod = input_mod or {}
local mod = strpe_modifiers(input_mod)
vterm.vterm_mouse_move(vt, row, col, mod)
end
local function mousebtn(press, button, vt, input_mod)
input_mod = input_mod or {}
local mod = strpe_modifiers(input_mod)
local flag = press == 'd' or press == 'D'
vterm.vterm_mouse_button(vt, button, flag, mod)
end
local function inchar(c, vt, input_mod)
input_mod = input_mod or {}
local mod = strpe_modifiers(input_mod)
vterm.vterm_keyboard_unichar(vt, c, mod)
end
local function inkey(input_key, vt, input_mod)
input_mod = input_mod or {}
local mod = strpe_modifiers(input_mod)
local key = strp_key(input_key)
vterm.vterm_keyboard_key(vt, key, mod)
end
before_each(function()
vim.fs.rm(t.paths.vterm_test_file, { force = true })
end)
describe('vterm', function()
itp('02parser', function()
local vt = init()
vterm.vterm_set_utf8(vt, false)
wantparser(vt)
-- Basic text
push('hello', vt)
expect('text 68,65,6c,6c,6f')
-- C0
push('\x03', vt)
expect('control 03')
push('\x1f', vt)
expect('control 1f')
-- C1 8bit
push('\x83', vt)
expect('control 83')
push('\x99', vt)
expect('control 99')
-- C1 7bit
push('\x1b\x43', vt)
expect('control 83')
push('\x1b\x59', vt)
expect('control 99')
-- High bytes
push('\xa0\xcc\xfe', vt)
expect('text a0,cc,fe')
-- Mixed
push('1\n2', vt)
expect('text 31\ncontrol 0a\ntext 32')
-- Escape
push('\x1b=', vt)
expect('escape =')
-- Escape 2-byte
push('\x1b(X', vt)
expect('escape (X')
-- Split write Escape
push('\x1b(', vt)
push('Y', vt)
expect('escape (Y')
-- Escape cancels Escape, starts another
push('\x1b(\x1b)Z', vt)
expect('escape )Z')
-- CAN cancels Escape, returns to normal mode
push('\x1b(\x18AB', vt)
expect('text 41,42')
-- C0 in Escape interrupts and continues
push('\x1b(\nX', vt)
expect('control 0a\nescape (X')
-- CSI 0 args
push('\x1b[a', vt)
expect('csi 61 *')
-- CSI 1 arg
push('\x1b[9b', vt)
expect('csi 62 9')
-- CSI 2 args
push('\x1b[3;4c', vt)
expect('csi 63 3,4')
-- CSI 1 arg 1 sub
push('\x1b[1:2c', vt)
expect('csi 63 1+,2')
-- CSI many digits
push('\x1b[678d', vt)
expect('csi 64 678')
-- CSI leading zero
push('\x1b[007e', vt)
expect('csi 65 7')
-- CSI qmark
push('\x1b[?2;7f', vt)
expect('csi 66 L=3f 2,7')
-- CSI greater
push('\x1b[>c', vt)
expect('csi 63 L=3e *')
-- CSI SP
push('\x1b[12 q', vt)
expect('csi 71 12 I=20')
-- Mixed CSI
push('A\x1b[8mB', vt)
expect('text 41\ncsi 6d 8\ntext 42')
-- Split write
push('\x1b', vt)
push('[a', vt)
expect('csi 61 *')
push('foo\x1b[', vt)
expect('text 66,6f,6f')
push('4b', vt)
expect('csi 62 4')
push('\x1b[12;', vt)
push('3c', vt)
expect('csi 63 12,3')
-- Escape cancels CSI, starts Escape
push('\x1b[123\x1b9', vt)
expect('escape 9')
-- CAN cancels CSI, returns to normal mode
push('\x1b[12\x18AB', vt)
expect('text 41,42')
-- C0 in Escape interrupts and continues
push('\x1b(\nX', vt)
expect('control 0a\nescape (X')
-- OSC BEL
push('\x1b]1;Hello\x07', vt)
expect('osc [1;Hello]')
-- OSC ST (7bit)
push('\x1b]1;Hello\x1b\\', vt)
expect('osc [1;Hello]')
-- OSC ST (8bit)
push('\x9d1;Hello\x9c', vt)
expect('osc [1;Hello]')
-- OSC in parts
push('\x1b]52;abc', vt)
expect('osc [52;abc')
push('def', vt)
expect('osc def')
push('ghi\x1b\\', vt)
expect('osc ghi]')
-- OSC BEL without semicolon
push('\x1b]1234\x07', vt)
expect('osc [1234;]')
-- OSC ST without semicolon
push('\x1b]1234\x1b\\', vt)
expect('osc [1234;]')
-- Escape cancels OSC, starts Escape
push('\x1b]Something\x1b9', vt)
expect('escape 9')
-- CAN cancels OSC, returns to normal mode
push('\x1b]12\x18AB', vt)
expect('text 41,42')
-- C0 in OSC interrupts and continues
push('\x1b]2;\nBye\x07', vt)
expect('osc [2;\ncontrol 0a\nosc Bye]')
-- DCS BEL
push('\x1bPHello\x07', vt)
expect('dcs [Hello]')
-- DCS ST (7bit)
push('\x1bPHello\x1b\\', vt)
expect('dcs [Hello]')
-- DCS ST (8bit)
push('\x90Hello\x9c', vt)
expect('dcs [Hello]')
-- Split write of 7bit ST
push('\x1bPABC\x1b', vt)
expect('dcs [ABC')
push('\\', vt)
expect('dcs ]')
-- Escape cancels DCS, starts Escape
push('\x1bPSomething\x1b9', vt)
expect('escape 9')
-- CAN cancels DCS, returns to normal mode
push('\x1bP12\x18AB', vt)
expect('text 41,42')
-- C0 in OSC interrupts and continues
push('\x1bPBy\ne\x07', vt)
expect('dcs [By\ncontrol 0a\ndcs e]')
-- APC BEL
push('\x1b_Hello\x07', vt)
expect('apc [Hello]')
-- APC ST (7bit)
push('\x1b_Hello\x1b\\', vt)
expect('apc [Hello]')
-- APC ST (8bit)
push('\x9fHello\x9c', vt)
expect('apc [Hello]')
-- PM BEL
push('\x1b^Hello\x07', vt)
expect('pm [Hello]')
-- PM ST (7bit)
push('\x1b^Hello\x1b\\', vt)
expect('pm [Hello]')
-- PM ST (8bit)
push('\x9eHello\x9c', vt)
expect('pm [Hello]')
-- SOS BEL
push('\x1bXHello\x07', vt)
expect('sos [Hello]')
-- SOS ST (7bit)
push('\x1bXHello\x1b\\', vt)
expect('sos [Hello]')
-- SOS ST (8bit)
push('\x98Hello\x9c', vt)
expect('sos [Hello]')
push('\x1bXABC\x01DEF\x1b\\', vt)
expect('sos [ABC\x01DEF]')
push('\x1bXABC\x99DEF\x1b\\', vt)
expect('sos [ABC\x99DEF]')
-- NUL ignored
push('\x00', vt)
-- NUL ignored within CSI
push('\x1b[12\x003m', vt)
expect('csi 6d 123')
-- DEL ignored
push('\x7f', vt)
-- DEL ignored within CSI
push('\x1b[12\x7f3m', vt)
expect('csi 6d 123')
-- DEL inside text"
push('AB\x7fC', vt)
expect('text 41,42\ntext 43')
end)
itp('03encoding_utf8', function()
local encoding = wantencoding()
-- Low
encin('123', encoding)
expect('encout 31,32,33')
-- We want to prove the UTF-8 parser correctly handles all the sequences.
-- Easy way to do this is to check it does low/high boundary cases, as that
-- leaves only two for each sequence length
--
-- These ranges are therefore:
--
-- Two bytes:
-- U+0080 = 000 10000000 => 00010 000000
-- => 11000010 10000000 = C2 80
-- U+07FF = 111 11111111 => 11111 111111
-- => 11011111 10111111 = DF BF
--
-- Three bytes:
-- U+0800 = 00001000 00000000 => 0000 100000 000000
-- => 11100000 10100000 10000000 = E0 A0 80
-- U+FFFD = 11111111 11111101 => 1111 111111 111101
-- => 11101111 10111111 10111101 = EF BF BD
-- (We avoid U+FFFE and U+FFFF as they're invalid codepoints)
--
-- Four bytes:
-- U+10000 = 00001 00000000 00000000 => 000 010000 000000 000000
-- => 11110000 10010000 10000000 10000000 = F0 90 80 80
-- U+1FFFFF = 11111 11111111 11111111 => 111 111111 111111 111111
-- => 11110111 10111111 10111111 10111111 = F7 BF BF BF
-- 2 byte
encin('\xC2\x80\xDF\xBF', encoding)
expect('encout 80,7ff')
-- 3 byte
encin('\xE0\xA0\x80\xEF\xBF\xBD', encoding)
expect('encout 800,fffd')
-- 4 byte
encin('\xF0\x90\x80\x80\xF7\xBF\xBF\xBF', encoding)
expect('encout 10000,1fffff')
-- Next up, we check some invalid sequences
-- + Early termination (back to low bytes too soon)
-- + Early restart (another sequence introduction before the previous one was finished)
-- Early termination
encin('\xC2!', encoding)
expect('encout fffd,21')
encin('\xE0!\xE0\xA0!', encoding)
expect('encout fffd,21,fffd,21')
encin('\xF0!\xF0\x90!\xF0\x90\x80!', encoding)
expect('encout fffd,21,fffd,21,fffd,21')
-- Early restart
encin('\xC2\xC2\x90', encoding)
expect('encout fffd,90')
encin('\xE0\xC2\x90\xE0\xA0\xC2\x90', encoding)
expect('encout fffd,90,fffd,90')
encin('\xF0\xC2\x90\xF0\x90\xC2\x90\xF0\x90\x80\xC2\x90', encoding)
expect('encout fffd,90,fffd,90,fffd,90')
-- Test the overlong sequences by giving an overlong encoding of U+0000 and
-- an encoding of the highest codepoint still too short
--
-- Two bytes:
-- U+0000 = C0 80
-- U+007F = 000 01111111 => 00001 111111 =>
-- => 11000001 10111111 => C1 BF
--
-- Three bytes:
-- U+0000 = E0 80 80
-- U+07FF = 00000111 11111111 => 0000 011111 111111
-- => 11100000 10011111 10111111 = E0 9F BF
--
-- Four bytes:
-- U+0000 = F0 80 80 80
-- U+FFFF = 11111111 11111111 => 000 001111 111111 111111
-- => 11110000 10001111 10111111 10111111 = F0 8F BF BF
-- Overlong
encin('\xC0\x80\xC1\xBF', encoding)
expect('encout fffd,fffd')
encin('\xE0\x80\x80\xE0\x9F\xBF', encoding)
expect('encout fffd,fffd')
encin('\xF0\x80\x80\x80\xF0\x8F\xBF\xBF', encoding)
expect('encout fffd,fffd')
-- UTF-16 surrogates U+D800 and U+DFFF
-- UTF-16 Surrogates
encin('\xED\xA0\x80\xED\xBF\xBF', encoding)
expect('encout fffd,fffd')
-- Split write
encin('\xC2', encoding)
encin('\xA0', encoding)
expect('encout a0')
encin('\xE0', encoding)
encin('\xA0\x80', encoding)
expect('encout 800')
encin('\xE0\xA0', encoding)
encin('\x80', encoding)
expect('encout 800')
encin('\xF0', encoding)
encin('\x90\x80\x80', encoding)
expect('encout 10000')
encin('\xF0\x90', encoding)
encin('\x80\x80', encoding)
expect('encout 10000')
encin('\xF0\x90\x80', encoding)
encin('\x80', encoding)
expect('encout 10000')
end)
itp('10state_putglyph', function()
local vt = init()
local state = wantstate(vt, { g = true })
-- Low
reset(state, nil)
push('ABC', vt)
expect('putglyph 41 1 0,0\nputglyph 42 1 0,1\nputglyph 43 1 0,2')
-- UTF-8 1 char
-- U+00C1 = 0xC3 0x81 name: LATIN CAPITAL LETTER A WITH ACUTE
-- U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE
reset(state, nil)
push('\xC3\x81\xC3\xA9', vt)
expect('putglyph c1 1 0,0\nputglyph e9 1 0,1')
-- UTF-8 split writes
reset(state, nil)
push('\xC3', vt)
push('\x81', vt)
expect('putglyph c1 1 0,0')
-- UTF-8 wide char
-- U+FF10 = EF BC 90 name: FULLWIDTH DIGIT ZERO
reset(state, nil)
push('\xEF\xBC\x90 ', vt)
expect('putglyph ff10 2 0,0\nputglyph 20 1 0,2')
-- UTF-8 emoji wide char
-- U+1F600 = F0 9F 98 80 name: GRINNING FACE
reset(state, nil)
push('\xF0\x9F\x98\x80 ', vt)
expect('putglyph 1f600 2 0,0\nputglyph 20 1 0,2')
-- UTF-8 combining chars
-- U+0301 = CC 81 name: COMBINING ACUTE
reset(state, nil)
push('e\xCC\x81Z', vt)
expect('putglyph 65,301 1 0,0\nputglyph 5a 1 0,1')
-- Combining across buffers
reset(state, nil)
push('e', vt)
expect('putglyph 65 1 0,0')
push('\xCC\x81Z', vt)
expect('putglyph 65,301 1 0,0\nputglyph 5a 1 0,1')
-- Spare combining chars get truncated
reset(state, nil)
push('e' .. string.rep('\xCC\x81', 20), vt)
expect('putglyph 65,301,301,301,301,301,301,301,301,301,301,301,301,301,301 1 0,0') -- and nothing more
reset(state, nil)
push('e', vt)
expect('putglyph 65 1 0,0')
push('\xCC\x81', vt)
expect('putglyph 65,301 1 0,0')
push('\xCC\x82', vt)