summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 14:31:35 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 14:31:35 +0200
commit6912c7a6adad1cb5503327896a25a4521a156485 (patch)
treead44621cd917726cda5bc7bdd907e78423889fc4
parent80f3025ed00f8c35ae0c83f4ed754a45d108ea1b (diff)
Connect operator and dot effects
-rw-r--r--lua/clever_f/host_adapter.lua70
-rw-r--r--tests/run.lua56
2 files changed, 126 insertions, 0 deletions
diff --git a/lua/clever_f/host_adapter.lua b/lua/clever_f/host_adapter.lua
index 7a71fb8..17c29ef 100644
--- a/lua/clever_f/host_adapter.lua
+++ b/lua/clever_f/host_adapter.lua
@@ -47,6 +47,7 @@ function HostAdapter.new(options)
timers = {},
cursor_presentations = {},
events = {},
+ dot_repeat = nil,
event_order = {},
augroup = nil,
}
@@ -84,6 +85,75 @@ function HostAdapter:lowercase(value)
return result
end
+function HostAdapter:read_pending_operator()
+ local operator = self:runtime().v.operator
+ if operator == nil then
+ return ""
+ end
+ return operator
+end
+
+function HostAdapter:apply_cursor(position)
+ position = domain.Position.coerce(position)
+ self:runtime().api.nvim_win_set_cursor(
+ 0,
+ { position.line, position.byte_column - 1 }
+ )
+end
+
+function HostAdapter:apply_selection(position)
+ if domain.Selection.is(position) then
+ if not position.active then
+ fail("selection movement requires an active selection", 2)
+ end
+ position = position.focus
+ end
+ return self:apply_cursor(position)
+end
+
+function HostAdapter:set_operator_inclusive(enabled)
+ if type(enabled) ~= "boolean" then
+ fail("operator inclusivity must be a Boolean", 2)
+ end
+ if enabled then
+ self:runtime().api.nvim_cmd({
+ cmd = "normal",
+ bang = true,
+ args = { "v" },
+ }, {})
+ end
+end
+
+function HostAdapter:register_dot_repeat(payload, callback)
+ if not domain.DotPayload.is(payload) then
+ fail("dot-repeat payload must be a DotPayload", 2)
+ end
+ if callback ~= nil and type(callback) ~= "function" then
+ fail("dot-repeat callback must be a function", 2)
+ end
+ adapter_records[self].dot_repeat = {
+ payload = payload,
+ callback = callback,
+ }
+ return payload
+end
+
+function HostAdapter:dot_repeat_payload()
+ local registration = adapter_records[self].dot_repeat
+ return registration and registration.payload or nil
+end
+
+function HostAdapter:replay_dot(count)
+ local registration = adapter_records[self].dot_repeat
+ if registration == nil or registration.callback == nil then
+ fail("dot repeat is not executable", 2)
+ end
+ return registration.callback(
+ registration.payload,
+ domain.Count.new(count)
+ )
+end
+
local function next_identity(adapter, prefix)
local record = adapter_records[adapter]
local identity = prefix .. "-" .. tostring(record.next_identity)
diff --git a/tests/run.lua b/tests/run.lua
index 871d977..e00f694 100644
--- a/tests/run.lua
+++ b/tests/run.lua
@@ -361,6 +361,62 @@ test("ActionOutcome and DotPayload retain resolved result data", function()
same("problem", domain.ActionOutcome.error(destination, "problem").diagnostic)
end)
+test("Host adapter connects motion endpoints and dot payloads", function()
+ local cursor
+ local commands = {}
+ local runtime = {
+ api = {
+ nvim_win_set_cursor = function(window, position)
+ same(0, window)
+ cursor = position
+ end,
+ nvim_cmd = function(command, options)
+ commands[#commands + 1] = command
+ truthy(vim.tbl_isempty(options))
+ end,
+ },
+ v = { operator = "d" },
+ }
+ local adapter = host_adapter.new({ runtime = runtime })
+
+ same("d", adapter:read_pending_operator())
+ adapter:apply_cursor(domain.Position.new(3, 5), {
+ descriptor = domain.Descriptor.FIND_FORWARD,
+ })
+ list_same({ 3, 4 }, cursor)
+ adapter:apply_selection(domain.Selection.active(
+ domain.SelectionKind.CHARACTER,
+ domain.Position.new(3, 1),
+ domain.Position.new(4, 2),
+ domain.SelectionOption.INCLUSIVE
+ ))
+ list_same({ 4, 1 }, cursor)
+
+ adapter:set_operator_inclusive(false)
+ same(0, #commands)
+ adapter:set_operator_inclusive(true)
+ same(1, #commands)
+ same("normal", commands[1].cmd)
+ truthy(commands[1].bang)
+ list_same({ "v" }, commands[1].args)
+
+ local payload = domain.DotPayload.new(
+ "t",
+ domain.TargetValue.character("x", string.byte("x"))
+ )
+ local replayed_payload
+ local replayed_count
+ same(payload, adapter:register_dot_repeat(payload, function(value, count)
+ replayed_payload = value
+ replayed_count = count
+ return "replayed"
+ end))
+ same(payload, adapter:dot_repeat_payload())
+ same("replayed", adapter:replay_dot(3))
+ same(payload, replayed_payload)
+ same(3, replayed_count.value)
+end)
+
test("Host adapter reads encoding and uses Nvim case conversion", function()
local lowered = {}
local runtime = {