diff --git a/lua/cursor_text_objects.lua b/lua/cursor_text_objects.lua index 29520fa..0aee575 100644 --- a/lua/cursor_text_objects.lua +++ b/lua/cursor_text_objects.lua @@ -9,14 +9,16 @@ local M = {} local _Direction = { down = "down", up = "up" } - -local unpack = unpack or table.unpack -- Future Lua versions use table.unpack +local _DirectionMark = { down = "]", up = "[" } +local _PositionType = { character_wise = "`", line_wise = "'" } local _CURSOR local _DIRECTION local _OPERATOR local _OPERATOR_FUNCTION +local unpack = unpack or table.unpack -- Future Lua versions use table.unpack + --- Check if the operatorfunc that is running will run on a whole-line. --- --- See Also: @@ -62,7 +64,7 @@ local function _adjust_marks(mode) local inclusive_toggle = "" if _DIRECTION == _Direction.up then - direction = "[" + direction = _DirectionMark.up if not is_line then local buffer, row, column, offset = unpack(vim.fn.getpos("'" .. direction)) @@ -75,7 +77,7 @@ local function _adjust_marks(mode) vim.fn.setpos("'" .. direction, { buffer, row, column, offset }) end else - direction = "]" + direction = _DirectionMark.down local buffer, row, column, offset = unpack(vim.fn.getpos("'" .. direction)) @@ -115,9 +117,38 @@ end --- The caller context. See `:help :map-operator` for details. --- function M.visual(mode) - local _, mark, direction = _adjust_marks(mode) + vim.fn.setpos(".", _CURSOR) + + local mark + + if _is_line_mode(mode) then + mark = "'" -- Includes only line information + else + mark = "`" -- Includes column information + end + + local direction + + if _DIRECTION == _Direction.up then + direction = _DirectionMark.up + else + direction = _DirectionMark.down + end + + local _, row, column, _ = unpack(vim.fn.getpos("'" .. direction)) + + if mark == _PositionType.line_wise then + if direction == _DirectionMark.down then + column = #vim.fn.getline(row) + else + column = 0 + end + else + column = column - 1 + end - vim.cmd(string.format("normal v%s%s", mark, direction)) + vim.cmd("normal v") + vim.api.nvim_win_set_cursor(0, { row, column }) end --- Remember anything that we will need to recall once we execute `operatorfunc`. diff --git a/spec/cursor_text_objects_spec.lua b/spec/cursor_text_objects_spec.lua index 95951b6..ff521b8 100644 --- a/spec/cursor_text_objects_spec.lua +++ b/spec/cursor_text_objects_spec.lua @@ -21,11 +21,17 @@ local function _call_command(keys) end --- Add mappings for unittests. -local function _initialize_mappings() - vim.keymap.set("o", "[", "(cursor-text-objects-up)") - vim.keymap.set("o", "]", "(cursor-text-objects-down)") - vim.keymap.set("x", "[", "(cursor-text-objects-up)") - vim.keymap.set("x", "]", "(cursor-text-objects-down)") +--- +---@param up string? The key(s) to set as the "go the top to the cursor". +---@param down string? The key(s) to set as the "go from the cursor down". +--- +local function _initialize_mappings(up, down) + up = up or "[" + down = down or "]" + vim.keymap.set("o", up, "(cursor-text-objects-up)") + vim.keymap.set("o", down, "(cursor-text-objects-down)") + vim.keymap.set("x", up, "(cursor-text-objects-up)") + vim.keymap.set("x", down, "(cursor-text-objects-down)") end --- Create a new Vim buffer with `text` contents. @@ -49,11 +55,18 @@ local function _make_buffer(text, file_type) end --- Remove any the default mappings that were added from `_initialize_mappings`. -local function _revert_mappings() - vim.keymap.del("o", "[") - vim.keymap.del("o", "]") - vim.keymap.del("x", "[") - vim.keymap.del("x", "]") +--- +---@param up string? The key(s) to set as the "go the top to the cursor". +---@param down string? The key(s) to set as the "go from the cursor down". +--- +local function _revert_mappings(up, down) + up = up or "[" + down = down or "]" + + vim.keymap.del("o", up) + vim.keymap.del("o", down) + vim.keymap.del("x", up) + vim.keymap.del("x", down) end --- Make sure `input` becomes `expected` when `keys` are called. @@ -130,6 +143,37 @@ describe("basic", function() end) end) +describe("custom mappings", function() + before_each(function() + _initialize_mappings("{", "}") + end) + after_each(function() + _revert_mappings("{", "}") + end) + + it("works with keys, not just [ / ]", function() + _run_simple_test( + { 2, 0 }, + "c}ap", + [[ + some text + more text <-- NOTE: The cursor will be set here + even more lines! + still part of the paragraph + + another paragraph + with text in it + ]], + [[ + some text + + another paragraph + with text in it + ]] + ) + end) +end) + describe(":help c", function() before_each(_initialize_mappings) after_each(_revert_mappings) @@ -1235,6 +1279,111 @@ describe(":help g~", function() end) end) +describe(":help v", function() + before_each(_initialize_mappings) + after_each(_revert_mappings) + + describe("character-wise", function() + describe("down", function() + it("works with visual selection", function() + local _, window = _make_buffer([[ + A paragraph of text and sentences. Here's another sentence + that spans multiple lines <-- NOTE: The cursor will be set here + but our code should handle it. And lastly. + A sentence that starts on its own line. + ]]) + vim.api.nvim_win_set_cursor(window, { 2, 23 }) + + -- NOTE: We yank the visual selection so we can assert it later + _call_command("v]asy") + + assert.same( + [[t spans multiple lines <-- NOTE: The cursor will be set here + but our code should handle it. ]], + vim.fn.getreg("") + ) + end) + end) + + describe("up", function() + it("works with visual selection", function() + local _, window = _make_buffer([[ + A paragraph of text and sentences. Here's another sentence + that spans multiple lines <-- NOTE: The cursor will be set here + but our code should handle it. And lastly. + A sentence that starts on its own line. + ]]) + vim.api.nvim_win_set_cursor(window, { 2, 23 }) + + -- NOTE: We yank the visual selection so we can assert it later + _call_command("v[asy") + + assert.same( + [[Here's another sentence + that]], + vim.fn.getreg("") + ) + end) + end) + end) + + describe("line-wise", function() + describe("down", function() + it("works with visual selection", function() + local _, window = _make_buffer([[ + aaaa + bbbb <-- NOTE: The cursor will be set here + cccc + + next + lines + blah + + ]]) + vim.api.nvim_win_set_cursor(window, { 2, 0 }) + + -- NOTE: We yank the visual selection so we can assert it later + _call_command("v]apy") + + assert.same( + [[ + bbbb <-- NOTE: The cursor will be set here + cccc + +]], + vim.fn.getreg("") + ) + end) + end) + + describe("up", function() + it("works with visual selection", function() + local _, window = _make_buffer([[ + aaaa + bbbb + cccc + + next + lines <-- NOTE: The cursor will be set here + blah + + ]]) + vim.api.nvim_win_set_cursor(window, { 6, 4 }) + + -- NOTE: We yank the visual selection so we can assert it later + _call_command("v[apy") + + assert.same( + [[ + next + ]], + vim.fn.getreg("") + ) + end) + end) + end) +end) + describe(":help y", function() before_each(_initialize_mappings) after_each(_revert_mappings)