diff options
| -rw-r--r-- | lua/clever_f/acquisition_service.lua | 101 | ||||
| -rw-r--r-- | tests/run.lua | 37 |
2 files changed, 122 insertions, 16 deletions
diff --git a/lua/clever_f/acquisition_service.lua b/lua/clever_f/acquisition_service.lua index d341414..45463e6 100644 --- a/lua/clever_f/acquisition_service.lua +++ b/lua/clever_f/acquisition_service.lua @@ -139,6 +139,7 @@ function AcquisitionResult.new(request, options) target_plan = options.target_plan, motion_plan = options.motion_plan, resolved_motion_plan = options.motion_plan, + previous_input_trigger = options.previous_input_trigger, completed = outcome ~= nil or target ~= nil, } return result @@ -180,6 +181,7 @@ function TemporaryResourceScope.new(request, feedback) cursor_presentation_lease = nil, input_packet = nil, acquired_target = nil, + previous_input_trigger = nil, outcome = nil, } return scope @@ -218,6 +220,10 @@ function TemporaryResourceScope:set_acquired_target(target) return set_scope_resource(self, "acquired_target", target) end +function TemporaryResourceScope:set_previous_input_trigger(trigger) + return set_scope_resource(self, "previous_input_trigger", trigger) +end + function TemporaryResourceScope:set_outcome(outcome) if not domain.ActionOutcome.is(outcome) then fail("temporary resource scope outcome must be an ActionOutcome", 2) @@ -412,6 +418,18 @@ local function first_editor_character(text) 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 @@ -428,17 +446,62 @@ function M.normalize_ordinary_input(packet) fail("ordinary input packet must contain text or raw bytes", 2) end local character = first_editor_character(text) - local runtime = rawget(_G, "vim") - local first_code - if type(runtime) == "table" - and type(runtime.fn) == "table" - and type(runtime.fn.char2nr) == "function" - then - first_code = runtime.fn.char2nr(character) - else - first_code = utf8_first_code(character) + 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 domain.TargetValue.character(character, first_code) + 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.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) @@ -537,12 +600,18 @@ function AcquisitionService:acquire(descriptor, context, position, count, macro_ scope:release() return AcquisitionResult.new(request, { outcome = outcome }) end - if packet.kind == domain.InputPacketKind.TEXT - or packet.kind == domain.InputPacketKind.RAW_BYTES - then - scope:set_acquired_target(M.normalize_ordinary_input(packet)) - end - return AcquisitionResult.new(request, { target = scope.acquired_target }) + 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 + ) + ) + return AcquisitionResult.new(request, { + target = target, + previous_input_trigger = trigger, + }) end function M.new(options, dependencies) diff --git a/tests/run.lua b/tests/run.lua index 9ce65d2..7b50190 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -5211,6 +5211,43 @@ test("Acquisition normalizes ordinary input to editor characters", function() same(1, control.first_code) end) +test("Acquisition compares first codes with previous-input triggers", function() + local _, transitions = fresh_sequence_state() + transitions:BeginAcquisition("v", "f") + transitions:CommitAcquiredTarget( + "v", + domain.TargetValue.character("h", 104) + ) + local host = MemoryHost.new({ + configuration = { + mark_cursor = false, + repeat_last_char_inputs = { "xy", "\r", "\227\129\130tail" }, + }, + input_packets = { { kind = "text", text = "x" } }, + }) + local service = acquisition_service.new({ + host = host, + transitions = transitions, + }) + + local result = service:acquire( + "f", + "n", + domain.Position.new(1, 1), + nil, + nil + ) + same("xy", result.previous_input_trigger) + same("xy", service:last_temporary_scope().previous_input_trigger) + local trigger, index = acquisition_service.match_previous_input_trigger( + 0x3042, + { "x", "\227\129\130more" } + ) + same("\227\129\130more", trigger) + same(2, index) + same(nil, acquisition_service.match_previous_input_trigger(122, { "xy" })) +end) + for _, item in ipairs(tests) do local ok, failure = xpcall(item.body, debug.traceback) if not ok then |
