diff options
| author | Jackson Moore <jacksonmoore@tuta.io> | 2026-09-04 18:44:32 +0200 |
|---|---|---|
| committer | Jackson Moore <jacksonmoore@tuta.io> | 2026-09-04 18:44:32 +0200 |
| commit | c7b14ffb6d14969c9c41864e827f33f8e80fc24e (patch) | |
| tree | c7c0ac23dc93a4a8b75449d2be4d0e05c4f05f9e /lua/clever_f/feedback_service.lua | |
| parent | 9013636a57144e1f57c9339e7888d588430aae6a (diff) | |
Rename plugin to clever-tee
Diffstat (limited to 'lua/clever_f/feedback_service.lua')
| -rw-r--r-- | lua/clever_f/feedback_service.lua | 1003 |
1 files changed, 0 insertions, 1003 deletions
diff --git a/lua/clever_f/feedback_service.lua b/lua/clever_f/feedback_service.lua deleted file mode 100644 index ca26746..0000000 --- a/lua/clever_f/feedback_service.lua +++ /dev/null @@ -1,1003 +0,0 @@ -local domain = require("clever_f.domain") -local policy = require("clever_f.policy") -local sequence_state = require("clever_f.sequence_state") -local state_transitions = require("clever_f.state_transitions") -local text_topology = require("clever_f.text_topology") - -local M = {} -local FeedbackService = {} -FeedbackService.__index = FeedbackService -M.FeedbackService = FeedbackService -local CursorPresentationLease = {} -M.CursorPresentationLease = CursorPresentationLease - -M.DEFAULT_LABEL_GROUP = "CleverFDefaultLabel" -M.Priority = { - HIGH = "high", - ORDINARY = "ordinary", -} -M.FINALIZER_EVENTS = { - "CursorMoved", - "InsertEnter", - "TextChanged", -} -M.EAGER_EVENTS = { - "WinEnter", - "WinLeave", - "CmdwinLeave", -} -M.FinalizerAction = { - PRESERVE = "preserve", - FINALIZE = "finalize", -} -M.MigrationReason = { - LINE_CHANGE = "line_change", - TILL_DIRECTION_CHANGE = "till_direction_change", -} - -local OVERLAY_PRIORITIES = { - CleverFCursor = M.Priority.HIGH, - CleverFChar = M.Priority.HIGH, - CleverFDirect = M.Priority.ORDINARY, -} - -local service_records = setmetatable({}, { __mode = "k" }) -local cursor_lease_records = setmetatable({}, { __mode = "k" }) -local temporary_release_records = setmetatable({}, { __mode = "k" }) - -local FEATURE_GROUPS = { - "CleverFCursor", - "CleverFChar", - "CleverFDirect", -} - -local LEGACY_NORMAL_EX_CONTEXTS = { - cv = true, - cvr = true, -} - -local DIRECT_FINALIZER_EVENTS = { - InsertEnter = true, - TextChanged = true, -} - -local EAGER_EVENT_SET = {} -for _, event_name in ipairs(M.EAGER_EVENTS) do - EAGER_EVENT_SET[event_name] = true -end - -local DEFAULT_LABEL_DEFINITION = { - guifg = "red", - guibg = "NONE", - gui = { - bold = true, - underline = true, - }, - ctermfg = "red", - ctermbg = "NONE", - cterm = { - bold = true, - underline = true, - }, -} - -local function copy(value) - if type(value) ~= "table" then - return value - end - local result = {} - for key, item in pairs(value) do - result[key] = copy(item) - end - return result -end - -function M.default_label_definition() - return copy(DEFAULT_LABEL_DEFINITION) -end - -function M.overlay_priority(group) - local priority = OVERLAY_PRIORITIES[group] - if priority == nil then - error("unknown feedback overlay group '" .. tostring(group) .. "'", 2) - end - return priority -end - -local function fail(message, level) - error(message, (level or 1) + 1) -end - -local function normalize_options(options) - if type(options) ~= "table" then - fail("FeedbackService options must be a table", 3) - end - if options.host == nil then - return { host = options } - end - return options -end - -local function require_host(host) - if type(host) ~= "table" - or type(host.read_highlight_group) ~= "function" - or type(host.define_highlight_group) ~= "function" - or type(host.create_highlight) ~= "function" - or type(host.remove_highlight) ~= "function" - or type(host.read_buffer) ~= "function" - or type(host.read_cursor) ~= "function" - or type(host.read_window) ~= "function" - or type(host.register_events) ~= "function" - or type(host.remove_event_registration) ~= "function" - or type(host.start_timer) ~= "function" - or type(host.stop_timer) ~= "function" - or type(host.supports_timers) ~= "function" - or type(host.supports_cursor_presentation) ~= "function" - or type(host.suppress_cursor_presentation) ~= "function" - or type(host.restore_cursor_presentation) ~= "function" - then - fail("FeedbackService host must provide highlight groups", 3) - end - return host -end - -local function require_transitions(transitions, state) - transitions = transitions or state_transitions.new(state) - if type(transitions) ~= "table" - or type(transitions.AddTemporaryOverlay) ~= "function" - or type(transitions.RemoveTemporaryOverlay) ~= "function" - or type(transitions.AddTargetOverlay) ~= "function" - or type(transitions.ClearTargetOverlays) ~= "function" - or type(transitions.AddFinalizer) ~= "function" - or type(transitions.RemoveFinalizer) ~= "function" - or type(transitions.FullFinalization) ~= "function" - or type(transitions.ClearTargetFeedback) ~= "function" - or type(transitions.SetHighlightTimer) ~= "function" - or type(transitions.ClearHighlightTimer) ~= "function" - then - fail("FeedbackService transitions must manage overlay resources", 3) - end - return transitions -end - -local function require_policy(service, host) - service = service or policy.new(host) - if type(service) ~= "table" - or type(service.evaluate_highlight_links) ~= "function" - or type(service.sample_acquisition) ~= "function" - or type(service.sample_markers) ~= "function" - or type(service.sample_timeouts) ~= "function" - or type(service.capture_activation) ~= "function" - then - fail("FeedbackService policy must evaluate highlight links", 3) - end - return service -end - -function FeedbackService.new(options) - if FeedbackService.is(options) then - return options - end - options = normalize_options(options) - local service = setmetatable({}, FeedbackService) - local host = require_host(options.host) - local state = options.state or sequence_state.get() - if not sequence_state.is(state) then - fail("FeedbackService state must be the plugin-global SequenceState", 2) - end - service_records[service] = { - host = host, - policy = require_policy(options.policy or options.policy_service, host), - state = state, - transitions = require_transitions( - options.transitions or options.state_transitions, - state - ), - persistent_requests = {}, - owned_finalizer = nil, - activation = nil, - eager_registration = nil, - last_eager_decision = nil, - } - return service -end - -function FeedbackService.is(value) - return type(value) == "table" and service_records[value] ~= nil -end - -local cursor_lease_metatable = { - __index = function(lease, key) - local method = CursorPresentationLease[key] - if method ~= nil then - return method - end - local record = cursor_lease_records[lease] - if key == "identity" then - return record.identity - end - if key == "active" then - return record.active - end - return nil - end, - __newindex = function() - fail("cursor presentation leases are read-only", 2) - end, - __metatable = "clever_f.feedback_service.CursorPresentationLease", -} - -local function new_cursor_presentation_lease(host, suppress) - local lease = setmetatable({}, cursor_lease_metatable) - local identity = suppress and host:suppress_cursor_presentation() or nil - cursor_lease_records[lease] = { - host = host, - identity = identity, - active = identity ~= nil, - } - return lease -end - -function CursorPresentationLease.is(value) - return type(value) == "table" and cursor_lease_records[value] ~= nil -end - -function CursorPresentationLease:release() - local record = cursor_lease_records[self] - if record == nil then - fail("cursor presentation lease is invalid", 2) - end - if not record.active then - return false - end - record.active = false - record.host:restore_cursor_presentation(record.identity) - return true -end - -function FeedbackService:create_cursor_presentation_lease(enabled) - local record = service_records[self] - if enabled == nil then - enabled = record.policy:sample_acquisition().hide_cursor_on_cmdline - elseif type(enabled) ~= "boolean" then - fail("cursor presentation policy must be a Boolean", 2) - end - local supported = enabled and record.host:supports_cursor_presentation() - return new_cursor_presentation_lease(record.host, supported == true) -end - -local function position_list(positions) - if type(positions) ~= "table" then - fail("direct marker positions must be a list", 3) - end - local result = {} - local item_count = 0 - for key, position in pairs(positions) do - if type(key) ~= "number" - or key ~= math.floor(key) - or key < 1 - or key > #positions - then - fail("direct marker positions must be a list", 3) - end - result[key] = domain.Position.coerce(position) - item_count = item_count + 1 - end - if item_count ~= #positions then - fail("direct marker positions must be a list", 3) - end - return result -end - -function FeedbackService:create_direct_markers(positions, window) - positions = position_list(positions) - if #positions == 0 then - return nil - end - if window == nil then - fail("direct marker window must identify its host window", 2) - end - - local record = service_records[self] - local identity = record.host:create_highlight({ - group = "CleverFDirect", - window = window, - positions = positions, - priority = M.overlay_priority("CleverFDirect"), - }) - record.transitions:AddTemporaryOverlay(identity, window, "CleverFDirect") - local resource = { - identity = identity, - window = window, - group = "CleverFDirect", - positions = positions, - } - temporary_release_records[resource] = false - return resource -end - -function FeedbackService:create_cursor_marker(position, window) - position = domain.Position.coerce(position) - if window == nil then - fail("cursor marker window must identify its host window", 2) - end - - local record = service_records[self] - local identity = record.host:create_highlight({ - group = "CleverFCursor", - window = window, - position = position, - priority = M.overlay_priority("CleverFCursor"), - }) - record.transitions:AddTemporaryOverlay(identity, window, "CleverFCursor") - local resource = { - identity = identity, - window = window, - group = "CleverFCursor", - position = position, - } - temporary_release_records[resource] = false - return resource -end - -function FeedbackService:remove_temporary_overlay(resource) - if type(resource) ~= "table" or resource.identity == nil then - fail("temporary overlay resource must identify its highlight", 2) - end - if temporary_release_records[resource] == true then - return false - end - temporary_release_records[resource] = true - local record = service_records[self] - local ok, removed = pcall( - record.host.remove_highlight, - record.host, - resource.identity - ) - record.transitions:RemoveTemporaryOverlay(resource.identity, resource.window) - if not ok then - error(removed, 0) - end - return removed -end - -function M.persistent_context_eligible(context) - context = domain.ModeContext.from_full_mode(context) - return context.key == "n" - or context.visual_kind ~= nil - or context.select_kind ~= nil - or LEGACY_NORMAL_EX_CONTEXTS[context.key] == true -end - -function M.persistent_destination( - view, - target_position, - descriptor, - endpoint_policy -) - if not text_topology.TextView.is(view) then - fail("persistent feedback requires a TextView", 2) - end - target_position = domain.Position.coerce(target_position) - descriptor = domain.Descriptor.from_string(descriptor) - endpoint_policy = domain.EndpointPolicy.from_string(endpoint_policy) - - if endpoint_policy == domain.EndpointPolicy.VISUAL_EXCLUSIVE - and descriptor.direction == domain.Direction.FORWARD - then - if descriptor.family == domain.Family.FIND then - return view:successor(target_position) - end - return target_position - end - if descriptor.family == domain.Family.FIND then - return target_position - end - if descriptor.direction == domain.Direction.FORWARD then - return view:predecessor(target_position) - end - return view:successor(target_position) -end - -function M.persistent_match_positions( - view, - match_start_line, - target_plan, - descriptor, - endpoint_policy -) - if not text_topology.TextView.is(view) then - fail("persistent feedback requires a TextView", 2) - end - if not domain.TargetPlan.is(target_plan) then - fail("persistent feedback requires a TargetPlan", 2) - end - local positions = {} - local seen = {} - local candidates = view:iter_line_forward(match_start_line) - while true do - local position, character = candidates() - if position == nil then - break - end - if target_plan:matches(character, position, view) then - local destination = M.persistent_destination( - view, - position, - descriptor, - endpoint_policy - ) - if destination ~= nil then - local key = tostring(destination.line) - .. ":" - .. tostring(destination.byte_column) - if not seen[key] then - seen[key] = true - positions[#positions + 1] = destination - end - end - end - end - return positions -end - -function FeedbackService:build_persistent(specification) - if type(specification) ~= "table" then - fail("persistent feedback request must be a table", 2) - end - local context = domain.ModeContext.from_full_mode(specification.context) - if not M.persistent_context_eligible(context) then - fail("persistent feedback request requires an eligible context", 2) - end - if not domain.TargetPlan.is(specification.target_plan) then - fail("persistent feedback request requires a TargetPlan", 2) - end - if not domain.ResolvedMotionPlan.is(specification.motion_plan) then - fail("persistent feedback request requires a ResolvedMotionPlan", 2) - end - if specification.motion_plan.target_plan ~= specification.target_plan then - fail("persistent feedback must reuse the movement TargetPlan", 2) - end - local descriptor = domain.Descriptor.from_string( - specification.descriptor or specification.motion_plan.descriptor - ) - local endpoint_policy = domain.EndpointPolicy.from_string( - specification.endpoint_policy or specification.motion_plan.endpoint_policy - ) - local anchor = domain.Position.coerce(specification.anchor) - local view = specification.text_view - or text_topology.from_host(service_records[self].host) - return { - context = context, - anchor = anchor, - target_plan = specification.target_plan, - motion_plan = specification.motion_plan, - descriptor = descriptor, - endpoint_policy = endpoint_policy, - anchor_line = anchor.line, - match_start_line = anchor.line, - positions = M.persistent_match_positions( - view, - anchor.line, - specification.target_plan, - descriptor, - endpoint_policy - ), - text_view = view, - window = specification.window, - } -end - -local function release_target_overlays(record, resources) - for _, resource in ipairs(resources) do - record.host:remove_highlight(resource.identity) - end -end - -local function remove_target_overlays(record, window) - local resources = record.transitions:ClearTargetOverlays(window) - release_target_overlays(record, resources) - return resources -end - -function FeedbackService:remove_character_overlays(window) - if window == nil then - fail("character overlay window must identify its host window", 2) - end - return remove_target_overlays(service_records[self], window) -end - -function FeedbackService:cursor_moved_decision() - local record = service_records[self] - local context = record.state.last_input_context - local expected = context and record.state:get_previous_landing(context) or nil - local actual = record.host:read_cursor() - return { - context = context, - expected = expected, - actual = actual, - equal = expected ~= nil and domain.Position.equal(actual, expected), - } -end - -local function release_finalizers(record, resources) - for _, resource in ipairs(resources) do - record.host:remove_event_registration(resource.identity) - end -end - -local function release_highlight_timer(record, identity) - if identity == nil then - return false - end - return record.host:stop_timer(identity) -end - -function FeedbackService:release_transition_cleanup(cleanup) - if type(cleanup) ~= "table" then - fail("feedback transition cleanup must be a table", 2) - end - local record = service_records[self] - release_finalizers(record, cleanup.finalizers or {}) - release_highlight_timer(record, cleanup.highlight_timer) - release_target_overlays(record, cleanup.target_overlays or {}) - if #(cleanup.finalizers or {}) > 0 then - record.owned_finalizer = nil - end - return cleanup -end - -function FeedbackService:full_finalize(window) - local record = service_records[self] - window = window or record.host:read_window() - local cleanup = record.transitions:FullFinalization(window) - self:release_transition_cleanup(cleanup) - record.owned_finalizer = nil - return cleanup -end - -function FeedbackService:handle_finalizer_event(name, payload) - if name == "CursorMoved" then - local decision = self:cursor_moved_decision() - if decision.equal then - decision.action = M.FinalizerAction.PRESERVE - else - decision.action = M.FinalizerAction.FINALIZE - decision.cleanup = self:full_finalize(payload and payload.window) - end - return decision - end - if DIRECT_FINALIZER_EVENTS[name] then - return { - action = M.FinalizerAction.FINALIZE, - cleanup = self:full_finalize(payload and payload.window), - } - end - return false -end - -local function register_finalizers(service, record) - local buffer = record.host:read_buffer() - local owned = record.owned_finalizer - if owned ~= nil and owned.buffer == buffer then - return owned - end - if owned ~= nil then - record.host:remove_event_registration(owned.identity) - record.transitions:RemoveFinalizer(owned.identity, owned.buffer) - end - - local identity = record.host:register_events( - M.FINALIZER_EVENTS, - function(name, payload) - service:handle_finalizer_event(name, payload) - end, - { buffer = buffer } - ) - record.transitions:AddFinalizer(identity, buffer) - owned = { - identity = identity, - buffer = buffer, - } - record.owned_finalizer = owned - return owned -end - -local function materialize_persistent(service, request) - if request.window == nil then - fail("persistent feedback window must identify its host window", 3) - end - local record = service_records[service] - remove_target_overlays(record, request.window) - request.identity = record.host:create_highlight({ - group = "CleverFChar", - window = request.window, - positions = request.positions, - priority = M.overlay_priority("CleverFChar"), - target_plan = request.target_plan, - descriptor = request.descriptor, - endpoint_policy = request.endpoint_policy, - anchor_line = request.anchor_line, - match_start_line = request.match_start_line, - }) - request.group = "CleverFChar" - request.priority = M.overlay_priority("CleverFChar") - local owned, ownership_error = pcall(function() - record.transitions:AddTargetOverlay( - request.identity, - request.window, - request.anchor.line - ) - request.finalizers = register_finalizers(service, record) - end) - if not owned then - local resources = record.transitions:RemoveTargetOverlay( - request.identity, - request.window - ) - if #resources == 0 then - resources[1] = { - identity = request.identity, - window = request.window, - } - end - pcall(release_target_overlays, record, resources) - error(ownership_error, 0) - end - local requests = record.persistent_requests - requests[#requests + 1] = request - return request -end - -function FeedbackService:request_persistent(specification) - return materialize_persistent(self, self:build_persistent(specification)) -end - -function FeedbackService:restore_primary(specification) - local restoration = self:build_primary_restoration(specification) - if restoration == nil then - return nil - end - return materialize_persistent(self, restoration) -end - -function FeedbackService:persistent_requests() - local result = {} - for index, request in ipairs(service_records[self].persistent_requests) do - result[index] = request - end - return result -end - -function M.repeated_till_migration_candidate(request) - if type(request) ~= "table" then - fail("command feedback migration request must be a table", 2) - end - local plan = request.resolved_motion_plan or request.plan - return domain.ResolvedMotionPlan.is(plan) - and plan.descriptor.family == domain.Family.TILL - and request.first_move == false -end - -function M.till_direction_changed(request) - if not M.repeated_till_migration_candidate(request) then - return false - end - if type(request.moved_forward) ~= "boolean" - or type(request.previous_moved_forward) ~= "boolean" - then - fail("TILL feedback migration requires movement directions", 2) - end - return request.moved_forward ~= request.previous_moved_forward -end - -function M.command_migration_reason(request) - if type(request) ~= "table" then - fail("command feedback migration request must be a table", 2) - end - local origin = domain.Position.coerce(request.origin) - local destination = domain.Position.coerce(request.destination) - if request.outcome ~= nil and request.outcome.complete ~= true then - return nil - end - if origin.line ~= destination.line then - return M.MigrationReason.LINE_CHANGE - end - if M.till_direction_changed(request) then - return M.MigrationReason.TILL_DIRECTION_CHANGE - end - return nil -end - -local function has_target_overlay(record, window) - for _, resource in ipairs(record.state.target_overlays) do - if resource.window == window then - return true - end - end - return false -end - -function FeedbackService:primary_restoration_active(context, window) - local record = service_records[self] - context = domain.ModeContext.from_full_mode(context) - local mark_char = record.policy:sample_markers().mark_char - if not mark_char or not M.persistent_context_eligible(context) then - return false - end - window = window or record.host:read_window() - return not has_target_overlay(record, window) -end - -function FeedbackService:build_primary_restoration(specification) - if type(specification) ~= "table" then - fail("primary feedback restoration must be a table", 2) - end - local window = specification.window - or service_records[self].host:read_window() - if not self:primary_restoration_active(specification.context, window) then - return nil - end - if not domain.TargetPlan.is(specification.target_plan) then - fail("primary feedback restoration requires an action TargetPlan", 2) - end - local action_motion_plan = specification.motion_plan - local search_scope = specification.search_scope - or (domain.ResolvedMotionPlan.is(action_motion_plan) - and action_motion_plan.search_scope) - or domain.SearchScope.BUFFER - local motion_plan = domain.ResolvedMotionPlan.new({ - target_plan = specification.target_plan, - descriptor = specification.stored_descriptor, - search_scope = search_scope, - endpoint_policy = specification.endpoint_policy, - }) - return self:build_persistent({ - context = specification.context, - anchor = specification.anchor, - target_plan = specification.target_plan, - motion_plan = motion_plan, - descriptor = specification.stored_descriptor, - endpoint_policy = specification.endpoint_policy, - text_view = specification.text_view, - window = window, - }) -end - -function FeedbackService:migrate_command(request) - local reason = M.command_migration_reason(request) - local record = service_records[self] - local window = request.window or record.host:read_window() - if reason == nil or not has_target_overlay(record, window) then - return { - migrated = false, - reason = reason, - } - end - - local plan = request.resolved_motion_plan or request.plan - if not domain.ResolvedMotionPlan.is(plan) then - fail("command feedback migration requires a ResolvedMotionPlan", 2) - end - local overlay = self:request_persistent({ - context = request.context, - anchor = request.destination, - target_plan = plan.target_plan, - motion_plan = plan, - descriptor = plan.descriptor, - endpoint_policy = plan.endpoint_policy, - window = window, - }) - return { - migrated = true, - reason = reason, - overlay = overlay, - } -end - -function FeedbackService:highlight_timer_delay() - local record = service_records[self] - if not record.policy:sample_markers().mark_char then - return nil - end - local delay = record.policy:sample_timeouts().highlight_timeout_ms - if delay == 0 or record.host:supports_timers() ~= true then - return nil - end - return delay -end - -function FeedbackService:cancel_highlight_timer() - local record = service_records[self] - local identity = record.transitions:ClearHighlightTimer() - release_highlight_timer(record, identity) - return identity -end - -function FeedbackService:handle_highlight_timer(callback_identity, window) - if callback_identity == nil then - fail("highlight timer callback identity must be present", 2) - end - local record = service_records[self] - if callback_identity ~= record.state.highlight_timer then - return false - end - local _, current = record.transitions:ClearHighlightTimer(callback_identity) - if not current then - return false - end - self:remove_character_overlays(window or record.host:read_window()) - return true -end - -function FeedbackService:start_highlight_timer(window) - local record = service_records[self] - local delay = self:highlight_timer_delay() - if delay == nil then - return nil - end - window = window or record.host:read_window() - self:cancel_highlight_timer() - local identity - identity = record.host:start_timer(delay, function(callback_identity) - self:handle_highlight_timer(callback_identity or identity, window) - end) - record.transitions:SetHighlightTimer(identity) - return identity -end - -function FeedbackService:refresh_primary(resolved_target, window) - if resolved_target == nil then - return nil - end - if not domain.TargetValue.is(resolved_target) then - fail("primary timer refresh requires a resolved TargetValue", 2) - end - return self:start_highlight_timer(window) -end - -function FeedbackService:handle_eager_event(name, payload) - if not EAGER_EVENT_SET[name] then - return false - end - local record = service_records[self] - local decision = { - event = name, - payload = payload, - mark_char = record.policy:sample_markers().mark_char, - cleaned = false, - } - if decision.mark_char then - local window = payload and payload.window or record.host:read_window() - local cleanup = record.transitions:ClearTargetFeedback(window) - release_highlight_timer(record, cleanup.highlight_timer) - release_target_overlays(record, cleanup.target_overlays) - decision.cleaned = true - decision.cleanup = cleanup - end - record.last_eager_decision = decision - return decision -end - -function FeedbackService:last_eager_decision() - local decision = service_records[self].last_eager_decision - return decision and copy(decision) or nil -end - -function FeedbackService:activate() - local record = service_records[self] - if record.activation ~= nil then - return copy(record.activation) - end - local sampled = record.policy:capture_activation() - local activation = { - clean_labels_eagerly = sampled.clean_labels_eagerly, - eager_registration = nil, - } - if sampled.clean_labels_eagerly then - local identity = record.host:register_events( - M.EAGER_EVENTS, - function(name, payload) - self:handle_eager_event(name, payload) - end, - { owner = "clever_f", lifecycle = "eager" } - ) - record.eager_registration = identity - activation.eager_registration = identity - end - record.activation = activation - return copy(activation) -end - -function FeedbackService:evaluate_feature_links() - local record = service_records[self] - local rules = record.policy:evaluate_highlight_links() - local results = {} - for _, group in ipairs(FEATURE_GROUPS) do - local rule = rules[group] - if rule.enabled then - if rule.configured_target ~= nil then - record.host:define_highlight_group( - group, - { link = rule.configured_target }, - { force = true } - ) - results[group] = { - group = group, - target = rule.configured_target, - source = "configured", - applied = true, - } - else - local existing = record.host:read_highlight_group(group) - if existing ~= nil then - results[group] = { - group = group, - definition = existing, - source = "colorscheme", - applied = false, - } - else - record.host:define_highlight_group( - group, - { link = rule.target }, - { default = true } - ) - results[group] = { - group = group, - target = rule.target, - source = "fallback", - applied = true, - } - end - end - end - end - return results -end - -function FeedbackService:ensure_default_label() - local existing = service_records[self].host:read_highlight_group( - M.DEFAULT_LABEL_GROUP - ) - if existing ~= nil then - return { - group = M.DEFAULT_LABEL_GROUP, - definition = existing, - source = "colorscheme", - applied = false, - } - end - - local definition = M.default_label_definition() - service_records[self].host:define_highlight_group( - M.DEFAULT_LABEL_GROUP, - definition, - { default = true } - ) - return { - group = M.DEFAULT_LABEL_GROUP, - definition = definition, - source = "fallback", - applied = true, - } -end - -function FeedbackService:evaluate_highlights() - return { - default_label = self:ensure_default_label(), - feature_links = self:evaluate_feature_links(), - } -end - -function M.new(options) - return FeedbackService.new(options) -end - -setmetatable(M, { - __call = function(_, options) - return FeedbackService.new(options) - end, -}) - -return M |
