local domain = require("clever_tee.domain") local M = {} local MotionPlanFactory = {} M.MotionPlanFactory = MotionPlanFactory local factory_records = setmetatable({}, { __mode = "k" }) local function fail(message, level) error(message, (level or 1) + 1) end local function normalize_options(options) if options == nil then return {} end if type(options) == "table" and type(options.sample_search) == "function" then return { policy = options } end if type(options) ~= "table" then fail("MotionPlanFactory options must be a table", 3) end return options end local function require_policy(service) if service ~= nil and (type(service) ~= "table" or type(service.sample_search) ~= "function") then fail("MotionPlanFactory policy must provide sample_search", 3) end return service end local factory_metatable = { __index = MotionPlanFactory, __newindex = function() fail("MotionPlanFactory values are immutable", 2) end, __tostring = function() return "motion-plan-factory" end, __metatable = "clever_tee.motion_plan.MotionPlanFactory", } function MotionPlanFactory.new(options) if MotionPlanFactory.is(options) then return options end options = normalize_options(options) local factory = setmetatable({}, factory_metatable) factory_records[factory] = { policy = require_policy(options.policy or options.policy_service), } return factory end function MotionPlanFactory.is(value) return type(value) == "table" and factory_records[value] ~= nil end local function resolved_scope(factory, search_scope) if search_scope == nil then local policy = factory_records[factory].policy if policy ~= nil then search_scope = policy:sample_search().search_scope else search_scope = domain.SearchScope.BUFFER end elseif search_scope == "line" then search_scope = domain.SearchScope.CURRENT_LINE end return domain.SearchScope.from_string(search_scope) end function MotionPlanFactory:build( target_plan, effective_descriptor, search_scope, endpoint_policy ) if not domain.TargetPlan.is(target_plan) then fail("motion target plan must be a TargetPlan", 2) end return domain.ResolvedMotionPlan.new({ target_plan = target_plan, descriptor = effective_descriptor, search_scope = resolved_scope(self, search_scope), endpoint_policy = endpoint_policy or domain.EndpointPolicy.REGULAR, }) end local function selection_option(selection) if selection == nil then return domain.SelectionOption.INCLUSIVE end if domain.Selection.is(selection) then return selection.option end if domain.SelectionOption.is(selection) then return selection end if type(selection) == "table" and selection.option ~= nil then return domain.SelectionOption.from_string(selection.option) end return domain.SelectionOption.from_string(selection) end function M.endpoint_policy(context, selection) context = domain.ModeContext.from_full_mode(context) local option = selection_option(selection) local visual_kind = context.visual_kind if option == domain.SelectionOption.EXCLUSIVE and (visual_kind == domain.SelectionKind.CHARACTER or visual_kind == domain.SelectionKind.LINE) then return domain.EndpointPolicy.VISUAL_EXCLUSIVE end return domain.EndpointPolicy.REGULAR end function MotionPlanFactory:endpoint_policy(context, selection) return M.endpoint_policy(context, selection) end function MotionPlanFactory:build_for_context( target_plan, effective_descriptor, context, selection, search_scope ) return self:build( target_plan, effective_descriptor, search_scope, self:endpoint_policy(context, selection) ) end function M.new(options) return MotionPlanFactory.new(options) end function M.build( target_plan, effective_descriptor, search_scope, endpoint_policy, options ) return MotionPlanFactory.new(options):build( target_plan, effective_descriptor, search_scope, endpoint_policy ) end function M.build_for_context( target_plan, effective_descriptor, context, selection, search_scope, options ) return MotionPlanFactory.new(options):build_for_context( target_plan, effective_descriptor, context, selection, search_scope ) end M.create = M.build M.resolve = M.build M.for_context = M.build_for_context setmetatable(M, { __call = function(_, options) return MotionPlanFactory.new(options) end, }) return M