summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 11:57:51 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 11:57:51 +0200
commit385be6c13c70bc458b13f000cbc59f07fb963182 (patch)
tree952a1948d203d00aeb3c82b603c9c4fee5cb019a
parentdec674e613fefd0b4cf764f121322dcaa1976031 (diff)
Create enabled acquisition cursor marker
-rw-r--r--lua/clever_f/acquisition_service.lua96
-rw-r--r--lua/clever_f/testing/memory_host.lua13
-rw-r--r--tests/run.lua27
3 files changed, 133 insertions, 3 deletions
diff --git a/lua/clever_f/acquisition_service.lua b/lua/clever_f/acquisition_service.lua
index 5f15fa1..a68e458 100644
--- a/lua/clever_f/acquisition_service.lua
+++ b/lua/clever_f/acquisition_service.lua
@@ -1,4 +1,8 @@
local domain = require("clever_f.domain")
+local feedback_service = require("clever_f.feedback_service")
+local policy = require("clever_f.policy")
+local sequence_state = require("clever_f.sequence_state")
+local state_transitions = require("clever_f.state_transitions")
local M = {}
local AcquisitionRequest = {}
@@ -98,14 +102,16 @@ local scope_metatable = {
__metatable = "clever_f.acquisition_service.TemporaryResourceScope",
}
-function TemporaryResourceScope.new(request)
+function TemporaryResourceScope.new(request, feedback)
if not AcquisitionRequest.is(request) then
fail("temporary resource scope requires an AcquisitionRequest", 2)
end
local scope = setmetatable({}, scope_metatable)
scope_records[scope] = {
request = request,
+ feedback = feedback,
active = true,
+ cursor_marker = nil,
}
return scope
end
@@ -114,6 +120,15 @@ function TemporaryResourceScope.is(value)
return type(value) == "table" and scope_records[value] ~= nil
end
+function TemporaryResourceScope:set_cursor_marker(marker)
+ local record = scope_records[self]
+ if record == nil or not record.active then
+ fail("temporary resource scope must be active", 2)
+ end
+ record.cursor_marker = marker
+ return marker
+end
+
function TemporaryResourceScope:release()
local record = scope_records[self]
if record == nil then
@@ -122,6 +137,42 @@ function TemporaryResourceScope:release()
record.active = false
end
+local function require_policy(service, host)
+ service = service or policy.new(host)
+ if type(service) ~= "table" or type(service.sample_acquisition) ~= "function" then
+ fail("AcquisitionService policy must sample acquisition settings", 3)
+ end
+ return service
+end
+
+local function require_state(state)
+ state = state or sequence_state.get()
+ if not sequence_state.is(state) then
+ fail("AcquisitionService state must be the plugin-global SequenceState", 3)
+ end
+ return state
+end
+
+local function require_transitions(transitions, state)
+ transitions = transitions or state_transitions.new(state)
+ if type(transitions) ~= "table" or type(transitions.BeginAcquisition) ~= "function" then
+ fail("AcquisitionService transitions must begin acquisition", 3)
+ end
+ return transitions
+end
+
+local function require_feedback(feedback, host, policy_service, transitions)
+ feedback = feedback or feedback_service.new({
+ host = host,
+ policy = policy_service,
+ transitions = transitions,
+ })
+ if type(feedback) ~= "table" or type(feedback.create_cursor_marker) ~= "function" then
+ fail("AcquisitionService feedback must create cursor markers", 3)
+ end
+ return feedback
+end
+
local function normalize_options(options, dependencies)
if AcquisitionService.is(options) and dependencies == nil then
return options
@@ -151,9 +202,27 @@ function AcquisitionService.new(options, dependencies)
if type(options.host) ~= "table" then
fail("AcquisitionService host must be a table", 2)
end
+ local state = require_state(options.state)
+ local transitions = require_transitions(
+ options.transitions or options.state_transitions,
+ state
+ )
+ local policy_service = require_policy(
+ options.policy or options.policy_service,
+ options.host
+ )
local service = setmetatable({}, AcquisitionService)
service_records[service] = {
host = options.host,
+ policy = policy_service,
+ transitions = transitions,
+ feedback = require_feedback(
+ options.feedback or options.feedback_service,
+ options.host,
+ policy_service,
+ transitions
+ ),
+ window = options.window or options.current_window,
last_scope = nil,
started_scope_count = 0,
}
@@ -168,10 +237,24 @@ function AcquisitionService:request(descriptor, context, position, count, macro_
return AcquisitionRequest.new(descriptor, context, position, count, macro_state)
end
+local function current_window(record)
+ local window = record.window
+ if type(window) == "function" then
+ window = window()
+ end
+ if window == nil and type(record.host.read_window) == "function" then
+ window = record.host:read_window()
+ end
+ if window == nil then
+ fail("AcquisitionService requires a current window identity", 3)
+ end
+ return window
+end
+
function AcquisitionService:start_temporary_scope(request)
request = AcquisitionRequest.new(request)
local record = service_records[self]
- local scope = TemporaryResourceScope.new(request)
+ local scope = TemporaryResourceScope.new(request, record.feedback)
record.last_scope = scope
record.started_scope_count = record.started_scope_count + 1
return scope
@@ -187,7 +270,14 @@ end
function AcquisitionService:acquire(descriptor, context, position, count, macro_state)
local request = self:request(descriptor, context, position, count, macro_state)
- self:start_temporary_scope(request)
+ local scope = self:start_temporary_scope(request)
+ local record = service_records[self]
+ if record.policy:sample_acquisition().mark_cursor then
+ scope:set_cursor_marker(record.feedback:create_cursor_marker(
+ request.position,
+ current_window(record)
+ ))
+ end
return request
end
diff --git a/lua/clever_f/testing/memory_host.lua b/lua/clever_f/testing/memory_host.lua
index 929b357..b815c2f 100644
--- a/lua/clever_f/testing/memory_host.lua
+++ b/lua/clever_f/testing/memory_host.lua
@@ -139,6 +139,7 @@ function MemoryHost.new(options)
local self = setmetatable({
_text = text_snapshot(options.text or options.buffer_lines or { "" }),
_cursor = domain.Position.coerce(options.cursor or { line = 1, byte_column = 1 }),
+ _window = options.window or "window-1",
_mode = raw_mode,
_selection = selection_value(options.selection),
_count = domain.Count.new(options.count),
@@ -228,6 +229,11 @@ function MemoryHost:read_cursor()
return self._cursor
end
+function MemoryHost:read_window()
+ self:_record("read_window", { window = self._window })
+ return self._window
+end
+
function MemoryHost:read_mode()
self:_record("read_mode", { mode = self._mode })
return self._mode
@@ -316,6 +322,13 @@ function MemoryHost:set_cursor(position)
self._cursor = domain.Position.coerce(position)
end
+function MemoryHost:set_window(window)
+ if window == nil then
+ error("window identity must be present", 2)
+ end
+ self._window = window
+end
+
function MemoryHost:set_mode(full_mode)
if domain.ModeContext.is(full_mode) then
full_mode = full_mode.full_mode
diff --git a/tests/run.lua b/tests/run.lua
index 951210a..87f54b3 100644
--- a/tests/run.lua
+++ b/tests/run.lua
@@ -4897,6 +4897,7 @@ test("Acquisition accepts complete initiating action inputs", function()
end)
test("Acquisition starts one temporary resource scope", function()
+ fresh_sequence_state()
local service = acquisition_service.new(MemoryHost.new())
local request = service:acquire(
"f",
@@ -4914,6 +4915,32 @@ test("Acquisition starts one temporary resource scope", function()
same(1, service:started_scope_count())
end)
+test("Acquisition creates cursor markers only when enabled", function()
+ local state, transitions = fresh_sequence_state()
+ local host = MemoryHost.new({
+ cursor = { line = 1, byte_column = 4 },
+ configuration = { mark_cursor = true },
+ })
+ local service = acquisition_service.new({
+ host = host,
+ transitions = transitions,
+ })
+
+ service:acquire("F", "n", host:read_cursor(), nil, nil)
+ local marker = service:last_temporary_scope().cursor_marker
+ truthy(marker ~= nil)
+ same(domain.Position.new(1, 4), marker.position)
+ same("window-1", marker.window)
+ same(marker.identity, state:temporary_overlay_identities()[1])
+
+ fresh_sequence_state()
+ local disabled = acquisition_service.new(MemoryHost.new({
+ configuration = { mark_cursor = false },
+ }))
+ disabled:acquire("f", "n", domain.Position.new(1, 1), nil, nil)
+ same(nil, disabled:last_temporary_scope().cursor_marker)
+end)
+
for _, item in ipairs(tests) do
local ok, failure = xpcall(item.body, debug.traceback)
if not ok then