diff --git a/.gitignore b/.gitignore index 00cb2b0c00b..9d73aa14a83 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ packages/test-harness/testSubsetGrep.properties cursorless.nvim/node/cursorless-neovim cursorless.nvim/node/test-harness +cursorless.nvim/test/xdg/ diff --git a/cursorless.nvim/.busted b/cursorless.nvim/.busted new file mode 100644 index 00000000000..6aaf2424041 --- /dev/null +++ b/cursorless.nvim/.busted @@ -0,0 +1,8 @@ +return { + _all = { + lua = './test/nvim-shim.sh' + }, + unit = { + ROOT = {'./test/unit/'}, + }, +} diff --git a/cursorless.nvim/lua/cursorless/cursorless.lua b/cursorless.nvim/lua/cursorless/cursorless.lua index 82c012c7464..3fc0c69a452 100644 --- a/cursorless.nvim/lua/cursorless/cursorless.lua +++ b/cursorless.nvim/lua/cursorless/cursorless.lua @@ -122,15 +122,11 @@ function M.buffer_get_selection_text() end -- https://github.com/nvim-treesitter/nvim-treesitter/blob/master/lua/nvim-treesitter/ts_utils.lua#L278 --- luacheck:ignore 631 --- https://github.com/nvim-treesitter/nvim-treesitter-textobjects/blob/master/lua/nvim-treesitter/textobjects/select.lua#L114 --- as an example if you put that in a vim buffer and do the following you can do a selection: --- :w c:\work\tmp\test.lua --- :so % --- :lua select_range(5, 12, 5, 30) --- for example it will highlight the last function name (nvim_win_set_cursor). --- another example is :tmap lua require("talon.cursorless").select_range(4, 0, 4, 38) +-- If you have a buffer with the line: "hello world" +-- :lua require("cursorless.cursorless").select_range(1, 2, 1, 4) +-- will highlight "llo" -- NOTE: works for any mode (n,i,v,nt) except in t mode +-- luacheck:ignore 631 function M.select_range(start_x, start_y, end_x, end_y) vim.cmd([[normal! :noh]]) vim.api.nvim_win_set_cursor(0, { start_x, start_y }) diff --git a/cursorless.nvim/test/nvim-shim.sh b/cursorless.nvim/test/nvim-shim.sh new file mode 100755 index 00000000000..6ca53a9f203 --- /dev/null +++ b/cursorless.nvim/test/nvim-shim.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +export XDG_CONFIG_HOME='test/xdg/config/' +export XDG_STATE_HOME='test/xdg/local/state/' +export XDG_DATA_HOME='test/xdg/local/share/' + +# FIXME: For the flake.nix devshell I plan to eventually just install the unwrapped binary straight up so I can +# reference it for these tests, but until then... +function resolve_nvim_path() { + nvim=$(type -p "nvim" | awk '{print $NF}') + if file "${nvim}" | grep -q 'executable'; then + echo "${nvim}" + return + fi + + # On NixOS, this might still be a wrapper, so we need to make sure we find the real binary + current_link="${nvim}" + while [ -L "${current_link}" ]; do + current_link=$(readlink "${current_link}") + done + nvim="${current_link}" + + if file "${current_link}" | grep -q 'script'; then + nvim=$(grep 'exec -a' "${current_link}" | cut -f4 -d" " | tr -d '"') + fi + echo "${nvim}" +} + +mkdir -p ${XDG_CONFIG_HOME} ${XDG_STATE_HOME} ${XDG_DATA_HOME} +mkdir -p ${XDG_DATA_HOME}/nvim/site/pack/testing/start || true +link_path="${XDG_DATA_HOME}/nvim/site/pack/testing/start/cursorless.nvim" +if [ ! -e "${link_path}" ]; then + # FIXME: Expects to be in cursorless.nvim, so we should force cd here? + ln -sf "${PWD}" ${link_path} +fi + +NVIM=$(resolve_nvim_path) + +# shellcheck disable=SC2068 +${NVIM} --cmd 'set loadplugins' -l $@ +exit_code=$? +rm ${link_path} 2>/dev/null || true + +exit $exit_code diff --git a/cursorless.nvim/test/unit/cursorless_spec.lua b/cursorless.nvim/test/unit/cursorless_spec.lua new file mode 100644 index 00000000000..f9770b1157d --- /dev/null +++ b/cursorless.nvim/test/unit/cursorless_spec.lua @@ -0,0 +1,95 @@ +describe("cursorless tests", function() + local cursorless = require("cursorless.cursorless") + + local get_selected_text = function() + local _, ls, cs = unpack(vim.fn.getpos("v")) + local _, le, ce = unpack(vim.fn.getpos(".")) + return vim.api.nvim_buf_get_text(0, ls - 1, cs - 1, le - 1, ce, {}) + end + + local function convert_table_entries(tbl, func) + local mapped = {} + for k, v in pairs(tbl) do + mapped[k] = func(v) + end + return mapped + end + + describe("window_get_visible_lines()", function() + before_each(function() end) + + it("Can read one visible line", function() + local pos = vim.api.nvim_win_get_cursor(0)[2] + local line = vim.api.nvim_get_current_line() + local nline = line:sub(0, pos) .. "hello" .. line:sub(pos + 1) + vim.api.nvim_set_current_line(nline) + + local visible = cursorless.window_get_visible_lines() + assert(table.concat(visible) == table.concat({ 1, 1 })) + end) + + it("Can read all lines visible on the window", function() + local maxlines = vim.api.nvim_win_get_height(0) + local lines = {} + for _ = 1, (maxlines + 1) do + table.insert(lines, "hello ") + end + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + local visible = cursorless.window_get_visible_lines() + assert(table.concat(visible) == table.concat({ 1, maxlines })) + end) + end) + describe("select_range()", function() + it("Selects the specified range", function() + local lines = "hello world" + vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(lines, "\n")) + cursorless.select_range(1, 2, 1, 4) + + assert(table.concat(get_selected_text()) == "llo") + end) + end) + describe("buffer_get_selection()", function() + it( + "Can get the forward selection in a format expected by cursorless", + function() + local lines = "hello world" + vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(lines, "\n")) + cursorless.select_range(1, 2, 1, 4) + assert( + table.concat( + convert_table_entries(cursorless.buffer_get_selection(), tostring), + ", " + ) + == table.concat( + convert_table_entries({ 1, 3, 1, 5, false }, tostring), + ", " + ) + ) + end + ) + it( + "Can get the backward selection in a format expected by cursorless", + function() + local lines = "hello world" + vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(lines, "\n")) + cursorless.select_range(1, 4, 1, 2) + print( + table.concat( + convert_table_entries(cursorless.buffer_get_selection(), tostring), + ", " + ) + ) + assert( + table.concat( + convert_table_entries(cursorless.buffer_get_selection(), tostring), + ", " + ) + == table.concat( + convert_table_entries({ 1, 3, 1, 5, true }, tostring), + ", " + ) + ) + end + ) + end) +end) diff --git a/docs/contributing/cursorless-in-neovim.md b/docs/contributing/cursorless-in-neovim.md index 1827917a791..f9b29526935 100644 --- a/docs/contributing/cursorless-in-neovim.md +++ b/docs/contributing/cursorless-in-neovim.md @@ -12,7 +12,7 @@ Follow the steps in [CONTRIBUTING.md](./CONTRIBUTING.md#initial-setup). Follow the installation steps in [cursorless.nvim](https://github.com/hands-free-vim/cursorless.nvim/tree/main#prerequisites). -Confirm that production cursorless.nvim is working in neovim, eg say `"take first paint"` in a nonempty document. +Confirm that production cursorless.nvim is working in neovim, eg say `"take first paint"` in a non-empty document. ### 3. Add nvim executable path to your PATH @@ -40,7 +40,13 @@ In order to test out your local version of the extension or to run unit tests lo The debug logs are written in `C:\path\to\cursorless\packages\cursorless-neovim\out\nvim_node.log`. -NOTE: This will spawn a standalone nvim instance that is independent of VSCode. Consequently after you're done debugging, you need to close nvim. +NOTE: This will spawn a standalone nvim instance that is independent of VSCode. Consequently after you're done +debugging, you need to close nvim. + +### Running lua tests + +Their are separate cursorless and lua tests. You can run the lua tests by entering the `cursorless.nvim` folder and +running: `busted --run unit`. These tests currently only work on Linux. ## Sending pull requests