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 case_policy = require("clever_f.case_policy")
local domain = require("clever_f.domain")
local text_topology = require("clever_f.text_topology")
local M = {}
local DirectPreviewPlanner = {}
DirectPreviewPlanner.__index = DirectPreviewPlanner
M.DirectPreviewPlanner = DirectPreviewPlanner
local planner_records = setmetatable({}, { __mode = "k" })
local function fail(message, level)
error(message, (level or 1) + 1)
end
local function require_view(view)
if not text_topology.TextView.is(view) then
fail("direct preview text must be a TextView", 3)
end
return view
end
local function require_case_resolver(options)
options = options or {}
if type(options) == "function" then
options = { lowercase = options }
end
if type(options) ~= "table" then
fail("DirectPreviewPlanner options must be a table", 3)
end
local resolver = options.case_resolver
if resolver == nil then
resolver = case_policy.new({ lowercase = options.lowercase })
end
if type(resolver) ~= "table" or type(resolver.lowercase) ~= "function" then
fail("DirectPreviewPlanner case resolver must provide lowercase", 3)
end
return resolver
end
function DirectPreviewPlanner.new(options)
if DirectPreviewPlanner.is(options) then
return options
end
local planner = setmetatable({}, DirectPreviewPlanner)
planner_records[planner] = {
case_resolver = require_case_resolver(options),
}
return planner
end
function DirectPreviewPlanner.is(value)
return type(value) == "table" and planner_records[value] ~= nil
end
function DirectPreviewPlanner:scan_current_line(view, origin, direction)
require_view(view)
origin = domain.Position.coerce(origin)
direction = domain.Direction.from_string(direction)
local records = {}
for position, character, span in view:iter_strict(
origin,
direction,
domain.SearchScope.CURRENT_LINE
) do
records[#records + 1] = {
position = position,
character = character,
span = span,
}
end
return records
end
function M.normalize_count(count)
return domain.Count.to_number(count)
end
function DirectPreviewPlanner:normalize_count(count)
return M.normalize_count(count)
end
function M.direction_for_descriptor(descriptor)
descriptor = domain.Descriptor.from_string(descriptor)
if domain.Descriptor.is_uppercase(descriptor) then
return domain.Direction.BACKWARD
end
return domain.Direction.FORWARD
end
function DirectPreviewPlanner:scan_for_descriptor(view, origin, descriptor)
return self:scan_current_line(
view,
origin,
M.direction_for_descriptor(descriptor)
)
end
local function increment_counter(counters, character)
local value = (counters[character] or 0) + 1
counters[character] = value
return value
end
local function increment_smart_counters(counters, character)
return increment_counter(counters, character)
end
local function require_case_setting(settings, name)
local value = settings[name]
if value == nil then
return false
end
if type(value) ~= "boolean" then
fail("direct preview " .. name .. " must be a Boolean", 3)
end
return value
end
function DirectPreviewPlanner:plan(view, origin, descriptor, count, settings)
settings = settings or {}
if type(settings) ~= "table" then
fail("direct preview case settings must be a table", 2)
end
local ignore_case = require_case_setting(settings, "ignore_case")
local smart_case = require_case_setting(settings, "smart_case")
local selected_occurrence = self:normalize_count(count)
local counters = {}
local positions = {}
local resolver = planner_records[self].case_resolver
for _, record in ipairs(self:scan_for_descriptor(view, origin, descriptor)) do
local selected
if ignore_case then
selected = increment_counter(
counters,
resolver:lowercase(record.character)
) == selected_occurrence
elseif smart_case then
selected = increment_smart_counters(
counters,
record.character
) == selected_occurrence
else
selected = increment_counter(
counters,
record.character
) == selected_occurrence
end
if selected then
positions[#positions + 1] = record.position
end
end
return positions
end
function M.new(options)
return DirectPreviewPlanner.new(options)
end
function M.scan_current_line(view, origin, direction)
return DirectPreviewPlanner.new():scan_current_line(view, origin, direction)
end
function M.scan_for_descriptor(view, origin, descriptor)
return DirectPreviewPlanner.new():scan_for_descriptor(view, origin, descriptor)
end
function M.plan(view, origin, descriptor, count, settings, options)
return DirectPreviewPlanner.new(options):plan(
view,
origin,
descriptor,
count,
settings
)
end
setmetatable(M, {
__call = function(_, options)
return DirectPreviewPlanner.new(options)
end,
})
return M
|