diff options
Diffstat (limited to 'lua/clever_f/direct_preview_planner.lua')
| -rw-r--r-- | lua/clever_f/direct_preview_planner.lua | 250 |
1 files changed, 0 insertions, 250 deletions
diff --git a/lua/clever_f/direct_preview_planner.lua b/lua/clever_f/direct_preview_planner.lua deleted file mode 100644 index 6735924..0000000 --- a/lua/clever_f/direct_preview_planner.lua +++ /dev/null @@ -1,250 +0,0 @@ -local case_policy = require("clever_f.case_policy") -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 - -local function require_case_resolver(options) - options = options or {} - if type(options) == "function" then - options = { lowercase = options } - end - if type(options) ~= "table" then - fail("DirectPreviewPlanner options must be a table", 3) - end - local resolver = options.case_resolver - if resolver == nil then - resolver = case_policy.new({ lowercase = options.lowercase }) - end - if type(resolver) ~= "table" or type(resolver.lowercase) ~= "function" then - fail("DirectPreviewPlanner case resolver must provide lowercase", 3) - end - return resolver -end - -function DirectPreviewPlanner.new(options) - if DirectPreviewPlanner.is(options) then - return options - end - local planner = setmetatable({}, DirectPreviewPlanner) - planner_records[planner] = { - case_resolver = require_case_resolver(options), - } - return planner -end - -function DirectPreviewPlanner.is(value) - return type(value) == "table" and planner_records[value] ~= nil -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.marker_position(descriptor, target_position) - domain.Descriptor.from_string(descriptor) - return domain.Position.coerce(target_position) -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 - -local function is_upper_ascii(character) - if type(character) ~= "string" or #character ~= 1 then - return false - end - local code = character:byte(1) - return code >= string.byte("A") and code <= string.byte("Z") -end - -local function increment_smart_counters(counters, character) - local exact_count = increment_counter(counters, character) - local lowercase_count - if is_upper_ascii(character) then - local lowercase = string.char(character:byte(1) + 32) - lowercase_count = increment_counter(counters, lowercase) - end - return exact_count, lowercase_count -end - -M.is_upper_ascii = is_upper_ascii - -local function require_case_setting(settings, name) - local value = settings[name] - if value == nil then - return false - end - if type(value) ~= "boolean" then - fail("direct preview " .. name .. " must be a Boolean", 3) - end - return value -end - -function M.case_grouping_settings(settings) - settings = settings or {} - if type(settings) ~= "table" then - fail("direct preview case settings must be a table", 2) - end - return { - ignore_case = require_case_setting(settings, "ignore_case"), - smart_case = require_case_setting(settings, "smart_case"), - } -end - -function M.validate_marker_positions(view, positions) - require_view(view) - if type(positions) ~= "table" then - fail("direct preview markers must be a list of positions", 2) - end - - local result = {} - local seen = {} - 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 preview markers must be a list of positions", 2) - end - position = domain.Position.coerce(position) - if not view:is_character_start(position) then - fail("direct preview marker must start an editor character", 2) - end - local identity = tostring(position.line) .. ":" .. tostring(position.byte_column) - if seen[identity] then - fail("direct preview marker positions must be unique", 2) - end - seen[identity] = true - result[key] = position - item_count = item_count + 1 - end - if item_count ~= #positions then - fail("direct preview markers must be a list of positions", 2) - end - return result -end - -function DirectPreviewPlanner:plan(view, origin, descriptor, count, settings) - local grouping = M.case_grouping_settings(settings) - local ignore_case = grouping.ignore_case - local smart_case = grouping.smart_case - local selected_occurrence = self:normalize_count(count) - local counters = {} - local positions = {} - local resolver = planner_records[self].case_resolver - for _, record in ipairs(self:scan_for_descriptor(view, origin, descriptor)) do - local selected - if ignore_case then - selected = increment_counter( - counters, - resolver:lowercase(record.character) - ) == selected_occurrence - elseif smart_case then - local exact_count, lowercase_count = increment_smart_counters( - counters, - record.character - ) - selected = exact_count == selected_occurrence - or lowercase_count == selected_occurrence - else - selected = increment_counter( - counters, - record.character - ) == selected_occurrence - end - if selected then - positions[#positions + 1] = M.marker_position(descriptor, record.position) - end - end - return M.validate_marker_positions(view, positions) -end - -function M.new(options) - return DirectPreviewPlanner.new(options) -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, settings, options) - return DirectPreviewPlanner.new(options):plan( - view, - origin, - descriptor, - count, - settings - ) -end - -setmetatable(M, { - __call = function(_, options) - return DirectPreviewPlanner.new(options) - end, -}) - -return M |
