summaryrefslogtreecommitdiff
path: root/lua/clever_f/acquisition_service.lua
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 18:44:32 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 18:44:32 +0200
commitc7b14ffb6d14969c9c41864e827f33f8e80fc24e (patch)
treec7c0ac23dc93a4a8b75449d2be4d0e05c4f05f9e /lua/clever_f/acquisition_service.lua
parent9013636a57144e1f57c9339e7888d588430aae6a (diff)
Rename plugin to clever-tee
Diffstat (limited to 'lua/clever_f/acquisition_service.lua')
-rw-r--r--lua/clever_f/acquisition_service.lua927
1 files changed, 0 insertions, 927 deletions
diff --git a/lua/clever_f/acquisition_service.lua b/lua/clever_f/acquisition_service.lua
deleted file mode 100644
index e0a6291..0000000
--- a/lua/clever_f/acquisition_service.lua
+++ /dev/null
@@ -1,927 +0,0 @@
-local domain = require("clever_f.domain")
-local direct_preview_planner = require("clever_f.direct_preview_planner")
-local feedback_service = require("clever_f.feedback_service")
-local motion_plan_factory = require("clever_f.motion_plan")
-local policy = require("clever_f.policy")
-local sequence_state = require("clever_f.sequence_state")
-local state_transitions = require("clever_f.state_transitions")
-local target_plan_factory = require("clever_f.target_plan")
-local text_topology = require("clever_f.text_topology")
-
-local M = {}
-local AcquisitionRequest = {}
-local AcquisitionResult = {}
-local AcquisitionService = {}
-local TemporaryResourceScope = {}
-AcquisitionService.__index = AcquisitionService
-M.AcquisitionRequest = AcquisitionRequest
-M.AcquisitionResult = AcquisitionResult
-M.AcquisitionService = AcquisitionService
-M.TemporaryResourceScope = TemporaryResourceScope
-M.RepeatedDirection = {
- SAME = "same",
-}
-M.PROMPT = "clever-f: "
-M.PREVIOUS_INPUT_NOT_FOUND = "Previous input not found."
-
-local request_records = setmetatable({}, { __mode = "k" })
-local result_records = setmetatable({}, { __mode = "k" })
-local service_records = setmetatable({}, { __mode = "k" })
-local scope_records = setmetatable({}, { __mode = "k" })
-
-local function fail(message, level)
- error(message, (level or 1) + 1)
-end
-
-local function normalize_macro_state(value)
- if type(value) == "table" and not domain.MacroState.is(value) then
- value = value.register
- end
- return domain.MacroState.new(value)
-end
-
-local request_metatable = {
- __index = function(request, key)
- local method = AcquisitionRequest[key]
- if method ~= nil then
- return method
- end
- return request_records[request][key]
- end,
- __newindex = function()
- fail("AcquisitionRequest values are immutable", 2)
- end,
- __tostring = function(request)
- return "acquisition-request:" .. request_records[request].descriptor.value
- end,
- __metatable = "clever_f.acquisition_service.AcquisitionRequest",
-}
-
-function AcquisitionRequest.new(descriptor, context, position, count, macro_state)
- if AcquisitionRequest.is(descriptor) then
- return descriptor
- end
- if type(descriptor) == "table" and not domain.Descriptor.is(descriptor) then
- local options = descriptor
- descriptor = options.descriptor
- context = options.context
- position = options.position or options.origin
- count = options.count
- macro_state = options.macro_state
- end
-
- local request = setmetatable({}, request_metatable)
- request_records[request] = {
- descriptor = domain.Descriptor.from_string(descriptor),
- context = domain.ModeContext.from_full_mode(context),
- position = domain.Position.coerce(position),
- count = domain.Count.new(count),
- macro_state = normalize_macro_state(macro_state),
- repeated_direction = M.RepeatedDirection.SAME,
- }
- return request
-end
-
-function AcquisitionRequest.is(value)
- return type(value) == "table" and request_records[value] ~= nil
-end
-
-function AcquisitionRequest:to_table()
- return {
- descriptor = self.descriptor.value,
- context = self.context.key,
- position = self.position:to_table(),
- count = self.count.value,
- macro_register = self.macro_state.register,
- repeated_direction = self.repeated_direction,
- }
-end
-
-local result_metatable = {
- __index = function(result, key)
- local method = AcquisitionResult[key]
- if method ~= nil then
- return method
- end
- return result_records[result][key]
- end,
- __newindex = function()
- fail("AcquisitionResult values are immutable", 2)
- end,
- __tostring = function(result)
- local outcome = result_records[result].outcome
- return outcome and tostring(outcome) or "acquisition:resolved"
- end,
- __metatable = "clever_f.acquisition_service.AcquisitionResult",
-}
-
-function AcquisitionResult.new(request, options)
- if AcquisitionResult.is(request) and options == nil then
- return request
- end
- if not AcquisitionRequest.is(request) then
- fail("acquisition result requires an AcquisitionRequest", 2)
- end
- options = options or {}
- if type(options) ~= "table" then
- fail("acquisition result options must be a table", 2)
- end
- local outcome = options.outcome
- if outcome ~= nil and not domain.ActionOutcome.is(outcome) then
- fail("acquisition result outcome must be an ActionOutcome", 2)
- end
- local target = options.target
- if target ~= nil and not domain.TargetValue.is(target) then
- fail("acquisition result target must be a TargetValue", 2)
- end
- local target_plan = options.target_plan
- if target_plan ~= nil and not domain.TargetPlan.is(target_plan) then
- fail("acquisition result target_plan must be a TargetPlan", 2)
- end
- local motion_plan = options.motion_plan
- if motion_plan ~= nil and not domain.ResolvedMotionPlan.is(motion_plan) then
- fail("acquisition result motion_plan must be a ResolvedMotionPlan", 2)
- end
- local result = setmetatable({}, result_metatable)
- result_records[result] = {
- request = request,
- outcome = outcome,
- target = target,
- target_plan = target_plan,
- motion_plan = motion_plan,
- resolved_motion_plan = motion_plan,
- previous_input_trigger = options.previous_input_trigger,
- previous_target_source = options.previous_target_source,
- cached_target = options.cached_target,
- missing_previous_input = options.missing_previous_input == true,
- acquisition_time_ms = options.acquisition_time_ms,
- persistent_feedback_request = options.persistent_feedback_request,
- resolved = target ~= nil and target_plan ~= nil and motion_plan ~= nil,
- completed = outcome ~= nil
- or (target ~= nil and target_plan ~= nil and motion_plan ~= nil),
- }
- return result
-end
-
-function AcquisitionResult.is(value)
- return type(value) == "table" and result_records[value] ~= nil
-end
-
-function AcquisitionResult:has_outcome()
- return self.outcome ~= nil
-end
-
-function AcquisitionResult:resolved_values()
- return self.target, self.target_plan, self.motion_plan
-end
-
-AcquisitionResult.unpack = AcquisitionResult.resolved_values
-
-local scope_metatable = {
- __index = function(scope, key)
- local method = TemporaryResourceScope[key]
- if method ~= nil then
- return method
- end
- return scope_records[scope][key]
- end,
- __newindex = function()
- fail("TemporaryResourceScope values are read-only", 2)
- end,
- __metatable = "clever_f.acquisition_service.TemporaryResourceScope",
-}
-
-function TemporaryResourceScope.new(request, feedback, host)
- 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,
- host = host,
- active = true,
- interactive = not request.macro_state.executing,
- prompt_shown = false,
- input_completed = false,
- acquisition_completed = false,
- cursor_marker = nil,
- direct_marker = nil,
- cursor_presentation_lease = nil,
- input_packet = nil,
- acquired_target = nil,
- resolved_target = nil,
- previous_input_trigger = nil,
- previous_target_source = nil,
- cached_target = nil,
- missing_previous_input = false,
- text_view = nil,
- target_plan = nil,
- motion_plan = nil,
- outcome = nil,
- }
- return scope
-end
-
-function TemporaryResourceScope.is(value)
- return type(value) == "table" and scope_records[value] ~= nil
-end
-
-local function set_scope_resource(scope, field, resource)
- local record = scope_records[scope]
- if record == nil or not record.active then
- fail("temporary resource scope must be active", 3)
- end
- record[field] = resource
- return resource
-end
-
-function TemporaryResourceScope:set_cursor_marker(marker)
- return set_scope_resource(self, "cursor_marker", marker)
-end
-
-function TemporaryResourceScope:set_direct_marker(marker)
- return set_scope_resource(self, "direct_marker", marker)
-end
-
-function TemporaryResourceScope:set_cursor_presentation_lease(lease)
- return set_scope_resource(self, "cursor_presentation_lease", lease)
-end
-
-function TemporaryResourceScope:set_input_packet(packet)
- return set_scope_resource(self, "input_packet", packet)
-end
-
-function TemporaryResourceScope:request_redraw(kind)
- local record = scope_records[self]
- if record == nil then
- fail("temporary resource scope is invalid", 2)
- end
- if not record.interactive then
- return false
- end
- record.host:redraw(kind)
- return true
-end
-
-function TemporaryResourceScope:mark_prompt_shown()
- return set_scope_resource(self, "prompt_shown", true)
-end
-
-function TemporaryResourceScope:mark_input_completed()
- return set_scope_resource(self, "input_completed", true)
-end
-
-function TemporaryResourceScope:mark_acquisition_completed()
- return set_scope_resource(self, "acquisition_completed", true)
-end
-
-function TemporaryResourceScope:set_acquired_target(target)
- return set_scope_resource(self, "acquired_target", target)
-end
-
-function TemporaryResourceScope:set_resolved_target(target)
- return set_scope_resource(self, "resolved_target", target)
-end
-
-function TemporaryResourceScope:set_previous_input_trigger(trigger)
- return set_scope_resource(self, "previous_input_trigger", trigger)
-end
-
-function TemporaryResourceScope:set_cached_target(context, target)
- set_scope_resource(self, "previous_target_source", context)
- return set_scope_resource(self, "cached_target", target)
-end
-
-function TemporaryResourceScope:set_missing_previous_input(missing)
- if type(missing) ~= "boolean" then
- fail("missing previous-input state must be a Boolean", 2)
- end
- return set_scope_resource(self, "missing_previous_input", missing)
-end
-
-function TemporaryResourceScope:set_text_view(view)
- if not text_topology.TextView.is(view) then
- fail("temporary resource scope text must be a TextView", 2)
- end
- return set_scope_resource(self, "text_view", view)
-end
-
-function TemporaryResourceScope:set_target_plan(target_plan)
- if not domain.TargetPlan.is(target_plan) then
- fail("temporary resource scope target plan must be a TargetPlan", 2)
- end
- return set_scope_resource(self, "target_plan", target_plan)
-end
-
-function TemporaryResourceScope:set_motion_plan(motion_plan)
- if not domain.ResolvedMotionPlan.is(motion_plan) then
- fail("temporary resource scope motion plan must be a ResolvedMotionPlan", 2)
- end
- return set_scope_resource(self, "motion_plan", motion_plan)
-end
-
-function TemporaryResourceScope:set_outcome(outcome)
- if not domain.ActionOutcome.is(outcome) then
- fail("temporary resource scope outcome must be an ActionOutcome", 2)
- end
- return set_scope_resource(self, "outcome", outcome)
-end
-
-function TemporaryResourceScope:release()
- local record = scope_records[self]
- if record == nil then
- fail("temporary resource scope is invalid", 2)
- end
- if not record.active then
- return false
- end
- record.active = false
-
- local first_error
- local function release_operation(operation)
- local ok, failure = pcall(operation)
- if not ok and first_error == nil then
- first_error = failure
- end
- end
-
- if record.interactive
- and record.prompt_shown
- and record.input_completed
- and record.acquisition_completed
- then
- release_operation(function()
- self:request_redraw("full")
- end)
- end
- if record.direct_marker ~= nil then
- release_operation(function()
- record.feedback:remove_temporary_overlay(record.direct_marker)
- end)
- end
- if record.cursor_marker ~= nil then
- release_operation(function()
- record.feedback:remove_temporary_overlay(record.cursor_marker)
- end)
- end
- if record.cursor_presentation_lease ~= nil then
- release_operation(function()
- record.cursor_presentation_lease:release()
- end)
- end
- if first_error ~= nil then
- error(first_error, 0)
- end
- return true
-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"
- or type(transitions.CommitAcquiredTarget) ~= "function"
- then
- fail("AcquisitionService transitions must commit acquisition state", 3)
- end
- return transitions
-end
-
-local function require_direct_planner(planner)
- planner = planner or direct_preview_planner.new()
- if type(planner) ~= "table" or type(planner.plan) ~= "function" then
- fail("AcquisitionService direct planner must provide plan", 3)
- end
- return planner
-end
-
-local function require_target_factory(factory, policy_service)
- factory = factory or target_plan_factory.new({ policy = policy_service })
- if type(factory) ~= "table" or type(factory.build) ~= "function" then
- fail("AcquisitionService target factory must provide build", 3)
- end
- return factory
-end
-
-local function require_motion_factory(factory, policy_service)
- factory = factory or motion_plan_factory.new({ policy = policy_service })
- if type(factory) ~= "table" or type(factory.build_for_context) ~= "function" then
- fail("AcquisitionService motion factory must build contextual plans", 3)
- end
- return factory
-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"
- or type(feedback.request_persistent) ~= "function"
- then
- fail("AcquisitionService feedback must manage acquisition markers", 3)
- end
- return feedback
-end
-
-local function normalize_options(options, dependencies)
- if AcquisitionService.is(options) and dependencies == nil then
- return options
- end
- if type(options) ~= "table" then
- fail("AcquisitionService options must be a table", 3)
- end
- if options.host ~= nil then
- if dependencies ~= nil then
- fail("AcquisitionService dependencies must be part of its options", 3)
- end
- return options
- end
- local result = {}
- for key, value in pairs(dependencies or {}) do
- result[key] = value
- end
- result.host = options
- return result
-end
-
-function AcquisitionService.new(options, dependencies)
- options = normalize_options(options, dependencies)
- if AcquisitionService.is(options) then
- return options
- end
- 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,
- state = state,
- transitions = transitions,
- feedback = require_feedback(
- options.feedback or options.feedback_service,
- options.host,
- policy_service,
- transitions
- ),
- direct_planner = require_direct_planner(
- options.direct_planner or options.direct_preview_planner
- ),
- target_factory = require_target_factory(
- options.target_factory or options.target_plan_factory,
- policy_service
- ),
- motion_factory = require_motion_factory(
- options.motion_factory or options.motion_plan_factory,
- policy_service
- ),
- window = options.window or options.current_window,
- last_scope = nil,
- started_scope_count = 0,
- }
- return service
-end
-
-function AcquisitionService.is(value)
- return type(value) == "table" and service_records[value] ~= nil
-end
-
-function AcquisitionService:request(descriptor, context, position, count, macro_state)
- 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, record.feedback, record.host)
- record.last_scope = scope
- record.started_scope_count = record.started_scope_count + 1
- return scope
-end
-
-function AcquisitionService:last_temporary_scope()
- return service_records[self].last_scope
-end
-
-function AcquisitionService:started_scope_count()
- return service_records[self].started_scope_count
-end
-
-local function utf8_first_code(character)
- local first = string.byte(character, 1)
- if first < 0x80 then
- return first
- end
- local length
- local code
- if first >= 0xc2 and first <= 0xdf then
- length = 2
- code = first - 0xc0
- elseif first >= 0xe0 and first <= 0xef then
- length = 3
- code = first - 0xe0
- elseif first >= 0xf0 and first <= 0xf4 then
- length = 4
- code = first - 0xf0
- else
- fail("ordinary input must start with a valid editor character", 3)
- end
- for index = 2, length do
- local byte = string.byte(character, index)
- if byte == nil or byte < 0x80 or byte > 0xbf then
- fail("ordinary input must contain a complete editor character", 3)
- end
- code = code * 0x40 + byte - 0x80
- end
- return code
-end
-
-local function first_editor_character(text)
- local characters = text_topology.split_editor_characters(text)
- if #characters == 0 then
- fail("ordinary input must contain an editor character", 3)
- end
- return characters[1]
-end
-
-function M.editor_character_code(character)
- character = first_editor_character(character)
- local runtime = rawget(_G, "vim")
- if type(runtime) == "table"
- and type(runtime.fn) == "table"
- and type(runtime.fn.char2nr) == "function"
- then
- return runtime.fn.char2nr(character)
- end
- return utf8_first_code(character)
-end
-
-function M.normalize_ordinary_input(packet)
- packet = domain.InputPacket.from_table(packet)
- local text
- if packet.kind == domain.InputPacketKind.TEXT then
- text = packet.text
- elseif packet.kind == domain.InputPacketKind.RAW_BYTES then
- local bytes = packet:bytes()
- local characters = {}
- for index = 1, #bytes do
- characters[index] = string.char(bytes[index])
- end
- text = table.concat(characters)
- else
- fail("ordinary input packet must contain text or raw bytes", 2)
- end
- local character = first_editor_character(text)
- return domain.TargetValue.character(
- character,
- M.editor_character_code(character)
- )
-end
-
-local function encoded_packet_value(packet)
- if packet.encoded ~= nil then
- return packet.encoded
- end
- local bytes = packet:bytes()
- if bytes == nil then
- return nil
- end
- local characters = {}
- for index = 1, #bytes do
- characters[index] = string.char(bytes[index])
- end
- return table.concat(characters)
-end
-
-function M.normalize_input_packet(packet)
- packet = domain.InputPacket.from_table(packet)
- if packet.kind == domain.InputPacketKind.ERROR then
- fail(packet.message, 2)
- end
- if packet.kind == domain.InputPacketKind.TEXT then
- return M.normalize_ordinary_input(packet)
- end
-
- local encoded = encoded_packet_value(packet)
- if encoded == nil then
- return domain.TargetValue.code_fallback(0)
- end
- if string.byte(encoded, 1) == 0x80 then
- return domain.TargetValue.special_key(encoded, 0x80)
- end
- return M.normalize_ordinary_input(domain.InputPacket.text(encoded))
-end
-
-function M.read_previous_target(state)
- if not sequence_state.is(state) then
- fail("previous-input reuse requires SequenceState", 2)
- end
- local context = state.last_input_context
- if context == nil then
- return nil, nil
- end
- return state:get_previous_target(context), context
-end
-
-function M.match_previous_input_trigger(first_code, triggers)
- if type(first_code) ~= "number" or first_code < 0 then
- fail("acquired first code must be nonnegative", 2)
- end
- if type(triggers) ~= "table" then
- fail("previous-input triggers must be a list", 2)
- end
- for index, trigger in ipairs(triggers) do
- if type(trigger) ~= "string" then
- fail("previous-input triggers must contain strings", 2)
- end
- if trigger ~= "" and M.editor_character_code(trigger) == first_code then
- return trigger, index
- end
- end
- return nil
-end
-
-function M.is_escape(packet)
- packet = domain.InputPacket.from_table(packet)
- if packet.kind == domain.InputPacketKind.SPECIAL_KEY
- and (packet.name == "Escape" or packet.name == "Esc")
- then
- return true
- end
- if packet.kind == domain.InputPacketKind.TEXT then
- return packet.text == string.char(27)
- end
- local bytes = packet:bytes()
- return bytes ~= nil and #bytes == 1 and bytes[1] == 27
-end
-
-function M.is_terminal_artifact(packet)
- packet = domain.InputPacket.from_table(packet)
- if packet.kind ~= domain.InputPacketKind.RAW_BYTES then
- return false
- end
- local bytes = packet:bytes()
- return #bytes == 3
- and bytes[1] == 0x80
- and bytes[2] == 0xfd
- and bytes[3] == 0x60
-end
-
-local function read_input_packet(host)
- while true do
- local packet = domain.InputPacket.from_table(host:read_input())
- if not M.is_terminal_artifact(packet) then
- return packet
- end
- end
-end
-
-local function direct_preview_settings(policy_service)
- if type(policy_service.sample_direct_preview) == "function" then
- return policy_service:sample_direct_preview()
- end
- if type(policy_service.get_boolean) == "function" then
- return {
- ignore_case = policy_service:get_boolean("ignore_case"),
- smart_case = policy_service:get_boolean("smart_case"),
- }
- end
- fail("AcquisitionService policy must sample direct preview settings", 3)
-end
-
-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(
- record.feedback:create_cursor_presentation_lease(
- interactive and acquisition.hide_cursor_on_cmdline
- )
- )
- if not interactive then
- record.host:redraw("suppressed")
- end
- if acquisition.mark_cursor and interactive then
- scope:set_cursor_marker(record.feedback:create_cursor_marker(
- request.position,
- current_window(record)
- ))
- scope:request_redraw("screen")
- end
- 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,
- request.position,
- request.descriptor,
- request.count,
- direct_preview_settings(record.policy)
- )
- local window = scope.cursor_marker
- and scope.cursor_marker.window
- or current_window(record)
- scope:set_direct_marker(record.feedback:create_direct_markers(
- positions,
- window
- ))
- scope:request_redraw("screen")
- end
- if acquisition.show_prompt and interactive then
- record.host:show_prompt(M.PROMPT)
- scope:mark_prompt_shown()
- end
- record.transitions:BeginAcquisition(request.context, request.descriptor)
- local packet = scope:set_input_packet(read_input_packet(record.host))
- scope:mark_input_completed()
- if M.is_escape(packet) then
- local outcome = scope:set_outcome(domain.ActionOutcome.escape(request.position))
- return AcquisitionResult.new(request, { outcome = outcome })
- end
- local target = scope:set_acquired_target(M.normalize_input_packet(packet))
- local previous_input = record.policy:sample_previous_input()
- local trigger = scope:set_previous_input_trigger(
- M.match_previous_input_trigger(
- target.first_code,
- previous_input.repeat_last_char_inputs
- )
- )
- if trigger ~= nil then
- local cached_target, source = M.read_previous_target(record.state)
- scope:set_cached_target(source, cached_target)
- if cached_target ~= nil then
- target = cached_target
- else
- target = nil
- scope:set_missing_previous_input(true)
- record.host:emit_diagnostic("error", M.PREVIOUS_INPUT_NOT_FOUND)
- local outcome = scope:set_outcome(
- domain.ActionOutcome.empty(request.position)
- )
- return AcquisitionResult.new(request, {
- outcome = outcome,
- previous_input_trigger = trigger,
- previous_target_source = scope.previous_target_source,
- missing_previous_input = true,
- })
- end
- end
- scope:set_resolved_target(target)
- local acquisition_time_ms
- if record.policy:sample_timeouts().repeat_timeout_ms > 0 then
- acquisition_time_ms = record.host:read_time_ms()
- end
- record.transitions:CommitAcquiredTarget(
- request.context,
- target,
- acquisition_time_ms
- )
- local view = scope.text_view
- or scope:set_text_view(text_topology.from_host(record.host))
- local search_scope = record.policy:sample_search().search_scope
- local target_plan = scope:set_target_plan(record.target_factory:build(
- target,
- nil,
- {
- text_view = view,
- origin = request.position,
- search_scope = search_scope,
- effective_encoding = view.effective_encoding,
- }
- ))
- local selection = request.context.visual and record.host:read_selection() or nil
- local motion_plan = scope:set_motion_plan(
- record.motion_factory:build_for_context(
- target_plan,
- request.descriptor,
- request.context,
- selection,
- search_scope
- )
- )
- local persistent_feedback_request
- if interactive
- and record.policy:sample_markers().mark_char
- and feedback_service.persistent_context_eligible(request.context)
- then
- local window = scope.cursor_marker
- and scope.cursor_marker.window
- or scope.direct_marker
- and scope.direct_marker.window
- or current_window(record)
- persistent_feedback_request = record.feedback:request_persistent({
- context = request.context,
- anchor = request.position,
- target_plan = target_plan,
- motion_plan = motion_plan,
- window = window,
- })
- end
- local result = AcquisitionResult.new(request, {
- target = target,
- target_plan = target_plan,
- motion_plan = motion_plan,
- acquisition_time_ms = acquisition_time_ms,
- persistent_feedback_request = persistent_feedback_request,
- previous_input_trigger = trigger,
- previous_target_source = scope.previous_target_source,
- cached_target = scope.cached_target,
- missing_previous_input = scope.missing_previous_input,
- })
- scope:mark_acquisition_completed()
- 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
-
-function M.new(options, dependencies)
- return AcquisitionService.new(options, dependencies)
-end
-
-setmetatable(M, {
- __call = function(_, options, dependencies)
- return AcquisitionService.new(options, dependencies)
- end,
-})
-
-return M