aboutsummaryrefslogtreecommitdiff
path: root/test/unit
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2024-11-19 22:57:13 +0000
committerJosh Rahm <joshuarahm@gmail.com>2024-11-19 22:57:13 +0000
commit9be89f131f87608f224f0ee06d199fcd09d32176 (patch)
tree11022dcfa9e08cb4ac5581b16734196128688d48 /test/unit
parentff7ed8f586589d620a806c3758fac4a47a8e7e15 (diff)
parent88085c2e80a7e3ac29aabb6b5420377eed99b8b6 (diff)
downloadrneovim-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.lua6
-rw-r--r--test/unit/api/testutil.lua12
-rw-r--r--test/unit/eval/encode_spec.lua36
-rw-r--r--test/unit/eval/typval_spec.lua136
-rw-r--r--test/unit/fixtures/rbuffer.c28
-rw-r--r--test/unit/fixtures/rbuffer.h9
-rw-r--r--test/unit/formatc.lua1
-rw-r--r--test/unit/mbyte_spec.lua138
-rw-r--r--test/unit/path_spec.lua34
-rw-r--r--test/unit/rbuffer_spec.lua340
-rw-r--r--test/unit/statusline_spec.lua4
-rw-r--r--test/unit/termkey_spec.lua975
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ΜΜˆΜ•ΜˆΜŽΜè̇̅̄̄̐mΜ…Μ–ΜŸΜ„ΜŸΜš', {'LΜ“Μ‰Μ‘Μ’ΜŒΜš', 'oΜŒΜ’Μ—Μ„Μ›Μ€', 'rΜΜˆΜ•ΜˆΜŽΜ', 'è̇̅̄̄̐', '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)