local domain = require("clever_f.domain") local text_topology = require("clever_f.text_topology") local M = {} local DirectPreviewPlanner = {} DirectPreviewPlanner.__index = DirectPreviewPlanner M.DirectPreviewPlanner = DirectPreviewPlanner local planner_records = setmetatable({}, { __mode = "k" }) local function fail(message, level) error(message, (level or 1) + 1) end local function require_view(view) if not text_topology.TextView.is(view) then fail("direct preview text must be a TextView", 3) end return view end function DirectPreviewPlanner.new() local planner = setmetatable({}, DirectPreviewPlanner) planner_records[planner] = true return planner end function DirectPreviewPlanner.is(value) return type(value) == "table" and planner_records[value] == true end function DirectPreviewPlanner:scan_current_line(view, origin, direction) require_view(view) origin = domain.Position.coerce(origin) direction = domain.Direction.from_string(direction) local records = {} for position, character, span in view:iter_strict( origin, direction, domain.SearchScope.CURRENT_LINE ) do records[#records + 1] = { position = position, character = character, span = span, } end return records end function M.normalize_count(count) return domain.Count.to_number(count) end function DirectPreviewPlanner:normalize_count(count) return M.normalize_count(count) end function M.direction_for_descriptor(descriptor) descriptor = domain.Descriptor.from_string(descriptor) if domain.Descriptor.is_uppercase(descriptor) then return domain.Direction.BACKWARD end return domain.Direction.FORWARD end function DirectPreviewPlanner:scan_for_descriptor(view, origin, descriptor) return self:scan_current_line( view, origin, M.direction_for_descriptor(descriptor) ) end local function increment_counter(counters, character) local value = (counters[character] or 0) + 1 counters[character] = value return value end function DirectPreviewPlanner:plan(view, origin, descriptor, count) local selected_occurrence = self:normalize_count(count) local counters = {} local positions = {} for _, record in ipairs(self:scan_for_descriptor(view, origin, descriptor)) do if increment_counter(counters, record.character) == selected_occurrence then positions[#positions + 1] = record.position end end return positions end function M.new() return DirectPreviewPlanner.new() end function M.scan_current_line(view, origin, direction) return DirectPreviewPlanner.new():scan_current_line(view, origin, direction) end function M.scan_for_descriptor(view, origin, descriptor) return DirectPreviewPlanner.new():scan_for_descriptor(view, origin, descriptor) end function M.plan(view, origin, descriptor, count) return DirectPreviewPlanner.new():plan(view, origin, descriptor, count) end setmetatable(M, { __call = function() return DirectPreviewPlanner.new() end, }) return M