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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
|
local destination_engine = require("clever_f.destination_engine")
local domain = require("clever_f.domain")
local text_topology = require("clever_f.text_topology")
local M = {}
local MotionExecutor = {}
M.MotionExecutor = MotionExecutor
M.ExecutionPath = {
VISUAL = "visual",
COMMAND = "command",
}
local executor_records = setmetatable({}, { __mode = "k" })
local function fail(message, level)
error(message, (level or 1) + 1)
end
function M.execution_path(context)
context = domain.ModeContext.from_full_mode(context)
if context.visual_kind ~= nil then
return M.ExecutionPath.VISUAL
end
return M.ExecutionPath.COMMAND
end
local function copy_options(options)
local result = {}
for key, value in pairs(options or {}) do
result[key] = value
end
return result
end
local function normalize_options(options, dependencies)
if MotionExecutor.is(options) and dependencies == nil then
return options
end
if type(options) ~= "table" then
fail("MotionExecutor options must be a table", 3)
end
if options.host ~= nil then
if dependencies ~= nil then
fail("MotionExecutor dependencies must be part of its options", 3)
end
return options
end
local result = copy_options(dependencies)
result.host = options
return result
end
local function require_host(host)
if type(host) ~= "table"
or type(host.read_cursor) ~= "function"
or type(host.apply_cursor) ~= "function"
then
fail("MotionExecutor host must provide cursor movement", 3)
end
return host
end
local function require_destination_engine(engine)
engine = engine or destination_engine.new()
if type(engine) ~= "table" or type(engine.calculate) ~= "function" then
fail("MotionExecutor destination engine must provide calculate", 3)
end
return engine
end
local executor_metatable = {
__index = MotionExecutor,
__newindex = function()
fail("MotionExecutor values are immutable", 2)
end,
__tostring = function()
return "motion-executor"
end,
__metatable = "clever_f.motion_executor.MotionExecutor",
}
function MotionExecutor.new(options, dependencies)
options = normalize_options(options, dependencies)
if MotionExecutor.is(options) then
return options
end
local executor = setmetatable({}, executor_metatable)
executor_records[executor] = {
host = require_host(options.host),
destination_engine = require_destination_engine(
options.destination_engine or options.engine
),
}
return executor
end
function MotionExecutor.is(value)
return type(value) == "table" and executor_records[value] ~= nil
end
local function execution_request(view, context, plan, count, first_move)
if not text_topology.TextView.is(view) then
fail("motion execution view must be a TextView", 3)
end
context = domain.ModeContext.from_full_mode(context)
if not domain.ResolvedMotionPlan.is(plan) then
fail("motion execution plan must be a ResolvedMotionPlan", 3)
end
count = domain.Count.new(count)
if type(first_move) ~= "boolean" then
fail("motion execution first_move must be a Boolean", 3)
end
return {
view = view,
context = context,
plan = plan,
count = count,
first_move = first_move,
}
end
local function calculate(executor, request, origin)
return executor_records[executor].destination_engine:calculate(
request.view,
origin,
request.plan,
request.count,
request.first_move
)
end
function MotionExecutor:_execute_command(request)
local host = executor_records[self].host
local origin = host:read_cursor()
local outcome = calculate(self, request, origin)
if outcome.successful_steps > 0 then
host:apply_cursor(outcome.endpoint)
end
if not outcome.complete then
return domain.ActionOutcome.from_search(outcome, request.plan.descriptor)
end
return domain.ActionOutcome.from_search(outcome, request.plan.descriptor)
end
function MotionExecutor:_execute_visual(request)
local host = executor_records[self].host
local origin = host:read_cursor()
local outcome = calculate(self, request, origin)
return domain.ActionOutcome.from_search(outcome, request.plan.descriptor)
end
function MotionExecutor:execute(view, context, plan, count, first_move)
local request = execution_request(view, context, plan, count, first_move)
if M.execution_path(request.context) == M.ExecutionPath.VISUAL then
return self:_execute_visual(request)
end
return self:_execute_command(request)
end
function M.new(options, dependencies)
return MotionExecutor.new(options, dependencies)
end
function M.execute(host, view, context, plan, count, first_move, dependencies)
return MotionExecutor.new(host, dependencies):execute(
view,
context,
plan,
count,
first_move
)
end
M.run = M.execute
setmetatable(M, {
__call = function(_, options, dependencies)
return MotionExecutor.new(options, dependencies)
end,
})
return M
|