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
|
local domain = require("clever_f.domain")
local text_topology = require("clever_f.text_topology")
local M = {}
local DestinationEngine = {}
M.DestinationEngine = DestinationEngine
local engines = setmetatable({}, { __mode = "k" })
local function fail(message, level)
error(message, (level or 1) + 1)
end
local engine_metatable = {
__index = DestinationEngine,
__newindex = function()
fail("DestinationEngine values are immutable", 2)
end,
__tostring = function()
return "destination-engine"
end,
__metatable = "clever_f.destination_engine.DestinationEngine",
}
function DestinationEngine.new()
local engine = setmetatable({}, engine_metatable)
engines[engine] = true
return engine
end
function DestinationEngine.is(value)
return type(value) == "table" and engines[value] == true
end
local function calculation_inputs(view, origin, plan, count, first_move)
if not text_topology.TextView.is(view) then
fail("destination calculation view must be a TextView", 3)
end
origin = domain.Position.coerce(origin)
if not view:is_valid_cursor_position(origin) then
fail("destination calculation origin must be a valid cursor position", 3)
end
if not domain.ResolvedMotionPlan.is(plan) then
fail("destination calculation plan must be a ResolvedMotionPlan", 3)
end
count = domain.Count.new(count)
if type(first_move) ~= "boolean" then
fail("destination calculation first_move must be a Boolean", 3)
end
local bounds = view:match_start_bounds(plan.search_scope, origin)
return {
view = view,
origin = origin,
plan = plan,
count = count,
first_move = first_move,
bounds = bounds,
}
end
local function candidate_starts(request, origin)
return request.view:iter_strict(
origin,
request.plan.descriptor.direction,
request.bounds
)
end
local function next_matching_start(request, origin)
local candidates = candidate_starts(request, origin)
while true do
local position, character = candidates()
if position == nil then
return nil
end
if request.plan.target_plan:matches(character, position, request.view) then
return position
end
end
end
local function regular_destination(request, target_position)
local descriptor = request.plan.descriptor
if descriptor.family == domain.Family.FIND then
return target_position
end
if descriptor.direction == domain.Direction.FORWARD then
return request.view:predecessor(target_position)
end
return target_position
end
local function target_destination(request, target_position)
return regular_destination(request, target_position)
end
function DestinationEngine:calculate(view, origin, plan, count, first_move)
local request = calculation_inputs(view, origin, plan, count, first_move)
local target_position = next_matching_start(request, request.origin)
if target_position == nil then
return domain.SearchOutcome.boundary_before_any(request.origin)
end
local destination = target_destination(request, target_position)
return domain.SearchOutcome.complete(destination, 1)
end
function M.new()
return DestinationEngine.new()
end
function M.calculate(view, origin, plan, count, first_move)
return DestinationEngine.new():calculate(view, origin, plan, count, first_move)
end
M.resolve = M.calculate
M.search = M.calculate
setmetatable(M, {
__call = function()
return DestinationEngine.new()
end,
})
return M
|