Skip to content

Commit

Permalink
feat: support dot-repeat in non-visual modes
Browse files Browse the repository at this point in the history
This addresses most of #6.
  • Loading branch information
gregorias committed Jun 22, 2024
1 parent aceef41 commit a811a4f
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 32 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,12 @@ You can register a new mode like so:
require"coerce".register_mode{
vim_mode = "v",
keymap_prefix = "gc",
selector = function()
selector = function(cb)
local s, e = -- Your function that finds start and end points.
-- For example, returning {0, 0}, {0, 5} selects the first 6
-- characters of the current buffer.
local region_m = require"coerce.region"
return region_m(region_m.modes.INLINE, s, e)
cb(region_m(region_m.modes.INLINE, s, e))
end,
transformer = require"coerce.transformer".transform_local,
}
Expand All @@ -189,6 +189,7 @@ to change case of the current keyword, Coerce is simpler.
| LSP rename ||||
| Kebab case ||||
| [Numeronym] “case” ||||
| Dot repeat support ||||
| Custom case support ||||
| Custom mode support ||||

Expand Down
31 changes: 19 additions & 12 deletions lua/coerce/conversion.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ M.Coercer = function(keymap_registry, notify)
self.keymap_registry.register_keymap(mode.vim_mode, mode.keymap_prefix .. case.keymap, function()
local coroutine_m = require("coerce.coroutine")
coroutine_m.fire_and_forget(function()
local error = M.coerce(mode.selector, mode.transformer, case.case)
if type(error) == "string" then
self.notify(error, "error", { title = "Coerce" })
end
M.coerce(mode.selector, mode.transformer, case.case, function(error)
if type(error) == "string" then
self.notify(error, "error", { title = "Coerce" })
end
end)
end)
end, case.description)
end,
Expand Down Expand Up @@ -61,16 +62,22 @@ end

--- Coerces selected text.
--
-- @tparam function select_text The function that returns selected text (Region) or an error.
-- `select_text` uses a callback to support dot-repeat functionality. If `select_text` uses operators, then
-- the callback can be used as the repeatable action.
--
-- @tparam function select_text The function that returns selected text (Region) or an error through a callback.
-- @tparam function transform_text The function to use to transform selected text.
-- @tparam function case The function to use to coerce case.
-- @tparam function cb The function to receive a string error or nil.
-- @treturn nil
M.coerce = function(select_text, transform_text, case)
local selected_region = select_text()
if type(selected_region) == "string" then
return selected_region
end
transform_text(selected_region, case)
M.coerce = function(select_text, transform_text, case, cb)
select_text(function(selected_region)
if type(selected_region) == "string" then
cb(selected_region)
end
transform_text(selected_region, case)
cb(nil)
end)
end

--- Converts the current word using the apply function.
Expand All @@ -82,7 +89,7 @@ end
--@treturn nil
M.coerce_current_word = function(transform_text, apply)
local selector = require("coerce.selector")
M.coerce(selector.select_current_word, transform_text, apply)
M.coerce(selector.select_current_word, transform_text, apply, function() end)
end

return M
33 changes: 19 additions & 14 deletions lua/coerce/selector.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,48 @@ local M = {}

--- Selects the current word.
--
-- This is a fire-and-forget coroutine function.
--
-- @treturn Region The selected region.
M.select_current_word = function()
-- @tparam function cb The callback to return the selected region to.
-- @treturn nil
M.select_current_word = function(cb)
local operator_m = require("coerce.operator")
return operator_m.operator("xn", "iw")
return operator_m.operator_cb(function(mmode)
local selected_region = operator_m.get_selected_region(mmode)
cb(selected_region)
end, "xn", "iw")
end

--- Selects with the user provided motion.
--
-- This is a fire-and-forget coroutine function.
--
-- @treturn Region The selected region.
M.select_with_motion = function()
-- @tparam function cb The callback to return the selected region to.
-- @treturn nil
M.select_with_motion = function(cb)
local operator_m = require("coerce.operator")
-- The i-mode is important. We might be running within a feedkeys() call, so we need to insert
-- the operator into the typeahead buffer immediately before the motion.
-- The n-mode is also important. We don’t want user remaps of g@ to interfere with the operator.
return operator_m.operator("in", "")
return operator_m.operator_cb(function(mmode)
local selected_region = operator_m.get_selected_region(mmode)
cb(selected_region)
end, "in", "")
end

--- Selects the current visual selection.
--
-- This plugin is only meant to work with keywords, so this function fails if
-- the selected region is multiline.
--
-- @treturn Region The selected region or an error.
M.select_current_visual_selection = function()
-- @tparam function cb The callback to return the selected region to.
M.select_current_visual_selection = function(cb)
local visual_m = require("coerce.visual")
local selected_region = visual_m.get_current_visual_selection()
local region = require("coerce.region")

local selected_line_count = region.lines(selected_region)
if selected_line_count > 1 then
return (selected_line_count .. " lines selected." .. " Coerce supports only single-line visual selections.")
cb(selected_line_count .. " lines selected." .. " Coerce supports only single-line visual selections.")
else
cb(selected_region)
end
return selected_region
end

return M
8 changes: 4 additions & 4 deletions tests/coerce/selector_spec.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
local selector = require("coerce.selector")
local cco = require("coerce.coroutine")
local region = require("coerce.region")
local test_helpers = require("tests.helpers")

Expand All @@ -9,9 +8,10 @@ describe("coerce.selector", function()
test_helpers.create_buf({ "Hello, world!" })

local selected_region = nil
cco.fire_and_forget(function()
selected_region = selector.select_with_motion()
end)
local cb = function(region_arg)
selected_region = region_arg
end
selector.select_with_motion(cb)
test_helpers.execute_keys("e", "x")

assert.are.same(region.region(region.modes.CHAR, { 0, 0 }, { 0, 4 }), selected_region)
Expand Down
14 changes: 14 additions & 0 deletions tests/coerce_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ describe("coerce", function()
assert.are.same({ "MY_CASE" }, lines)
end)

it("supports dot repeats", function()
local buf = test_helpers.create_buf({ "myCase", "yourCase" })
c.setup({})
-- `gcr` starts the operator pending mode
-- `u` select upper case coercion
-- `e` select the keyword
-- `j` goes down a line
-- `.` repeats the last action
test_helpers.execute_keys("gcruej.", "x")

local lines = vim.api.nvim_buf_get_lines(buf, 0, 2, true)
assert.are.same({ "MY_CASE", "YOUR_CASE" }, lines)
end)

it("uses LSP’s rename method when available", function()
local buf = test_helpers.create_buf({ "myCase", "local myCase" })
-- LSP rename only works on named buffers.
Expand Down

0 comments on commit a811a4f

Please sign in to comment.