diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bcd76a8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,93 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + tags: + - "v*" + +jobs: + test: + name: Test (Neovim ${{ matrix.nvim }}) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + nvim: + - stable + # - nightly # Temporarily disabled: Lua print() is not recorded in :messages on Nightly. + - v0.10.0 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Checkout keyevent.nvim + uses: actions/checkout@v4 + with: + repository: kibi2/keyevent.nvim + ref: v0.1.1 + path: .deps/keyevent.nvim + + - name: Setup Neovim + uses: rhysd/action-setup-vim@v1 + with: + neovim: true + version: ${{ matrix.nvim }} + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Install system packages + run: | + sudo apt-get update + sudo apt-get install -y jq luarocks + + - name: Install LuaRocks & LuaCov + run: | + luarocks install --local luacov + luarocks install --local luacov-html + eval "$(luarocks path --bin)" + + - name: Show versions + run: | + nvim --version + lua -v || true + + - name: Run tests + run: | + chmod +x tests/run.sh + tests/run.sh + + - name: Upload diff artifacts (on failure) + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-diffs-${{ matrix.nvim }} + path: | + tests/cases/**/diff-*.txt + tests/stdout.txt + tests/stderr.txt + + - name: Generate coverage report + run: | + chmod +x tests/coverage.sh + tests/coverage.sh + + - name: Upload coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ matrix.nvim }} + path: luacov.report.html + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + files: ./luacov.report.out + fail_ci_if_error: true + verbose: true diff --git a/.luacov b/.luacov new file mode 100644 index 0000000..6a7f1ba --- /dev/null +++ b/.luacov @@ -0,0 +1,16 @@ +return { + statsfile = "luacov.stats.out", + reportfile = "luacov.report.out", + + include = { + "lua/.*", + }, + + exclude = { + ".*/nvim/runtime/.*", + ".*/.luarocks/.*", + ".*/site/pack/.*", + ".*/tests/cases/.*", + ".*/keyevent.nvim/.*", + }, +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2daa133..a9d7539 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,20 @@ All notable changes to this project will be documented in this file. ## [0.2.1] - TBD +### Added + +* Added shared counts across paired motion keys. +* Added CI tests. +* Added `luacov` for code coverage. + +### Changed + +* Changed deceleration to holding the opposite motion key. For example, holding `k` while holding `j` reduces the motion amount. +* Consecutive taps now increase the motion amount up to 4×; a third consecutive tap resets the acceleration. + ### Fixed +* Prevented acceleration when an event is detected as an invalid repeat (`ng_repeat`). * Fixed a one-key delay when applying the effect of a held key. ## [0.2.0] - 2026-09-26 diff --git a/README.md b/README.md index 9aa9bac..8db8eba 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,24 @@ # rush.nvim -**Time-based key mappings for Neovim.** +[![CI](https://github.com/kibi2/rush.nvim/actions/workflows/ci.yml/badge.svg)](https://github.com/kibi2/rush.nvim/actions) +[![codecov](https://codecov.io/gh/kibi2/rush.nvim/branch/main/graph/badge.svg)](https://codecov.io/gh/kibi2/rush.nvim) +![GitHub release](https://img.shields.io/github/v/release/kibi2/rush.nvim) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +![Neovim](https://img.shields.io/badge/Neovim-0.10+-57A143?logo=neovim) + +**Control Vim motions with key repeats and holds.** `rush.nvim` changes the amount of motion based on how you repeat and hold a key. For example: ```text -For example: - j → normal Vim behavior jj → normal Vim behavior +j + hold j → hold j is replaced with "2j" jj + hold j → hold j is replaced with "4j" 7 + hold j → hold j is replaced with "7j" +5 + j + hold j → share count 5 between j and k ``` You can also change the motion amount while holding a key: @@ -23,12 +29,14 @@ tap j + hold j → start with a larger motion while holding j: hold j again → increase the motion - tap j + hold j → decrease the motion + hold k → decrease the motion ``` The motion can eventually change direction: -j → larger j motions → smaller j motions → -j → larger -j motions +```text +j → larger j motions → smaller j motions → reverse direction → larger reverse motions +``` The idea is simple: @@ -36,14 +44,15 @@ The idea is simple: ## Features -* Time-based key input detection -* Distinguishes clicks, taps, and key repeats -* Accelerates and decelerates repeated motions -* Uses tap sequences to change the motion amount -* Uses Ctrl and Alt as acceleration/deceleration controls on macOS -* Works in normal and visual modes -* Configurable timing thresholds -* Built-in diagnosis command for measuring key repeat timing +- Time-based key input detection +- Distinguishes clicks, taps, and key repeats +- Accelerates and decelerates repeated motions +- Uses tap sequences to change the motion amount +- Uses Ctrl and Alt as acceleration/deceleration controls on macOS +- Shares motion amounts between `j`/`k` or `h`/`l` +- Works in normal and visual modes +- Configurable timing thresholds +- Built-in diagnosis command for measuring key repeat timing ## Example @@ -52,7 +61,7 @@ Except for held keys, `rush.nvim` behaves like normal Vim motions. The following motion keys can be accelerated: ```text -h j k l w b e +h j k l w b ``` When you hold one of these keys, `rush.nvim` changes the motion amount according to the preceding taps, the repeat sequence, and, on macOS, the state of the modifier keys. @@ -62,37 +71,36 @@ When you hold one of these keys, `rush.nvim` changes the motion amount according A count is preserved throughout a key repeat sequence. ```text -5 + hold j → 5j, 5j, 5j, 5j, 5j, ... +5 + hold j → 5j repeat ``` In other words, each repeated `j` is effectively replaced with `5j`. ### Increase the motion from the beginning -The number of preceding taps determines the initial motion amount. - -Acceleration starts one repeat later because `rush.nvim` needs to distinguish taps from holds. +The number of consecutive taps determines the motion amount for subsequent repeats. ```text -hold j → 1j, 1j, 1j, 1j, 1j, ... - -tap j + hold j → 1j, 1j, 2j, 2j, 2j, ... - -tap j + tap j + hold j → 1j, 1j, 1j, 4j, 4j, ... +hold j → 1j repeat +tap j + hold j → 2j repeat +tap j + tap j + hold j → 4j repeat +tap j + tap j + tap j + hold j → 1j repeat ``` -Each additional tap before the hold doubles the motion amount. +The initial `j` of a sequence is always executed as `1j`; the increased motion applies to subsequent repeats. + +Each additional tap doubles the motion amount, up to 4×. A third consecutive tap resets the acceleration. ### Increase the motion while holding You can double the motion amount by releasing the key and holding it again. ```text -hold j → 1j, 1j, 1j, ... +hold j → 1j repeat - + hold j → 1j, 2j, 2j, ... ++ hold j → 2j repeat - + hold j → 2j, 4j, 4j, ... ++ hold j → 4j repeat ``` Each additional hold doubles the motion amount. @@ -104,11 +112,11 @@ You can also halve the motion amount. For example: ```text - + hold j → 2j, 2j, 2j, 2j, ... ++ hold j → 2j repeat - + tap j + hold j → 2j, 2j, 1j, 1j, ... ++ hold k → 1j repeat - + tap j + hold j → 1j, 1j, -1j, -1j, ... ++ hold k → 1k repeat ``` When the motion amount reaches one, another decrease reverses the direction. @@ -125,18 +133,51 @@ hold j + tap Ctrl → double the motion amount hold j + tap Alt → halve the motion amount ``` -Tapping Alt has the same effect as `tap j + hold j`. +Tapping Ctrl provides a convenient way to accelerate the motion without releasing the motion key. + +Tapping Alt provides a convenient way to decelerate the motion without changing the direction. > **Note:** On Windows and Ubuntu, pressing a modifier key while a key is repeating may stop the key repeat. macOS does not have this behavior, so modifier-key control is currently supported on macOS only. -You can also change the motion amount with `hold j` or `tap j + hold j`, but `hold j` requires a short wait before the repeat starts. +You can also change the motion amount with `hold j` or `hold k`. For example, while holding `j`, holding `k` decreases the motion amount and eventually reverses the direction. -Using a modifier key avoids this delay, so the motion amount can be changed immediately while the key is repeating. +Modifier keys provide an alternative way to change the motion amount while a key is repeating. Unlike `hold j` or `hold k`, they do not require releasing and holding another motion key. Modifier keys are also less affected by tap/hold detection errors, since they do not require distinguishing between a tap and a hold. However, modifier keys may be harder to press depending on the keyboard layout and the position of the motion key. +Choose whichever method feels more comfortable for your keyboard and workflow. + +### Share the motion + +A motion amount can be shared between `j` and `k`. + +For example: + +```text +5j + hold j → 5j repeat + +k, k, j, k → 5k, 5k, 5j, 5k + +1k, k, j, k → 1k, k, j, k + (shared count is reset) +``` + +The shared count can also be cleared by pressing `Esc`. + +Motion amounts can be shared between the following pairs: + +```text +j / k +h / l +w / b +``` + +Using another count with a motion key, such as `3h`, exits shared mode. + +Using a count with another Vim command, such as `2dd`, does not exit shared mode. + ## How it works `rush.nvim` uses [`keyevent.nvim`](https://github.com/kibi2/keyevent.nvim) to detect sequences of key events and their timing. @@ -147,8 +188,8 @@ The same motion key can therefore have different meanings depending on how it is j j j ───────────── hold - ↓ - key repeats + ↓ + key repeats ``` The exact behavior depends on the timing thresholds configured for your environment. @@ -219,8 +260,8 @@ The goal is not to replace Vim's motions, but to make moving around large docume ## Requirements -* Neovim 0.10+ -* A system with key repeat support +- Neovim 0.10+ +- A system with key repeat support ## Limitations @@ -228,9 +269,9 @@ The goal is not to replace Vim's motions, but to make moving around large docume The exact timing characteristics depend on: -* your operating system -* your keyboard -* your OS key repeat settings +- your operating system +- your keyboard +- your OS key repeat settings Use: @@ -242,4 +283,4 @@ to measure the key repeat timing on your system and determine suitable values. ## License -MIT +MIT \ No newline at end of file diff --git a/lua/rush/config.lua b/lua/rush/config.lua deleted file mode 100644 index 5ab630e..0000000 --- a/lua/rush/config.lua +++ /dev/null @@ -1,24 +0,0 @@ -local M = {} - -local default_config = { - log = { - level = vim.log.levels.ERROR, - output = "file", -- "buffer", "file", "print", "notify" - buffer_name = "rush://log", - file_name = "/tmp/rush.log", - use_timestamp = false, - single_line = true, - probe = true, - monitor = false, - }, -} - -function M.setup(opts) - local config = - vim.tbl_deep_extend("force", vim.deepcopy(default_config), opts or {}) - for key, value in pairs(config) do - M[key] = value - end -end - -return M diff --git a/lua/rush/init.lua b/lua/rush/init.lua index 43b68c7..da6ce46 100644 --- a/lua/rush/init.lua +++ b/lua/rush/init.lua @@ -1,104 +1,107 @@ -local config = require("rush.config") -local KeyEvent = require("keyevent.keyevent") -local bitflag = require("keyevent.bitflag") -local log = require("rush.log") +local keyevent = require("keyevent.keyevent") +local log = require("keyevent.log") local M = {} -local META = KeyEvent.meta() +local META = keyevent.meta() ---@enum RushState local STATE = { + INIT = "init", NORMAL = "normal", - HOLD1 = "hold1", - HOLD2 = "hold2", + REPEAT = "repeat", } local KEYMAP_MODE = { "n", "x" } -local key_sets = { - h = "l", - j = "k", - k = "j", - l = "h", - w = "b", - b = "w", - e = "ge", +local key_pairs = { + { "h", "l" }, + { "j", "k" }, + { "w", "b" }, + -- {"e", "ge"}, } +local rush_keys = {} local accelerate = { forward = META.C, backward = META.A, } -local state = STATE.NORMAL -local rush_count = 0 -local saved_keymaps +-- attribute +local state = STATE.INIT +local vim_count = 1 +local level = 0 +local share_key = "" +local share_count = 0 + +local saved_keymaps = nil local rush_motions = {} ----@return integer -local function get_count() - return math.abs(rush_count) +---@param event KeyEvent +local function is_reverse_key(event) + return rush_keys[event.key] == event.prev_key end ----@return string -local function get_key(event) - if rush_count < 0 then - return key_sets[event.key] - end - return event.key +local function is_share_key(event) + return event.key == share_key or event.key == rush_keys[share_key] end ---@param event KeyEvent ----@return string -local function get_new_motion(event) - if state == STATE.NORMAL then - return event.key - end - local count = get_count() - if count == 0 then - return "" - elseif count == 1 then - return get_key(event) +---@return integer +local function get_count_normal(event) + if is_share_key(event) then + return share_count == 0 and 1 or share_count else - return count .. get_key(event) + return 1 end end -local function debug_event(event) - log.debug( - "%7s %3s %s", - state, - get_new_motion(event), - KeyEvent.to_string(event) - ) +---@return integer +local function get_count_rush() + local scale + if level >= 0 then + scale = 2 ^ level + else + scale = -(2 ^ (-level - 1)) + end + return math.max(vim_count, share_count, 1) * scale end -local function increase() - if rush_count < 0 then - rush_count = math.modf(rush_count / 2) - if rush_count == 0 then - rush_count = 1 - end +---@param event KeyEvent +---@return integer +local function get_count(event) + if event.nr <= 1 then + return get_count_normal(event) else - rush_count = rush_count * 2 + return get_count_rush() end end -local function decrease() - if rush_count < 0 then - rush_count = rush_count * 2 +---@param event KeyEvent +---@return string +local function get_new_motion(event) + local count = get_count(event) + local key = event.key + if count < 0 then + key = rush_keys[event.key] + count = -count + end + if count == 1 then + return key else - rush_count = math.modf(rush_count / 2) - if rush_count == 0 then - rush_count = -1 - end + return count .. key end end +---@param event KeyEvent +---@param new_motion string +local function debug_event(event, new_motion) + log.watch("RSH", "%7s %3s %s", state, new_motion, keyevent.to_string(event)) +end + ---@param key string local function save_keymap(maps, key) for _, map in ipairs(maps) do - if KeyEvent.is_same_key_notation(map.lhs, key) then + if keyevent.is_same_key_notation(map.lhs, key) then return map end end @@ -109,8 +112,12 @@ local function save_keymaps(bufnr) local save = {} for _, mode in ipairs(KEYMAP_MODE) do local maps = vim.api.nvim_buf_get_keymap(bufnr, mode) + for _, key in ipairs(rush_motions) do - save[#save + 1] = save_keymap(maps, key) + local map = save_keymap(maps, key) + if map then + save[#save + 1] = save_keymap(maps, key) + end end end saved_keymaps = { @@ -124,7 +131,7 @@ local function set_metakeymaps() save_keymaps(bufnr) for _, motion in ipairs(rush_motions) do vim.keymap.set(KEYMAP_MODE, motion, function() - return M.key_process(motion) + return M._key_process(motion) end, { expr = true, buffer = bufnr }) end end @@ -164,118 +171,224 @@ end ---@param new_state RushState local function transition(new_state) - if state == STATE.NORMAL and new_state == STATE.HOLD1 then - set_metakeymaps() - elseif state ~= STATE.NORMAL and new_state == STATE.NORMAL then + if new_state == STATE.REPEAT then + if not saved_keymaps then + set_metakeymaps() + end + else if saved_keymaps then remove_metakeymap(saved_keymaps.bufnr) restore_metakeymaps(saved_keymaps.bufnr, saved_keymaps.maps) saved_keymaps = nil end - -- Reset rush count for the next normal motion. - rush_count = 1 end state = new_state end ---@param event KeyEvent -local function check_transition(event) - if state == STATE.NORMAL then - if event.nr == 1 then - transition(STATE.HOLD1) - end - elseif state == STATE.HOLD1 then - if event.nr == 1 then - transition(STATE.HOLD2) - end +local function set_share(event) + share_key = event.key + share_count = vim_count + vim_count = 0 +end + +local function clear_share() + share_key = "" + share_count = 0 +end + +local function process_init() + level = 0 + vim_count = vim.v.count + if vim_count ~= 0 then + clear_share() end - if state ~= STATE.NORMAL then - if - not KeyEvent.is_same_key(event) - or event.type == KeyEvent.KEY_EVENT_TYPE.CLICK - then - transition(STATE.NORMAL) - end + transition(STATE.NORMAL) +end + +---@param event KeyEvent +---@return boolean +local function is_share_event(event) + if vim_count == 0 then + return false end + if event.nt ~= 2 then + return false + end + local event3 = keyevent.peek(3) + local prev_key = event3.prev_key or "" + return prev_key:match("^%d+$") end ---@param event KeyEvent -local function process_normal(event) - if not KeyEvent.is_same_key(event) then - rush_count = vim.v.count1 +local function check_share(event) + if is_share_event(event) then + level = level - 1 + set_share(event) end end ---@param event KeyEvent -local function process_rush(event) - if state == STATE.HOLD1 then - if event.nr == 1 then - rush_count = rush_count * 2 ^ (event.nt - 1) +local function process_normal(event) + if event.type == keyevent.KEY_EVENT_TYPE.REPEAT then + check_share(event) + if event.ng_repeat ~= 0 and event.nh >= 2 then + level = level - 1 end - elseif state == STATE.HOLD2 then - if event.nr == 1 then - if event.nt == 1 then - increase() - else - decrease() - end + transition(STATE.REPEAT) + return + end + if 2 <= event.nt and event.nt <= 3 then + if event.ng_repeat == 0 then + level = level + 1 end + else + level = 0 + vim_count = 0 end - if - bitflag.is_off(event.prev_meta, accelerate.forward) - and bitflag.is_on(event.meta, accelerate.forward) - then - increase() +end + +---@param event KeyEvent +local function process_repeat_meta(event) + if event.meta == event.prev_meta then + return end - if - bitflag.is_off(event.prev_meta, accelerate.backward) - and bitflag.is_on(event.meta, accelerate.backward) - then - decrease() + if event.meta == accelerate.forward then + level = level + 1 + elseif event.meta == accelerate.backward then + level = level - 1 end end +---@param event KeyEvent +local function process_repeat_tap(event) + if keyevent.is_same_key(event) then + level = level + 1 + elseif is_reverse_key(event) then + level = -level + else + level = 0 + end +end + +---@param event KeyEvent +local function process_repeat(event) + if event.type == keyevent.KEY_EVENT_TYPE.REPEAT then + process_repeat_meta(event) + return + end + if event.type == keyevent.KEY_EVENT_TYPE.TAP then + process_repeat_tap(event) + else + level = 0 + end + transition(STATE.NORMAL) +end + +-- | state | event | next state | action | | +-- | | | | level | | +-- | INIT | CTR | -> NORMAL | 0 | vim_count | +-- | NORMAL | R | -> REPEAT | - | share_count | +-- | | CT nt<=1 | - | - | | +-- | | T nt>=2 | - | up | | +-- | REPEAT | R | - | - | check_meta | +-- | | T+ | -> NORMAL | up | | +-- | | T- | -> NORMAL | reverse | | +-- | | Tx | -> NORMAL | 0 | | +-- | | C | -> NORMAL | 0 | | ---@param event KeyEvent local function process_event(event) - check_transition(event) - if state == STATE.NORMAL then + if event.ng_repeat >= 3 then + event.type = keyevent.KEY_EVENT_TYPE.REPEAT + event.nr = event.ng_repeat + end + if state == STATE.INIT then + process_init() + elseif state == STATE.NORMAL then process_normal(event) + elseif state == STATE.REPEAT then + process_repeat(event) else - process_rush(event) + assert(false, "Invalid state: " .. state) end end -function M.key_process(motion) - local event = KeyEvent.keymap_event(motion) - process_event(event) - debug_event(event) - return get_new_motion(event) +local function test_output(event, new_key) + if vim.g.kibi2_test_mode == 1 then + local str = string.format( + "%7s %3s (%d %d %d) %s", + state, + new_key, + vim_count, + level, + share_count, + keyevent.to_string(event) + ) + print(str) + end +end + +---@param event KeyEvent +---@return boolean +local function is_break_or_end(event) + return event.type == keyevent.KEY_EVENT_TYPE.BREAK + or event.type == keyevent.KEY_EVENT_TYPE.REPEAT_END end -function M.setup(opts) - config.setup(opts or {}) - for motion, _ in pairs(key_sets) do +local function initialize_rush_keys() + for _, set in ipairs(key_pairs) do + rush_keys[set[1]] = set[2] + rush_keys[set[2]] = set[1] + end +end + +local function initialize_keymaps() + for motion, _ in pairs(rush_keys) do vim.keymap.set(KEYMAP_MODE, motion, function() - return M.key_process(motion) + return M._key_process(motion) end, { expr = true }) end +end + +local function initialize_rush_motions() rush_motions = {} - for motion, _ in pairs(key_sets) do + for motion, _ in pairs(rush_keys) do for _, meta in ipairs({ accelerate.forward, accelerate.backward }) do - local motion = KeyEvent.unparse(motion, meta) - rush_motions[#rush_motions + 1] = motion + rush_motions[#rush_motions + 1] = keyevent.unparse(motion, meta) end end end -KeyEvent.on_event(function(event) - if event.type == KeyEvent.KEY_EVENT_TYPE.REPEAT_END then - debug_event(event) - transition(STATE.NORMAL) - elseif event.type == KeyEvent.KEY_EVENT_TYPE.BREAK then - debug_event(event) - transition(STATE.NORMAL) +local function on_event(event) + if is_break_or_end(event) or rush_keys[event.key] == nil then + debug_event(event, "") + transition(STATE.INIT) end -end) + if event.key == "" then + clear_share() + end +end + +local function initialize() + initialize_rush_keys() + initialize_keymaps() + initialize_rush_motions() + keyevent.on_event(function(event) + on_event(event) + end) +end + +function M._key_process(motion) + local event = keyevent.keymap_event(motion) + process_event(event) + local new_motion = get_new_motion(event) + test_output(event, new_motion) + debug_event(event, new_motion) + return new_motion +end + +function M.setup() end + +initialize() return M diff --git a/lua/rush/log.lua b/lua/rush/log.lua deleted file mode 100644 index a29231a..0000000 --- a/lua/rush/log.lua +++ /dev/null @@ -1,469 +0,0 @@ -local api = vim.api -- Neovim -local bo = vim.bo -local fn = vim.fn - -local config = require("rush.config") -local notify = require("rush./notify") - --- ============================================================================= - -local M = {} - -local levels = vim.log.levels - -local level_names = {} -local PREFIX = "TIR" -local uv = vim.loop -local queue = {} -local scheduled = false -local log_bufnr = nil - -for name, value in pairs(levels) do - level_names[value] = name -end - -local last_tick = {} -local last_mem = 0 -local last_time = uv.hrtime() -local monitoring = false - --- ============================================================================= ---#region Private - ----@return integer -local function get_tick() - local bufnr = api.nvim_get_current_buf() - return api.nvim_buf_get_changedtick(bufnr) -end - ----@return integer -local function get_delta_tick() - local bufnr = api.nvim_get_current_buf() - local curr_tick = api.nvim_buf_get_changedtick(bufnr) - local prev_tick = last_tick[bufnr] - last_tick[bufnr] = curr_tick - if not prev_tick then - return 0 - end - -- local delta_tick = curr_tick - prev_tick - -- if delta_tick > 1 then - -- M.probe( - -- "TICK [%d] %d - %d = %d", - -- bufnr, - -- curr_tick, - -- prev_tick, - -- delta_tick - -- ) - -- end - return curr_tick - prev_tick -end - ----@return integer -local function get_mem_mb() - return collectgarbage("count") / 1024 -end - ----@return string -local function get_monitor() - if not config.log.monitor then - return "" - end - local current_mem = get_mem_mb() - local delta_mem = current_mem - last_mem - return string.format("[%d:%d(+%d)MB]", get_tick(), current_mem, delta_mem) -end - -local function monitor() - if monitoring then - return - end - monitoring = true - - local mem = get_mem_mb() - local now = uv.hrtime() - - local delta_tick = get_delta_tick() - if now - last_time < 1000 * 1e6 then - if delta_tick > 100 then - M.error("changedtick runaway detected") - end - end - - if mem > 2000 then - M.error("memory runaway: " .. mem .. "MB") - end - - last_mem = mem - last_time = now - - monitoring = false -end - ----@param value any ----@return any -local function normalize_for_log(value) - local v_type = type(value) - if v_type ~= "table" then - return value - end - local mt = getmetatable(value) - if mt and mt.__tostring then - return tostring(value) - end - local result = {} - for key, val in pairs(value) do - result[key] = normalize_for_log(val) - end - return result -end - ----@param value any ----@return string -local function stringify(value) - local v_type = type(value) - - if v_type == "table" then - local normalized = normalize_for_log(value) - if config.log.single_line then - return string.format( - " %s", - vim.inspect(normalized, { - newline = " ", - indent = "", - depth = 4, - }) - ) - else - return string.format("
\n%s", vim.inspect(value)) - end - elseif v_type == "string" then - return string.format(" %s", value) - else - return string.format("<%s> %s", v_type, tostring(value)) - end -end - ----@return string -local function get_timestamp() - if not config.log.use_timestamp then - return "" - end - local now = uv.hrtime() - if not last_time then - last_time = now - ---@type string - return os.date("[%H:%M:%S]") - end - local delta_ms = (now - last_time) / 1e6 - last_time = now - return string.format("[+%.0fms]", delta_ms) -end - -local function get_bufnr_by_name(name) - for _, bufnr in ipairs(api.nvim_list_bufs()) do - if api.nvim_buf_get_name(bufnr) == name then - return bufnr - end - end - return nil -end - -local category_hl_map = {} -local category_match_id = {} -api.nvim_set_hl(0, "TirLog_Entry", { fg = "#55ffff", bold = true }) -api.nvim_set_hl( - 0, - "TirLog_Error", - { fg = "#ffffff", bg = "#ff0000", bold = true } -) -api.nvim_set_hl(0, "TirLog_num", { fg = "#cc55cc", bold = true }) - ----@param bufnr number -local function apply_log_highlight(bufnr) - local winid = fn.bufwinid(bufnr) - if winid == -1 then - return - end - api.nvim_win_call(winid, function() - fn.clearmatches() - fn.matchadd("TirLog_Entry", "===") - --fn.matchadd("TirLog_Error", [[\[[0-9,]\+\]]]) - fn.matchadd("TirLog_num", "\\[[0-9,]\\+\\]") - fn.matchadd("TirLog_Error", "\\[ERROR\\]") - for cat, hl in pairs(category_hl_map) do - fn.matchadd(hl, "\\[" .. cat .. "\\]") - end - end) -end - -local aug = api.nvim_create_augroup("TirenviLogHL", { clear = true }) ----@param bufnr number -local function register_autocmds(bufnr) - api.nvim_create_autocmd("BufWinEnter", { - group = aug, - buffer = bufnr, - callback = function(args) - --if args.buf == log_bufnr then - apply_log_highlight(args.buf) - --end - end, - }) -end - ----@return number -local function ensure_log_buf() - if log_bufnr and api.nvim_buf_is_valid(log_bufnr) then - return log_bufnr - end - log_bufnr = get_bufnr_by_name(config.log.buffer_name) - if log_bufnr then - return log_bufnr - end - ---@type number - log_bufnr = api.nvim_create_buf(false, true) - api.nvim_buf_set_name(log_bufnr, config.log.buffer_name) - - bo[log_bufnr].buftype = "nofile" - bo[log_bufnr].bufhidden = "hide" - bo[log_bufnr].swapfile = false - register_autocmds(log_bufnr) - return log_bufnr -end - -local function flush_buffer(buf_string) - local bufnr = ensure_log_buf() - api.nvim_buf_set_lines(bufnr, -1, -1, false, vim.split(buf_string, "\n")) - local line_count = api.nvim_buf_line_count(bufnr) - local win = fn.bufwinid(bufnr) - if win ~= -1 then - api.nvim_win_set_cursor(win, { line_count, 0 }) - api.nvim_win_call(win, function() - -- vim.cmd("normal! 3kzz") - end) - end -end - -local initialized = false -local function flush_file(buf_string) - local file = config.log.file_name or "/tmp/tirenvi.log" - if not initialized then - local fds = io.open(file, "w") - if fds then - fds:close() - else - error("tirenvi: failed to open log file: " .. file) - end - initialized = true - end - local fds = io.open(file, "a") - if fds then - fds:write(buf_string .. "\n") - fds:close() - end -end - -local function flush() - scheduled = false - if #queue == 0 then - return - end - local buf_string = table.concat(queue, "\n") - buf_string = buf_string:gsub("\r", "") - if config.log.output == "buffer" then - flush_buffer(buf_string) - elseif config.log.output == "file" then - flush_file(buf_string) - end - queue = {} -end - ----@param msg string -local function write_buffer(msg) - table.insert(queue, msg) - if not scheduled then - scheduled = true - vim.schedule(flush) - end -end - -local unpack = table.unpack or unpack -- Lua 5.1/5.2 compatibility -local palette = { - "#ff5555", - "#55aaaa", - "#ffff55", - "#aaff55", - "#aa55ff", - "#ffaa55", -} - -local assigned = {} -local used = {} - -local function pick_color(cat) - if assigned[cat] then - return assigned[cat] - end - local hash = fn.sha256(cat) - local base = (fn.str2nr(hash:sub(1, 8), 16) % #palette) + 1 - for i = 0, #palette - 1 do - local idx = ((base + i - 1) % #palette) + 1 - local color = palette[idx] - if not used[color] then - assigned[cat] = color - used[color] = true - return color - end - end - local color = palette[base] - assigned[cat] = color - return color -end - -local function ensure_category_highlight(category) - if category_hl_map[category] then - return category_hl_map[category] - end - - local hl = "TirLog_" .. category - local color = pick_color(category) - - api.nvim_set_hl(0, hl, { fg = color, bold = true }) - - category_hl_map[category] = hl - return hl -end - -local function ensure_match(bufnr, category, hl) - if category_match_id[category] then - return - end - - api.nvim_buf_call(bufnr, function() - local pattern = "\\[" .. category .. "\\]" - local id = fn.matchadd(hl, pattern) - category_match_id[category] = id - end) -end - ----@param force boolean ----@param level integer ----@param opts? table ----@param fmt any ----@param ... unknown -local function emit(force, level, opts, fmt, ...) - monitor() - if not force and level < config.log.level then - return - end - if opts == nil or fmt == nil then - return - end - local category = opts and opts.category - if category then - local bufnr = ensure_log_buf() - local hl = ensure_category_highlight(category) - ensure_match(bufnr, category, hl) - end - local info = debug.getinfo(3, "Sl") - local file = info and (info.short_src:match("([^/\\]+)$")) or "?" - local line = info and info.currentline or 0 - local args = { ... } - local msg - local ok, result = pcall(function() - return string.format(fmt, unpack(args)) - end) - if ok then - msg = result - else - local args = { fmt, ... } - local parts = vim.tbl_map(stringify, args) - msg = table.concat(parts, " ") - end - local ts = get_timestamp() - local mon = get_monitor() - local name = level_names[level] - if force then - name = "🟧PRB" - elseif category then - name = category - end - local final = string.format( - "[%s]%s%s[%s][%s %d] %s", - PREFIX, - ts, - mon, - name, - file, - line, - msg - ) - if config.log.output == "buffer" then - write_buffer(final) - elseif config.log.output == "file" then - write_buffer(final) - elseif config.log.output == "print" then - print(final) - else - notify.notify(final, level) - end -end - ---#endregion --- ============================================================================= --- Public API - ----@param ... unknown -function M.debug(...) - emit(false, levels.DEBUG, {}, ...) -end - ----@param ... unknown -function M.info(...) - emit(false, levels.INFO, {}, ...) -end - ----@param ... unknown -function M.warn(...) - emit(false, levels.WARN, {}, ...) -end - ----@param ... unknown -function M.error(...) - emit(false, levels.ERROR, {}, ...) -end - ----@param ... unknown -function M.probe(...) - if not config.log.probe then - return - end - emit(true, levels.ERROR, {}, ...) -end - ----@param category string ----@param ... unknown -function M.watch(category, ...) - if levels.DEBUG < config.log.level then - return - end - emit(false, levels.DEBUG, { category = category }, ...) -end - ----@return boolean -function M.is_debug() - return levels.DEBUG >= config.log.level -end - ----@param condition any ----@param message string ----@param ... unknown -function M.assert(condition, message, ...) - if M.is_debug() then - assert(condition, message, ...) - end - if not condition then - M.error(message, ...) - end -end - -return M diff --git a/lua/rush/notify.lua b/lua/rush/notify.lua deleted file mode 100644 index 6e02b85..0000000 --- a/lua/rush/notify.lua +++ /dev/null @@ -1,51 +0,0 @@ --- ============================================================================= - -local M = {} - --- ============================================================================= ---#region Private - ----@param msg string ----@param level integer -local function emit(msg, level) - if msg == nil then - return - end - -- In test mode, avoid scheduling for deterministic behavior. - if vim.g.tirenvi_test_mode then - if msg ~= "" then - print(msg) - end - else - vim.schedule(function() - vim.notify(msg, level) - end) - end -end - ---#endregion --- ============================================================================= --- Public API - ----@param msg string -function M.error(msg) - emit(msg, vim.log.levels.ERROR) -end - ----@param msg string -function M.warn(msg) - emit(msg, vim.log.levels.WARN) -end - ----@param msg string -function M.info(msg) - emit(msg, vim.log.levels.INFO) -end - ----@param msg string ----@param level integer -function M.notify(msg, level) - emit(msg, level) -end - -return M diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..b584f8c --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,18 @@ +# --- Generated by test runner --- +!data/ + +# Actual output (never commit) +out-actual.txt +gen* +diff-* +work + +# Captured logs +stdout.txt +stderr.txt + +# Temporary files +*.tmp +*.swp +*.swo +*~ diff --git a/tests/backend/nvim.sh b/tests/backend/nvim.sh new file mode 100644 index 0000000..ea5eced --- /dev/null +++ b/tests/backend/nvim.sh @@ -0,0 +1,41 @@ +TEST_BIN="${TEST_BIN:-nvim}" + +backend_setup() { + echo "Running with: $TEST_BIN" + printf "${BOLD}${GREEN}%s${RESET}\n" "$($TEST_BIN --version | head -n 1)" + STATS="luacov.stats.out" + eval "$(luarocks path)" + : > "$ROOT_DIR/$STATS" +} + +backend_case_setup() { + if [ ! -e "./$STATS" ]; then + ln -s "$ROOT_DIR/$STATS" "./$STATS" + fi +} + +backend_case_cleanup() { + : +} + +backend_runner() { + echo "run.vim" +} + +backend_run() { + if [ -f run.sh ]; then + TEST_BIN=$TEST_BIN sh run.sh > stdout.txt 2> stderr.txt + else + [ -f run.env ] && . run.env + env \ + NVIM_APPNAME="${NVIM_APPNAME:-}" \ + NVIM_TIRENVI_DEV=1 \ + $TEST_BIN --headless -u NONE -n \ + --cmd "set rtp+=$ROOT_DIR/.deps/keyevent.nvim" \ + -c "lua require('luacov')" \ + -c "source run.vim" \ + -c "lua require('luacov').save_stats()" \ + -c "qa!" \ + >> stdout.txt 2>> stderr.txt + fi +} \ No newline at end of file diff --git a/tests/cases/basic/out-expected.txt b/tests/cases/basic/out-expected.txt new file mode 100644 index 0000000..349110b --- /dev/null +++ b/tests/cases/basic/out-expected.txt @@ -0,0 +1,60 @@ +--- basic --- +=== MESSAGE === + +--- CASE 6: : tan tan ta ta tan --- + (v l s) src type (t h r) key (itv h) seq + normal j (0 0 0) map click (1 0 0) j (900 0) j + repeat j (0 0 0) map repeat (1 1 1) j (500 0) j↻ + repeat j (0 0 0) map repeat (1 1 2) j (150 0) j↻ + repeat j (0 0 0) map repeat (1 1 3) j (150 0) j↻ + repeat j (0 0 0) map repeat (1 1 4) j (150 0) j↻ + normal j (0 1 0) map tap (1 1 0) j (300 0) j↻j + repeat j (0 1 0) map repeat (1 2 1) j (500 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 2) j (150 0) j↻j↻ + normal j (0 2 0) map tap (1 2 0) j (300 0) j↻j↻j + normal j (0 3 0) map tap (2 2 0) j (300 0) j↻j↻jj + normal j (0 4 0) map tap (3 2 0) j (300 0) j↻j↻jjj + repeat j (0 4 0) map repeat (3 3 1) j (500 0) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 2) j (150 0) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 3) j (150 0) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 4) j (150 0) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 5) j (150 1) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 6) j (150 1) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 7) j (150 1) j↻j↻jjj↻ +hold_time = 500 + 150 * 6 = 1400 + +--- CASE 9: : tan tan ta ta tan (diff key) --- + (v l s) src type (t h r) key (itv h) seq + normal h (0 0 0) map tap (1 1 0) h (300 0) x↻h + repeat h (0 0 0) map repeat (1 2 1) h (500 0) x↻h↻ + repeat h (0 0 0) map repeat (1 2 2) h (150 0) x↻h↻ + normal w (0 0 0) map tap (1 2 0) w (300 0) x↻h↻w + normal j (0 0 0) map tap (1 2 0) j (300 0) x↻h↻wej + repeat j (0 0 0) map repeat (1 3 1) j (500 0) x↻h↻wej↻ + repeat j (0 0 0) map repeat (1 3 2) j (150 0) x↻h↻wej↻ + normal k (0 0 0) map click (1 0 0) k (900 0) k + +--- CASE 12: : tan tan ta ta tan (interval) --- + (v l s) src type (t h r) key (itv h) seq + normal j (0 0 0) map click (1 0 0) j (900 0) j + repeat j (0 0 0) map repeat (1 1 1) j (200 0) j↻ + repeat j (0 0 0) map repeat (1 1 2) j (300 0) j↻ + repeat j (0 0 0) map repeat (1 1 3) j (300 0) j↻ + repeat j (0 0 0) map repeat (1 1 4) j (300 1) j↻ + normal j (0 1 0) map tap (1 1 0) j (100 0) j↻j + repeat j (0 1 0) map repeat (1 2 1) j (200 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 2) j (300 0) j↻j↻ + normal j (0 2 0) map tap (1 2 0) j (100 0) j↻j↻j + normal j (0 3 0) map tap (2 2 0) j (100 0) j↻j↻jj + normal j (0 4 0) map tap (3 2 0) j (100 0) j↻j↻jjj + repeat j (0 4 0) map repeat (3 3 1) j (200 0) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 2) j (300 0) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 3) j (300 0) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 4) j (300 1) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 5) j (300 1) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 6) j (300 1) j↻j↻jjj↻ + repeat 16j (0 4 0) map repeat (3 3 7) j (300 2) j↻j↻jjj↻ + normal j (0 0 0) map click (1 0 0) j (311 0) j + +=== DISPLAY === + diff --git a/tests/cases/basic/run.vim b/tests/cases/basic/run.vim new file mode 100644 index 0000000..8c36740 --- /dev/null +++ b/tests/cases/basic/run.vim @@ -0,0 +1,15 @@ +source $KIBI2_REPO_ROOT/tests/common.vim + +" ===== BASIC ===== +new + +CASE : tan tan ta ta tan +lua require "run1" + +CASE : tan tan ta ta tan (diff key) +lua require "run3" + +CASE : tan tan ta ta tan (interval) +lua require "run4" + +call Snapshot({ 'desc': 'basic' }) diff --git a/tests/cases/basic/run1.lua b/tests/cases/basic/run1.lua new file mode 100644 index 0000000..ee987ee --- /dev/null +++ b/tests/cases/basic/run1.lua @@ -0,0 +1,27 @@ +local common_rush = require("common_rush") + +---@type KeyInfo[] +local tan_tan_ta_ta_tann = { + { key = "j", interval = CLICK }, -- tan + { key = "j", interval = HOLD, keymap = true }, -- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- repeat 3 + { key = "j", interval = REPEAT }, -- repeat 4 + { key = "j", interval = TAP }, -- tan + { key = "j", interval = HOLD }, -- repeat 1 nh = 2 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = TAP }, -- ta + { key = "j", interval = TAP }, -- ta + { key = "j", interval = TAP }, -- tan + { key = "j", interval = HOLD }, -- repeat 1 nh = 3 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- repeat 3 + { key = "j", interval = REPEAT }, -- repeat 4 + { key = "j", interval = REPEAT }, -- repeat 5 + { key = "j", interval = REPEAT }, -- repeat 6 + { key = "j", interval = REPEAT }, -- repeat 7 +} + +common_rush.run_sequence(tan_tan_ta_ta_tann) +local events = KeyEvent.get_evcents(2) +print("hold_time = 500 + 150 * 6 = ", KeyEvent.hold_time(events[1])) diff --git a/tests/cases/basic/run2.lua b/tests/cases/basic/run2.lua new file mode 100644 index 0000000..7147a31 --- /dev/null +++ b/tests/cases/basic/run2.lua @@ -0,0 +1,21 @@ +local common_rush = require("common_rush") + +---@type KeyInfo[] +local meta = { + { key = "j", interval = CLICK }, -- tan + { key = "j", interval = HOLD + DELTA, keymap = true }, -- repeat 1 + { key = "J", interval = REPEAT + DELTA }, -- repeat 2 + { key = "j", interval = REPEAT - DELTA }, -- repeat 3 + { key = "", interval = REPEAT }, -- repeat 4 + { key = "", interval = TAP - DELTA }, -- tan + { key = "", interval = HOLD }, -- repeat 1 nh = 2 + { key = "", interval = REPEAT }, -- repeat 2 + { key = "", interval = TAP - DELTA }, -- ta + { key = "j", interval = TAP + DELTA }, -- ta + { key = "J", interval = TAP }, -- tan + { key = "", interval = HOLD - DELTA }, -- repeat 1 nh = 3 + { key = "", interval = REPEAT }, -- repeat 2 + { key = "k", interval = CLICK + 50 }, -- click k +} + +common_rush.run_sequence(meta) diff --git a/tests/cases/basic/run3.lua b/tests/cases/basic/run3.lua new file mode 100644 index 0000000..033b406 --- /dev/null +++ b/tests/cases/basic/run3.lua @@ -0,0 +1,21 @@ +local common_rush = require("common_rush") + +---@type KeyInfo[] +local diff_key = { + { key = "x", interval = CLICK }, -- tan + { key = "x", interval = HOLD, keymap = true }, -- repeat 1 + { key = "x", interval = REPEAT }, -- repeat 2 + { key = "x", interval = REPEAT }, -- repeat 3 + { key = "x", interval = REPEAT }, -- repeat 4 + { key = "h", interval = TAP }, -- tan h + { key = "h", interval = HOLD }, -- repeat 1 nh = 2 + { key = "h", interval = REPEAT }, -- repeat 2 + { key = "w", interval = TAP }, -- ta w + { key = "e", interval = TAP }, -- ta e + { key = "j", interval = TAP }, -- tan j + { key = "j", interval = HOLD }, -- repeat 1 nh = 3 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "k", interval = CLICK }, -- click k +} + +common_rush.run_sequence(diff_key) diff --git a/tests/cases/basic/run4.lua b/tests/cases/basic/run4.lua new file mode 100644 index 0000000..d68f337 --- /dev/null +++ b/tests/cases/basic/run4.lua @@ -0,0 +1,41 @@ +local common_rush = require("common_rush") + +DELTA = 10 +TAP = 100 +HOLD = 200 +REPEAT = 300 + +local opts = { + threshold = { + delta = DELTA, + tap = TAP, + delay = HOLD, + interval = REPEAT, + }, +} + +require("keyevent").setup(opts) +---@type KeyInfo[] +local tan_tan_ta_ta_tann = { + { key = "j", interval = CLICK }, -- tan + { key = "j", interval = HOLD }, -- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- repeat 3 + { key = "j", interval = REPEAT }, -- repeat 4 + { key = "j", interval = TAP }, -- tan + { key = "j", interval = HOLD }, -- repeat 1 nh = 2 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = TAP }, -- ta + { key = "j", interval = TAP }, -- ta + { key = "j", interval = TAP }, -- tan + { key = "j", interval = HOLD }, -- repeat 1 nh = 3 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- repeat 3 + { key = "j", interval = REPEAT }, -- repeat 4 + { key = "j", interval = REPEAT }, -- repeat 5 + { key = "j", interval = REPEAT }, -- repeat 6 + { key = "j", interval = REPEAT }, -- repeat 7 + { key = "j", interval = REPEAT + DELTA + 1 }, -- repeat 7 +} + +common_rush.run_sequence(tan_tan_ta_ta_tann) diff --git a/tests/cases/beginning/out-expected.txt b/tests/cases/beginning/out-expected.txt new file mode 100644 index 0000000..346e930 --- /dev/null +++ b/tests/cases/beginning/out-expected.txt @@ -0,0 +1,50 @@ +--- basic --- +=== MESSAGE === + +--- CASE 6: : count --- + (v l s) src type (t h r) key (itv h) seq + normal j (0 0 0) map click (1 0 0) j (900 0) j + repeat j (0 0 0) map repeat (1 1 1) j (500 0) j↻ + repeat j (0 0 0) map repeat (1 1 2) j (150 0) j↻ + repeat j (0 0 0) map repeat (1 1 3) j (150 0) j↻ + normal k (0 0 0) map click (1 0 0) k (900 0) k + normal k (0 1 0) map tap (2 0 0) k (300 0) kk + repeat k (0 1 0) map repeat (2 1 1) k (500 0) kk↻ + repeat 2k (0 1 0) map repeat (2 1 2) k (150 0) kk↻ + repeat 2k (0 1 0) map repeat (2 1 3) k (150 0) kk↻ + normal h (0 0 0) map click (1 0 0) h (900 0) h + normal h (0 1 0) map tap (2 0 0) h (300 0) hh + normal h (0 2 0) map tap (3 0 0) h (300 0) hhh + repeat h (0 2 0) map repeat (3 1 1) h (500 0) hhh↻ + repeat 4h (0 2 0) map repeat (3 1 2) h (150 0) hhh↻ + repeat 4h (0 2 0) map repeat (3 1 3) h (150 0) hhh↻ + normal h (0 3 0) map tap (1 1 0) h (300 0) hhh↻h + normal h (0 4 0) map tap (2 1 0) h (300 0) hhh↻hh + normal h (0 5 0) map tap (3 1 0) h (300 0) hhh↻hhh + normal l (0 0 0) map tap (1 1 0) l (300 0) hhh↻hhhl + normal l (0 1 0) map tap (2 1 0) l (300 0) hhh↻hhhll + normal l (0 2 0) map tap (3 1 0) l (300 0) hhh↻hhhlll + normal l (0 0 0) map tap (4 1 0) l (300 0) hh↻hhhllll + repeat l (0 0 0) map repeat (4 2 1) l (500 0) h↻hhhllll↻ + repeat l (0 0 0) map repeat (4 2 2) l (150 0) h↻hhhllll↻ + repeat l (0 0 0) map repeat (4 2 3) l (150 0) h↻hhhllll↻ + normal w (0 0 0) map click (1 0 0) w (900 0) w + normal w (0 1 0) map tap (2 0 0) w (300 0) ww + normal w (0 2 0) map tap (3 0 0) w (300 0) www + repeat w (0 2 0) map repeat (3 1 1) w (500 0) www↻ + repeat 4w (0 2 0) map repeat (3 1 2) w (150 0) www↻ + repeat 4w (0 2 0) map repeat (3 1 3) w (150 0) www↻ + normal b (0 0 0) map click (1 0 0) b (900 0) b + normal b (0 0 0) map click (1 0 0) b (900 0) b + normal b (0 1 0) map tap (2 0 0) b (300 0) bb + normal b (0 2 0) map tap (3 0 0) b (300 0) bbb + normal b (0 0 0) map tap (4 0 0) b (300 0) bbbb + normal b (0 0 0) map tap (5 0 0) b (300 0) bbbbb + normal b (0 0 0) map tap (6 0 0) b (300 0) bbbbbb + repeat b (0 0 0) map repeat (6 1 1) b (500 0) bbbbbb↻ + repeat b (0 0 0) map repeat (6 1 2) b (150 0) bbbbbb↻ + repeat b (0 0 0) map repeat (6 1 3) b (150 0) bbbbbb↻ + normal b (0 1 0) map tap (1 1 0) b (300 0) bbbbbb↻b + +=== DISPLAY === + diff --git a/tests/cases/beginning/run.vim b/tests/cases/beginning/run.vim new file mode 100644 index 0000000..22975c0 --- /dev/null +++ b/tests/cases/beginning/run.vim @@ -0,0 +1,9 @@ +source $KIBI2_REPO_ROOT/tests/common.vim + +" ===== BASIC ===== +new + +CASE : count +lua require "run1" + +call Snapshot({ 'desc': 'basic' }) diff --git a/tests/cases/beginning/run1.lua b/tests/cases/beginning/run1.lua new file mode 100644 index 0000000..9d02143 --- /dev/null +++ b/tests/cases/beginning/run1.lua @@ -0,0 +1,49 @@ +local common_key = require("common_rush") + +---@type KeyInfo[] +local count3 = { + { key = "j", interval = CLICK }, + { key = "j", interval = HOLD }, -- "j" wait for the next repeat + { key = "j", interval = REPEAT }, -- "1j" + { key = "j", interval = REPEAT }, + { key = "k", interval = CLICK }, + { key = "k", interval = TAP }, + { key = "k", interval = HOLD }, -- "k" wait for the next repeat + { key = "k", interval = REPEAT }, -- "2k" + { key = "k", interval = REPEAT }, + { key = "h", interval = CLICK }, + { key = "h", interval = TAP }, + { key = "h", interval = TAP }, + { key = "h", interval = HOLD }, -- "h" wait for the next repeat + { key = "h", interval = REPEAT }, -- "4h" + { key = "h", interval = REPEAT }, + { key = "h", interval = TAP }, + { key = "h", interval = TAP }, + { key = "h", interval = TAP }, + { key = "l", interval = TAP }, + { key = "l", interval = TAP }, + { key = "l", interval = TAP }, + { key = "l", interval = TAP }, -- reset + { key = "l", interval = HOLD }, -- "l" wait for the next repeat + { key = "l", interval = REPEAT }, -- "1l" reset + { key = "l", interval = REPEAT }, + { key = "w", interval = CLICK }, + { key = "w", interval = TAP }, + { key = "w", interval = TAP }, + { key = "w", interval = HOLD }, -- "w" wait for the next repeat + { key = "w", interval = REPEAT }, -- "4w" + { key = "w", interval = REPEAT }, + { key = "b", interval = CLICK }, + { key = "b", interval = CLICK }, + { key = "b", interval = TAP }, + { key = "b", interval = TAP }, + { key = "b", interval = TAP }, -- reset + { key = "b", interval = TAP }, + { key = "b", interval = TAP }, + { key = "b", interval = HOLD }, -- "b" wait for the next repeat + { key = "b", interval = REPEAT }, -- "1b" + { key = "b", interval = REPEAT }, + { key = "b", interval = TAP }, +} + +common_key.run_sequence(count3) diff --git a/tests/cases/check/format/out-expected.txt b/tests/cases/check/format/out-expected.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/cases/check/format/run.sh b/tests/cases/check/format/run.sh new file mode 100644 index 0000000..6882292 --- /dev/null +++ b/tests/cases/check/format/run.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +stylua --check $KIBI2_REPO_ROOT/lua $KIBI2_REPO_ROOT/tests > out-actual.txt \ No newline at end of file diff --git a/tests/cases/check/format/skip-ci b/tests/cases/check/format/skip-ci new file mode 100644 index 0000000..e69de29 diff --git a/tests/cases/check/rg_ng_char/out-expected.txt b/tests/cases/check/rg_ng_char/out-expected.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/cases/check/rg_ng_char/run.sh b/tests/cases/check/rg_ng_char/run.sh new file mode 100644 index 0000000..5c6e1da --- /dev/null +++ b/tests/cases/check/rg_ng_char/run.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +echo $KIBI2_REPO_ROOT +LC_ALL=C rg --sort=path -g '*.lua' -g '*.sh' -g '*.vim' '[^\x00-\x7F]' $KIBI2_REPO_ROOT > out-actual.txt # no ascii + +LC_ALL=C rg --sort=path "\bM:[a-zA-Z_]" $KIBI2_REPO_ROOT/lua | grep -v function >> out-actual.txt # no colon \ No newline at end of file diff --git a/tests/cases/check/rg_ng_char/skip-ci b/tests/cases/check/rg_ng_char/skip-ci new file mode 100644 index 0000000..e69de29 diff --git a/tests/cases/check/rg_ng_char/sort b/tests/cases/check/rg_ng_char/sort new file mode 100644 index 0000000..e69de29 diff --git a/tests/cases/check/rg_probe/out-expected.txt b/tests/cases/check/rg_probe/out-expected.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/cases/check/rg_probe/run.sh b/tests/cases/check/rg_probe/run.sh new file mode 100644 index 0000000..89a4a34 --- /dev/null +++ b/tests/cases/check/rg_probe/run.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +rg "log\.probe\(" $KIBI2_REPO_ROOT/lua > out-actual.txt \ No newline at end of file diff --git a/tests/cases/count/out-expected.txt b/tests/cases/count/out-expected.txt new file mode 100644 index 0000000..9b3092c --- /dev/null +++ b/tests/cases/count/out-expected.txt @@ -0,0 +1,41 @@ +--- basic --- +=== MESSAGE === + +--- CASE 6: : count --- + (v l s) src type (t h r) key (itv h) seq + normal j (3 0 0) map tap (1 0 0) j ( 0 0) 3j + repeat j (3 0 0) map repeat (1 1 1) j (500 0) 3j↻ + repeat 3j (3 0 0) map repeat (1 1 2) j (150 0) 3j↻ + repeat 3j (3 0 0) map repeat (1 1 3) j (150 0) 3j↻ + normal j (1 0 0) map tap (1 0 0) j ( 0 0) 1j + repeat j (1 0 0) map repeat (1 1 1) j (500 0) 1j↻ + repeat j (1 0 0) map repeat (1 1 2) j (150 0) 1j↻ + repeat j (1 0 0) map repeat (1 1 3) j (150 0) 1j↻ + normal j (1 1 0) map tap (1 1 0) j (300 0) 1j↻j + normal k (5 0 0) map tap (1 0 0) k ( 0 0) 5k + repeat k (5 0 0) map repeat (1 1 1) k (500 0) 5k↻ + repeat 5k (5 0 0) map repeat (1 1 2) k (150 0) 5k↻ + repeat 5k (5 0 0) map repeat (1 1 3) k (150 0) 5k↻ + normal k (5 1 0) map tap (1 1 0) k (300 0) 5k↻k + normal k (5 2 0) map tap (2 1 0) k (300 0) 5k↻kk + repeat k (5 2 0) map repeat (2 2 1) k (500 0) 5k↻kk↻ + repeat 20k (5 2 0) map repeat (2 2 2) k (150 0) 5k↻kk↻ + repeat 20k (5 2 0) map repeat (2 2 3) k (150 0) 5k↻kk↻ + normal k (5 3 0) map tap (1 2 0) k (300 0) 5k↻kk↻k + normal k (5 4 0) map tap (2 2 0) k (300 0) 5k↻kk↻kk + normal k (5 5 0) map tap (3 2 0) k (300 0) 5k↻kk↻kkk + normal k (0 0 0) map tap (4 2 0) k (300 0) 5k↻kk↻kkkk + repeat k (0 0 0) map repeat (4 3 1) k (500 0) k↻kk↻kkkk↻ + repeat k (0 0 0) map repeat (4 3 2) k (150 0) k↻kk↻kkkk↻ + repeat k (0 0 0) map repeat (4 3 3) k (150 0) k↻kk↻kkkk↻ + normal k (6 0 0) map tap (1 3 0) k ( 0 0) kk↻kkkk↻6k + repeat k (6 0 0) map repeat (1 4 1) k (500 0) k↻kkkk↻6k↻ + repeat 6k (6 0 0) map repeat (1 4 2) k (150 0) k↻kkkk↻6k↻ + repeat 6k (6 0 0) map repeat (1 4 3) k (150 0) k↻kkkk↻6k↻ + normal k (3 0 0) map tap (1 4 0) k ( 0 0) kkkk↻6k↻3k + repeat k (3 0 0) map repeat (1 5 1) k (500 0) kkk↻6k↻3k↻ + repeat 3k (3 0 0) map repeat (1 5 2) k (150 0) kkk↻6k↻3k↻ + repeat 3k (3 0 0) map repeat (1 5 3) k (150 0) kkk↻6k↻3k↻ + +=== DISPLAY === + diff --git a/tests/cases/count/run.vim b/tests/cases/count/run.vim new file mode 100644 index 0000000..22975c0 --- /dev/null +++ b/tests/cases/count/run.vim @@ -0,0 +1,9 @@ +source $KIBI2_REPO_ROOT/tests/common.vim + +" ===== BASIC ===== +new + +CASE : count +lua require "run1" + +call Snapshot({ 'desc': 'basic' }) diff --git a/tests/cases/count/run1.lua b/tests/cases/count/run1.lua new file mode 100644 index 0000000..6ffe8da --- /dev/null +++ b/tests/cases/count/run1.lua @@ -0,0 +1,40 @@ +local common_key = require("common_rush") + +---@type KeyInfo[] +local count3 = { + { key = "3j", interval = TAP }, + { key = "j", interval = HOLD }, -- j wait for the next repeat + { key = "j", interval = REPEAT }, -- 3j + { key = "j", interval = REPEAT }, + { key = "1j", interval = CLICK }, + { key = "j", interval = HOLD }, -- j wait for the next repeat + { key = "j", interval = REPEAT }, + { key = "j", interval = REPEAT }, + { key = "j", interval = TAP }, + { key = "5k", interval = CLICK }, + { key = "k", interval = HOLD }, -- j wait for the next repeat + { key = "k", interval = REPEAT }, -- 5j + { key = "k", interval = REPEAT }, + { key = "k", interval = TAP }, + { key = "k", interval = TAP }, + { key = "k", interval = HOLD }, + { key = "k", interval = REPEAT }, -- 20k + { key = "k", interval = REPEAT }, + { key = "k", interval = TAP }, + { key = "k", interval = TAP }, + { key = "k", interval = TAP }, -- reset + { key = "k", interval = TAP }, + { key = "k", interval = HOLD }, + { key = "k", interval = REPEAT }, -- 20k + { key = "k", interval = REPEAT }, + { key = "6k", interval = TAP }, + { key = "k", interval = HOLD }, + { key = "k", interval = REPEAT }, -- 6k + { key = "k", interval = REPEAT }, + { key = "3k", interval = TAP }, + { key = "k", interval = HOLD }, + { key = "k", interval = REPEAT }, -- 3k + { key = "k", interval = REPEAT }, +} + +common_key.run_sequence(count3) diff --git a/tests/cases/decrease/out-expected.txt b/tests/cases/decrease/out-expected.txt new file mode 100644 index 0000000..b532dea --- /dev/null +++ b/tests/cases/decrease/out-expected.txt @@ -0,0 +1,44 @@ +--- basic --- +=== MESSAGE === + +--- CASE 6: : count --- + (v l s) src type (t h r) key (itv h) seq + normal j (0 0 0) map click (1 0 0) j (900 0) j + normal j (0 1 0) map tap (2 0 0) j (300 0) jj + repeat j (0 1 0) map repeat (2 1 1) j (500 0) jj↻ + repeat 2j (0 1 0) map repeat (2 1 2) j (150 0) jj↻ + repeat 2j (0 1 0) map repeat (2 1 3) j (150 0) jj↻ + normal j (0 2 0) map tap (1 1 0) j (300 0) jj↻j + repeat j (0 2 0) map repeat (1 2 1) j (500 0) jj↻j↻ + repeat 4j (0 2 0) map repeat (1 2 2) j (150 0) jj↻j↻ + repeat 4j (0 2 0) map repeat (1 2 3) j (150 0) jj↻j↻ + normal k (0 -2 0) map tap (1 2 0) k (300 0) jj↻j↻k + repeat k (0 -2 0) map repeat (1 3 1) k (500 0) jj↻j↻k↻ + repeat 2j (0 -2 0) map repeat (1 3 2) k (150 0) jj↻j↻k↻ + repeat 2j (0 -2 0) map repeat (1 3 3) k (150 0) jj↻j↻k↻ + normal k (0 -1 0) map tap (1 3 0) k (300 0) jj↻j↻k↻k + repeat k (0 -1 0) map repeat (1 4 1) k (500 0) jj↻j↻k↻k↻ + repeat j (0 -1 0) map repeat (1 4 2) k (150 0) jj↻j↻k↻k↻ + repeat j (0 -1 0) map repeat (1 4 3) k (150 0) jj↻j↻k↻k↻ + normal k (0 0 0) map tap (1 4 0) k (300 0) jj↻j↻k↻k↻k + repeat k (0 0 0) map repeat (1 5 1) k (500 0) j↻j↻k↻k↻k↻ + repeat k (0 0 0) map repeat (1 5 2) k (150 0) j↻j↻k↻k↻k↻ + repeat k (0 0 0) map repeat (1 5 3) k (150 0) j↻j↻k↻k↻k↻ + normal k (0 1 0) map tap (1 5 0) k (300 0) ↻j↻k↻k↻k↻k + normal k (0 2 0) map tap (2 5 0) k (300 0) j↻k↻k↻k↻kk + repeat k (0 2 0) map repeat (2 6 1) k (500 0) ↻k↻k↻k↻kk↻ + repeat 4k (0 2 0) map repeat (2 6 2) k (150 0) ↻k↻k↻k↻kk↻ + repeat 4k (0 2 0) map repeat (2 6 3) k (150 0) ↻k↻k↻k↻kk↻ + normal j (0 -2 0) map tap (1 6 0) j (300 0) k↻k↻k↻kk↻j + repeat j (0 -2 0) map repeat (1 7 1) j (500 0) ↻k↻k↻kk↻j↻ + repeat 2k (0 -2 0) map repeat (1 7 2) j (150 0) ↻k↻k↻kk↻j↻ + repeat 2k (0 -2 0) map repeat (1 7 3) j (150 0) ↻k↻k↻kk↻j↻ + normal j (0 -1 0) map tap (1 7 0) j (300 0) k↻k↻kk↻j↻j + normal j (0 0 0) map tap (2 7 0) j (300 0) ↻k↻kk↻j↻jj + normal j (0 1 0) map tap (3 7 0) j (300 0) k↻kk↻j↻jjj + repeat j (0 1 0) map repeat (3 8 1) j (500 0) ↻kk↻j↻jjj↻ + repeat 2j (0 1 0) map repeat (3 8 2) j (150 0) ↻kk↻j↻jjj↻ + repeat 2j (0 1 0) map repeat (3 8 3) j (150 0) ↻kk↻j↻jjj↻ + +=== DISPLAY === + diff --git a/tests/cases/decrease/run.vim b/tests/cases/decrease/run.vim new file mode 100644 index 0000000..22975c0 --- /dev/null +++ b/tests/cases/decrease/run.vim @@ -0,0 +1,9 @@ +source $KIBI2_REPO_ROOT/tests/common.vim + +" ===== BASIC ===== +new + +CASE : count +lua require "run1" + +call Snapshot({ 'desc': 'basic' }) diff --git a/tests/cases/decrease/run1.lua b/tests/cases/decrease/run1.lua new file mode 100644 index 0000000..b2461df --- /dev/null +++ b/tests/cases/decrease/run1.lua @@ -0,0 +1,43 @@ +local common_key = require("common_rush") + +---@type KeyInfo[] +local count3 = { + { key = "j", interval = CLICK }, + { key = "j", interval = TAP }, + { key = "j", interval = HOLD }, -- "j" wait for the next repeat + { key = "j", interval = REPEAT }, -- "2j" + { key = "j", interval = REPEAT }, + { key = "j", interval = TAP }, + { key = "j", interval = HOLD }, -- "4j" + { key = "j", interval = REPEAT }, + { key = "j", interval = REPEAT }, + { key = "k", interval = TAP }, + { key = "k", interval = HOLD }, -- "2j" + { key = "k", interval = REPEAT }, + { key = "k", interval = REPEAT }, + { key = "k", interval = TAP }, + { key = "k", interval = HOLD }, -- "j" + { key = "k", interval = REPEAT }, + { key = "k", interval = REPEAT }, + { key = "k", interval = TAP }, + { key = "k", interval = HOLD }, -- "k" + { key = "k", interval = REPEAT }, + { key = "k", interval = REPEAT }, + { key = "k", interval = TAP }, + { key = "k", interval = TAP }, + { key = "k", interval = HOLD }, -- "4k" + { key = "k", interval = REPEAT }, + { key = "k", interval = REPEAT }, + { key = "j", interval = TAP }, + { key = "j", interval = HOLD }, -- "2k" + { key = "j", interval = REPEAT }, + { key = "j", interval = REPEAT }, + { key = "j", interval = TAP }, + { key = "j", interval = TAP }, + { key = "j", interval = TAP }, + { key = "j", interval = HOLD }, -- "2j" + { key = "j", interval = REPEAT }, + { key = "j", interval = REPEAT }, +} + +common_key.run_sequence(count3) diff --git a/tests/cases/increase/out-expected.txt b/tests/cases/increase/out-expected.txt new file mode 100644 index 0000000..2b3165e --- /dev/null +++ b/tests/cases/increase/out-expected.txt @@ -0,0 +1,34 @@ +--- basic --- +=== MESSAGE === + +--- CASE 6: : count --- + (v l s) src type (t h r) key (itv h) seq + normal j (0 0 0) map click (1 0 0) j (900 0) j + repeat j (0 0 0) map repeat (1 1 1) j (500 0) j↻ + repeat j (0 0 0) map repeat (1 1 2) j (150 0) j↻ + repeat j (0 0 0) map repeat (1 1 3) j (150 0) j↻ + normal j (0 1 0) map tap (1 1 0) j (300 0) j↻j + repeat j (0 1 0) map repeat (1 2 1) j (500 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 2) j (150 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 3) j (150 0) j↻j↻ + normal j (0 2 0) map tap (1 2 0) j (300 0) j↻j↻j + normal j (0 3 0) map tap (2 2 0) j (300 0) j↻j↻jj + repeat j (0 3 0) map repeat (2 3 1) j (500 0) j↻j↻jj↻ + repeat 8j (0 3 0) map repeat (2 3 2) j (150 0) j↻j↻jj↻ + repeat 8j (0 3 0) map repeat (2 3 3) j (150 0) j↻j↻jj↻ + normal j (0 4 0) map tap (1 3 0) j (300 0) j↻j↻jj↻j + normal j (0 5 0) map tap (2 3 0) j (300 0) j↻j↻jj↻jj + normal j (0 6 0) map tap (3 3 0) j (300 0) j↻j↻jj↻jjj + repeat j (0 6 0) map repeat (3 4 1) j (500 0) ↻j↻jj↻jjj↻ + repeat 64j (0 6 0) map repeat (3 4 2) j (150 0) ↻j↻jj↻jjj↻ + repeat 64j (0 6 0) map repeat (3 4 3) j (150 0) ↻j↻jj↻jjj↻ + normal j (0 7 0) map tap (1 4 0) j (300 0) j↻jj↻jjj↻j + normal j (0 8 0) map tap (2 4 0) j (300 0) ↻jj↻jjj↻jj + normal j (0 9 0) map tap (3 4 0) j (300 0) jj↻jjj↻jjj + normal j (0 0 0) map tap (4 4 0) j (300 0) j↻jjj↻jjjj + repeat j (0 0 0) map repeat (4 5 1) j (500 0) ↻jjj↻jjjj↻ + repeat j (0 0 0) map repeat (4 5 2) j (150 0) ↻jjj↻jjjj↻ + repeat j (0 0 0) map repeat (4 5 3) j (150 0) ↻jjj↻jjjj↻ + +=== DISPLAY === + diff --git a/tests/cases/increase/run.vim b/tests/cases/increase/run.vim new file mode 100644 index 0000000..22975c0 --- /dev/null +++ b/tests/cases/increase/run.vim @@ -0,0 +1,9 @@ +source $KIBI2_REPO_ROOT/tests/common.vim + +" ===== BASIC ===== +new + +CASE : count +lua require "run1" + +call Snapshot({ 'desc': 'basic' }) diff --git a/tests/cases/increase/run1.lua b/tests/cases/increase/run1.lua new file mode 100644 index 0000000..76a400b --- /dev/null +++ b/tests/cases/increase/run1.lua @@ -0,0 +1,33 @@ +local common_key = require("common_rush") + +---@type KeyInfo[] +local count3 = { + { key = "j", interval = CLICK }, + { key = "j", interval = HOLD }, -- "j" wait for the next repeat + { key = "j", interval = REPEAT }, -- "1j" + { key = "j", interval = REPEAT }, + { key = "j", interval = TAP }, + { key = "j", interval = HOLD }, -- "2j" + { key = "j", interval = REPEAT }, + { key = "j", interval = REPEAT }, + { key = "j", interval = TAP }, + { key = "j", interval = TAP }, + { key = "j", interval = HOLD }, -- "8j" + { key = "j", interval = REPEAT }, + { key = "j", interval = REPEAT }, + { key = "j", interval = TAP }, + { key = "j", interval = TAP }, + { key = "j", interval = TAP }, + { key = "j", interval = HOLD }, -- "64j" + { key = "j", interval = REPEAT }, + { key = "j", interval = REPEAT }, + { key = "j", interval = TAP }, + { key = "j", interval = TAP }, + { key = "j", interval = TAP }, + { key = "j", interval = TAP }, -- reset + { key = "j", interval = HOLD }, -- "1j" + { key = "j", interval = REPEAT }, + { key = "j", interval = REPEAT }, +} + +common_key.run_sequence(count3) diff --git a/tests/cases/meta/out-expected.txt b/tests/cases/meta/out-expected.txt new file mode 100644 index 0000000..abf798e --- /dev/null +++ b/tests/cases/meta/out-expected.txt @@ -0,0 +1,25 @@ +--- basic --- +=== MESSAGE === + +--- CASE 6: : count --- + (v l s) src type (t h r) key (itv h) seq + normal j (0 0 0) map tap (1 0 0) j (300 0) j + repeat j (0 0 0) map repeat (1 1 1) j (500 0) j↻ + repeat j (0 0 0) map repeat (1 1 2) j (150 0) j↻ + repeat 2j (0 1 0) map repeat (1 1 3) C-j (150 0) j↻ + repeat 2j (0 1 0) map repeat (1 1 4) C-j (150 0) j↻ + repeat 2j (0 1 0) map repeat (1 1 5) j (150 1) j↻ + repeat 4j (0 2 0) map repeat (1 1 6) C-j (150 1) j↻ + repeat 4j (0 2 0) map repeat (1 1 7) j (150 1) j↻ + repeat 2j (0 1 0) map repeat (1 1 8) A-j (150 1) j↻ + repeat 2j (0 1 0) map repeat (1 1 9) j (150 1) j↻ + repeat j (0 0 0) map repeat (1 1 10) A-j (150 1) j↻ + repeat j (0 0 0) map repeat (1 1 11) A-j (150 2) j↻ + repeat j (0 0 0) map repeat (1 1 12) j (150 2) j↻ + repeat k (0 -1 0) map repeat (1 1 13) A-j (150 2) j↻ + repeat k (0 -1 0) map repeat (1 1 14) j (150 2) j↻ + repeat 2k (0 -2 0) map repeat (1 1 15) A-j (150 2) j↻ + repeat 2k (0 -2 0) map repeat (1 1 16) j (150 2) j↻ + +=== DISPLAY === + diff --git a/tests/cases/meta/run.vim b/tests/cases/meta/run.vim new file mode 100644 index 0000000..22975c0 --- /dev/null +++ b/tests/cases/meta/run.vim @@ -0,0 +1,9 @@ +source $KIBI2_REPO_ROOT/tests/common.vim + +" ===== BASIC ===== +new + +CASE : count +lua require "run1" + +call Snapshot({ 'desc': 'basic' }) diff --git a/tests/cases/meta/run1.lua b/tests/cases/meta/run1.lua new file mode 100644 index 0000000..a9c27d5 --- /dev/null +++ b/tests/cases/meta/run1.lua @@ -0,0 +1,24 @@ +local common_key = require("common_rush") + +---@type KeyInfo[] +local count3 = { + { key = "j", interval = TAP }, + { key = "j", interval = HOLD }, + { key = "j", interval = REPEAT }, + { key = "", interval = REPEAT }, + { key = "", interval = REPEAT }, + { key = "j", interval = REPEAT }, + { key = "", interval = REPEAT }, + { key = "j", interval = REPEAT }, + { key = "", interval = REPEAT }, + { key = "j", interval = REPEAT }, + { key = "", interval = REPEAT }, + { key = "", interval = REPEAT }, + { key = "j", interval = REPEAT }, + { key = "", interval = REPEAT }, + { key = "j", interval = REPEAT }, + { key = "", interval = REPEAT }, + { key = "j", interval = REPEAT }, +} + +common_key.run_sequence(count3) diff --git a/tests/cases/ng_case/out-expected.txt b/tests/cases/ng_case/out-expected.txt new file mode 100644 index 0000000..dc187f1 --- /dev/null +++ b/tests/cases/ng_case/out-expected.txt @@ -0,0 +1,141 @@ +--- NG case --- +=== MESSAGE === + +--- CASE 6: : repeat tap -> repeat repeat --- + + + (v l s) src type (t h r) key (itv h) seq + normal j (0 0 0) map click (1 0 0) j (900 0) j + repeat j (0 0 0) map repeat (1 1 1) j (500 0) j↻ + repeat j (0 0 0) map repeat (1 1 2) j (150 0) j↻ + normal j (0 1 0) map tap (1 1 0) j (300 0) j↻j + repeat j (0 1 0) map repeat (1 2 1) j (500 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 2) j (150 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 3) j (150 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 4) j (150 0) j↻j↻ + normal j (0 2 0) map tap (1 2 0) j (300 0) j↻j↻j + repeat j (0 2 0) map repeat (1 3 1) j (500 0) j↻j↻j↻ + repeat 4j (0 2 0) map repeat (1 3 2) j (150 0) j↻j↻j↻ + repeat 4j (0 2 0) map repeat (1 3 3) j (150 0) j↻j↻j↻ + + + (v l s) src type (t h r) key (itv h) seq + normal j (0 0 0) map click (1 0 0) j (900 0) j + repeat j (0 0 0) map repeat (1 1 1) j (500 0) j↻ + repeat j (0 0 0) map repeat (1 1 2) j (150 0) j↻ + repeat j (0 0 0) map repeat (1 1 3) j (150 0) j↻ + normal j (0 1 0) map 1-tap (1 1 0) j (500 0) j↻j + normal j (0 1 0) map 2-tap (2 1 0) j (150 0) j↻jj + repeat 2j (0 1 0) map 3-repeat (3 1 3) j (150 6) j↻jjj + repeat 2j (0 1 0) map 4-repeat (4 1 4) j (150 6) j↻jjjj + normal j (0 2 0) map tap (5 1 0) j (300 0) j↻jjjjj + repeat j (0 2 0) map repeat (5 2 1) j (500 0) j↻jjjjj↻ + repeat 4j (0 2 0) map repeat (5 2 2) j (150 0) j↻jjjjj↻ + repeat 4j (0 2 0) map repeat (5 2 3) j (150 0) j↻jjjjj↻ + +--- CASE 9: : repeat repeat -> repeat tap --- + + + (v l s) src type (t h r) key (itv h) seq + normal j (0 0 0) map click (1 0 0) j (900 0) j + repeat j (0 0 0) map repeat (1 1 1) j (500 0) j↻ + repeat j (0 0 0) map repeat (1 1 2) j (150 0) j↻ + normal j (0 1 0) map tap (1 1 0) j (300 0) j↻j + repeat j (0 1 0) map repeat (1 2 1) j (500 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 2) j (150 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 3) j (150 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 4) j (150 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 5) j (150 1) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 6) j (150 1) j↻j↻ + normal j (0 2 0) map tap (1 2 0) j (300 0) j↻j↻j + repeat j (0 2 0) map repeat (1 3 1) j (500 0) j↻j↻j↻ + repeat 4j (0 2 0) map repeat (1 3 2) j (150 0) j↻j↻j↻ + repeat 4j (0 2 0) map repeat (1 3 3) j (150 0) j↻j↻j↻ + + + (v l s) src type (t h r) key (itv h) seq + normal j (0 0 0) map click (1 0 0) j (900 0) j + repeat j (0 0 0) map repeat (1 1 1) j (500 0) j↻ + repeat j (0 0 0) map repeat (1 1 2) j (150 0) j↻ + normal j (0 1 0) map tap (1 1 0) j (300 0) j↻j + repeat j (0 1 0) map repeat (1 2 1) j (500 0) j↻j↻ + repeat 2j (0 1 0) map repeat (1 2 2) j (150 0) j↻j↻ + normal j (0 2 0) map tap (1 2 0) j (300 0) j↻j↻j + normal j (0 2 0) map 1-tap (2 2 0) j (150 0) j↻j↻jj + normal j (0 2 0) map 2-tap (3 2 0) j (150 0) j↻j↻jjj + repeat 2j (0 1 0) map 3-repeat (4 2 3) j (150 15) j↻j↻jjjj + normal j (0 2 0) map tap (5 2 0) j (300 0) j↻j↻jjjjj + repeat j (0 2 0) map repeat (5 3 1) j (500 0) j↻j↻jjjjj↻ + repeat 4j (0 2 0) map repeat (5 3 2) j (150 0) j↻j↻jjjjj↻ + repeat 4j (0 2 0) map repeat (5 3 3) j (150 0) j↻j↻jjjjj↻ + +--- CASE 12: : tap tap -> tap repeat --- + + + (v l s) src type (t h r) key (itv h) seq + normal k (0 0 0) map click (1 0 0) k (900 0) k + repeat k (0 0 0) map repeat (1 1 1) k (500 0) k↻ + repeat k (0 0 0) map repeat (1 1 2) k (150 0) k↻ + normal k (0 1 0) map tap (1 1 0) k (300 0) k↻k + normal k (0 2 0) map tap (2 1 0) k (300 0) k↻kk + normal k (0 3 0) map tap (3 1 0) k (300 0) k↻kkk + normal k (0 0 0) map tap (4 1 0) k (300 0) k↻kkkk + + + (v l s) src type (t h r) key (itv h) seq + normal k (0 0 0) map click (1 0 0) k (900 0) k + repeat k (0 0 0) map repeat (1 1 1) k (500 0) k↻ + repeat k (0 0 0) map repeat (1 1 2) k (150 0) k↻ + normal k (0 1 0) map tap (1 1 0) k (300 0) k↻k + normal k (0 2 0) map tap (2 1 0) k (300 0) k↻kk + normal k (0 2 0) map 1-tap (3 1 0) k (150 0) k↻kkk + normal k (0 0 0) map tap (4 1 0) k (300 0) k↻kkkk + +--- CASE 15: : tap hold -> hold hold --- + + + (v l s) src type (t h r) key (itv h) seq + normal k (0 0 0) map click (1 0 0) k (900 0) k + normal k (0 1 0) map tap (2 0 0) k (300 0) kk + normal k (0 2 0) map tap (3 0 0) k (300 0) kkk + repeat k (0 2 0) map repeat (3 1 1) k (500 0) kkk↻ + repeat 4k (0 2 0) map repeat (3 1 2) k (150 0) kkk↻ + normal k (0 3 0) map tap (1 1 0) k (300 0) kkk↻k + normal k (0 4 0) map tap (2 1 0) k (300 0) kkk↻kk + normal k (0 5 0) map tap (3 1 0) k (300 0) kkk↻kkk + normal k (0 0 0) map tap (4 1 0) k (300 0) kkk↻kkkk + + + (v l s) src type (t h r) key (itv h) seq + normal k (0 0 0) map click (1 0 0) k (900 0) k + normal k (0 1 0) map tap (2 0 0) k (300 0) kk + repeat k (0 1 0) map repeat (2 1 1) k (500 0) kk↻ + repeat k (0 1 0) map 1-repeat (2 1 1) k (500 0) kk↻ + repeat 2k (0 1 0) map repeat (2 1 2) k (150 0) kk↻ + normal k (0 2 0) map tap (1 1 0) k (300 0) kk↻k + normal k (0 3 0) map tap (2 1 0) k (300 0) kk↻kk + normal k (0 4 0) map tap (3 1 0) k (300 0) kk↻kkk + normal k (0 0 0) map tap (4 1 0) k (300 0) kk↻kkkk + +--- CASE 18: : hold repeat -> tap repeat --- + + + (v l s) src type (t h r) key (itv h) seq + normal k (0 0 0) map click (1 0 0) k (900 0) k + normal k (0 1 0) map tap (2 0 0) k (300 0) kk + repeat k (0 1 0) map repeat (2 1 1) k (500 0) kk↻ + repeat 2k (0 1 0) map repeat (2 1 2) k (150 0) kk↻ + repeat 2k (0 1 0) map repeat (2 1 3) k (150 0) kk↻ + repeat 2k (0 1 0) map repeat (2 1 4) k (150 0) kk↻ + + + (v l s) src type (t h r) key (itv h) seq + normal k (0 0 0) map click (1 0 0) k (900 0) k + normal k (0 1 0) map tap (2 0 0) k (300 0) kk + normal k (0 2 0) map tap (3 0 0) k (300 0) kkk + normal k (0 0 0) map 1-tap (4 0 0) k (150 0) kkkk + normal k (0 0 0) map 2-tap (5 0 0) k (150 0) kkkkk + repeat k (0 0 0) map 3-repeat (6 0 3) k (150 32) kkkkkk + +=== DISPLAY === + diff --git a/tests/cases/ng_case/run.vim b/tests/cases/ng_case/run.vim new file mode 100644 index 0000000..78ce1c9 --- /dev/null +++ b/tests/cases/ng_case/run.vim @@ -0,0 +1,21 @@ +source $KIBI2_REPO_ROOT/tests/common.vim + +" ===== NG case ===== +new + +CASE : repeat tap -> repeat repeat +lua require "run_1" + +CASE : repeat repeat -> repeat tap +lua require "run_2" + +CASE : tap tap -> tap repeat +lua require "run_3" + +CASE : tap hold -> hold hold +lua require "run_4" + +CASE : hold repeat -> tap repeat +lua require "run_5" + +call Snapshot({ 'desc': 'NG case' }) diff --git a/tests/cases/ng_case/run_1.lua b/tests/cases/ng_case/run_1.lua new file mode 100644 index 0000000..d5fea6a --- /dev/null +++ b/tests/cases/ng_case/run_1.lua @@ -0,0 +1,39 @@ +local common_rush = require("common_rush") + +---@type KeyInfo[] +local tan_click_normal = { + { key = "j", interval = CLICK }, -- tan + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = TAP }, -- tan + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- repeat 3 + { key = "j", interval = REPEAT }, -- repeat 3 + { key = "j", interval = TAP }, -- tan + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- repeat 3 +} + +print("\n") +common_rush.run_sequence(tan_click_normal) + +---@type KeyInfo[] +local tan_click_ng1 = { + { key = "j", interval = CLICK }, -- tan + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- tap -> repeat : NG! + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- repeat 3 + { key = "j", interval = REPEAT }, -- repeat 3 + { key = "j", interval = TAP }, -- tan + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- repeat 3 +} + +print("\n") +common_rush.run_sequence(tan_click_ng1) diff --git a/tests/cases/ng_case/run_2.lua b/tests/cases/ng_case/run_2.lua new file mode 100644 index 0000000..350941a --- /dev/null +++ b/tests/cases/ng_case/run_2.lua @@ -0,0 +1,43 @@ +local common_rush = require("common_rush") + +---@type KeyInfo[] +local tan_click_normal = { + { key = "j", interval = CLICK }, -- tan + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = TAP }, -- tan + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- repeat 3 + { key = "j", interval = REPEAT }, -- repeat 3 + { key = "j", interval = REPEAT }, -- repeat 4 + { key = "j", interval = REPEAT }, -- repeat 5 + { key = "j", interval = TAP }, -- tan + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- repeat 3 +} + +print("\n") +common_rush.run_sequence(tan_click_normal) + +---@type KeyInfo[] +local tan_click_ng1 = { + { key = "j", interval = CLICK }, -- tan + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = TAP }, -- tan + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = TAP }, -- repeat -> tap : NG! + { key = "j", interval = REPEAT }, -- repeat 3 + { key = "j", interval = REPEAT }, -- repeat 4 + { key = "j", interval = REPEAT }, -- repeat 5 + { key = "j", interval = TAP }, -- tan + { key = "j", interval = HOLD }, --- repeat 1 + { key = "j", interval = REPEAT }, -- repeat 2 + { key = "j", interval = REPEAT }, -- repeat 3 +} + +print("\n") +common_rush.run_sequence(tan_click_ng1) diff --git a/tests/cases/ng_case/run_3.lua b/tests/cases/ng_case/run_3.lua new file mode 100644 index 0000000..8a0e32f --- /dev/null +++ b/tests/cases/ng_case/run_3.lua @@ -0,0 +1,29 @@ +local common_rush = require("common_rush") + +---@type KeyInfo[] +local tan_click_normal = { + { key = "k", interval = CLICK }, -- tan + { key = "k", interval = HOLD }, --- repeat 1 + { key = "k", interval = REPEAT }, -- repeat 2 + { key = "k", interval = TAP }, -- tan + { key = "k", interval = TAP }, -- tan + { key = "k", interval = TAP }, -- tan + { key = "k", interval = TAP }, -- tan +} + +print("\n") +common_rush.run_sequence(tan_click_normal) + +---@type KeyInfo[] +local tan_click_ng1 = { + { key = "k", interval = CLICK }, -- tan + { key = "k", interval = HOLD }, --- repeat 1 + { key = "k", interval = REPEAT }, -- repeat 2 + { key = "k", interval = TAP }, -- tan + { key = "k", interval = TAP }, -- tan + { key = "k", interval = REPEAT }, -- tap -> repeat : NG! + { key = "k", interval = TAP }, -- tan +} + +print("\n") +common_rush.run_sequence(tan_click_ng1) diff --git a/tests/cases/ng_case/run_4.lua b/tests/cases/ng_case/run_4.lua new file mode 100644 index 0000000..faac018 --- /dev/null +++ b/tests/cases/ng_case/run_4.lua @@ -0,0 +1,33 @@ +local common_rush = require("common_rush") + +---@type KeyInfo[] +local tan_click_normal = { + { key = "k", interval = CLICK }, -- ta + { key = "k", interval = TAP }, -- ta + { key = "k", interval = TAP }, -- tan + { key = "k", interval = HOLD }, --- repeat 1 + { key = "k", interval = REPEAT }, -- repeat 2 + { key = "k", interval = TAP }, -- tan + { key = "k", interval = TAP }, -- tan + { key = "k", interval = TAP }, -- tan + { key = "k", interval = TAP }, -- tan +} + +print("\n") +common_rush.run_sequence(tan_click_normal) + +---@type KeyInfo[] +local tan_click_ng1 = { + { key = "k", interval = CLICK }, -- ta + { key = "k", interval = TAP }, -- ta + { key = "k", interval = HOLD }, -- tap -> hold : NG! + { key = "k", interval = HOLD }, --- repeat 1 + { key = "k", interval = REPEAT }, -- repeat 2 + { key = "k", interval = TAP }, -- tan + { key = "k", interval = TAP }, -- tan + { key = "k", interval = TAP }, -- tan + { key = "k", interval = TAP }, -- tan +} + +print("\n") +common_rush.run_sequence(tan_click_ng1) diff --git a/tests/cases/ng_case/run_5.lua b/tests/cases/ng_case/run_5.lua new file mode 100644 index 0000000..d199cfb --- /dev/null +++ b/tests/cases/ng_case/run_5.lua @@ -0,0 +1,27 @@ +local common_rush = require("common_rush") + +---@type KeyInfo[] +local tan_click_normal = { + { key = "k", interval = CLICK }, -- ta + { key = "k", interval = TAP }, -- tan + { key = "k", interval = HOLD }, --- repeat 1 + { key = "k", interval = REPEAT }, -- repeat 2 + { key = "k", interval = REPEAT }, -- tan + { key = "k", interval = REPEAT }, -- tan +} + +print("\n") +common_rush.run_sequence(tan_click_normal) + +---@type KeyInfo[] +local tan_click_ng1 = { + { key = "k", interval = CLICK }, -- ta + { key = "k", interval = TAP }, -- tan + { key = "k", interval = TAP }, --- hold -> tap : NG! + { key = "k", interval = REPEAT }, -- repeat 2 + { key = "k", interval = REPEAT }, -- tan + { key = "k", interval = REPEAT }, -- tan +} + +print("\n") +common_rush.run_sequence(tan_click_ng1) diff --git a/tests/cases/share/out-expected.txt b/tests/cases/share/out-expected.txt new file mode 100644 index 0000000..ac377c0 --- /dev/null +++ b/tests/cases/share/out-expected.txt @@ -0,0 +1,28 @@ +--- basic --- +=== MESSAGE === + +--- CASE 6: : count --- + (v l s) src type (t h r) key (itv h) seq + normal j (3 0 0) map tap (1 0 0) j ( 0 0) 3j + normal j (3 1 0) map tap (2 0 0) j (300 0) 3jj + repeat 3j (0 0 3) map repeat (2 1 1) j (500 0) 3jj↻ + repeat 3j (0 0 3) map repeat (2 1 2) j (150 0) 3jj↻ + repeat 3j (0 0 3) map repeat (2 1 3) j (150 0) 3jj↻ + normal 3k (0 0 3) map tap (1 1 0) k (300 0) 3jj↻k + normal h (0 0 3) map click (1 0 0) h (900 0) h + normal 3k (0 0 3) map click (1 0 0) k (900 0) k + normal 3k (0 1 3) map tap (2 0 0) k (300 0) kk + repeat 3k (0 1 3) map repeat (2 1 1) k (500 0) kk↻ + repeat 6k (0 1 3) map repeat (2 1 2) k (150 0) kk↻ + repeat 6k (0 1 3) map repeat (2 1 3) k (150 0) kk↻ + normal l (1 0 0) map tap (1 0 0) l ( 0 0) 1l + normal j (0 0 0) map click (1 0 0) j (900 0) j + normal w (13 0 0) map tap (1 0 0) w ( 0 0) j1w + normal w (13 1 0) map tap (2 0 0) w (300 0) j1ww + repeat 13w (0 0 13) map repeat (2 1 1) w (500 0) j1ww↻ + repeat 13w (0 0 13) map repeat (2 1 2) w (150 0) j1ww↻ + normal 13b (0 0 13) map tap (1 1 0) b (150 0) j1ww↻b + normal w (0 0 0) map tap (1 1 0) w (300 0) j1ww↻bw + +=== DISPLAY === + diff --git a/tests/cases/share/run.vim b/tests/cases/share/run.vim new file mode 100644 index 0000000..22975c0 --- /dev/null +++ b/tests/cases/share/run.vim @@ -0,0 +1,9 @@ +source $KIBI2_REPO_ROOT/tests/common.vim + +" ===== BASIC ===== +new + +CASE : count +lua require "run1" + +call Snapshot({ 'desc': 'basic' }) diff --git a/tests/cases/share/run1.lua b/tests/cases/share/run1.lua new file mode 100644 index 0000000..0d34200 --- /dev/null +++ b/tests/cases/share/run1.lua @@ -0,0 +1,29 @@ +local common_key = require("common_rush") + +---@type KeyInfo[] +local count3 = { + -- Shared counts persist in the opposite direction too. + { key = "3j", interval = TAP }, + { key = "j", interval = TAP }, + { key = "j", interval = HOLD }, -- j wait for the next repeat + { key = "j", interval = REPEAT }, -- 3j + { key = "j", interval = REPEAT }, + { key = "k", interval = TAP }, -- The opposite direction shares the count too. + { key = "h", interval = CLICK }, -- Pressing another key in between + { key = "k", interval = CLICK }, -- does not change the shared state. + { key = "k", interval = TAP }, + { key = "k", interval = HOLD }, + { key = "k", interval = REPEAT }, -- 6k + { key = "k", interval = REPEAT }, + { key = "1l", interval = CLICK }, -- Using a count + { key = "j", interval = CLICK }, -- resets the shared state. + { key = "13w", interval = TAP }, + { key = "w", interval = TAP }, + { key = "w", interval = HOLD }, + { key = "w", interval = REPEAT }, -- 13w + { key = "b", interval = REPEAT }, -- 13b + { key = "", interval = HOLD }, -- Escape also + { key = "w", interval = TAP }, -- resets the shared state. +} + +common_key.run_sequence(count3) diff --git a/tests/common.vim b/tests/common.vim new file mode 100644 index 0000000..9eef475 --- /dev/null +++ b/tests/common.vim @@ -0,0 +1,140 @@ +" ===== common.vim ===== + +source $KIBI2_REPO_ROOT/tests/common_rush.vim + +set noswapfile +set nobackup +set nowritebackup +set noundofile +set shortmess+=A + +let s:root = $KIBI2_REPO_ROOT +execute 'set rtp+=' . s:root +let g:kibi2_test_mode = 1 +filetype plugin indent on + +lua << EOF +local opts = { + log = { + level = vim.log.levels.WARN, + -- level = vim.log.levels.DEBUG, + use_timestamp = false, + monitor = false, + -- probe = false, + probe = true, + output = "print", + }, + table = { + wrap_mode = "nowrap", + }, +} +require("keyevent").setup(opts) +require("rush").setup() +-- KeyEvent = require("keyevent.keyevent") +-- Os = require("keyevent.os") +-- KeyEvent.setup({ + -- time = function() + -- return 123 + -- end +-- }) +local root = assert(os.getenv("KIBI2_REPO_ROOT")) +package.path = root .. "/tests/?.lua;" .. package.path +EOF + +" ---------------------------- +let g:case_no = 0 +let g:case_name = "" + +function! CaseImpl(id, desc) abort + echomsg " " + echomsg printf("--- CASE %d: %s ---", a:id, a:desc) + let g:case_tag = printf("CASE %d", a:id) +endfunction + +command! -nargs=1 CASE call CaseImpl(expand(''), ) + +function! Dump(cmd) abort + redir => msg + execute a:cmd + redir END + + let msg = substitute(msg, '\n', ' ', 'g') + echomsg printf('%s => %s', a:cmd, msg) +endfunction + +let s:last_msg_count = 0 + +function! s:CollectMessages() abort + redir => l:msgs + silent messages + redir END + let l:lines = split(l:msgs, "\n") + let l:new = l:lines[s:last_msg_count :] + let s:last_msg_count = len(l:lines) + return l:new +endfunction + +function! s:CollectDisplay() + return getline(1, '$') +endfunction + +function! s:CollectFile(path) + if a:path !=# '' && filereadable(a:path) + return readfile(a:path) + endif + return [] +endfunction + +function! s:LoadFileForCI(file) abort + if fnamemodify(a:file, ':e') ==# 'tir' + return systemlist('jq -cS . ' . shellescape(a:file)) + endif + return s:CollectFile(a:file) +endfunction + +function! s:CollectAll(opts) abort + let l:out = [] + + " DESCRIPTION + if has_key(a:opts, 'desc') + call add(l:out, '--- ' . a:opts.desc . ' ---') + endif + + " MESSAGE + if !has_key(a:opts, 'nomessage') + let l:msgs = s:CollectMessages() + call filter(l:msgs, 'v:val !~# " written$"') + if !empty(l:msgs) + call add(l:out, '=== MESSAGE ===') + let l:out += l:msgs + endif + endif + + " DISPLAY + call add(l:out, '') + call add(l:out, '=== DISPLAY ===') + let l:out += s:CollectDisplay() + + " FILE + if has_key(a:opts, 'file') + let l:file = s:LoadFileForCI(a:opts.file) + if !empty(l:file) + call add(l:out, '') + call add(l:out, '=== FILE ===') + let l:out += l:file + endif + endif + + return l:out +endfunction + +" ---------------------------- +" opts: +" file: 'output.csv' +" desc: 'description' +function! Snapshot(opts) abort + lua vim.wait(50) + let l:out = s:CollectAll(a:opts) + " call writefile([''], 'out-actual.txt', 'a') + call writefile(l:out, 'out-actual.txt', 'a') +endfunction \ No newline at end of file diff --git a/tests/common_rush.lua b/tests/common_rush.lua new file mode 100644 index 0000000..d57faff --- /dev/null +++ b/tests/common_rush.lua @@ -0,0 +1,54 @@ +local root = assert(os.getenv("KIBI2_REPO_ROOT")) + +vim.opt.rtp:prepend(root) +vim.opt.rtp:prepend(root .. "/../keyevent.nvim") + +KeyEvent = require("keyevent.keyevent") +Config = require("keyevent.config") +Os = require("keyevent.os") +local log = require("keyevent.log") + +local M = {} + +local test_time = 0 + +--@class KeyInfo +--@field key string +--@field interval integer +--@field keymap boolean|nil + +function M.advance(ms) + test_time = test_time + ms +end + +--- @param sequence KeyInfo[] +function M.run_sequence(sequence) + print(" (v l s) src type (t h r) key (itv h) seq") + for _, keyinfo in ipairs(sequence) do + M.advance(keyinfo.interval) + local keys = + vim.api.nvim_replace_termcodes(keyinfo.key, true, false, true) + -- print(keys) + vim.api.nvim_feedkeys(keys, "xt", false) + KeyEvent.on_key_test(keyinfo.key) + end +end + +KeyEvent.setup({ + time = function() + return test_time + end, +}) + +CLICK = 900 +TAP = 300 +HOLD = 500 +REPEAT = 150 +DELTA = 20 + +if not Config.threshold.delay then + Config.threshold.delay = HOLD + Config.threshold.interval = REPEAT +end + +return M diff --git a/tests/common_rush.vim b/tests/common_rush.vim new file mode 100644 index 0000000..a08edb9 --- /dev/null +++ b/tests/common_rush.vim @@ -0,0 +1,6 @@ +lua << EOF +local root = assert(os.getenv("KIBI2_REPO_ROOT")) + +vim.opt.rtp:prepend(root) +vim.opt.rtp:prepend(root .. "/../keyevent.nvim") +EOF \ No newline at end of file diff --git a/tests/coverage.sh b/tests/coverage.sh new file mode 100755 index 0000000..e08a084 --- /dev/null +++ b/tests/coverage.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +ROOT_DIR=$(cd "$SCRIPT_DIR/.." && pwd) + +cd "$ROOT_DIR" + +eval "$(luarocks path)" + +if [ ! -f "$ROOT_DIR/luacov.stats.out" ]; then + echo "No coverage data. Run tests first." + exit 1 +fi + +nvim --headless +"lua require('luacov.reporter.html').report()" +qa +mv luacov.report.out luacov.report.html +nvim --headless +"lua require('luacov.reporter.default').report()" +qa + +echo "Coverage report: $ROOT_DIR/luacov.report.html" + +if [ -z "${GITHUB_ACTIONS:-}" ] && command -v open >/dev/null 2>&1; then + open "$ROOT_DIR/luacov.report.html" +fi \ No newline at end of file diff --git a/tests/data/sample.md b/tests/data/sample.md new file mode 100644 index 0000000..3a9fcb2 --- /dev/null +++ b/tests/data/sample.md @@ -0,0 +1,316 @@ +# KeyEvent Laboratory + +これは keyevent.nvim と rush.nvim の動作を確認するためのテスト用 Markdown 文書です。 +日本語、English、数字、記号、Markdown 構文を意図的に混在させています。 + +## 01. はじめに + +キーボードから入力された文字は、単純な一文字ではなく、時間の流れを持ったイベントとして観察できます。 +たとえば `j` を一度押す場合と、`j` を押し続ける場合では、同じキーでも異なるイベント列になります。 +`click`、`tap`、`repeat`、そしてキーを離した瞬間を観察すると、入力の時間的な構造が見えてきます。 + +ここでは特定の文章を読むことよりも、カーソルを自由に動かして、キー入力の挙動を眺めることを目的とします。 + +## 02. 短い行 + +a +ab +abc +abcd +日本 +日本語 +テスト +ABC +ABC123 +1234567890 +! +!! +??? +--- + +--- + +短い行の次には、普通の長さの行を置きます。 + +## 03. 普通の文章 + +春の朝、窓を開けると静かな風が部屋の中へ入ってきました。机の上には古いノートと新しいキーボードが並んでいます。 +プログラムを書いていると、画面上の小さなカーソルが意外なほど重要な存在であることに気づきます。 + +日本語の文章では、漢字、ひらがな、カタカナが自然に混ざります。 +English words may appear in the middle of a Japanese sentence, and numbers such as 2026 or 3.141592653589793 may appear as well. + +キー操作のテストでは、`h`、`j`、`k`、`l` を何度も入力します。 +さらに `w`、`b`、`e`、`ge` のような単語移動も試します。 + +## 04. 記号 + +ASCII 記号をまとめて並べます。 + +```text +! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ +``` + +日本語の記号も確認します。 + +```text +「こんにちは」『テスト文章』【重要】(補足)[配列]〈項目〉《引用》〔注記〕 +``` + +似ている文字でも Unicode 上では別の文字です。 + +```text +() () [] [] {} {} <> <> -- ー ― ~ ~ +``` + +## 05. Markdown + +Markdown の強調記法を確認します。 + +**太字の日本語**、*斜体の日本語*、`inline code`、~~取り消し線~~。 +リンク風の文字列は [example](https://example.com/test?q=123&lang=ja) と書けます。 + +> これは引用文です。カーソルを行頭から行末まで何度も往復させてみてください。 + +* 最初の項目 +* 二番目の項目 + + * 内側の項目 + * もう一つの項目 +* 最後の項目 + +1. 一番目 +2. 二番目 +3. 三番目 + +## 06. 数字と英字 + +数字を連続させます。 + +```text +0123456789 1234567890 9876543210 314159265358979323846264338327950288419716939937510 +``` + +英字も連続させます。 + +```text +abcdefghijklmnopqrstuvwxyz +ABCDEFGHIJKLMNOPQRSTUVWXYZ +Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz +``` + +識別子らしい文字列もあります。 + +```text +keyevent.nvim +rush.nvim +vim.v.count +vim.keymap.set +nvim_buf_get_lines +on_key +repeat_end +``` + +## 07. CJK と ASCII の混在 + +日本語ABC日本語123日本語xyz日本語---日本語+++日本語===日本語___日本語。 +Hello世界、world世界、Neovim日本語、Luaコード、GitHubリポジトリ、OSS開発。 +`日本語` と `English` と `12345` と `!@#$%` を同じ行に置いてみます。 + +「ABC」「日本語」「123」「!@#」「foo_bar」「foo-bar」「foo/bar」「foo.bar」を比較します。 + +## 08. URL とパス + +URL は長くなることがあります。 + +https://example.com/path/to/document?id=12345&lang=ja&mode=test#section-04 + +ファイルパスもあります。 + +```text +~/oss/keyevent.nvim/lua/keyevent/keyevent.lua +~/.config/nvim/lua/custom/plugins/rush.lua +/usr/local/bin/nvim +/Users/example/Documents/project/test-data.md +``` + +Windows 風のパスも置きます。 + +```text +C:\Users\example\Documents\project\test.md +D:\work\keyevent\data\sample.txt +``` + +## 09. 長い行 + +この行は横方向のカーソル移動を試すために意図的に長くしています。日本語の文字列と English words、1234567890、記号 !@#$%^&*()_+-=[]{};:',.<>/?|、Markdown の **bold** や *italic*、`inline code`、URL https://example.com/path/to/resource?q=123&lang=ja#section などを混在させています。さらに「日本語の括弧(全角)」「半角括弧 (ASCII)」「[全角角括弧] [ASCII brackets]」「【隅付き括弧】」「」「key=value」「foo->bar」「a::b::c」といったパターンも含め、横方向に長く連続させています。 + +この文章は rush.nvim の h/l による移動を試すためのものです。カーソルを行頭に置いてから l を長押しし、行末付近まで移動してみます。その後 h を長押しして逆方向へ戻ります。日本語と ASCII の幅が異なるため、表示上の位置と内部的な文字位置について考えるきっかけにもなります。さらに w や b を使えば、単純な一文字単位とは違う移動の様子も確認できます。 + +## 10. さらに長い行 + +keyevent.nvim はキー入力を時間情報とともに扱いますが、テストデータそのものには時間情報がありません。そのため、このような長い行を用意しておくと、実際のキーボード操作で click、tap、repeat が発生したときに、カーソルがどの位置まで進んだのかを目視で確認できます。たとえば `8j` の後に j を押し続ける操作、`6l` の後に l を押し続ける操作、あるいは w と b を交互に押す操作などを組み合わせると、通常の Neovim の移動と rush.nvim による加速の違いを確認しやすくなります。 + +## 11. 表 + +| key | event | meaning | example | +| --- | ------ | ------------------ | -------- | +| `j` | click | normal motion | 下へ移動 | +| `j` | repeat | accelerated motion | 長押し | +| `k` | repeat | reverse motion | 上へ移動 | +| `w` | tap | word motion | 次の単語 | +| `b` | tap | backward motion | 前の単語 | +| `l` | repeat | horizontal motion | 右へ移動 | + +表の中にも日本語と ASCII を混ぜています。 + +## 12. コードブロック + +```lua +local KeyEvent = require("keyevent") + +local count = 8 +local direction = "j" + +print("count =", count) +print("direction =", direction) + +for index = 1, count do + print(index, direction) +end +``` + +コードの中には `local`、`function`、`return`、`end`、`if`、`then` などの短い単語が現れます。 + +## 13. 記号の密集 + +```text +foo(bar[baz{qux}])! +foo(bar[baz{qux}])? +foo::bar::baz->qux=>xyz +a==b && c!=d || e<=f && g>=h +x[0] = y[1] + z[2] * 3 / 4 +``` + +記号が密集した場所では、`w` や `e` の動作も試してみます。 + +## 14. 日本語の句読点 + +日本語にはさまざまな句読点があります。 +「これは引用です。」(これは括弧です。)【これは別の括弧です】[こちらも括弧です]〈さらに別の括弧〉。 +文章の途中に……三点リーダがあります。――ダッシュもあります。〜波線もあります。 + +句読点の直後に英単語を置きます。たとえば、Neovim、Lua、Git、GitHub、Markdown などです。 + +## 15. 数値 + +整数: 0, 1, 2, 3, 10, 100, 1000 +負数: -1, -2, -100 +小数: 0.1, 1.5, 3.141592653589793 +指数: 1e3, 2.5e-4 +16進数: 0x00, 0x10, 0xff, 0x12345678 + +大きな整数もあります。 + +```text +12345678901234567890123456789012345678901234567890 +``` + +## 16. 短い行と長い行 + +ここからは行長を意図的に変化させます。 + +短い。 + +少し長い行です。 + +もう少し長い行です。日本語と English を混ぜます。 + +これはさらに長い行です。カーソルを左右に移動させて、画面端付近でスクロールがどのように発生するかを確認してください。1234567890 !@#$% `code` **bold** [link](https://example.com)。 + +## 17. 箇条書きの深さ + +* level 1 + + * level 2 + + * level 3 + + * level 4 + + * level 5 +* 日本語の level 1 + + * 日本語の level 2 + + * 日本語の level 3 + +## 18. 空行 + +上の節とこの節の間には、複数の空行があります。 + +空行を越えてカーソルを移動するときの動作も確認できます。 + +## 19. 混在した文章 + +あるところでは日本語だけを書き、別のところでは English only の文章を書き、その間に 12345 や記号 !@#$% を挿入します。すると、カーソルは文字種の境界を何度も通過することになります。たとえば `日本語ABC123` のような短い文字列から始めて、`日本語---ABC---123---xyz---!@#$%` のような文字列へ進み、最後には URL やファイルパスまで続けることができます。 + +## 20. テストシナリオ + +まずカーソルをこの行の先頭へ移動します。 +次に `l` を一回ずつ入力します。 +続いて `l` を押し続けます。 +その後 `h` を押し続けて元の位置へ戻ります。 + +次に `j` を押します。 +そのまま `j` を連続して入力します。 +最後に `k` を連続して入力して戻ります。 + +## 21. 特殊な文字列 + +```text +aaaaabbbbbcccccdddddeeeee +jjjjjkkkkklllllhhhhh +wwwwwbbbbbeeeeegegege +-----=====+++++***** +.....,,,,,;;;;;::::: +``` + +日本語でも同じような繰り返しを置きます。 + +```text +あああああいいいいいううううう +漢漢漢漢漢字字字字字 +テテテテテストストスト +``` + +## 22. Markdown の境界 + +**bold** の直前と直後、`code` の前後、[link](https://example.com) の前後には記号があります。 +たとえば `foo**bold**bar`、`foo`code`bar`、`foo[link](https://example.com)bar` のような文字列です。 + +## 23. コメント風文字列 + +```text +# heading +// comment +/* comment */ +-- lua comment + +``` + +これらはすべて単なるテストデータです。 + +## 24. 500文字近いテスト行 + +最後に非常に長い行を置きます。この行では、日本語、English、数字、記号、Markdown、URL、ファイルパス、括弧、引用符、演算子などを一つの行にまとめています。日本語の文章を書きながら `keyevent.nvim` と `rush.nvim` の名前を何度も登場させ、さらに `h j k l w b e ge`、``、``、``、`vim.v.count`、`vim.keymap.set()` といった文字列を追加します。数字は 0123456789、16進数は 0x123456789abcdef、記号は !@#$%^&*()_+-=[]{};:'",.<>/?|~ を使用します。URL は https://example.com/a/b/c?q=123&lang=ja#test、パスは ~/oss/keyevent.nvim/lua/keyevent/keyevent.lua、括弧は (日本語)(ASCII)[日本語][ASCII]【日本語】{ASCII}、引用符は「日本語」"English" 'single' `code` とします。さらに ABCDEFGHIJKLMNOPQRSTUVWXYZ と abcdefghijklmnopqrstuvwxyz を加え、最後に 日本語ABC123xyz!@#$% を置きます。このように異なる文字種を連続して配置すると、カーソル移動、スクロール、単語移動、キーリピートによる加速などを一つの行でまとめて観察できます。 + +## 25. 終了 + +ここまでがテストデータです。 + +最後の確認として、`h`、`j`、`k`、`l`、`w`、`b`、`e`、`ge` を自由に組み合わせてカーソルを動かしてください。 + +長い行では横方向の移動を、短い行では行間の移動を、コードブロックでは記号を、通常の文章では日本語と ASCII の混在を確認できます。 + +keyevent.nvim のイベント表示と rush.nvim の加速動作が、この文書を使って観察できればテスト成功です。 diff --git a/tests/data/simple.md b/tests/data/simple.md new file mode 100644 index 0000000..eaf9258 --- /dev/null +++ b/tests/data/simple.md @@ -0,0 +1,9 @@ +# KeyEvent Laboratory + +これは keyevent.nvim と rush.nvim の動作を確認するためのテスト用 Markdown 文書です。日本語、English、数字、記号、Markdown 構文を意図的に混在させています。 + +## 01. はじめに + +キーボードから入力された文字は、単純な一文字ではなく、時間の流れを持ったイベントとして観察できます。たとえば `j` を一度押す場合と、`j` を押し続ける場合では、同じキーでも異なるイベント列になります。`click`、`tap`、`repeat`、そしてキーを離した瞬間を観察すると、入力の時間的な構造が見えてきます。 + +ここでは特定の文章を読むことよりも、カーソルを自由に動かして、キー入力の挙動を眺めることを目的とします。 diff --git a/tests/format.sh b/tests/format.sh new file mode 100755 index 0000000..e0d6136 --- /dev/null +++ b/tests/format.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +ROOT_DIR=$(cd "$SCRIPT_DIR/.." && pwd) + +echo "Format lua tests" + +stylua $ROOT_DIR/lua $ROOT_DIR/tests \ No newline at end of file diff --git a/tests/golden_test.sh b/tests/golden_test.sh new file mode 100755 index 0000000..71f86ba --- /dev/null +++ b/tests/golden_test.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash + +# Usage: +# ./run.sh # run all tests +# ./run.sh file/csv # run tests matching pattern +# ./run.sh --update # generate expected outputs (for new tests) +# +# Environment variables: +# FAIL_FAST=1 # stop on first failure +# NO_COLOR=1 # disable colored output +# +# Notes: +# - Tests are located under cases/ +# - Each test must have run.sh or run.vim +# - --update does NOT overwrite existing out-expected.txt + +set -euo pipefail + +run_hook() { + local phase="$1" + case "$phase" in + pre|post) + ;; + *) + echo "Unknown hook phase: $phase" >&2 + exit 1 + ;; + esac + if [ -f hook.sh ]; then + sh hook.sh "$phase" >> stdout.txt 2>> stderr.txt + fi +} + +normalize_output() { + LC_ALL=C sed -E '/PRB/! s/ [0-9]+]/]/g' \ + out-actual.txt > gen.txt + mv gen.txt out-actual.txt + + LC_ALL=C sed -E \ + 's/[0-9]+ seconds? ago/