summaryrefslogtreecommitdiff
path: root/lua/clever_f/sequence_coordinator.lua
blob: d1316e60e5abd817e5320216b3130d5140a54ce2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
local domain = require("clever_f.domain")

local M = {}
local SequenceCoordinator = {}
SequenceCoordinator.__index = SequenceCoordinator
M.SequenceCoordinator = SequenceCoordinator

local coordinator_records = setmetatable({}, { __mode = "k" })

local function fail(message, level)
  error(message, (level or 1) + 1)
end

local function descriptor_text(value)
  if domain.Descriptor.is(value) then
    return value.value
  end
  return tostring(value)
end

function M.validate_primary_descriptor(value)
  local descriptor = domain.Descriptor.try_from_string(value)
  if descriptor == nil then
    fail("clever-f: Invalid mapping '" .. descriptor_text(value) .. "'", 2)
  end
  return descriptor
end

function SequenceCoordinator.new(options)
  if SequenceCoordinator.is(options) then
    return options
  end
  if type(options) ~= "table" then
    fail("SequenceCoordinator options must be a table", 2)
  end
  local coordinator = setmetatable({}, SequenceCoordinator)
  coordinator_records[coordinator] = {
    host = options.host or options,
  }
  return coordinator
end

function SequenceCoordinator.is(value)
  return type(value) == "table" and coordinator_records[value] ~= nil
end

function SequenceCoordinator:validate_primary_descriptor(value)
  return M.validate_primary_descriptor(value)
end

local function require_primary_reader(host)
  if type(host) ~= "table"
    or type(host.read_mode) ~= "function"
    or type(host.read_cursor) ~= "function"
    or type(host.read_count) ~= "function"
    or type(host.read_macro_state) ~= "function"
  then
    fail("SequenceCoordinator host must provide primary action state", 3)
  end
  return host
end

function SequenceCoordinator:read_primary_invocation()
  local host = require_primary_reader(coordinator_records[self].host)
  local context = domain.ModeContext.from_full_mode(host:read_mode())
  local position = domain.Position.coerce(host:read_cursor())
  local count = domain.Count.new(host:read_count())
  local macro_state = domain.MacroState.new(host:read_macro_state())
  return {
    context = context,
    position = position,
    origin = position,
    count = count,
    macro_state = macro_state,
  }
end

function SequenceCoordinator:inspect_fold_open_policy(invocation)
  if type(invocation) ~= "table" or not domain.Position.is(invocation.position) then
    fail("fold preflight requires primary invocation state", 2)
  end
  local host = coordinator_records[self].host
  if type(host.read_fold_state) ~= "function" then
    fail("SequenceCoordinator host must provide fold state", 2)
  end
  local fold_state = host:read_fold_state()
  if not domain.FoldState.is(fold_state) then
    fail("SequenceCoordinator host must return FoldState", 2)
  end
  return fold_state
end

local function fold_open_enabled(fold_state)
  return fold_state:opens("horizontal") or fold_state:opens("all")
end

function SequenceCoordinator:open_enclosing_folds(invocation, fold_state)
  if type(invocation) ~= "table" or not domain.Position.is(invocation.position) then
    fail("fold opening requires primary invocation state", 2)
  end
  if not domain.FoldState.is(fold_state) then
    fail("fold opening requires FoldState", 2)
  end
  if not fold_open_enabled(fold_state) then
    return 0
  end

  local host = coordinator_records[self].host
  if type(host.open_fold) ~= "function" then
    fail("SequenceCoordinator host must open folds", 2)
  end
  local opened = 0
  while fold_state.closed_levels > 0 do
    if host:open_fold(invocation.position) ~= true then
      break
    end
    opened = opened + 1
    fold_state = self:inspect_fold_open_policy(invocation)
  end
  return opened
end

function SequenceCoordinator:primary(value)
  local descriptor = self:validate_primary_descriptor(value)
  local invocation = self:read_primary_invocation()
  invocation.fold_state = self:inspect_fold_open_policy(invocation)
  invocation.opened_folds = self:open_enclosing_folds(
    invocation,
    invocation.fold_state
  )
  return descriptor
end

function M.new(options)
  return SequenceCoordinator.new(options)
end

setmetatable(M, {
  __call = function(_, options)
    return SequenceCoordinator.new(options)
  end,
})

return M