local domain = require("clever_tee.domain") local M = {} local CasePolicyResolver = {} M.CasePolicyResolver = CasePolicyResolver local resolver_records = setmetatable({}, { __mode = "k" }) local function fail(message, level) error(message, (level or 1) + 1) end local function require_target(target) if not domain.TargetValue.is(target) then fail("case policy target must be a TargetValue", 2) end return target end local function require_boolean(value, name) if type(value) ~= "boolean" then fail((name or "value") .. " must be a Boolean", 2) end return value end local function require_string(value, name, allow_empty) if type(value) ~= "string" or (not allow_empty and value == "") then fail((name or "value") .. " must be a string", 2) end return value end local function default_lowercase(value) local runtime = rawget(_G, "vim") if type(runtime) ~= "table" or type(runtime.fn) ~= "table" or type(runtime.fn.tolower) ~= "function" then fail("editor-compatible case conversion requires Nvim or a lowercase converter", 2) end return runtime.fn.tolower(value) end local function lowercase_function(options) if options == nil then return default_lowercase end if type(options) == "function" then return options end if type(options) ~= "table" then fail("case policy options must be a table or lowercase function", 3) end local lowercase = options.lowercase or options.to_lower if lowercase == nil then return default_lowercase end if type(lowercase) ~= "function" then fail("case policy lowercase converter must be a function", 3) end return lowercase end local resolver_metatable = { __index = CasePolicyResolver, __newindex = function() fail("CasePolicyResolver values are immutable", 2) end, __tostring = function() return "case-policy-resolver" end, __metatable = "clever_tee.case_policy.CasePolicyResolver", } function CasePolicyResolver.new(options) if CasePolicyResolver.is(options) then return options end local resolver = setmetatable({}, resolver_metatable) resolver_records[resolver] = { lowercase = lowercase_function(options), } return resolver end function CasePolicyResolver.is(value) return type(value) == "table" and resolver_records[value] ~= nil end function M.new(options) return CasePolicyResolver.new(options) end setmetatable(M, { __call = function(_, options) return CasePolicyResolver.new(options) end, }) function M.is_lower_ascii(value) if domain.TargetValue.is(value) then value = value.value end if type(value) ~= "string" or #value ~= 1 then return false end local byte = value:byte(1) return byte >= string.byte("a") and byte <= string.byte("z") end function M.resolve_case_mode(target, ignore_case, smart_case) target = require_target(target) require_boolean(ignore_case, "ignore_case") require_boolean(smart_case, "smart_case") if ignore_case then return domain.CaseMode.INSENSITIVE end if smart_case and M.is_lower_ascii(target) then return domain.CaseMode.INSENSITIVE end return domain.CaseMode.SENSITIVE end function CasePolicyResolver:resolve(target, ignore_case, smart_case) if type(ignore_case) == "table" and smart_case == nil then local match_policy = ignore_case ignore_case = match_policy.ignore_case smart_case = match_policy.smart_case end return M.resolve_case_mode(target, ignore_case, smart_case) end function CasePolicyResolver:lowercase(value) require_string(value, "case comparison value", true) local lowercase = resolver_records[self].lowercase(value) if type(lowercase) ~= "string" then fail("case policy lowercase converter must return a string", 2) end return lowercase end function CasePolicyResolver:equal(left, right, case_mode) require_string(left, "left case comparison value", true) require_string(right, "right case comparison value", true) case_mode = domain.CaseMode.from_string(case_mode) if case_mode == domain.CaseMode.SENSITIVE then return left == right end return self:lowercase(left) == self:lowercase(right) end function CasePolicyResolver:comparator(target_character, case_mode) require_string(target_character, "target character", true) case_mode = domain.CaseMode.from_string(case_mode) if target_character == "" then return function() return false end end if case_mode == domain.CaseMode.SENSITIVE then return function(candidate_character) return type(candidate_character) == "string" and candidate_character ~= "" and candidate_character == target_character end end local folded_target = self:lowercase(target_character) local lowercase = resolver_records[self].lowercase return function(candidate_character) if type(candidate_character) ~= "string" or candidate_character == "" then return false end local folded_candidate = lowercase(candidate_character) if type(folded_candidate) ~= "string" then fail("case policy lowercase converter must return a string", 2) end return folded_candidate == folded_target end end M.resolve = M.resolve_case_mode M.is_lowercase_ascii = M.is_lower_ascii return M