summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lua/clever_f/acquisition_service.lua56
-rw-r--r--tests/run.lua53
2 files changed, 99 insertions, 10 deletions
diff --git a/lua/clever_f/acquisition_service.lua b/lua/clever_f/acquisition_service.lua
index a50e614..a19f8f8 100644
--- a/lua/clever_f/acquisition_service.lua
+++ b/lua/clever_f/acquisition_service.lua
@@ -695,10 +695,7 @@ local function direct_preview_settings(policy_service)
fail("AcquisitionService policy must sample direct preview settings", 3)
end
-function AcquisitionService:acquire(descriptor, context, position, count, macro_state)
- local request = self:request(descriptor, context, position, count, macro_state)
- local scope = self:start_temporary_scope(request)
- local record = service_records[self]
+local function acquire_in_scope(record, request, scope)
local acquisition = record.policy:sample_acquisition()
local interactive = not request.macro_state.executing
scope:set_cursor_presentation_lease(
@@ -711,11 +708,11 @@ function AcquisitionService:acquire(descriptor, context, position, count, macro_
request.position,
current_window(record)
))
- if not request.macro_state.executing then
+ if interactive then
record.host:redraw("screen")
end
end
- if acquisition.mark_direct and not request.macro_state.executing then
+ if acquisition.mark_direct and interactive then
local view = scope:set_text_view(text_topology.from_host(record.host))
local positions = record.direct_planner:plan(
view,
@@ -733,7 +730,7 @@ function AcquisitionService:acquire(descriptor, context, position, count, macro_
))
record.host:redraw("screen")
end
- if acquisition.show_prompt and not request.macro_state.executing then
+ if acquisition.show_prompt and interactive then
record.host:show_prompt(M.PROMPT)
scope:mark_prompt_shown()
end
@@ -742,7 +739,6 @@ function AcquisitionService:acquire(descriptor, context, position, count, macro_
scope:mark_input_completed()
if M.is_escape(packet) then
local outcome = scope:set_outcome(domain.ActionOutcome.escape(request.position))
- scope:release()
return AcquisitionResult.new(request, { outcome = outcome })
end
local target = scope:set_acquired_target(M.normalize_input_packet(packet))
@@ -765,7 +761,6 @@ function AcquisitionService:acquire(descriptor, context, position, count, macro_
local outcome = scope:set_outcome(
domain.ActionOutcome.empty(request.position)
)
- scope:release()
return AcquisitionResult.new(request, {
outcome = outcome,
previous_input_trigger = trigger,
@@ -836,7 +831,48 @@ function AcquisitionService:acquire(descriptor, context, position, count, macro_
missing_previous_input = scope.missing_previous_input,
})
scope:mark_acquisition_completed()
- scope:release()
+ return result
+end
+
+local function error_message(failure)
+ local message = tostring(failure)
+ if message == "" then
+ return "clever-f: Acquisition failed"
+ end
+ return message
+end
+
+function AcquisitionService:acquire(descriptor, context, position, count, macro_state)
+ local request = self:request(descriptor, context, position, count, macro_state)
+ local scope = self:start_temporary_scope(request)
+ local record = service_records[self]
+ local ok, result = xpcall(function()
+ return acquire_in_scope(record, request, scope)
+ end, function(failure)
+ return failure
+ end)
+
+ if not ok then
+ local diagnostic = error_message(result)
+ result = AcquisitionResult.new(request, {
+ outcome = domain.ActionOutcome.error(request.position, diagnostic),
+ })
+ end
+
+ local cleanup_ok, cleanup_error = pcall(function()
+ scope:release()
+ end)
+ if not cleanup_ok and ok then
+ local diagnostic = error_message(cleanup_error)
+ result = AcquisitionResult.new(request, {
+ outcome = domain.ActionOutcome.error(request.position, diagnostic),
+ })
+ ok = false
+ end
+
+ if not ok then
+ pcall(record.host.emit_diagnostic, record.host, "error", result.outcome.diagnostic)
+ end
return result
end
diff --git a/tests/run.lua b/tests/run.lua
index 1df3d0e..4139544 100644
--- a/tests/run.lua
+++ b/tests/run.lua
@@ -5563,6 +5563,59 @@ test("Acquisition releases direct cursor and presentation resources", function()
truthy(restore_index > removals[2].index)
end)
+test("Acquisition cleanup runs from a finally block", function()
+ local state = fresh_sequence_state()
+ local prior = {
+ hidden = false,
+ guicursor = "n:block",
+ }
+ local host = MemoryHost.new({
+ buffer_lines = { "abc" },
+ cursor_presentation = prior,
+ configuration = {
+ mark_cursor = true,
+ mark_direct = true,
+ mark_char = false,
+ show_prompt = true,
+ },
+ input_packets = { { kind = "error", message = "input failed" } },
+ })
+ local service = acquisition_service.new(host)
+
+ local result = service:acquire(
+ "f",
+ "n",
+ domain.Position.new(1, 1),
+ nil,
+ nil
+ )
+ same(domain.ActionKind.ERROR, result.outcome.kind)
+ same("input failed", result.outcome.diagnostic)
+ falsy(service:last_temporary_scope().active)
+ same(0, #state:temporary_overlay_identities())
+ same(0, map_size(host:highlights()))
+ same(prior.hidden, host:cursor_presentation().hidden)
+ same(prior.guicursor, host:cursor_presentation().guicursor)
+ same(1, #host:diagnostics())
+ same("input failed", host:diagnostics()[1].text)
+
+ local read_index
+ local remove_index
+ local restore_index
+ for index, operation in ipairs(host:operations()) do
+ if operation.operation == "read_input" then
+ read_index = index
+ elseif operation.operation == "remove_highlight" then
+ remove_index = index
+ elseif operation.operation == "restore_cursor_presentation" then
+ restore_index = index
+ end
+ end
+ truthy(remove_index > read_index)
+ truthy(restore_index > remove_index)
+ same(2, #host:redraws())
+end)
+
for _, item in ipairs(tests) do
local ok, failure = xpcall(item.body, debug.traceback)
if not ok then