summaryrefslogtreecommitdiff
path: root/lua/clever_tee/composition_root.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/clever_tee/composition_root.lua')
-rw-r--r--lua/clever_tee/composition_root.lua232
1 files changed, 232 insertions, 0 deletions
diff --git a/lua/clever_tee/composition_root.lua b/lua/clever_tee/composition_root.lua
new file mode 100644
index 0000000..4741bd4
--- /dev/null
+++ b/lua/clever_tee/composition_root.lua
@@ -0,0 +1,232 @@
+local action_facade = require("clever_tee.action_facade")
+local capabilities = require("clever_tee.capabilities")
+local feedback_service = require("clever_tee.feedback_service")
+local policy = require("clever_tee.policy")
+local sequence_coordinator = require("clever_tee.sequence_coordinator")
+local sequence_state = require("clever_tee.sequence_state")
+local state_transitions = require("clever_tee.state_transitions")
+
+local M = {}
+local CompositionRoot = {}
+CompositionRoot.__index = CompositionRoot
+M.CompositionRoot = CompositionRoot
+M.ACTION_NAMES = {
+ "StartFindForward",
+ "StartFindBackward",
+ "StartTillForward",
+ "StartTillBackward",
+ "Reset",
+ "RepeatSameDirection",
+ "RepeatOppositeDirection",
+}
+M.DEFAULT_MAPPING_MODES = { "n", "x", "o" }
+M.DEFAULT_MAPPING_OPTIONS = {
+ silent = true,
+ remap = false,
+ preserve_count = true,
+}
+M.DEFAULT_MAPPINGS = {
+ { lhs = "f", action = "StartFindForward" },
+ { lhs = "F", action = "StartFindBackward" },
+ { lhs = "t", action = "StartTillForward" },
+ { lhs = "T", action = "StartTillBackward" },
+}
+
+local ACTION_INVOCATIONS = {
+ StartFindForward = function(facade)
+ return facade:start_find_forward()
+ end,
+ StartFindBackward = function(facade)
+ return facade:start_find_backward()
+ end,
+ StartTillForward = function(facade)
+ return facade:start_till_forward()
+ end,
+ StartTillBackward = function(facade)
+ return facade:start_till_backward()
+ end,
+ Reset = function(facade)
+ return facade:reset()
+ end,
+ RepeatSameDirection = function(facade)
+ return facade:repeat_same_direction()
+ end,
+ RepeatOppositeDirection = function(facade)
+ return facade:repeat_opposite_direction()
+ end,
+}
+
+local root_records = setmetatable({}, { __mode = "k" })
+
+local function fail(message, level)
+ error(message, (level or 1) + 1)
+end
+
+local function normalize_options(options)
+ if type(options) ~= "table" then
+ fail("CompositionRoot options must be a table", 3)
+ end
+ if options.host == nil then
+ return { host = options }
+ end
+ return options
+end
+
+function CompositionRoot.new(options)
+ if CompositionRoot.is(options) then
+ return options
+ end
+ options = normalize_options(options)
+ local host = capabilities.assert_implements(options.host)
+ local state = sequence_state.new()
+ local transitions = options.transitions
+ or options.state_transitions
+ or state_transitions.new(state)
+ local policy_service = options.policy
+ or options.policy_service
+ or policy.new(host)
+ local feedback = options.feedback
+ or options.feedback_service
+ or feedback_service.new({
+ host = host,
+ state = state,
+ transitions = transitions,
+ policy = policy_service,
+ })
+ local coordinator = options.coordinator
+ or options.sequence_coordinator
+ or sequence_coordinator.new({
+ host = host,
+ state = state,
+ transitions = transitions,
+ policy = policy_service,
+ feedback = feedback,
+ })
+ local facade = options.facade
+ or options.action_facade
+ or action_facade.new({ coordinator = coordinator })
+
+ local root = setmetatable({}, CompositionRoot)
+ root_records[root] = {
+ host = host,
+ state = state,
+ transitions = transitions,
+ policy = policy_service,
+ feedback = feedback,
+ coordinator = coordinator,
+ facade = facade,
+ activation = nil,
+ last_highlight_refresh = nil,
+ }
+ return root
+end
+
+function CompositionRoot.is(value)
+ return type(value) == "table" and root_records[value] ~= nil
+end
+
+local function register_logical_actions(record)
+ local registrations = {}
+ for _, name in ipairs(M.ACTION_NAMES) do
+ local invoke = ACTION_INVOCATIONS[name]
+ registrations[name] = record.host:register_action(name, function(...)
+ return invoke(record.facade, ...)
+ end)
+ end
+ return registrations
+end
+
+local function register_default_mappings(record, setup)
+ local registrations = {}
+ if not setup.install_default_mappings then
+ return registrations
+ end
+ for _, mapping in ipairs(M.DEFAULT_MAPPINGS) do
+ registrations[mapping.lhs] = record.host:register_mapping(
+ M.DEFAULT_MAPPING_MODES,
+ mapping.lhs,
+ mapping.action,
+ M.DEFAULT_MAPPING_OPTIONS
+ )
+ end
+ return registrations
+end
+
+function CompositionRoot:activate()
+ local record = root_records[self]
+ if record.activation == nil then
+ local setup = record.policy:capture_activation()
+ local feedback_activation = record.feedback:activate()
+ local highlights = record.feedback:evaluate_highlights()
+ local colorscheme_registration = record.host:register_events(
+ "ColorScheme",
+ function()
+ record.last_highlight_refresh = record.feedback:evaluate_highlights()
+ end,
+ { owner = "clever_tee", lifecycle = "colorscheme" }
+ )
+ local actions = register_logical_actions(record)
+ record.activation = {
+ state = record.state,
+ setup = setup,
+ feedback = feedback_activation,
+ highlights = highlights,
+ colorscheme_registration = colorscheme_registration,
+ actions = actions,
+ mappings = register_default_mappings(record, setup),
+ }
+ end
+ return record.activation
+end
+
+function CompositionRoot:last_highlight_refresh()
+ return root_records[self].last_highlight_refresh
+end
+
+function CompositionRoot:host()
+ return root_records[self].host
+end
+
+function CompositionRoot:state()
+ return root_records[self].state
+end
+
+function CompositionRoot:transitions()
+ return root_records[self].transitions
+end
+
+function CompositionRoot:policy()
+ return root_records[self].policy
+end
+
+function CompositionRoot:feedback()
+ return root_records[self].feedback
+end
+
+function CompositionRoot:coordinator()
+ return root_records[self].coordinator
+end
+
+function CompositionRoot:facade()
+ return root_records[self].facade
+end
+
+function CompositionRoot:invoke_descriptor(value)
+ return self:facade():invoke_descriptor(value)
+end
+
+function CompositionRoot:diagnostic_full_reset()
+ return self:facade():diagnostic_full_reset()
+end
+
+function M.new(options)
+ return CompositionRoot.new(options)
+end
+
+setmetatable(M, {
+ __call = function(_, options)
+ return CompositionRoot.new(options)
+ end,
+})
+
+return M