summaryrefslogtreecommitdiff
path: root/lua/clever_f/domain.lua
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 18:44:32 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 18:44:32 +0200
commitc7b14ffb6d14969c9c41864e827f33f8e80fc24e (patch)
treec7c0ac23dc93a4a8b75449d2be4d0e05c4f05f9e /lua/clever_f/domain.lua
parent9013636a57144e1f57c9339e7888d588430aae6a (diff)
Rename plugin to clever-tee
Diffstat (limited to 'lua/clever_f/domain.lua')
-rw-r--r--lua/clever_f/domain.lua1390
1 files changed, 0 insertions, 1390 deletions
diff --git a/lua/clever_f/domain.lua b/lua/clever_f/domain.lua
deleted file mode 100644
index 0d802b3..0000000
--- a/lua/clever_f/domain.lua
+++ /dev/null
@@ -1,1390 +0,0 @@
-local M = {}
-
-local records = setmetatable({}, { __mode = "k" })
-local record_types = setmetatable({}, { __mode = "k" })
-local methods = {}
-local formatters = {}
-local equalities = {}
-local metatables = {}
-
-local function fail(message, level)
- error(message, (level or 1) + 1)
-end
-
-local function is_integer(value)
- return type(value) == "number"
- and value > -math.huge
- and value < math.huge
- and value == math.floor(value)
-end
-
-local function register_type(type_name, type_methods, formatter, equality)
- methods[type_name] = type_methods or {}
- formatters[type_name] = formatter
- equalities[type_name] = equality
-
- local mt = {
- __index = function(value, key)
- local field = records[value][key]
- if field ~= nil then
- return field
- end
- return methods[type_name][key]
- end,
- __newindex = function()
- fail(type_name .. " values are immutable", 2)
- end,
- __tostring = function(value)
- local format = formatters[type_name]
- if format then
- return format(records[value])
- end
- return type_name
- end,
- __eq = function(left, right)
- if record_types[left] ~= type_name or record_types[right] ~= type_name then
- return false
- end
- local equal = equalities[type_name]
- if equal then
- return equal(records[left], records[right])
- end
- return rawequal(left, right)
- end,
- __metatable = "clever_f.domain." .. type_name,
- }
- metatables[type_name] = mt
-end
-
-local function new_record(type_name, fields)
- local value = {}
- records[value] = fields
- record_types[value] = type_name
- return setmetatable(value, metatables[type_name])
-end
-
-local function is_record(value, type_name)
- return record_types[value] == type_name
-end
-
-local function require_record(value, type_name, name)
- if not is_record(value, type_name) then
- fail((name or "value") .. " must be a " .. type_name, 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 .. " must be " .. (allow_empty and "a string" or "a nonempty string"), 2)
- end
- return value
-end
-
-local function require_boolean(value, name)
- if type(value) ~= "boolean" then
- fail(name .. " must be a Boolean", 2)
- end
- return value
-end
-
-local function require_nonnegative_integer(value, name)
- if not is_integer(value) or value < 0 then
- fail(name .. " must be a nonnegative integer", 2)
- end
- return value
-end
-
-function M.type_of(value)
- return record_types[value]
-end
-
-local function define_enum(type_name, entries)
- local enum_methods = {}
- local namespace = {}
- local by_value = {}
-
- register_type(type_name, enum_methods, function(data)
- return data.value
- end)
-
- for constant, serialized in pairs(entries) do
- local value = new_record(type_name, {
- name = constant,
- value = serialized,
- })
- namespace[constant] = value
- by_value[serialized] = value
- end
-
- function namespace.from_string(value)
- if is_record(value, type_name) then
- return value
- end
- local result = by_value[value]
- if result == nil then
- fail("value must be a valid " .. type_name, 2)
- end
- return result
- end
-
- function namespace.is(value)
- return is_record(value, type_name)
- end
-
- function enum_methods:to_string()
- return records[self].value
- end
-
- return namespace
-end
-
-M.Family = define_enum("Family", {
- FIND = "FIND",
- TILL = "TILL",
-})
-
-M.Direction = define_enum("Direction", {
- FORWARD = "forward",
- BACKWARD = "backward",
-})
-
-M.SelectionKind = define_enum("SelectionKind", {
- NONE = "none",
- CHARACTER = "character",
- LINE = "line",
- BLOCK = "block",
-})
-
-M.SelectionOption = define_enum("SelectionOption", {
- INCLUSIVE = "inclusive",
- EXCLUSIVE = "exclusive",
-})
-
-M.TargetKind = define_enum("TargetKind", {
- CHARACTER = "character",
- SPECIAL_KEY = "special_key",
- CODE_FALLBACK = "code_fallback",
-})
-
-M.CaseMode = define_enum("CaseMode", {
- SENSITIVE = "sensitive",
- INSENSITIVE = "insensitive",
-})
-
-M.TargetPlanKind = define_enum("TargetPlanKind", {
- EMPTY = "empty",
- LITERAL = "literal",
- BACKSLASH = "backslash",
- SYMBOL = "symbol",
- MIGEMO = "migemo",
-})
-
-M.SearchScope = define_enum("SearchScope", {
- BUFFER = "buffer",
- CURRENT_LINE = "current_line",
-})
-
-M.EndpointPolicy = define_enum("EndpointPolicy", {
- REGULAR = "regular",
- VISUAL_EXCLUSIVE = "visual_exclusive",
-})
-
-M.SearchStatus = define_enum("SearchStatus", {
- COMPLETE = "complete",
- BOUNDARY_AFTER_PARTIAL = "boundary_after_partial",
- BOUNDARY_BEFORE_ANY = "boundary_before_any",
-})
-
-M.ActionKind = define_enum("ActionKind", {
- MOVEMENT = "movement",
- NEUTRAL = "neutral",
- ESCAPE = "escape",
- FAILED_SEARCH = "failed_search",
- ERROR = "error",
- EMPTY = "empty",
-})
-
-local Position = {}
-M.Position = Position
-
-register_type("Position", Position, function(data)
- return string.format("(%d,%d)", data.line, data.byte_column)
-end, function(left, right)
- return left.line == right.line and left.byte_column == right.byte_column
-end)
-
-function Position.new(line, byte_column)
- if not is_integer(line) or line < 1 then
- fail("line must be a positive one-based integer", 2)
- end
- if not is_integer(byte_column) or byte_column < 1 then
- fail("byte_column must be a positive one-based integer", 2)
- end
- return new_record("Position", {
- line = line,
- byte_column = byte_column,
- })
-end
-
-function Position.coerce(value)
- if Position.is(value) then
- return value
- end
- if type(value) ~= "table" then
- fail("position must be a Position or position table", 2)
- end
- return Position.new(value.line, value.byte_column)
-end
-
-function Position.is(value)
- return is_record(value, "Position")
-end
-
-function Position.compare(left, right)
- require_record(left, "Position", "left")
- require_record(right, "Position", "right")
- if left.line < right.line then
- return -1
- end
- if left.line > right.line then
- return 1
- end
- if left.byte_column < right.byte_column then
- return -1
- end
- if left.byte_column > right.byte_column then
- return 1
- end
- return 0
-end
-
-function Position.equal(left, right)
- return Position.compare(left, right) == 0
-end
-
-function Position.stationary(left, right)
- return Position.equal(left, right)
-end
-
-function Position.is_forward(candidate, origin)
- return Position.compare(candidate, origin) > 0
-end
-
-function Position.is_backward(candidate, origin)
- return Position.compare(candidate, origin) < 0
-end
-
-function Position:to_table()
- return {
- line = self.line,
- byte_column = self.byte_column,
- }
-end
-
-local Descriptor = {}
-M.Descriptor = Descriptor
-
-register_type("Descriptor", Descriptor, function(data)
- return data.value
-end)
-
-local descriptors_by_string = {}
-local descriptors_by_parts = {}
-
-local function descriptor_key(family, direction)
- return family.value .. ":" .. direction.value
-end
-
-local function define_descriptor(name, serialized, family, direction)
- local descriptor = new_record("Descriptor", {
- name = name,
- value = serialized,
- family = family,
- direction = direction,
- uppercase = serialized:match("%u") ~= nil,
- })
- descriptors_by_string[serialized] = descriptor
- descriptors_by_parts[descriptor_key(family, direction)] = descriptor
- Descriptor[name] = descriptor
- Descriptor[serialized] = descriptor
- return descriptor
-end
-
-Descriptor.FIND_FORWARD = define_descriptor(
- "FIND_FORWARD",
- "f",
- M.Family.FIND,
- M.Direction.FORWARD
-)
-Descriptor.FIND_BACKWARD = define_descriptor(
- "FIND_BACKWARD",
- "F",
- M.Family.FIND,
- M.Direction.BACKWARD
-)
-Descriptor.TILL_FORWARD = define_descriptor(
- "TILL_FORWARD",
- "t",
- M.Family.TILL,
- M.Direction.FORWARD
-)
-Descriptor.TILL_BACKWARD = define_descriptor(
- "TILL_BACKWARD",
- "T",
- M.Family.TILL,
- M.Direction.BACKWARD
-)
-
-function Descriptor.is(value)
- return is_record(value, "Descriptor")
-end
-
-function Descriptor.is_valid(value)
- return Descriptor.is(value) or descriptors_by_string[value] ~= nil
-end
-
-function Descriptor.from_string(value)
- if Descriptor.is(value) then
- return value
- end
- local descriptor = descriptors_by_string[value]
- if descriptor == nil then
- fail("descriptor must be one of f, F, t, or T", 2)
- end
- return descriptor
-end
-
-function Descriptor.try_from_string(value)
- if Descriptor.is(value) then
- return value
- end
- return descriptors_by_string[value]
-end
-
-function Descriptor.from_parts(family, direction)
- family = M.Family.from_string(family)
- direction = M.Direction.from_string(direction)
- return descriptors_by_parts[descriptor_key(family, direction)]
-end
-
-function Descriptor.to_string(value)
- return Descriptor.from_string(value).value
-end
-
-function Descriptor.is_uppercase(value)
- return Descriptor.from_string(value).uppercase
-end
-
-function Descriptor.is_lowercase(value)
- return not Descriptor.is_uppercase(value)
-end
-
-function Descriptor.swap(value)
- local descriptor = Descriptor.from_string(value)
- local direction = descriptor.direction == M.Direction.FORWARD
- and M.Direction.BACKWARD
- or M.Direction.FORWARD
- return Descriptor.from_parts(descriptor.family, direction)
-end
-
-function Descriptor.lowercase(value)
- local descriptor = Descriptor.from_string(value)
- return Descriptor.from_parts(descriptor.family, M.Direction.FORWARD)
-end
-
-function Descriptor.uppercase(value)
- local descriptor = Descriptor.from_string(value)
- return Descriptor.from_parts(descriptor.family, M.Direction.BACKWARD)
-end
-
-local Count = {}
-M.Count = Count
-
-register_type("Count", Count, function(data)
- return tostring(data.value)
-end, function(left, right)
- return left.value == right.value
-end)
-
-local count_one
-
-function Count.new(value)
- if Count.is(value) then
- return value
- end
- if value == nil then
- value = 1
- end
- if not is_integer(value) or value < 1 then
- fail("count must be a positive integer", 2)
- end
- if value == 1 and count_one ~= nil then
- return count_one
- end
- local count = new_record("Count", { value = value })
- if value == 1 then
- count_one = count
- end
- return count
-end
-
-function Count.is(value)
- return is_record(value, "Count")
-end
-
-function Count.to_number(value)
- return Count.new(value).value
-end
-
-Count.ONE = Count.new(1)
-
-local ModeContext = {}
-M.ModeContext = ModeContext
-
-register_type("ModeContext", ModeContext, function(data)
- return data.key
-end)
-
-local mode_contexts = {}
-local CTRL_V = string.char(0x16)
-local CTRL_S = string.char(0x13)
-
-local operator_modes = {
- no = true,
- nov = true,
- noV = true,
- ["no" .. CTRL_V] = true,
-}
-
-local function mode_traits(full_mode)
- local operator = operator_modes[full_mode] == true
- local visual_kind
- local select_kind
-
- if not operator then
- local lead = full_mode:sub(1, 1)
- if lead == "v" then
- visual_kind = M.SelectionKind.CHARACTER
- elseif lead == "V" then
- visual_kind = M.SelectionKind.LINE
- elseif lead == CTRL_V then
- visual_kind = M.SelectionKind.BLOCK
- elseif lead == "s" then
- select_kind = M.SelectionKind.CHARACTER
- elseif lead == "S" then
- select_kind = M.SelectionKind.LINE
- elseif lead == CTRL_S then
- select_kind = M.SelectionKind.BLOCK
- end
- end
-
- return operator, visual_kind, select_kind
-end
-
-function ModeContext.from_full_mode(full_mode)
- if ModeContext.is(full_mode) then
- return full_mode
- end
- require_string(full_mode, "full_mode", false)
-
- local operator, visual_kind, select_kind = mode_traits(full_mode)
- local key = operator and "no" or full_mode
- local context = mode_contexts[key]
- if context ~= nil then
- return context
- end
-
- context = new_record("ModeContext", {
- key = key,
- full_mode = key,
- operator = operator,
- visual_kind = visual_kind,
- select_kind = select_kind,
- visual = visual_kind ~= nil,
- select = select_kind ~= nil,
- command_path = visual_kind == nil,
- })
- mode_contexts[key] = context
- return context
-end
-
-function ModeContext.is(value)
- return is_record(value, "ModeContext")
-end
-
-function ModeContext.equal(left, right)
- require_record(left, "ModeContext", "left")
- require_record(right, "ModeContext", "right")
- return left.key == right.key
-end
-
-function ModeContext:to_key()
- return self.key
-end
-
-local Selection = {}
-M.Selection = Selection
-
-register_type("Selection", Selection, function(data)
- return data.active and ("selection:" .. data.kind.value) or "selection:none"
-end, function(left, right)
- return left.active == right.active
- and left.kind == right.kind
- and left.anchor == right.anchor
- and left.focus == right.focus
- and left.option == right.option
-end)
-
-function Selection.new(options)
- if Selection.is(options) then
- return options
- end
- if type(options) ~= "table" then
- fail("selection options must be a table", 2)
- end
-
- local active = require_boolean(options.active, "selection.active")
- local kind = M.SelectionKind.from_string(options.kind)
- local option = M.SelectionOption.from_string(options.option or "inclusive")
- local anchor = options.anchor
- local focus = options.focus
-
- if active then
- if kind == M.SelectionKind.NONE then
- fail("an active selection must have a selection kind", 2)
- end
- anchor = Position.coerce(anchor)
- focus = Position.coerce(focus)
- else
- if kind ~= M.SelectionKind.NONE then
- fail("an inactive selection must use the none kind", 2)
- end
- if anchor ~= nil or focus ~= nil then
- fail("an inactive selection must have empty endpoints", 2)
- end
- end
-
- return new_record("Selection", {
- active = active,
- kind = kind,
- anchor = anchor,
- focus = focus,
- option = option,
- })
-end
-
-function Selection.inactive(option)
- return Selection.new({
- active = false,
- kind = M.SelectionKind.NONE,
- option = option or M.SelectionOption.INCLUSIVE,
- })
-end
-
-function Selection.active(kind, anchor, focus, option)
- return Selection.new({
- active = true,
- kind = kind,
- anchor = anchor,
- focus = focus,
- option = option or M.SelectionOption.INCLUSIVE,
- })
-end
-
-function Selection.is(value)
- return is_record(value, "Selection")
-end
-
-function Selection:with_focus(focus, kind)
- if not self.active then
- fail("selection must be active", 2)
- end
- return Selection.active(kind or self.kind, self.anchor, focus, self.option)
-end
-
-function Selection:to_table()
- return {
- active = self.active,
- kind = self.kind.value,
- anchor = self.anchor and self.anchor:to_table() or nil,
- focus = self.focus and self.focus:to_table() or nil,
- option = self.option.value,
- }
-end
-
-local TextSnapshot = {}
-M.TextSnapshot = TextSnapshot
-local text_lines = setmetatable({}, { __mode = "k" })
-
-register_type("TextSnapshot", TextSnapshot, function(data)
- return "text:" .. tostring(data.line_count) .. " lines"
-end, function(left, right)
- if left.line_count ~= right.line_count then
- return false
- end
- local left_lines = text_lines[left.identity]
- local right_lines = text_lines[right.identity]
- for index = 1, left.line_count do
- if left_lines[index] ~= right_lines[index] then
- return false
- end
- end
- return true
-end)
-
-function TextSnapshot.new(lines)
- if TextSnapshot.is(lines) then
- return lines
- end
- if type(lines) ~= "table" or #lines < 1 then
- fail("text lines must be a nonempty list", 2)
- end
- local copy = {}
- for index = 1, #lines do
- if type(lines[index]) ~= "string" then
- fail("each text line must be a string", 2)
- end
- copy[index] = lines[index]
- end
- local identity = {}
- text_lines[identity] = copy
- return new_record("TextSnapshot", {
- identity = identity,
- line_count = #copy,
- })
-end
-
-function TextSnapshot.is(value)
- return is_record(value, "TextSnapshot")
-end
-
-function TextSnapshot:line(line_number)
- if not is_integer(line_number) or line_number < 1 or line_number > self.line_count then
- fail("line_number must identify a line in the text snapshot", 2)
- end
- return text_lines[self.identity][line_number]
-end
-
-function TextSnapshot:lines()
- local result = {}
- local source = text_lines[self.identity]
- for index = 1, self.line_count do
- result[index] = source[index]
- end
- return result
-end
-
-function TextSnapshot:to_table()
- return { lines = self:lines() }
-end
-
-local MacroState = {}
-M.MacroState = MacroState
-
-register_type("MacroState", MacroState, function(data)
- return data.executing and ("macro:" .. data.register) or "macro:inactive"
-end, function(left, right)
- return left.register == right.register
-end)
-
-function MacroState.new(register)
- if MacroState.is(register) then
- return register
- end
- if register == "" then
- register = nil
- end
- if register ~= nil then
- require_string(register, "macro register", false)
- end
- return new_record("MacroState", {
- register = register,
- executing = register ~= nil,
- })
-end
-
-function MacroState.is(value)
- return is_record(value, "MacroState")
-end
-
-local FoldState = {}
-M.FoldState = FoldState
-local fold_policies = setmetatable({}, { __mode = "k" })
-
-register_type("FoldState", FoldState, function(data)
- return "folds:" .. tostring(data.closed_levels)
-end)
-
-function FoldState.new(open_policy, closed_levels)
- if FoldState.is(open_policy) and closed_levels == nil then
- return open_policy
- end
- if type(open_policy) ~= "table" then
- fail("fold open policy must be a list", 2)
- end
- require_nonnegative_integer(closed_levels, "closed fold levels")
-
- local identity = {}
- local policies = {}
- local seen = {}
- for index = 1, #open_policy do
- local policy = require_string(open_policy[index], "fold policy item", false)
- if not seen[policy] then
- seen[policy] = true
- policies[#policies + 1] = policy
- end
- end
- fold_policies[identity] = {
- list = policies,
- set = seen,
- }
- return new_record("FoldState", {
- identity = identity,
- closed_levels = closed_levels,
- })
-end
-
-function FoldState.is(value)
- return is_record(value, "FoldState")
-end
-
-function FoldState:opens(policy)
- require_string(policy, "fold policy", false)
- return fold_policies[self.identity].set[policy] == true
-end
-
-function FoldState:policies()
- local result = {}
- local source = fold_policies[self.identity].list
- for index = 1, #source do
- result[index] = source[index]
- end
- return result
-end
-
-local InputPacket = {}
-M.InputPacket = InputPacket
-local packet_bytes = setmetatable({}, { __mode = "k" })
-
-M.InputPacketKind = define_enum("InputPacketKind", {
- TEXT = "text",
- RAW_BYTES = "raw_bytes",
- SPECIAL_KEY = "special_key",
- ERROR = "error",
-})
-
-register_type("InputPacket", InputPacket, function(data)
- return "input:" .. data.kind.value
-end)
-
-function InputPacket.text(text)
- require_string(text, "input text", false)
- return new_record("InputPacket", {
- kind = M.InputPacketKind.TEXT,
- text = text,
- })
-end
-
-local function validated_packet_bytes(bytes, kind)
- if type(bytes) ~= "table" or #bytes < 1 then
- fail(kind .. " bytes must be a nonempty list", 3)
- end
- local copy = {}
- for index = 1, #bytes do
- local byte = bytes[index]
- if not is_integer(byte) or byte < 0 or byte > 255 then
- fail(kind .. " bytes must contain byte values", 3)
- end
- copy[index] = byte
- end
- return copy
-end
-
-local function bytes_from_string(value)
- local bytes = {}
- for index = 1, #value do
- bytes[index] = string.byte(value, index)
- end
- return bytes
-end
-
-local function string_from_bytes(bytes)
- local characters = {}
- for index = 1, #bytes do
- characters[index] = string.char(bytes[index])
- end
- return table.concat(characters)
-end
-
-function InputPacket.raw_bytes(bytes)
- local identity = {}
- packet_bytes[identity] = validated_packet_bytes(bytes, "raw input")
- return new_record("InputPacket", {
- kind = M.InputPacketKind.RAW_BYTES,
- identity = identity,
- })
-end
-
-function InputPacket.special_key(name, encoded)
- require_string(name, "special key name", false)
- local bytes
- if type(encoded) == "table" then
- bytes = validated_packet_bytes(encoded, "special key")
- encoded = string_from_bytes(bytes)
- elseif encoded ~= nil then
- require_string(encoded, "encoded special key", false)
- bytes = bytes_from_string(encoded)
- end
- local identity
- if bytes ~= nil then
- identity = {}
- packet_bytes[identity] = bytes
- end
- return new_record("InputPacket", {
- kind = M.InputPacketKind.SPECIAL_KEY,
- name = name,
- encoded = encoded,
- identity = identity,
- })
-end
-
-function InputPacket.error(message)
- require_string(message, "input error message", false)
- return new_record("InputPacket", {
- kind = M.InputPacketKind.ERROR,
- message = message,
- })
-end
-
-function InputPacket.from_table(packet)
- if InputPacket.is(packet) then
- return packet
- end
- if type(packet) ~= "table" then
- fail("input packet must be an InputPacket or packet table", 2)
- end
- local kind = M.InputPacketKind.from_string(packet.kind)
- if kind == M.InputPacketKind.TEXT then
- return InputPacket.text(packet.text)
- end
- if kind == M.InputPacketKind.RAW_BYTES then
- return InputPacket.raw_bytes(packet.bytes)
- end
- if kind == M.InputPacketKind.SPECIAL_KEY then
- return InputPacket.special_key(packet.name, packet.bytes or packet.encoded)
- end
- return InputPacket.error(packet.message)
-end
-
-function InputPacket.is(value)
- return is_record(value, "InputPacket")
-end
-
-function InputPacket:bytes()
- if self.kind ~= M.InputPacketKind.RAW_BYTES
- and self.kind ~= M.InputPacketKind.SPECIAL_KEY
- then
- return nil
- end
- local source = self.identity and packet_bytes[self.identity] or nil
- if source == nil then
- return nil
- end
- local result = {}
- for index = 1, #source do
- result[index] = source[index]
- end
- return result
-end
-
-function InputPacket:to_table()
- local result = { kind = self.kind.value }
- if self.kind == M.InputPacketKind.TEXT then
- result.text = self.text
- elseif self.kind == M.InputPacketKind.RAW_BYTES then
- result.bytes = self:bytes()
- elseif self.kind == M.InputPacketKind.SPECIAL_KEY then
- result.name = self.name
- result.bytes = self:bytes()
- else
- result.message = self.message
- end
- return result
-end
-
-local TargetValue = {}
-M.TargetValue = TargetValue
-
-register_type("TargetValue", TargetValue, function(data)
- return "target:" .. data.kind.value .. ":" .. tostring(data.first_code)
-end, function(left, right)
- return left.kind == right.kind
- and left.value == right.value
- and left.first_code == right.first_code
-end)
-
-function TargetValue.character(value, first_code)
- require_string(value, "target character", false)
- require_nonnegative_integer(first_code, "target first code")
- return new_record("TargetValue", {
- kind = M.TargetKind.CHARACTER,
- value = value,
- first_code = first_code,
- })
-end
-
-function TargetValue.special_key(value, first_code)
- require_string(value, "encoded special key", false)
- first_code = first_code or string.byte(value, 1)
- require_nonnegative_integer(first_code, "target first code")
- if first_code ~= 0x80 then
- fail("an encoded special key must start with hexadecimal 80", 2)
- end
- return new_record("TargetValue", {
- kind = M.TargetKind.SPECIAL_KEY,
- value = value,
- first_code = first_code,
- })
-end
-
-function TargetValue.code_fallback(first_code)
- first_code = first_code or 0
- require_nonnegative_integer(first_code, "fallback character code")
- return new_record("TargetValue", {
- kind = M.TargetKind.CODE_FALLBACK,
- value = "",
- first_code = first_code,
- })
-end
-
-function TargetValue.from_table(target)
- if TargetValue.is(target) then
- return target
- end
- if type(target) ~= "table" then
- fail("target must be a TargetValue or target table", 2)
- end
- local kind = M.TargetKind.from_string(target.kind)
- if kind == M.TargetKind.CHARACTER then
- return TargetValue.character(target.value, target.first_code)
- end
- if kind == M.TargetKind.SPECIAL_KEY then
- return TargetValue.special_key(target.value, target.first_code)
- end
- return TargetValue.code_fallback(target.first_code)
-end
-
-function TargetValue.is(value)
- return is_record(value, "TargetValue")
-end
-
-function TargetValue:to_table()
- return {
- kind = self.kind.value,
- value = self.value,
- first_code = self.first_code,
- }
-end
-
-local TargetPlan = {}
-M.TargetPlan = TargetPlan
-
-register_type("TargetPlan", TargetPlan, function(data)
- return "target-plan:" .. data.kind.value
-end)
-
-function TargetPlan.new(options)
- if TargetPlan.is(options) then
- return options
- end
- if type(options) ~= "table" then
- fail("target plan options must be a table", 2)
- end
- local target = require_record(options.target, "TargetValue", "target plan target")
- local kind = M.TargetPlanKind.from_string(options.kind)
- local case_mode = M.CaseMode.from_string(options.case_mode)
- if type(options.matcher) ~= "function" then
- fail("target plan matcher must be a function", 2)
- end
- return new_record("TargetPlan", {
- target = target,
- kind = kind,
- case_mode = case_mode,
- matcher = options.matcher,
- })
-end
-
-function TargetPlan.is(value)
- return is_record(value, "TargetPlan")
-end
-
-function TargetPlan:matches(...)
- return self.matcher(...)
-end
-
-function TargetPlan:matches_at(text_view, position)
- if type(text_view) ~= "table" or type(text_view.character_at) ~= "function" then
- fail("target plan match requires a text view", 2)
- end
- position = Position.coerce(position)
- return self.matcher(text_view:character_at(position), position, text_view)
-end
-
-function TargetPlan:to_table()
- return {
- target = self.target:to_table(),
- kind = self.kind.value,
- case_mode = self.case_mode.value,
- }
-end
-
-local ResolvedMotionPlan = {}
-M.ResolvedMotionPlan = ResolvedMotionPlan
-
-register_type("ResolvedMotionPlan", ResolvedMotionPlan, function(data)
- return "motion-plan:" .. data.descriptor.value
-end)
-
-function ResolvedMotionPlan.new(options)
- if ResolvedMotionPlan.is(options) then
- return options
- end
- if type(options) ~= "table" then
- fail("resolved motion plan options must be a table", 2)
- end
- return new_record("ResolvedMotionPlan", {
- target_plan = require_record(options.target_plan, "TargetPlan", "target plan"),
- descriptor = Descriptor.from_string(options.descriptor),
- search_scope = M.SearchScope.from_string(options.search_scope),
- endpoint_policy = M.EndpointPolicy.from_string(options.endpoint_policy),
- })
-end
-
-function ResolvedMotionPlan.is(value)
- return is_record(value, "ResolvedMotionPlan")
-end
-
-function ResolvedMotionPlan:to_table()
- return {
- target_plan = self.target_plan:to_table(),
- descriptor = self.descriptor.value,
- search_scope = self.search_scope.value,
- endpoint_policy = self.endpoint_policy.value,
- }
-end
-
-local MotionRequest = {}
-M.MotionRequest = MotionRequest
-
-register_type("MotionRequest", MotionRequest, function(data)
- return "motion-request:" .. data.descriptor.value
-end)
-
-function MotionRequest.new(options)
- if MotionRequest.is(options) then
- return options
- end
- if type(options) ~= "table" then
- fail("motion request options must be a table", 2)
- end
- if options.policy == nil then
- fail("motion request policy is required", 2)
- end
- return new_record("MotionRequest", {
- context = require_record(options.context, "ModeContext", "motion context"),
- origin = Position.coerce(options.origin),
- descriptor = Descriptor.from_string(options.descriptor),
- target = require_record(options.target, "TargetValue", "motion target"),
- count = Count.new(options.count),
- policy = options.policy,
- first_move = require_boolean(options.first_move, "first_move"),
- })
-end
-
-function MotionRequest.is(value)
- return is_record(value, "MotionRequest")
-end
-
-local SearchOutcome = {}
-M.SearchOutcome = SearchOutcome
-
-register_type("SearchOutcome", SearchOutcome, function(data)
- return "search:" .. data.status.value
-end)
-
-local function new_search_outcome(status, endpoint, successful_steps)
- status = M.SearchStatus.from_string(status)
- endpoint = Position.coerce(endpoint)
- require_nonnegative_integer(successful_steps, "successful_steps")
-
- if status == M.SearchStatus.COMPLETE and successful_steps < 1 then
- fail("a complete search must contain a successful step", 3)
- end
- if status == M.SearchStatus.BOUNDARY_AFTER_PARTIAL and successful_steps < 1 then
- fail("a partial search must contain a successful step", 3)
- end
- if status == M.SearchStatus.BOUNDARY_BEFORE_ANY and successful_steps ~= 0 then
- fail("a boundary-before-any search must contain zero successful steps", 3)
- end
-
- return new_record("SearchOutcome", {
- status = status,
- endpoint = endpoint,
- successful_steps = successful_steps,
- complete = status == M.SearchStatus.COMPLETE,
- })
-end
-
-function SearchOutcome.new(options)
- if SearchOutcome.is(options) then
- return options
- end
- if type(options) ~= "table" then
- fail("search outcome options must be a table", 2)
- end
- return new_search_outcome(options.status, options.endpoint, options.successful_steps)
-end
-
-function SearchOutcome.complete(endpoint, successful_steps)
- return new_search_outcome(M.SearchStatus.COMPLETE, endpoint, successful_steps)
-end
-
-function SearchOutcome.boundary_after_partial(endpoint, successful_steps)
- return new_search_outcome(
- M.SearchStatus.BOUNDARY_AFTER_PARTIAL,
- endpoint,
- successful_steps
- )
-end
-
-function SearchOutcome.boundary_before_any(origin)
- return new_search_outcome(M.SearchStatus.BOUNDARY_BEFORE_ANY, origin, 0)
-end
-
-function SearchOutcome.is(value)
- return is_record(value, "SearchOutcome")
-end
-
-function SearchOutcome:to_table()
- return {
- status = self.status.value,
- endpoint = self.endpoint:to_table(),
- successful_steps = self.successful_steps,
- complete = self.complete,
- }
-end
-
-local DotPayload = {}
-M.DotPayload = DotPayload
-
-register_type("DotPayload", DotPayload, function(data)
- return "dot:" .. data.descriptor.value
-end, function(left, right)
- return left.descriptor == right.descriptor and left.target == right.target
-end)
-
-function DotPayload.new(descriptor, target)
- if DotPayload.is(descriptor) and target == nil then
- return descriptor
- end
- return new_record("DotPayload", {
- descriptor = Descriptor.from_string(descriptor),
- target = require_record(target, "TargetValue", "dot target"),
- })
-end
-
-function DotPayload.is(value)
- return is_record(value, "DotPayload")
-end
-
-function DotPayload:to_table()
- return {
- descriptor = self.descriptor.value,
- target = self.target:to_table(),
- }
-end
-
-local ExplicitRepeatRequest = {}
-M.ExplicitRepeatRequest = ExplicitRepeatRequest
-
-register_type("ExplicitRepeatRequest", ExplicitRepeatRequest, function(data)
- if data.neutral then
- return "explicit-repeat:neutral"
- end
- return "explicit-repeat:" .. data.descriptor.value
-end, function(left, right)
- return left.neutral == right.neutral
- and left.descriptor == right.descriptor
- and left.target == right.target
-end)
-
-local neutral_explicit_repeat_request
-
-function ExplicitRepeatRequest.new(descriptor, target)
- if ExplicitRepeatRequest.is(descriptor) and target == nil then
- return descriptor
- end
- descriptor = Descriptor.from_string(descriptor)
- target = require_record(target, "TargetValue", "explicit repeat target")
- return new_record("ExplicitRepeatRequest", {
- descriptor = descriptor,
- effective_descriptor = descriptor,
- target = target,
- neutral = false,
- })
-end
-
-function ExplicitRepeatRequest.neutral()
- if neutral_explicit_repeat_request == nil then
- neutral_explicit_repeat_request = new_record("ExplicitRepeatRequest", {
- descriptor = nil,
- effective_descriptor = nil,
- target = nil,
- neutral = true,
- })
- end
- return neutral_explicit_repeat_request
-end
-
-function ExplicitRepeatRequest.is(value)
- return is_record(value, "ExplicitRepeatRequest")
-end
-
-function ExplicitRepeatRequest:is_neutral()
- return self.neutral
-end
-
-function ExplicitRepeatRequest:to_table()
- if self.neutral then
- return { neutral = true }
- end
- return {
- descriptor = self.descriptor.value,
- target = self.target:to_table(),
- neutral = false,
- }
-end
-
-local ActionOutcome = {}
-M.ActionOutcome = ActionOutcome
-
-register_type("ActionOutcome", ActionOutcome, function(data)
- return "action:" .. data.kind.value
-end)
-
-local function new_action_outcome(options)
- local kind = M.ActionKind.from_string(options.kind)
- local position = Position.coerce(options.position)
- local search_outcome = options.search_outcome
- local descriptor = options.effective_descriptor
- local dot_payload = options.dot_payload
-
- if search_outcome ~= nil then
- require_record(search_outcome, "SearchOutcome", "search outcome")
- end
- if descriptor ~= nil then
- descriptor = Descriptor.from_string(descriptor)
- end
- if dot_payload ~= nil then
- require_record(dot_payload, "DotPayload", "dot payload")
- end
- if options.diagnostic ~= nil then
- require_string(options.diagnostic, "diagnostic", false)
- end
-
- if kind == M.ActionKind.MOVEMENT then
- if search_outcome == nil or not search_outcome.complete then
- fail("a movement action requires a complete search outcome", 3)
- end
- elseif kind == M.ActionKind.FAILED_SEARCH then
- if search_outcome == nil or search_outcome.complete then
- fail("a failed-search action requires an incomplete search outcome", 3)
- end
- elseif search_outcome ~= nil then
- fail("only movement and failed-search actions can contain a search outcome", 3)
- end
-
- if kind == M.ActionKind.ERROR and options.diagnostic == nil then
- fail("an error action requires a diagnostic", 3)
- end
-
- local complete
- if search_outcome ~= nil then
- complete = search_outcome.complete
- end
-
- return new_record("ActionOutcome", {
- kind = kind,
- position = position,
- search_outcome = search_outcome,
- complete = complete,
- successful_steps = search_outcome and search_outcome.successful_steps or 0,
- effective_descriptor = descriptor,
- dot_payload = dot_payload,
- diagnostic = options.diagnostic,
- })
-end
-
-function ActionOutcome.new(options)
- if ActionOutcome.is(options) then
- return options
- end
- if type(options) ~= "table" then
- fail("action outcome options must be a table", 2)
- end
- return new_action_outcome(options)
-end
-
-function ActionOutcome.from_search(search_outcome, descriptor, dot_payload)
- require_record(search_outcome, "SearchOutcome", "search outcome")
- return new_action_outcome({
- kind = search_outcome.complete and M.ActionKind.MOVEMENT or M.ActionKind.FAILED_SEARCH,
- position = search_outcome.endpoint,
- search_outcome = search_outcome,
- effective_descriptor = descriptor,
- dot_payload = dot_payload,
- })
-end
-
-local function simple_action(kind, position, diagnostic)
- return new_action_outcome({
- kind = kind,
- position = position,
- diagnostic = diagnostic,
- })
-end
-
-function ActionOutcome.neutral(position)
- return simple_action(M.ActionKind.NEUTRAL, position)
-end
-
-function ActionOutcome.escape(position)
- return simple_action(M.ActionKind.ESCAPE, position)
-end
-
-function ActionOutcome.empty(position)
- return simple_action(M.ActionKind.EMPTY, position)
-end
-
-function ActionOutcome.error(position, diagnostic)
- return simple_action(M.ActionKind.ERROR, position, diagnostic)
-end
-
-function ActionOutcome.is(value)
- return is_record(value, "ActionOutcome")
-end
-
-function ActionOutcome:to_table()
- return {
- kind = self.kind.value,
- position = self.position:to_table(),
- complete = self.complete,
- successful_steps = self.successful_steps,
- effective_descriptor = self.effective_descriptor and self.effective_descriptor.value or nil,
- dot_payload = self.dot_payload and self.dot_payload:to_table() or nil,
- diagnostic = self.diagnostic,
- }
-end
-
-return M