diff options
| author | Josh Rahm <joshuarahm@gmail.com> | 2024-11-19 22:57:13 +0000 |
|---|---|---|
| committer | Josh Rahm <joshuarahm@gmail.com> | 2024-11-19 22:57:13 +0000 |
| commit | 9be89f131f87608f224f0ee06d199fcd09d32176 (patch) | |
| tree | 11022dcfa9e08cb4ac5581b16734196128688d48 /test/unit | |
| parent | ff7ed8f586589d620a806c3758fac4a47a8e7e15 (diff) | |
| parent | 88085c2e80a7e3ac29aabb6b5420377eed99b8b6 (diff) | |
| download | rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.gz rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.bz2 rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.zip | |
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'test/unit')
| -rw-r--r-- | test/unit/api/private_helpers_spec.lua | 6 | ||||
| -rw-r--r-- | test/unit/api/testutil.lua | 12 | ||||
| -rw-r--r-- | test/unit/eval/encode_spec.lua | 36 | ||||
| -rw-r--r-- | test/unit/eval/typval_spec.lua | 136 | ||||
| -rw-r--r-- | test/unit/fixtures/rbuffer.c | 28 | ||||
| -rw-r--r-- | test/unit/fixtures/rbuffer.h | 9 | ||||
| -rw-r--r-- | test/unit/formatc.lua | 1 | ||||
| -rw-r--r-- | test/unit/mbyte_spec.lua | 138 | ||||
| -rw-r--r-- | test/unit/path_spec.lua | 34 | ||||
| -rw-r--r-- | test/unit/rbuffer_spec.lua | 340 | ||||
| -rw-r--r-- | test/unit/statusline_spec.lua | 4 | ||||
| -rw-r--r-- | test/unit/termkey_spec.lua | 975 |
12 files changed, 1194 insertions, 525 deletions
diff --git a/test/unit/api/private_helpers_spec.lua b/test/unit/api/private_helpers_spec.lua index a31374bd70..bdfc83a031 100644 --- a/test/unit/api/private_helpers_spec.lua +++ b/test/unit/api/private_helpers_spec.lua @@ -43,9 +43,9 @@ describe('vim_to_object', function() simple_test('converts empty string', '') simple_test('converts non-empty string', 'foobar') simple_test('converts integer 10', { [type_key] = int_type, value = 10 }) - simple_test('converts empty dictionary', {}) - simple_test('converts dictionary with scalar values', { test = 10, test2 = true, test3 = 'test' }) - simple_test('converts dictionary with containers inside', { test = {}, test2 = { 1, 2 } }) + simple_test('converts empty dict', {}) + simple_test('converts dict with scalar values', { test = 10, test2 = true, test3 = 'test' }) + simple_test('converts dict with containers inside', { test = {}, test2 = { 1, 2 } }) simple_test('converts empty list', { [type_key] = list_type }) simple_test('converts list with scalar values', { 1, 2, 'test', 'foo' }) simple_test( diff --git a/test/unit/api/testutil.lua b/test/unit/api/testutil.lua index 0946ef194c..bb387ae0e1 100644 --- a/test/unit/api/testutil.lua +++ b/test/unit/api/testutil.lua @@ -35,10 +35,10 @@ local function init_obj2lua_tab() end return ret end, - [tonumber(api.kObjectTypeDictionary)] = function(obj) + [tonumber(api.kObjectTypeDict)] = function(obj) local ret = {} - for i = 1, tonumber(obj.data.dictionary.size) do - local kv_pair = obj.data.dictionary.items[i - 1] + for i = 1, tonumber(obj.data.dict.size) do + local kv_pair = obj.data.dict.items[i - 1] ret[ffi.string(kv_pair.key.data, kv_pair.key.size)] = obj2lua(kv_pair.value) end return ret @@ -112,8 +112,8 @@ local lua2obj_type_tab = { end end local len = #kvs - local dct = obj(api.kObjectTypeDictionary, { - dictionary = { + local dct = obj(api.kObjectTypeDict, { + dict = { size = len, capacity = len, items = ffi.cast('KeyValuePair *', api.xmalloc(len * ffi.sizeof('KeyValuePair'))), @@ -121,7 +121,7 @@ local lua2obj_type_tab = { }) for i = 1, len do local key, val = unpack(kvs[i]) - dct.data.dictionary.items[i - 1] = ffi.new( + dct.data.dict.items[i - 1] = ffi.new( 'KeyValuePair', { key = ffi.gc(lua2obj(key), nil).data.string, value = ffi.gc(lua2obj(val), nil) } ) diff --git a/test/unit/eval/encode_spec.lua b/test/unit/eval/encode_spec.lua index 5b9188163e..9f193bc2f9 100644 --- a/test/unit/eval/encode_spec.lua +++ b/test/unit/eval/encode_spec.lua @@ -21,81 +21,81 @@ describe('encode_list_write()', function() itp('writes empty string', function() local l = list() - eq(0, encode_list_write(l, '')) + encode_list_write(l, '') eq({ [type_key] = list_type }, lst2tbl(l)) end) itp('writes ASCII string literal with printable characters', function() local l = list() - eq(0, encode_list_write(l, 'abc')) + encode_list_write(l, 'abc') eq({ 'abc' }, lst2tbl(l)) end) itp('writes string starting with NL', function() local l = list() - eq(0, encode_list_write(l, '\nabc')) + encode_list_write(l, '\nabc') eq({ null_string, 'abc' }, lst2tbl(l)) end) itp('writes string starting with NL twice', function() local l = list() - eq(0, encode_list_write(l, '\nabc')) + encode_list_write(l, '\nabc') eq({ null_string, 'abc' }, lst2tbl(l)) - eq(0, encode_list_write(l, '\nabc')) + encode_list_write(l, '\nabc') eq({ null_string, 'abc', 'abc' }, lst2tbl(l)) end) itp('writes string ending with NL', function() local l = list() - eq(0, encode_list_write(l, 'abc\n')) + encode_list_write(l, 'abc\n') eq({ 'abc', null_string }, lst2tbl(l)) end) itp('writes string ending with NL twice', function() local l = list() - eq(0, encode_list_write(l, 'abc\n')) + encode_list_write(l, 'abc\n') eq({ 'abc', null_string }, lst2tbl(l)) - eq(0, encode_list_write(l, 'abc\n')) + encode_list_write(l, 'abc\n') eq({ 'abc', 'abc', null_string }, lst2tbl(l)) end) itp('writes string starting, ending and containing NL twice', function() local l = list() - eq(0, encode_list_write(l, '\na\nb\n')) + encode_list_write(l, '\na\nb\n') eq({ null_string, 'a', 'b', null_string }, lst2tbl(l)) - eq(0, encode_list_write(l, '\na\nb\n')) + encode_list_write(l, '\na\nb\n') eq({ null_string, 'a', 'b', null_string, 'a', 'b', null_string }, lst2tbl(l)) end) itp('writes string starting, ending and containing NUL with NL between twice', function() local l = list() - eq(0, encode_list_write(l, '\0\n\0\n\0')) + encode_list_write(l, '\0\n\0\n\0') eq({ '\n', '\n', '\n' }, lst2tbl(l)) - eq(0, encode_list_write(l, '\0\n\0\n\0')) + encode_list_write(l, '\0\n\0\n\0') eq({ '\n', '\n', '\n\n', '\n', '\n' }, lst2tbl(l)) end) itp('writes string starting, ending and containing NL with NUL between twice', function() local l = list() - eq(0, encode_list_write(l, '\n\0\n\0\n')) + encode_list_write(l, '\n\0\n\0\n') eq({ null_string, '\n', '\n', null_string }, lst2tbl(l)) - eq(0, encode_list_write(l, '\n\0\n\0\n')) + encode_list_write(l, '\n\0\n\0\n') eq({ null_string, '\n', '\n', null_string, '\n', '\n', null_string }, lst2tbl(l)) end) itp('writes string containing a single NL twice', function() local l = list() - eq(0, encode_list_write(l, '\n')) + encode_list_write(l, '\n') eq({ null_string, null_string }, lst2tbl(l)) - eq(0, encode_list_write(l, '\n')) + encode_list_write(l, '\n') eq({ null_string, null_string, null_string }, lst2tbl(l)) end) itp('writes string containing a few NLs twice', function() local l = list() - eq(0, encode_list_write(l, '\n\n\n')) + encode_list_write(l, '\n\n\n') eq({ null_string, null_string, null_string, null_string }, lst2tbl(l)) - eq(0, encode_list_write(l, '\n\n\n')) + encode_list_write(l, '\n\n\n') eq( { null_string, null_string, null_string, null_string, null_string, null_string, null_string }, lst2tbl(l) diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index c69c9b0fae..14fd7986e7 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -1267,26 +1267,19 @@ describe('typval.c', function() local l2 = list() -- NULL lists are equal to empty lists - eq(true, lib.tv_list_equal(l, nil, true, false)) - eq(true, lib.tv_list_equal(nil, l, false, false)) - eq(true, lib.tv_list_equal(nil, l, false, true)) - eq(true, lib.tv_list_equal(l, nil, true, true)) + eq(true, lib.tv_list_equal(l, nil, true)) + eq(true, lib.tv_list_equal(nil, l, false)) -- NULL lists are equal themselves - eq(true, lib.tv_list_equal(nil, nil, true, false)) - eq(true, lib.tv_list_equal(nil, nil, false, false)) - eq(true, lib.tv_list_equal(nil, nil, false, true)) - eq(true, lib.tv_list_equal(nil, nil, true, true)) + eq(true, lib.tv_list_equal(nil, nil, true)) + eq(true, lib.tv_list_equal(nil, nil, false)) -- As well as empty lists - eq(true, lib.tv_list_equal(l, l, true, false)) - eq(true, lib.tv_list_equal(l, l2, false, false)) - eq(true, lib.tv_list_equal(l2, l, false, true)) - eq(true, lib.tv_list_equal(l2, l2, true, true)) - end) - -- Must not use recursive=true argument in the following tests because it - -- indicates that tv_equal_recurse_limit and recursive_cnt were set which - -- is essential. This argument will be set when comparing inner lists. + eq(true, lib.tv_list_equal(l, l, true)) + eq(true, lib.tv_list_equal(l, l2, false)) + eq(true, lib.tv_list_equal(l2, l, false)) + eq(true, lib.tv_list_equal(l2, l2, true)) + end) itp('compares lists correctly when case is not ignored', function() local l1 = list('abc', { 1, 2, 'Abc' }, 'def') local l2 = list('abc', { 1, 2, 'Abc' }) @@ -1298,15 +1291,15 @@ describe('typval.c', function() local l8 = list('abc', nil, 'def') local l9 = list('abc', { 1, 2, nil }, 'def') - eq(true, lib.tv_list_equal(l1, l1, false, false)) - eq(false, lib.tv_list_equal(l1, l2, false, false)) - eq(false, lib.tv_list_equal(l1, l3, false, false)) - eq(false, lib.tv_list_equal(l1, l4, false, false)) - eq(false, lib.tv_list_equal(l1, l5, false, false)) - eq(true, lib.tv_list_equal(l1, l6, false, false)) - eq(false, lib.tv_list_equal(l1, l7, false, false)) - eq(false, lib.tv_list_equal(l1, l8, false, false)) - eq(false, lib.tv_list_equal(l1, l9, false, false)) + eq(true, lib.tv_list_equal(l1, l1, false)) + eq(false, lib.tv_list_equal(l1, l2, false)) + eq(false, lib.tv_list_equal(l1, l3, false)) + eq(false, lib.tv_list_equal(l1, l4, false)) + eq(false, lib.tv_list_equal(l1, l5, false)) + eq(true, lib.tv_list_equal(l1, l6, false)) + eq(false, lib.tv_list_equal(l1, l7, false)) + eq(false, lib.tv_list_equal(l1, l8, false)) + eq(false, lib.tv_list_equal(l1, l9, false)) end) itp('compares lists correctly when case is ignored', function() local l1 = list('abc', { 1, 2, 'Abc' }, 'def') @@ -1319,15 +1312,15 @@ describe('typval.c', function() local l8 = list('abc', nil, 'def') local l9 = list('abc', { 1, 2, nil }, 'def') - eq(true, lib.tv_list_equal(l1, l1, true, false)) - eq(false, lib.tv_list_equal(l1, l2, true, false)) - eq(true, lib.tv_list_equal(l1, l3, true, false)) - eq(false, lib.tv_list_equal(l1, l4, true, false)) - eq(true, lib.tv_list_equal(l1, l5, true, false)) - eq(true, lib.tv_list_equal(l1, l6, true, false)) - eq(true, lib.tv_list_equal(l1, l7, true, false)) - eq(false, lib.tv_list_equal(l1, l8, true, false)) - eq(false, lib.tv_list_equal(l1, l9, true, false)) + eq(true, lib.tv_list_equal(l1, l1, true)) + eq(false, lib.tv_list_equal(l1, l2, true)) + eq(true, lib.tv_list_equal(l1, l3, true)) + eq(false, lib.tv_list_equal(l1, l4, true)) + eq(true, lib.tv_list_equal(l1, l5, true)) + eq(true, lib.tv_list_equal(l1, l6, true)) + eq(true, lib.tv_list_equal(l1, l7, true)) + eq(false, lib.tv_list_equal(l1, l8, true)) + eq(false, lib.tv_list_equal(l1, l9, true)) end) end) describe('find', function() @@ -2326,7 +2319,7 @@ describe('typval.c', function() return lib.tv_dict_extend(d1, d2, action) end, emsg) end - itp('works', function() + pending('works (skip due to flakiness)', function() local d1 = dict() alloc_log:check({ a.dict(d1) }) eq({}, dct2tbl(d1)) @@ -2448,8 +2441,8 @@ describe('typval.c', function() end) end) describe('equal()', function() - local function tv_dict_equal(d1, d2, ic, recursive) - return lib.tv_dict_equal(d1, d2, ic or false, recursive or false) + local function tv_dict_equal(d1, d2, ic) + return lib.tv_dict_equal(d1, d2, ic or false) end itp('works', function() eq(true, tv_dict_equal(nil, nil)) @@ -2494,7 +2487,6 @@ describe('typval.c', function() eq(true, tv_dict_equal(d_kupper_upper, d_kupper_lower, true)) eq(false, tv_dict_equal(d_kupper_upper, d_lower, true)) eq(false, tv_dict_equal(d_kupper_upper, d_upper, true)) - eq(true, tv_dict_equal(d_upper, d_upper, true, true)) alloc_log:check({}) end) end) @@ -2923,26 +2915,19 @@ describe('typval.c', function() local nl = lua2typvalt(null_list) -- NULL lists are equal to empty lists - eq(true, lib.tv_equal(l, nl, true, false)) - eq(true, lib.tv_equal(nl, l, false, false)) - eq(true, lib.tv_equal(nl, l, false, true)) - eq(true, lib.tv_equal(l, nl, true, true)) + eq(true, lib.tv_equal(l, nl, true)) + eq(true, lib.tv_equal(nl, l, false)) -- NULL lists are equal themselves - eq(true, lib.tv_equal(nl, nl, true, false)) - eq(true, lib.tv_equal(nl, nl, false, false)) - eq(true, lib.tv_equal(nl, nl, false, true)) - eq(true, lib.tv_equal(nl, nl, true, true)) + eq(true, lib.tv_equal(nl, nl, true)) + eq(true, lib.tv_equal(nl, nl, false)) -- As well as empty lists - eq(true, lib.tv_equal(l, l, true, false)) - eq(true, lib.tv_equal(l, l2, false, false)) - eq(true, lib.tv_equal(l2, l, false, true)) - eq(true, lib.tv_equal(l2, l2, true, true)) - end) - -- Must not use recursive=true argument in the following tests because it - -- indicates that tv_equal_recurse_limit and recursive_cnt were set which - -- is essential. This argument will be set when comparing inner lists. + eq(true, lib.tv_equal(l, l, true)) + eq(true, lib.tv_equal(l, l2, false)) + eq(true, lib.tv_equal(l2, l, false)) + eq(true, lib.tv_equal(l2, l2, true)) + end) itp('compares lists correctly when case is not ignored', function() local l1 = lua2typvalt({ 'abc', { 1, 2, 'Abc' }, 'def' }) local l2 = lua2typvalt({ 'abc', { 1, 2, 'Abc' } }) @@ -2954,15 +2939,15 @@ describe('typval.c', function() local l8 = lua2typvalt({ 'abc', nil, 'def' }) local l9 = lua2typvalt({ 'abc', { 1, 2, nil }, 'def' }) - eq(true, lib.tv_equal(l1, l1, false, false)) - eq(false, lib.tv_equal(l1, l2, false, false)) - eq(false, lib.tv_equal(l1, l3, false, false)) - eq(false, lib.tv_equal(l1, l4, false, false)) - eq(false, lib.tv_equal(l1, l5, false, false)) - eq(true, lib.tv_equal(l1, l6, false, false)) - eq(false, lib.tv_equal(l1, l7, false, false)) - eq(false, lib.tv_equal(l1, l8, false, false)) - eq(false, lib.tv_equal(l1, l9, false, false)) + eq(true, lib.tv_equal(l1, l1, false)) + eq(false, lib.tv_equal(l1, l2, false)) + eq(false, lib.tv_equal(l1, l3, false)) + eq(false, lib.tv_equal(l1, l4, false)) + eq(false, lib.tv_equal(l1, l5, false)) + eq(true, lib.tv_equal(l1, l6, false)) + eq(false, lib.tv_equal(l1, l7, false)) + eq(false, lib.tv_equal(l1, l8, false)) + eq(false, lib.tv_equal(l1, l9, false)) end) itp('compares lists correctly when case is ignored', function() local l1 = lua2typvalt({ 'abc', { 1, 2, 'Abc' }, 'def' }) @@ -2975,18 +2960,18 @@ describe('typval.c', function() local l8 = lua2typvalt({ 'abc', nil, 'def' }) local l9 = lua2typvalt({ 'abc', { 1, 2, nil }, 'def' }) - eq(true, lib.tv_equal(l1, l1, true, false)) - eq(false, lib.tv_equal(l1, l2, true, false)) - eq(true, lib.tv_equal(l1, l3, true, false)) - eq(false, lib.tv_equal(l1, l4, true, false)) - eq(true, lib.tv_equal(l1, l5, true, false)) - eq(true, lib.tv_equal(l1, l6, true, false)) - eq(true, lib.tv_equal(l1, l7, true, false)) - eq(false, lib.tv_equal(l1, l8, true, false)) - eq(false, lib.tv_equal(l1, l9, true, false)) - end) - local function tv_equal(d1, d2, ic, recursive) - return lib.tv_equal(d1, d2, ic or false, recursive or false) + eq(true, lib.tv_equal(l1, l1, true)) + eq(false, lib.tv_equal(l1, l2, true)) + eq(true, lib.tv_equal(l1, l3, true)) + eq(false, lib.tv_equal(l1, l4, true)) + eq(true, lib.tv_equal(l1, l5, true)) + eq(true, lib.tv_equal(l1, l6, true)) + eq(true, lib.tv_equal(l1, l7, true)) + eq(false, lib.tv_equal(l1, l8, true)) + eq(false, lib.tv_equal(l1, l9, true)) + end) + local function tv_equal(d1, d2, ic) + return lib.tv_equal(d1, d2, ic or false) end itp('works with dictionaries', function() local nd = lua2typvalt(null_dict) @@ -3033,7 +3018,6 @@ describe('typval.c', function() eq(true, tv_equal(d_kupper_upper, d_kupper_lower, true)) eq(false, tv_equal(d_kupper_upper, d_lower, true)) eq(false, tv_equal(d_kupper_upper, d_upper, true)) - eq(true, tv_equal(d_upper, d_upper, true, true)) alloc_log:check({}) end) end) diff --git a/test/unit/fixtures/rbuffer.c b/test/unit/fixtures/rbuffer.c deleted file mode 100644 index d587d6b054..0000000000 --- a/test/unit/fixtures/rbuffer.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "nvim/rbuffer.h" -#include "rbuffer.h" - - -void ut_rbuffer_each_read_chunk(RBuffer *buf, each_ptr_cb cb) -{ - RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) { - cb(rptr, rcnt); - rbuffer_consumed(buf, rcnt); - } -} - -void ut_rbuffer_each_write_chunk(RBuffer *buf, each_ptr_cb cb) -{ - RBUFFER_UNTIL_FULL(buf, wptr, wcnt) { - cb(wptr, wcnt); - rbuffer_produced(buf, wcnt); - } -} -void ut_rbuffer_each(RBuffer *buf, each_cb cb) -{ - RBUFFER_EACH(buf, c, i) cb(c, i); -} - -void ut_rbuffer_each_reverse(RBuffer *buf, each_cb cb) -{ - RBUFFER_EACH_REVERSE(buf, c, i) cb(c, i); -} diff --git a/test/unit/fixtures/rbuffer.h b/test/unit/fixtures/rbuffer.h deleted file mode 100644 index 640092c627..0000000000 --- a/test/unit/fixtures/rbuffer.h +++ /dev/null @@ -1,9 +0,0 @@ -#include "nvim/rbuffer.h" - -typedef void(*each_ptr_cb)(char *ptr, size_t cnt); -typedef void(*each_cb)(char c, size_t i); - -void ut_rbuffer_each_read_chunk(RBuffer *buf, each_ptr_cb cb); -void ut_rbuffer_each_write_chunk(RBuffer *buf, each_ptr_cb cb); -void ut_rbuffer_each(RBuffer *buf, each_cb cb); -void ut_rbuffer_each_reverse(RBuffer *buf, each_cb cb); diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua index ce9cb81f4a..04a8b4009f 100644 --- a/test/unit/formatc.lua +++ b/test/unit/formatc.lua @@ -264,6 +264,7 @@ local function formatc(str) -- and ';' indicates we're at the end of a statement, so we put end -- it with a newline. token[1] = ';\n' + end_at_brace = false end elseif typ == 'whitespace' then -- replace all whitespace by one space diff --git a/test/unit/mbyte_spec.lua b/test/unit/mbyte_spec.lua index 8fcc67d20b..bdc111de2c 100644 --- a/test/unit/mbyte_spec.lua +++ b/test/unit/mbyte_spec.lua @@ -3,8 +3,14 @@ local itp = t.gen_itp(it) local ffi = t.ffi local eq = t.eq +local to_cstr = t.to_cstr -local lib = t.cimport('./src/nvim/mbyte.h', './src/nvim/charset.h', './src/nvim/grid.h') +local lib = t.cimport( + './src/nvim/mbyte.h', + './src/nvim/charset.h', + './src/nvim/grid.h', + './src/nvim/option_vars.h' +) describe('mbyte', function() -- Convert from bytes to string @@ -45,12 +51,21 @@ describe('mbyte', function() end) end - describe('utfc_ptr2schar_len', function() + describe('utfc_ptr2schar', function() local function test_seq(seq) local firstc = ffi.new('int[1]') local buf = ffi.new('char[32]') - lib.schar_get(buf, lib.utfc_ptr2schar_len(to_string(seq), #seq, firstc)) - return { ffi.string(buf), firstc[0] } + lib.schar_get(buf, lib.utfc_ptr2schar(to_string(seq), firstc)) + local str = ffi.string(buf) + if 1 > 2 then -- for debugging + local tabel = {} + for i = 1, #str do + table.insert(tabel, string.format('0x%02x', string.byte(str, i))) + end + print('{ ' .. table.concat(tabel, ', ') .. ' }') + io.stdout:flush() + end + return { str, firstc[0] } end local function byte(val) @@ -88,7 +103,9 @@ describe('mbyte', function() eq(byte(0x7f), test_seq { 0x7f, 0xc2, 0x80 }) -- Combining character is U+0300 - eq({ '\x7f\xcc\x80', 0x7f }, test_seq { 0x7f, 0xcc, 0x80 }) + eq({ '\x29\xcc\x80', 0x29 }, test_seq { 0x29, 0xcc, 0x80 }) + -- invalid start byte for combining + eq({ '\x7f', 0x7f }, test_seq { 0x7f, 0xcc, 0x80 }) -- No UTF-8 sequence eq({ '', 0xc2 }, test_seq { 0xc2, 0x7f, 0xcc }) @@ -102,18 +119,21 @@ describe('mbyte', function() itp('4-byte sequences', function() -- No following combining character eq(byte(0x7f), test_seq { 0x7f, 0x7f, 0xcc, 0x80 }) + eq(byte(0x29), test_seq { 0x29, 0x29, 0xcc, 0x80 }) -- No second UTF-8 character eq(byte(0x7f), test_seq { 0x7f, 0xc2, 0xcc, 0x80 }) -- Combining character U+0300 - eq({ '\x7f\xcc\x80', 0x7f }, test_seq { 0x7f, 0xcc, 0x80, 0xcc }) + eq({ '\x29\xcc\x80', 0x29 }, test_seq { 0x29, 0xcc, 0x80, 0xcc }) -- No UTF-8 sequence eq({ '', 0xc2 }, test_seq { 0xc2, 0x7f, 0xcc, 0x80 }) -- No following UTF-8 character eq({ '\xc2\x80', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0xcc }) -- Combining character U+0301 - eq({ '\xc2\x80\xcc\x81', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0x81 }) + eq({ '\xc2\xbc\xcc\x81', 0xbc }, test_seq { 0xc2, 0xbc, 0xcc, 0x81 }) + -- U+0080 : not a valid start char + eq({ '\xc2\x80', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0x81 }) -- One UTF-8 character eq({ '\xf4\x80\x80\x80', 0x100000 }, test_seq { 0xf4, 0x80, 0x80, 0x80 }) @@ -126,36 +146,36 @@ describe('mbyte', function() eq(byte(0x7f), test_seq { 0x7f, 0xc2, 0xcc, 0x80, 0x80 }) -- Combining character U+0300 - eq({ '\x7f\xcc\x80', 0x7f }, test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x00 }) + eq({ '\x29\xcc\x80', 0x29 }, test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x00 }) -- Combining characters U+0300 and U+0301 - eq({ '\x7f\xcc\x80\xcc\x81', 0x7f }, test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81 }) + eq({ '\x29\xcc\x80\xcc\x81', 0x29 }, test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81 }) -- Combining characters U+0300, U+0301, U+0302 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82', 0x7f }, - test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82 } + { '\x29\xcc\x80\xcc\x81\xcc\x82', 0x29 }, + test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82 } ) -- Combining characters U+0300, U+0301, U+0302, U+0303 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83', 0x7f }, - test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83 } + { '\x29\xcc\x80\xcc\x81\xcc\x82\xcc\x83', 0x29 }, + test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83 } ) -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84', 0x7f }, - test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84 } + { '\x29\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84', 0x29 }, + test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84 } ) -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304, U+0305 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85', 0x7f }, - test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85 } + { '\x29\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85', 0x29 }, + test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85 } ) -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304, U+0305, U+0306 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85\xcc\x86', 0x7f }, + { '\x29\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85\xcc\x86', 0x29 }, test_seq { - 0x7f, + 0x29, 0xcc, 0x80, 0xcc, @@ -175,18 +195,18 @@ describe('mbyte', function() -- Only three following combining characters U+0300, U+0301, U+0302 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82', 0x7f }, - test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xc2, 0x80, 0xcc, 0x84, 0xcc, 0x85 } + { '\x29\xcc\x80\xcc\x81\xcc\x82', 0x29 }, + test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xc2, 0x80, 0xcc, 0x84, 0xcc, 0x85 } ) -- No UTF-8 sequence eq({ '', 0xc2 }, test_seq { 0xc2, 0x7f, 0xcc, 0x80, 0x80 }) -- No following UTF-8 character - eq({ '\xc2\x80', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0xcc, 0x80 }) + eq({ '\xc2\xbc', 0xbc }, test_seq { 0xc2, 0xbc, 0xcc, 0xcc, 0x80 }) -- Combining character U+0301 - eq({ '\xc2\x80\xcc\x81', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0x81, 0x7f }) + eq({ '\xc2\xbc\xcc\x81', 0xbc }, test_seq { 0xc2, 0xbc, 0xcc, 0x81, 0x7f }) -- Combining character U+0301 - eq({ '\xc2\x80\xcc\x81', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0x81, 0xcc }) + eq({ '\xc2\xbc\xcc\x81', 0xbc }, test_seq { 0xc2, 0xbc, 0xcc, 0x81, 0xcc }) -- One UTF-8 character eq({ '\xf4\x80\x80\x80', 0x100000 }, test_seq { 0xf4, 0x80, 0x80, 0x80, 0x7f }) @@ -205,8 +225,6 @@ describe('mbyte', function() end) describe('utf_cp_bounds_len', function() - local to_cstr = t.to_cstr - local tests = { { name = 'for valid string', @@ -273,4 +291,72 @@ describe('mbyte', function() eq(expected_offsets, { b = b_offsets, e = e_offsets }) end) end) + + itp('utf_head_off', function() + local function check(str, expected_glyphs) + local len = #str + local cstr = to_cstr(str) + local breaks = { 0 } -- SOT + local pos = 0 + local mb_glyphs = {} + while pos < len do + local clen = lib.utfc_ptr2len(cstr + pos) + if clen == 0 then + eq(0, string.byte(str, pos + 1)) -- only NUL bytes can has length zery + clen = 1 -- but skip it, otherwise we get stuck + end + if clen > 1 then + table.insert(mb_glyphs, string.sub(str, pos + 1, pos + clen)) + end + pos = pos + clen + table.insert(breaks, pos) + end + eq(breaks[#breaks], len) -- include EOT as break + -- we could also send in breaks, but this is more human readable + eq(mb_glyphs, expected_glyphs) + + for i = 1, #breaks - 1 do + local start, next = breaks[i], breaks[i + 1] + + for p = start, next - 1 do + eq(p - start, lib.utf_head_off(cstr, cstr + p)) + end + end + eq(0, lib.utf_head_off(cstr, cstr + len)) -- NUL byte is safe + end + -- stylua doesn't like ZWJ chars.. + -- stylua: ignore start + check('hej och hΓ₯ π§βπΎ!', { 'Γ₯', 'π§βπΎ' }) + + -- emoji (various kinds of combinations, use g8 to see them) + check("π³οΈββ§οΈπ§βπΎβ€οΈππ΄ββ οΈ", {"π³οΈββ§οΈ", "π§βπΎ", "β€οΈ", "π", "π΄ββ οΈ"}) + check('π³οΈββ§οΈxyπ§βπΎ\rβ€οΈπΓ₯π΄ββ οΈΒ', { 'π³οΈββ§οΈ', 'π§βπΎ', 'β€οΈ', 'π', 'Γ₯', 'π΄ββ οΈ', 'Β' }) + check('π³οΈββ§οΈ\000π§βπΎ\000β€οΈ\000π\000Γ₯\000π΄ββ οΈ\000Β', { 'π³οΈββ§οΈ', 'π§βπΎ', 'β€οΈ', 'π', 'Γ₯', 'π΄ββ οΈ', 'Β' }) + check('\195π³οΈββ§οΈ\198π§βπΎ\165β€οΈ\168\195π\255π΄ββ οΈ\129Β\165', { 'π³οΈββ§οΈ', 'π§βπΎ', 'β€οΈ', 'π', 'π΄ββ οΈ', 'Β' }) + + check('π¦π
±οΈ π¦π½ π¦π¨π¦ π²π½πΉπ±',{'π¦', 'π
±οΈ', 'π¦π½', 'π¦π¨', 'π¦', 'π²π½', 'πΉπ±'}) + check('π΄σ §σ ’σ ³σ £σ ΄σ Ώπ΄σ §σ ’σ ·σ ¬σ ³σ Ώ', {'π΄σ §σ ’σ ³σ £σ ΄σ Ώ', 'π΄σ §σ ’σ ·σ ¬σ ³σ Ώ'}) + + check('Γ₯\165ΓΌ\195aΓ«q\168Ξ²\000\169ζ¬\255', {'Γ₯', 'ΓΌ', 'Γ«', 'Ξ²', 'ζ¬'}) + + lib.p_arshape = true -- default + check('Ψ³ΩΨ§Ω
', { 'Ψ³', 'ΩΨ§', 'Ω
' }) + lib.p_arshape = false + check('Ψ³ΩΨ§Ω
', { 'Ψ³', 'Ω', 'Ψ§', 'Ω
' }) + + check('LΜΜΜΜΜΜoΜΜΜΜΜΜrΜΜΜΜΜΜeΜΜΜ
ΜΜΜmΜ
ΜΜΜΜΜ', {'LΜΜΜΜΜΜ', 'oΜΜΜΜΜΜ', 'rΜΜΜΜΜΜ', 'eΜΜΜ
ΜΜΜ', 'mΜ
ΜΜΜΜΜ'}) + -- stylua: ignore end + end) + + describe('utf_fold', function() + itp('does not crash with surrogates #30527', function() + eq(0xddfb, lib.utf_fold(0xddfb)) -- low surrogate, invalid as a character + eq(0xd800, lib.utf_fold(0xd800)) -- high surrogate, invalid as a character + end) + + itp("doesn't crash on invalid codepoints", function() + eq(9000000, lib.utf_fold(9000000)) + eq(0, lib.utf_fold(0)) + end) + end) end) diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index 6f6a80f44e..ffad552a8a 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -468,8 +468,11 @@ describe('path.c', function() eq(OK, result) end) - itp('concatenates directory name if it does not contain a slash', function() - local expected = uv.cwd() .. '/..' + itp('produces absolute path for .. without a slash', function() + local old_dir = uv.cwd() + uv.chdir('..') + local expected = uv.cwd() + uv.chdir(old_dir) local filename = '..' local buflen = get_buf_len(expected, filename) local do_expand = 1 @@ -478,21 +481,18 @@ describe('path.c', function() eq(OK, result) end) - itp( - 'enters given directory (instead of just concatenating the strings) if possible and if path contains a slash', - function() - local old_dir = uv.cwd() - uv.chdir('..') - local expected = uv.cwd() .. '/test.file' - uv.chdir(old_dir) - local filename = '../test.file' - local buflen = get_buf_len(expected, filename) - local do_expand = 1 - local buf, result = vim_FullName(filename, buflen, do_expand) - eq(expected, ffi.string(buf)) - eq(OK, result) - end - ) + itp('produces absolute path if possible and if path contains a slash', function() + local old_dir = uv.cwd() + uv.chdir('..') + local expected = uv.cwd() .. '/test.file' + uv.chdir(old_dir) + local filename = '../test.file' + local buflen = get_buf_len(expected, filename) + local do_expand = 1 + local buf, result = vim_FullName(filename, buflen, do_expand) + eq(expected, ffi.string(buf)) + eq(OK, result) + end) itp('just copies the path if it is already absolute and force=0', function() local absolute_path = '/absolute/path' diff --git a/test/unit/rbuffer_spec.lua b/test/unit/rbuffer_spec.lua deleted file mode 100644 index ad18ea2ddc..0000000000 --- a/test/unit/rbuffer_spec.lua +++ /dev/null @@ -1,340 +0,0 @@ -local t = require('test.unit.testutil') -local itp = t.gen_itp(it) - -local eq = t.eq -local ffi = t.ffi -local cstr = t.cstr -local to_cstr = t.to_cstr -local child_call_once = t.child_call_once - -local rbuffer = t.cimport('./test/unit/fixtures/rbuffer.h') - -describe('rbuffer functions', function() - local capacity = 16 - local rbuf - - local function inspect() - return ffi.string(rbuf.start_ptr, capacity) - end - - local function write(str) - local buf = to_cstr(str) - return rbuffer.rbuffer_write(rbuf, buf, #str) - end - - local function read(len) - local buf = cstr(len) - len = rbuffer.rbuffer_read(rbuf, buf, len) - return ffi.string(buf, len) - end - - local function get(idx) - return ffi.string(rbuffer.rbuffer_get(rbuf, idx), 1) - end - - before_each(function() - child_call_once(function() - rbuf = ffi.gc(rbuffer.rbuffer_new(capacity), rbuffer.rbuffer_free) - -- fill the internal buffer with the character '0' to simplify inspecting - ffi.C.memset(rbuf.start_ptr, string.byte('0'), capacity) - end) - end) - - describe('RBUFFER_UNTIL_FULL', function() - local chunks - - local function collect_write_chunks() - rbuffer.ut_rbuffer_each_write_chunk(rbuf, function(wptr, wcnt) - table.insert(chunks, ffi.string(wptr, wcnt)) - end) - end - - before_each(function() - chunks = {} - end) - - describe('with empty buffer in one contiguous chunk', function() - itp('is called once with the empty chunk', function() - collect_write_chunks() - eq({ '0000000000000000' }, chunks) - end) - end) - - describe('with partially empty buffer in one contiguous chunk', function() - itp('is called once with the empty chunk', function() - write('string') - collect_write_chunks() - eq({ '0000000000' }, chunks) - end) - end) - - describe('with filled buffer in one contiguous chunk', function() - itp('is not called', function() - write('abcdefghijklmnopq') - collect_write_chunks() - eq({}, chunks) - end) - end) - - describe('with buffer partially empty in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('1234567890') - read(8) - collect_write_chunks() - eq({ '000000', '12345678' }, chunks) - end) - end) - - describe('with buffer empty in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('12345678') - read(8) - collect_write_chunks() - eq({ '00000000', '12345678' }, chunks) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('is not called', function() - write('12345678') - read(8) - write('abcdefghijklmnopq') - collect_write_chunks() - eq({}, chunks) - end) - end) - end) - - describe('RBUFFER_UNTIL_EMPTY', function() - local chunks - - local function collect_read_chunks() - rbuffer.ut_rbuffer_each_read_chunk(rbuf, function(rptr, rcnt) - table.insert(chunks, ffi.string(rptr, rcnt)) - end) - end - - before_each(function() - chunks = {} - end) - - describe('with empty buffer', function() - itp('is not called', function() - collect_read_chunks() - eq({}, chunks) - end) - end) - - describe('with partially filled buffer in one contiguous chunk', function() - itp('is called once with the filled chunk', function() - write('string') - collect_read_chunks() - eq({ 'string' }, chunks) - end) - end) - - describe('with filled buffer in one contiguous chunk', function() - itp('is called once with the filled chunk', function() - write('abcdefghijklmnopq') - collect_read_chunks() - eq({ 'abcdefghijklmnop' }, chunks) - end) - end) - - describe('with buffer partially filled in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('1234567890') - read(10) - write('long string') - collect_read_chunks() - eq({ 'long s', 'tring' }, chunks) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('12345678') - read(8) - write('abcdefghijklmnopq') - collect_read_chunks() - eq({ 'abcdefgh', 'ijklmnop' }, chunks) - end) - end) - end) - - describe('RBUFFER_EACH', function() - local chars - - local function collect_chars() - rbuffer.ut_rbuffer_each(rbuf, function(c, i) - table.insert(chars, { string.char(c), tonumber(i) }) - end) - end - before_each(function() - chars = {} - end) - - describe('with empty buffer', function() - itp('is not called', function() - collect_chars() - eq({}, chars) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('collects each character and index', function() - write('1234567890') - read(10) - write('long string') - collect_chars() - eq({ - { 'l', 0 }, - { 'o', 1 }, - { 'n', 2 }, - { 'g', 3 }, - { ' ', 4 }, - { 's', 5 }, - { 't', 6 }, - { 'r', 7 }, - { 'i', 8 }, - { 'n', 9 }, - { 'g', 10 }, - }, chars) - end) - end) - end) - - describe('RBUFFER_EACH_REVERSE', function() - local chars - - local function collect_chars() - rbuffer.ut_rbuffer_each_reverse(rbuf, function(c, i) - table.insert(chars, { string.char(c), tonumber(i) }) - end) - end - before_each(function() - chars = {} - end) - - describe('with empty buffer', function() - itp('is not called', function() - collect_chars() - eq({}, chars) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('collects each character and index', function() - write('1234567890') - read(10) - write('long string') - collect_chars() - eq({ - { 'g', 10 }, - { 'n', 9 }, - { 'i', 8 }, - { 'r', 7 }, - { 't', 6 }, - { 's', 5 }, - { ' ', 4 }, - { 'g', 3 }, - { 'n', 2 }, - { 'o', 1 }, - { 'l', 0 }, - }, chars) - end) - end) - end) - - describe('rbuffer_cmp', function() - local function cmp(str) - local rv = rbuffer.rbuffer_cmp(rbuf, to_cstr(str), #str) - if rv == 0 then - return 0 - else - return rv / math.abs(rv) - end - end - - describe('with buffer filled in two contiguous chunks', function() - itp('compares the common longest sequence', function() - write('1234567890') - read(10) - write('long string') - eq(0, cmp('long string')) - eq(0, cmp('long strin')) - eq(-1, cmp('long striM')) - eq(1, cmp('long strio')) - eq(0, cmp('long')) - eq(-1, cmp('lonG')) - eq(1, cmp('lonh')) - end) - end) - - describe('with empty buffer', function() - itp('returns 0 since no characters are compared', function() - eq(0, cmp('')) - end) - end) - end) - - describe('rbuffer_write', function() - itp('fills the internal buffer and returns the write count', function() - eq(12, write('short string')) - eq('short string0000', inspect()) - end) - - itp('wont write beyond capacity', function() - eq(16, write('very very long string')) - eq('very very long s', inspect()) - end) - end) - - describe('rbuffer_read', function() - itp('reads what was previously written', function() - write('to read') - eq('to read', read(20)) - end) - - itp('reads nothing if the buffer is empty', function() - eq('', read(20)) - write('empty') - eq('empty', read(20)) - eq('', read(20)) - end) - end) - - describe('rbuffer_get', function() - itp('fetch the pointer at offset, wrapping if required', function() - write('1234567890') - read(10) - write('long string') - eq('l', get(0)) - eq('o', get(1)) - eq('n', get(2)) - eq('g', get(3)) - eq(' ', get(4)) - eq('s', get(5)) - eq('t', get(6)) - eq('r', get(7)) - eq('i', get(8)) - eq('n', get(9)) - eq('g', get(10)) - end) - end) - - describe('wrapping behavior', function() - itp('writing/reading wraps across the end of the internal buffer', function() - write('1234567890') - eq('1234', read(4)) - eq('5678', read(4)) - write('987654321') - eq('3214567890987654', inspect()) - eq('90987654321', read(20)) - eq('', read(4)) - write('abcdefghijklmnopqrs') - eq('nopabcdefghijklm', inspect()) - eq('abcdefghijklmnop', read(20)) - end) - end) -end) diff --git a/test/unit/statusline_spec.lua b/test/unit/statusline_spec.lua index 973d9ec992..a97a4f41d7 100644 --- a/test/unit/statusline_spec.lua +++ b/test/unit/statusline_spec.lua @@ -56,14 +56,14 @@ describe('build_stl_str_hl', function() -- @param input_stl The format string for the statusline -- @param expected_stl The expected result string for the statusline -- - -- @param arg Options can be placed in an optional dictionary as the last parameter + -- @param arg Options can be placed in an optional dict as the last parameter -- .expected_cell_count The expected number of cells build_stl_str_hl will return -- .expected_byte_length The expected byte length of the string (defaults to byte length of expected_stl) -- .file_name The name of the file to be tested (useful in %f type tests) -- .fillchar The character that will be used to fill any 'extra' space in the stl local function statusline_test(description, statusline_cell_count, input_stl, expected_stl, arg) -- arg is the optional parameter - -- so we either fill in option with arg or an empty dictionary + -- so we either fill in option with arg or an empty dict local option = arg or {} local fillchar = option.fillchar or ' ' diff --git a/test/unit/termkey_spec.lua b/test/unit/termkey_spec.lua new file mode 100644 index 0000000000..0381cfd15a --- /dev/null +++ b/test/unit/termkey_spec.lua @@ -0,0 +1,975 @@ +local t = require('test.unit.testutil') +local itp = t.gen_itp(it) +local bit = require('bit') + +--- @alias TermKeyKey {utf8: string, type: integer, modifiers: integer, code: {codepoint: integer, sym: any, number: integer}} + +--- @class termkey +--- @field TERMKEY_CANON_SPACESYMBOL integer +--- @field TERMKEY_FLAG_SPACESYMBOL integer +--- @field TERMKEY_FLAG_UTF8 integer +--- @field TERMKEY_FORMAT_ALTISMETA integer +--- @field TERMKEY_FORMAT_CARETCTRL integer +--- @field TERMKEY_FORMAT_LONGMOD integer +--- @field TERMKEY_FORMAT_LOWERMOD integer +--- @field TERMKEY_FORMAT_LOWERSPACE integer +--- @field TERMKEY_FORMAT_MOUSE_POS integer +--- @field TERMKEY_FORMAT_SPACEMOD integer +--- @field TERMKEY_FORMAT_WRAPBRACKET integer +--- @field TERMKEY_KEYMOD_ALT integer +--- @field TERMKEY_KEYMOD_CTRL integer +--- @field TERMKEY_MOUSE_DRAG integer +--- @field TERMKEY_MOUSE_PRESS integer +--- @field TERMKEY_MOUSE_RELEASE integer +--- @field TERMKEY_RES_AGAIN integer +--- @field TERMKEY_RES_KEY integer +--- @field TERMKEY_RES_NONE integer +--- @field TERMKEY_SYM_DOWN integer +--- @field TERMKEY_SYM_PAGEUP integer +--- @field TERMKEY_SYM_SPACE integer +--- @field TERMKEY_SYM_UNKNOWN integer +--- @field TERMKEY_SYM_UP integer +--- @field TERMKEY_TYPE_DCS integer +--- @field TERMKEY_TYPE_FUNCTION integer +--- @field TERMKEY_TYPE_KEYSYM integer +--- @field TERMKEY_TYPE_MODEREPORT integer +--- @field TERMKEY_TYPE_MOUSE integer +--- @field TERMKEY_TYPE_OSC integer +--- @field TERMKEY_TYPE_POSITION integer +--- @field TERMKEY_TYPE_UNICODE integer +--- @field TERMKEY_TYPE_UNKNOWN_CSI integer +--- @field termkey_canonicalise fun(any, any):any +--- @field termkey_destroy fun(any) +--- @field termkey_get_buffer_remaining fun(any):integer +--- @field termkey_get_buffer_size fun(any):integer +--- @field termkey_get_canonflags fun(any):any +--- @field termkey_get_keyname fun(any, any):any +--- @field termkey_getkey fun(any, any):any +--- @field termkey_getkey_force fun(any, any):any +--- @field termkey_interpret_csi fun(any, any, any, any, any):any +--- @field termkey_interpret_modereport fun(any, any, any, any, any):any +--- @field termkey_interpret_mouse fun(any, any, TermKeyKey, integer, integer, integer):any +--- @field termkey_interpret_position fun(any, any, any, any):any +--- @field termkey_interpret_string fun(any, TermKeyKey, any):any +--- @field termkey_lookup_keyname fun(any, any, any):any +--- @field termkey_new_abstract fun(string, integer):any +--- @field termkey_push_bytes fun(any, string, integer):integer +--- @field termkey_set_buffer_size fun(any, integer):integer +--- @field termkey_set_canonflags fun(any, any):any +--- @field termkey_set_flags fun(any, integer) +--- @field termkey_start fun(any):integer +--- @field termkey_stop fun(any):integer +--- @field termkey_strfkey fun(any, string, integer, any, any):integer +local termkey = t.cimport( + './src/nvim/tui/termkey/termkey.h', + './src/nvim/tui/termkey/termkey-internal.h', + './src/nvim/tui/termkey/termkey_defs.h', + './src/nvim/tui/termkey/driver-csi.h' +) + +describe('termkey', function() + itp('01base', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + t.neq(tk, nil) + + t.eq(termkey.termkey_get_buffer_size(tk), 256) + t.eq(tk.is_started, 1) -- tk->is_started true after construction + + termkey.termkey_stop(tk) + t.neq(tk.is_started, 1) -- tk->is_started false after termkey_stop() + + termkey.termkey_start(tk) + t.eq(tk.is_started, 1) -- tk->is_started true after termkey_start() + + termkey.termkey_destroy(tk) + end) + + itp('02getkey', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + + t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free initially 256 + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey yields RES_NONE when empty + + t.eq(termkey.termkey_push_bytes(tk, 'h', 1), 1) -- push_bytes returns 1 + + t.eq(termkey.termkey_get_buffer_remaining(tk), 255) -- buffer free 255 after push_bytes + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after h + + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after h + t.eq(key.code.codepoint, string.byte('h')) -- key.code.codepoint after h + t.eq(key.modifiers, 0) -- key.modifiers after h + t.eq(t.ffi.string(key.utf8), 'h') -- key.utf8 after h + + t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free 256 after getkey + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey yields RES_NONE a second time + + termkey.termkey_push_bytes(tk, '\x01', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after C-a + + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after C-a + t.eq(key.code.codepoint, string.byte('a')) -- key.code.codepoint after C-a + t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_CTRL) -- key.modifiers after C-a + + termkey.termkey_push_bytes(tk, '\033OA', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Up + + -- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Up"); + -- is_int(key.code.sym, TERMKEY_SYM_UP, "key.code.sym after Up"); + t.eq(key.modifiers, 0) -- key.modifiers after Up + + t.eq(termkey.termkey_push_bytes(tk, '\033O', 2), 2) -- push_bytes returns 2 + + -- is_int(termkey_get_buffer_remaining(tk), 254, "buffer free 254 after partial write"); + + -- is_int(termkey_getkey(tk, &key), TERMKEY_RES_AGAIN, "getkey yields RES_AGAIN after partial write"); + + termkey.termkey_push_bytes(tk, 'C', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Right completion + + -- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Right"); + -- is_int(key.code.sym, TERMKEY_SYM_RIGHT, "key.code.sym after Right"); + -- is_int(key.modifiers, 0, "key.modifiers after Right"); + + -- is_int(termkey_get_buffer_remaining(tk), 256, "buffer free 256 after completion"); + + termkey.termkey_push_bytes(tk, '\033[27;5u', 7) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Ctrl-Escape + + -- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Ctrl-Escape"); + -- is_int(key.code.sym, TERMKEY_SYM_ESCAPE, "key.code.sym after Ctrl-Escape"); + -- is_int(key.modifiers, TERMKEY_KEYMOD_CTRL, "key.modifiers after Ctrl-Escape"); + + termkey.termkey_push_bytes(tk, '\0', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Ctrl-Space + + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after Ctrl-Space + -- t.eq(key.code.codepoint, string.byte(' ')) -- key.code.codepoint after Ctrl-Space + -- is_int(key.modifiers, TERMKEY_KEYMOD_CTRL, "key.modifiers after Ctrl-Space"); + + termkey.termkey_destroy(tk) + end) + + itp('03utf8', function() + local tk = termkey.termkey_new_abstract('vt100', termkey.TERMKEY_FLAG_UTF8) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + + termkey.termkey_push_bytes(tk, 'a', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY low ASCII + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type low ASCII + t.eq(key.code.codepoint, string.byte('a')) -- key.code.codepoint low ASCII + + -- 2-byte UTF-8 range is U+0080 to U+07FF (0xDF 0xBF) + -- However, we'd best avoid the C1 range, so we'll start at U+00A0 (0xC2 0xA0) + + termkey.termkey_push_bytes(tk, '\xC2\xA0', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 low + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 2 low + t.eq(key.code.codepoint, 0x00A0) -- key.code.codepoint UTF-8 2 low + + termkey.termkey_push_bytes(tk, '\xDF\xBF', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 high + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 2 high + t.eq(key.code.codepoint, 0x07FF) -- key.code.codepoint UTF-8 2 high + + -- 3-byte UTF-8 range is U+0800 (0xE0 0xA0 0x80) to U+FFFD (0xEF 0xBF 0xBD) + + termkey.termkey_push_bytes(tk, '\xE0\xA0\x80', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 low + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 3 low + t.eq(key.code.codepoint, 0x0800) -- key.code.codepoint UTF-8 3 low + + termkey.termkey_push_bytes(tk, '\xEF\xBF\xBD', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 high + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 3 high + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 high + + -- 4-byte UTF-8 range is U+10000 (0xF0 0x90 0x80 0x80) to U+10FFFF (0xF4 0x8F 0xBF 0xBF) + + termkey.termkey_push_bytes(tk, '\xF0\x90\x80\x80', 4) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 low + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 4 low + t.eq(key.code.codepoint, 0x10000) -- key.code.codepoint UTF-8 4 low + + termkey.termkey_push_bytes(tk, '\xF4\x8F\xBF\xBF', 4) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 high + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 4 high + t.eq(key.code.codepoint, 0x10FFFF) -- key.code.codepoint UTF-8 4 high + + -- Invalid continuations + + termkey.termkey_push_bytes(tk, '\xC2!', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 invalid cont + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 2 invalid cont + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 2 invalid after + + termkey.termkey_push_bytes(tk, '\xE0!', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid cont + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 invalid cont + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 3 invalid after + + termkey.termkey_push_bytes(tk, '\xE0\xA0!', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid cont 2 + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 invalid cont 2 + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 3 invalid after + + termkey.termkey_push_bytes(tk, '\xF0!', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after + + termkey.termkey_push_bytes(tk, '\xF0\x90!', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont 2 + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont 2 + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after + + termkey.termkey_push_bytes(tk, '\xF0\x90\x80!', 4) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont 3 + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont 3 + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after + + -- Partials + + termkey.termkey_push_bytes(tk, '\xC2', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 2 partial + + termkey.termkey_push_bytes(tk, '\xA0', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 partial + t.eq(key.code.codepoint, 0x00A0) -- key.code.codepoint UTF-8 2 partial + + termkey.termkey_push_bytes(tk, '\xE0', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 3 partial + + termkey.termkey_push_bytes(tk, '\xA0', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 3 partial + + termkey.termkey_push_bytes(tk, '\x80', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 partial + t.eq(key.code.codepoint, 0x0800) -- key.code.codepoint UTF-8 3 partial + + termkey.termkey_push_bytes(tk, '\xF0', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial + + termkey.termkey_push_bytes(tk, '\x90', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial + + termkey.termkey_push_bytes(tk, '\x80', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial + + termkey.termkey_push_bytes(tk, '\x80', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 partial + t.eq(key.code.codepoint, 0x10000) -- key.code.codepoint UTF-8 4 partial + + termkey.termkey_destroy(tk) + end) + + itp('04flags', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + + termkey.termkey_push_bytes(tk, ' ', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after space + + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after space + t.eq(key.code.codepoint, string.byte(' ')) -- key.code.codepoint after space + t.eq(key.modifiers, 0) -- key.modifiers after space + + termkey.termkey_set_flags(tk, termkey.TERMKEY_FLAG_SPACESYMBOL) + + termkey.termkey_push_bytes(tk, ' ', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after space + + t.eq(key.type, termkey.TERMKEY_TYPE_KEYSYM) -- key.type after space with FLAG_SPACESYMBOL + t.eq(key.code.sym, termkey.TERMKEY_SYM_SPACE) -- key.code.sym after space with FLAG_SPACESYMBOL + t.eq(key.modifiers, 0) -- key.modifiers after space with FLAG_SPACESYMBOL + + termkey.termkey_destroy(tk) + end) + + itp('06buffer', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + + t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free initially 256 + t.eq(termkey.termkey_get_buffer_size(tk), 256) -- buffer size initially 256 + + t.eq(termkey.termkey_push_bytes(tk, 'h', 1), 1) -- push_bytes returns 1 + + t.eq(termkey.termkey_get_buffer_remaining(tk), 255) -- buffer free 255 after push_bytes + t.eq(termkey.termkey_get_buffer_size(tk), 256) -- buffer size 256 after push_bytes + + t.eq(not not termkey.termkey_set_buffer_size(tk, 512), true) -- buffer set size OK + + t.eq(termkey.termkey_get_buffer_remaining(tk), 511) -- buffer free 511 after push_bytes + t.eq(termkey.termkey_get_buffer_size(tk), 512) -- buffer size 512 after push_bytes + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- buffered key still usable after resize + + termkey.termkey_destroy(tk) + end) + + local function termkey_keyname2sym(tk, keyname) + local sym = t.ffi.new('TermKeySym[1]') + local endp = termkey.termkey_lookup_keyname(tk, keyname, sym) + if endp == nil then + return termkey.TERMKEY_SYM_UNKNOWN + end + return sym + end + + itp('10keyname', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + + local sym = termkey_keyname2sym(tk, 'SomeUnknownKey') + t.eq(sym, termkey.TERMKEY_SYM_UNKNOWN) -- keyname2sym SomeUnknownKey + + sym = termkey_keyname2sym(tk, 'Space') + t.eq(sym[0], termkey.TERMKEY_SYM_SPACE) -- keyname2sym Space + + local _end = termkey.termkey_lookup_keyname(tk, 'Up', sym) + t.neq(_end, nil) -- termkey_get_keyname Up returns non-NULL + t.eq(t.ffi.string(_end), '') -- termkey_get_keyname Up return points at endofstring + t.eq(sym[0], termkey.TERMKEY_SYM_UP) -- termkey_get_keyname Up yields Up symbol + + _end = termkey.termkey_lookup_keyname(tk, 'DownMore', sym) + t.neq(_end, nil) -- termkey_get_keyname DownMore returns non-NULL + t.eq(t.ffi.string(_end), 'More') -- termkey_get_keyname DownMore return points at More + t.eq(sym[0], termkey.TERMKEY_SYM_DOWN) -- termkey_get_keyname DownMore yields Down symbol + + _end = termkey.termkey_lookup_keyname(tk, 'SomeUnknownKey', sym) + t.eq(_end, nil) -- termkey_get_keyname SomeUnknownKey returns NULL + + t.eq(t.ffi.string(termkey.termkey_get_keyname(tk, termkey.TERMKEY_SYM_SPACE)), 'Space') -- "get_keyname SPACE"); + + termkey.termkey_destroy(tk) + end) + + itp('11strfkey', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + ---@type TermKeyKey + local key = t.ffi.new( + 'TermKeyKey', + { type = termkey.TERMKEY_TYPE_UNICODE, code = { codepoint = string.byte('A') } } + ) + local buffer = t.ffi.new('char[16]') + + local len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 1) -- length for unicode/A/0 + t.eq(t.ffi.string(buffer), 'A') -- buffer for unicode/A/0 + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_WRAPBRACKET + ) + t.eq(len, 1) -- length for unicode/A/0 wrapbracket + t.eq(t.ffi.string(buffer), 'A') -- buffer for unicode/A/0 wrapbracket + + ---@type TermKeyKey + key = t.ffi.new('TermKeyKey', { + type = termkey.TERMKEY_TYPE_UNICODE, + code = { codepoint = string.byte('b') }, + modifiers = termkey.TERMKEY_KEYMOD_CTRL, + }) + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 3) -- length for unicode/b/CTRL + t.eq(t.ffi.string(buffer), 'C-b') -- buffer for unicode/b/CTRL + + len = + termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, termkey.TERMKEY_FORMAT_LONGMOD) + t.eq(len, 6) -- length for unicode/b/CTRL longmod + t.eq(t.ffi.string(buffer), 'Ctrl-b') -- buffer for unicode/b/CTRL longmod + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_SPACEMOD) + ) + t.eq(len, 6) -- length for unicode/b/CTRL longmod|spacemod + t.eq(t.ffi.string(buffer), 'Ctrl b') -- buffer for unicode/b/CTRL longmod|spacemod + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_LOWERMOD) + ) + t.eq(len, 6) -- length for unicode/b/CTRL longmod|lowermod + t.eq(t.ffi.string(buffer), 'ctrl-b') -- buffer for unicode/b/CTRL longmod|lowermod + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + bit.bor( + termkey.TERMKEY_FORMAT_LONGMOD, + termkey.TERMKEY_FORMAT_SPACEMOD, + termkey.TERMKEY_FORMAT_LOWERMOD + ) + ) + t.eq(len, 6) -- length for unicode/b/CTRL longmod|spacemod|lowermode + t.eq(t.ffi.string(buffer), 'ctrl b') -- buffer for unicode/b/CTRL longmod|spacemod|lowermode + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_CARETCTRL + ) + t.eq(len, 2) -- length for unicode/b/CTRL caretctrl + t.eq(t.ffi.string(buffer), '^B') -- buffer for unicode/b/CTRL caretctrl + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_WRAPBRACKET + ) + t.eq(len, 5) -- length for unicode/b/CTRL wrapbracket + t.eq(t.ffi.string(buffer), '<C-b>') -- buffer for unicode/b/CTRL wrapbracket + + ---@type TermKeyKey + key = t.ffi.new('TermKeyKey', { + type = termkey.TERMKEY_TYPE_UNICODE, + code = { codepoint = string.byte('c') }, + modifiers = termkey.TERMKEY_KEYMOD_ALT, + }) + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 3) -- length for unicode/c/ALT + t.eq(t.ffi.string(buffer), 'A-c') -- buffer for unicode/c/ALT + + len = + termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, termkey.TERMKEY_FORMAT_LONGMOD) + t.eq(len, 5) -- length for unicode/c/ALT longmod + t.eq(t.ffi.string(buffer), 'Alt-c') -- buffer for unicode/c/ALT longmod + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_ALTISMETA + ) + t.eq(len, 3) -- length for unicode/c/ALT altismeta + t.eq(t.ffi.string(buffer), 'M-c') -- buffer for unicode/c/ALT altismeta + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_ALTISMETA) + ) + t.eq(len, 6) -- length for unicode/c/ALT longmod|altismeta + t.eq(t.ffi.string(buffer), 'Meta-c') -- buffer for unicode/c/ALT longmod|altismeta + + ---@type TermKeyKey + key = t.ffi.new( + 'TermKeyKey', + { type = termkey.TERMKEY_TYPE_KEYSYM, code = { sym = termkey.TERMKEY_SYM_UP } } + ) + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 2) -- length for sym/Up/0 + t.eq(t.ffi.string(buffer), 'Up') -- buffer for sym/Up/0 + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_WRAPBRACKET + ) + t.eq(len, 4) -- length for sym/Up/0 wrapbracket + t.eq(t.ffi.string(buffer), '<Up>') -- buffer for sym/Up/0 wrapbracket + + ---@type TermKeyKey + key = t.ffi.new( + 'TermKeyKey', + { type = termkey.TERMKEY_TYPE_KEYSYM, code = { sym = termkey.TERMKEY_SYM_PAGEUP } } + ) + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 6) -- length for sym/PageUp/0 + t.eq(t.ffi.string(buffer), 'PageUp') -- buffer for sym/PageUp/0 + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_LOWERSPACE + ) + t.eq(len, 7) -- length for sym/PageUp/0 lowerspace + t.eq(t.ffi.string(buffer), 'page up') -- buffer for sym/PageUp/0 lowerspace + + -- If size of buffer is too small, strfkey should return something consistent + len = termkey.termkey_strfkey(tk, buffer, 4, key, 0) + t.eq(len, 6) -- length for sym/PageUp/0 + t.eq(t.ffi.string(buffer), 'Pag') -- buffer of len 4 for sym/PageUp/0 + + len = termkey.termkey_strfkey(tk, buffer, 4, key, termkey.TERMKEY_FORMAT_LOWERSPACE) + t.eq(len, 7) -- length for sym/PageUp/0 lowerspace + t.eq(t.ffi.string(buffer), 'pag') -- buffer of len 4 for sym/PageUp/0 lowerspace + + key = t.ffi.new('TermKeyKey', { type = termkey.TERMKEY_TYPE_FUNCTION, code = { number = 5 } }) ---@type TermKeyKey + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 2) -- length for func/5/0 + t.eq(t.ffi.string(buffer), 'F5') -- buffer for func/5/0 + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_WRAPBRACKET + ) + t.eq(len, 4) -- length for func/5/0 wrapbracket + t.eq(t.ffi.string(buffer), '<F5>') -- buffer for func/5/0 wrapbracket + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_LOWERSPACE + ) + t.eq(len, 2) -- length for func/5/0 lowerspace + t.eq(t.ffi.string(buffer), 'f5') -- buffer for func/5/0 lowerspace + + termkey.termkey_destroy(tk) + end) + + itp('13cmpkey', function() + local function termkey_keycmp(tk, key1, key2) + termkey.termkey_canonicalise(tk, key1) + termkey.termkey_canonicalise(tk, key2) + + if key1.type ~= key2.type then + return key1.type - key2.type + end + + if key1.type == termkey.TERMKEY_TYPE_UNICODE then + if key1.code.codepoint ~= key2.code.codepoint then + return key1.code.codepoint - key2.code.codepoint + end + end + + return key1.modifiers - key2.modifiers + end + + local tk = termkey.termkey_new_abstract('vt100', 0) + ---@type TermKeyKey + local key1 = t.ffi.new('TermKeyKey', { + type = termkey.TERMKEY_TYPE_UNICODE, + code = { codepoint = string.byte('A') }, + modifiers = 0, + }) + ---@type TermKeyKey + local key2 = t.ffi.new('TermKeyKey', { + type = termkey.TERMKEY_TYPE_UNICODE, + code = { codepoint = string.byte('A') }, + modifiers = 0, + }) + + t.eq(termkey_keycmp(tk, key1, key1), 0) -- cmpkey same structure + t.eq(termkey_keycmp(tk, key1, key2), 0) -- cmpkey identical structure + + key2.modifiers = termkey.TERMKEY_KEYMOD_CTRL + + t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders CTRL after nomod + t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders nomod before CTRL + + key2.code.codepoint = string.byte('B') + key2.modifiers = 0 + + t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders 'B' after 'A' + t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders 'A' before 'B' + + key1.modifiers = termkey.TERMKEY_KEYMOD_CTRL + + t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders nomod 'B' after CTRL 'A' + t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders CTRL 'A' before nomod 'B' + + key2.type = termkey.TERMKEY_TYPE_KEYSYM + key2.code.sym = termkey.TERMKEY_SYM_UP + + t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders KEYSYM after UNICODE + t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders UNICODE before KEYSYM + + key1.type = termkey.TERMKEY_TYPE_KEYSYM + key1.code.sym = termkey.TERMKEY_SYM_SPACE + key1.modifiers = 0 + key2.type = termkey.TERMKEY_TYPE_UNICODE + key2.code.codepoint = string.byte(' ') + key2.modifiers = 0 + + t.eq(termkey_keycmp(tk, key1, key2), 0) -- cmpkey considers KEYSYM/SPACE and UNICODE/SP identical + + termkey.termkey_set_canonflags( + tk, + bit.bor(termkey.termkey_get_canonflags(tk), termkey.TERMKEY_CANON_SPACESYMBOL) + ) + t.eq(termkey_keycmp(tk, key1, key2), 0) -- "cmpkey considers KEYSYM/SPACE and UNICODE/SP identical under SPACESYMBOL"); + + termkey.termkey_destroy(tk) + end) + + itp('30mouse', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey', { type = -1 }) ---@type TermKeyKey + local ev = t.ffi.new('TermKeyMouseEvent[1]') + local button = t.ffi.new('int[1]') + local line = t.ffi.new('int[1]') + local col = t.ffi.new('int[1]') + local buffer = t.ffi.new('char[32]') + + termkey.termkey_push_bytes(tk, '\x1b[M !!', 6) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press + + t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press + + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press + t.eq(button[0], 1) -- mouse button for press + t.eq(line[0], 1) -- mouse line for press + t.eq(col[0], 1) -- mouse column for press + t.eq(key.modifiers, 0) -- modifiers for press + + local len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 13) -- string length for press + t.eq(t.ffi.string(buffer), 'MousePress(1)') -- string buffer for press + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_MOUSE_POS + ) + t.eq(len, 21) -- string length for press + t.eq(t.ffi.string(buffer), 'MousePress(1) @ (1,1)') -- string buffer for press + + termkey.termkey_push_bytes(tk, '\x1b[M@"!', 6) + + termkey.termkey_getkey(tk, key) + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_DRAG) -- mouse event for drag + t.eq(button[0], 1) -- mouse button for drag + t.eq(line[0], 1) -- mouse line for drag + t.eq(col[0], 2) -- mouse column for drag + t.eq(key.modifiers, 0) -- modifiers for press + + termkey.termkey_push_bytes(tk, '\x1b[M##!', 6) + + termkey.termkey_getkey(tk, key) + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release + t.eq(line[0], 1) -- mouse line for release + t.eq(col[0], 3) -- mouse column for release + t.eq(key.modifiers, 0) -- modifiers for press + + termkey.termkey_push_bytes(tk, '\x1b[M0++', 6) + + termkey.termkey_getkey(tk, key) + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for Ctrl-press + t.eq(button[0], 1) -- mouse button for Ctrl-press + t.eq(line[0], 11) -- mouse line for Ctrl-press + t.eq(col[0], 11) -- mouse column for Ctrl-press + t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_CTRL) -- modifiers for Ctrl-press + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 15) -- string length for Ctrl-press + t.eq(t.ffi.string(buffer), 'C-MousePress(1)') -- string buffer for Ctrl-press + + termkey.termkey_push_bytes(tk, '\x1b[M`!!', 6) + + termkey.termkey_getkey(tk, key) + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for wheel down + t.eq(button[0], 4) -- mouse button for wheel down + + termkey.termkey_push_bytes(tk, '\x1b[Mb!!', 6) + + termkey.termkey_getkey(tk, key) + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for wheel left + t.eq(button[0], 6) -- mouse button for wheel left + + -- rxvt protocol + termkey.termkey_push_bytes(tk, '\x1b[0;20;20M', 10) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press rxvt protocol + + t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press rxvt protocol + + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press rxvt protocol + t.eq(button[0], 1) -- mouse button for press rxvt protocol + t.eq(line[0], 20) -- mouse line for press rxvt protocol + t.eq(col[0], 20) -- mouse column for press rxvt protocol + t.eq(key.modifiers, 0) -- modifiers for press rxvt protocol + + termkey.termkey_push_bytes(tk, '\x1b[3;20;20M', 10) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse release rxvt protocol + + t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse release rxvt protocol + + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release rxvt protocol + t.eq(line[0], 20) -- mouse line for release rxvt protocol + t.eq(col[0], 20) -- mouse column for release rxvt protocol + t.eq(key.modifiers, 0) -- modifiers for release rxvt protocol + + -- SGR protocol + termkey.termkey_push_bytes(tk, '\x1b[<0;30;30M', 11) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press SGR encoding + + t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press SGR encoding + + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press SGR + t.eq(button[0], 1) -- mouse button for press SGR + t.eq(line[0], 30) -- mouse line for press SGR + t.eq(col[0], 30) -- mouse column for press SGR + t.eq(key.modifiers, 0) -- modifiers for press SGR + + termkey.termkey_push_bytes(tk, '\x1b[<0;30;30m', 11) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse release SGR encoding + + t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse release SGR encoding + + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release SGR + + termkey.termkey_push_bytes(tk, '\x1b[<0;500;300M', 13) + + termkey.termkey_getkey(tk, key) + termkey.termkey_interpret_mouse(tk, key, ev, button, line, col) + + t.eq(line[0], 300) -- mouse line for press SGR wide + t.eq(col[0], 500) -- mouse column for press SGR wide + + termkey.termkey_destroy(tk) + end) + + itp('31position', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + local line = t.ffi.new('int[1]') + local col = t.ffi.new('int[1]') + + termkey.termkey_push_bytes(tk, '\x1b[?15;7R', 8) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for position report + + t.eq(key.type, termkey.TERMKEY_TYPE_POSITION) -- key.type for position report + + t.eq(termkey.termkey_interpret_position(tk, key, line, col), termkey.TERMKEY_RES_KEY) -- interpret_position yields RES_KEY + + t.eq(line[0], 15) -- line for position report + t.eq(col[0], 7) -- column for position report + + -- A plain CSI R is likely to be <F3> though. + -- This is tricky :/ + + termkey.termkey_push_bytes(tk, '\x1b[R', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for <F3> + + t.eq(key.type, termkey.TERMKEY_TYPE_FUNCTION) -- key.type for <F3> + t.eq(key.code.number, 3) -- key.code.number for <F3> + + termkey.termkey_destroy(tk) + end) + + itp('32modereport', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + local initial = t.ffi.new('int[1]') + local mode = t.ffi.new('int[1]') + local value = t.ffi.new('int[1]') + + termkey.termkey_push_bytes(tk, '\x1b[?1;2$y', 8) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mode report + + t.eq(key.type, termkey.TERMKEY_TYPE_MODEREPORT) -- key.type for mode report + + t.eq( + termkey.termkey_interpret_modereport(tk, key, initial, mode, value), + termkey.TERMKEY_RES_KEY + ) -- interpret_modereoprt yields RES_KEY + + t.eq(initial[0], 63) -- initial indicator from mode report + t.eq(mode[0], 1) -- mode number from mode report + t.eq(value[0], 2) -- mode value from mode report + + termkey.termkey_push_bytes(tk, '\x1b[4;1$y', 7) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mode report + + t.eq(key.type, termkey.TERMKEY_TYPE_MODEREPORT) -- key.type for mode report + + t.eq( + termkey.termkey_interpret_modereport(tk, key, initial, mode, value), + termkey.TERMKEY_RES_KEY + ) -- interpret_modereoprt yields RES_KEY + + t.eq(initial[0], 0) -- initial indicator from mode report + t.eq(mode[0], 4) -- mode number from mode report + t.eq(value[0], 1) -- mode value from mode report + + termkey.termkey_destroy(tk) + end) + + itp('38csi', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + local args = t.ffi.new('TermKeyCsiParam[16]') + local nargs = t.ffi.new('size_t[1]') + local command = t.ffi.new('unsigned[1]') + + termkey.termkey_push_bytes(tk, '\x1b[5;25v', 7) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI v + + t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI + + t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY + + t.eq(nargs[0], 2) -- nargs for unknown CSI + -- t.eq(args[0], 5) -- args[0] for unknown CSI + -- t.eq(args[1], 25) -- args[1] for unknown CSI + t.eq(command[0], 118) -- command for unknown CSI + + termkey.termkey_push_bytes(tk, '\x1b[?w', 4) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI ? w + t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI + t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY + t.eq(command[0], bit.bor(bit.lshift(63, 8), 119)) -- command for unknown CSI + + termkey.termkey_push_bytes(tk, '\x1b[?$x', 5) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI ? $x + t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI + t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY + t.eq(command[0], bit.bor(bit.lshift(36, 16), bit.lshift(63, 8), 120)) -- command for unknown CSI + + termkey.termkey_destroy(tk) + end) + + itp('39dcs', function() + local tk = termkey.termkey_new_abstract('xterm', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + + -- 7bit DCS + termkey.termkey_push_bytes(tk, '\x1bP1$r1 q\x1b\\', 10) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for DCS + + t.eq(key.type, termkey.TERMKEY_TYPE_DCS) -- key.type for DCS + t.eq(key.modifiers, 0) -- key.modifiers for DCS + + local str = t.ffi.new('const char*[1]') + t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- termkey_interpret_string() gives string + t.eq(t.ffi.string(str[0]), '1$r1 q') -- termkey_interpret_string() yields correct string + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey again yields RES_NONE + + -- 8bit DCS + termkey.termkey_push_bytes(tk, '\x901$r2 q\x9c', 8) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for DCS + + t.eq(key.type, termkey.TERMKEY_TYPE_DCS) -- key.type for DCS + t.eq(key.modifiers, 0) -- key.modifiers for DCS + + t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- "termkey_interpret_string() gives string"); + t.eq(t.ffi.string(str[0]), '1$r2 q') -- "termkey_interpret_string() yields correct string"); + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- "getkey again yields RES_NONE"); + + -- 7bit OSC + termkey.termkey_push_bytes(tk, '\x1b]15;abc\x1b\\', 10) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for OSC + + t.eq(key.type, termkey.TERMKEY_TYPE_OSC) -- key.type for OSC + t.eq(key.modifiers, 0) -- key.modifiers for OSC + + t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- "termkey_interpret_string() gives string"); + t.eq(t.ffi.string(str[0]), '15;abc') -- "termkey_interpret_string() yields correct string"); + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey again yields RES_NONE + + -- False alarm + termkey.termkey_push_bytes(tk, '\x1bP', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN for false alarm + + t.eq(termkey.termkey_getkey_force(tk, key), termkey.TERMKEY_RES_KEY) -- getkey_force yields RES_KEY for false alarm + + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type for false alarm + t.eq(key.code.codepoint, string.byte('P')) -- key.code.codepoint for false alarm + t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_ALT) -- key.modifiers for false alarm + + termkey.termkey_destroy(tk) + end) +end) |