fix an issue with multi-line lists:
[lisp-parkour.git] / walker.lua
blob60bfc260a1ab89fc71980a9977d658de60af38e5
1 local M = {}
3 local function finishof(node) return node.finish end
4 local function startof(node) return node.start end
5 local function is_comment(node) return node.is_comment end
7 function M.new(parser, eol_at, bol_at)
9 local function in_quasilist(t, range)
10 return t and t.d and not t.is_list and
11 range.start >= t.start + #t.d and range.finish <= t.finish + 1 - #parser.opposite[t.d]
12 end
14 local function sexp_at(range, true_sexp)
15 local node, parent, nth = parser.tree.sexp_at(range)
16 if not in_quasilist(node, range) or true_sexp then
17 return node, parent, nth
18 else
19 return node.word_at(range), node
20 end
21 end
23 local function find_before_innermost(t, range, key, pred)
24 if t.is_root or (t.d and t.start < range.start) then
25 if t.d and not t.is_list then
26 local keypos = key(t)
27 local start = keypos == startof(t)
28 local finish = keypos == finishof(t)
29 local newpos
30 if finish then
31 newpos = t.finish_before(range.finish)
32 elseif start then
33 newpos = t.start_before(range.start)
34 end
35 if newpos then
36 local word = t.word_at({start = newpos, finish = newpos})
37 if word and pred(word) then
38 return word
39 end
40 end
41 return
42 end
43 local _, nearest_n = t.before(range.start, startof)
44 if nearest_n then
45 for n = nearest_n, 1, -1 do
46 local child = t[n]
47 if child.d and not child.is_empty then
48 local ret = find_before_innermost(child, range, key, pred)
49 if ret then
50 return ret
51 end
52 elseif key(child) < key(range) and pred(child, n, #t) then
53 return child, n, #t
54 end
55 end
56 end
57 if not t.is_root and pred(t) then
58 return t
59 end
60 end
61 end
63 local function find_after_innermost(t, range, key, pred)
64 if t.is_root or (t.d and t.finish >= range.finish) then
65 if t.d and not t.is_list then
66 local keypos = key(t)
67 local start = keypos == startof(t)
68 local finish = keypos == finishof(t)
69 local newpos
70 if finish then
71 newpos = t.finish_after(math.max(t.start + #t.d, range.finish))
72 elseif start then
73 newpos = range.finish < t.start and t.start + #t.d or t.start_after(range.start)
74 end
75 if newpos then
76 local word = t.word_at({start = newpos, finish = newpos})
77 if word and pred(word) then
78 return word
79 end
80 end
81 return
82 end
83 local _, nearest_n = t.after(range.finish, finishof)
84 if nearest_n then
85 for n = nearest_n, #t do
86 local child = t[n]
87 if child.d and not child.is_empty then
88 local ret = find_after_innermost(child, range, key, pred)
89 if ret then
90 return ret
91 end
92 elseif key(child) >= key(range) and pred(child, n, #t) then
93 return child, n, #t
94 end
95 end
96 end
97 if not t.is_root and pred(t) then
98 return t
99 end
103 return {
105 start_before = function(range, skip)
106 local _, parent = parser.tree.sexp_at(range)
107 local node = parent.before(range.start, startof, skip)
108 return node and startof(node)
109 end,
111 start_after = function(range, skip)
112 local _, parent = parser.tree.sexp_at(range)
113 local node = parent.after(range.finish, startof, skip)
114 return node and startof(node)
115 end,
117 finish_before = function(range, skip)
118 local _, parent = parser.tree.sexp_at(range)
119 local node = parent.before(range.start, finishof, skip)
120 return node and finishof(node) + 1
121 end,
123 finish_after = function(range, skip)
124 local _, parent = parser.tree.sexp_at(range)
125 local node = parent.after(range.finish, finishof, skip)
126 return node and finishof(node) + 1
127 end,
129 start_down_after = function(range, skip)
130 local node, parent = parser.tree.sexp_at(range)
131 if in_quasilist(node, range) then return end
132 if node and node.p then
133 -- XXX: if we are past a prefix start, this will prevent skipping over its list
134 range.start = node.start
136 local next_list = parent.find_after(range, function(t) return t.is_list end)
137 if next_list then
138 local first = next_list.after(range.finish, startof, skip)
139 if first then
140 return first.start
141 else
142 return next_list.start + (next_list.p and #next_list.p or 0) + 1
145 end,
147 start_up_before = function(range)
148 local _, parent = sexp_at(range)
149 return parent.d and parent.start
150 end,
152 finish_down_before = function(range, skip)
153 local node, parent = parser.tree.sexp_at(range)
154 if in_quasilist(node, range) then return end
155 local prev_list = parent.find_before(range, function(t) return t.is_list end)
156 if prev_list then
157 local last = prev_list.before(range.start, finishof, skip)
158 if last then
159 return (last.finish or last.p and last.start + #last.p) + 1, prev_list
160 else
161 return prev_list.finish, prev_list
164 end,
166 finish_up_after = function(range)
167 local _, parent = sexp_at(range)
168 return parent.d and parent.finish + 1
169 end,
171 indented_before = function(range)
172 return find_before_innermost(parser.tree, range, startof, function(t) return t.indent end)
173 end,
175 start_float_before = function(range, up_empty_list)
176 if up_empty_list then
177 local _, parent = sexp_at(range)
178 if parent.is_empty then
179 return parent.start, parent
182 local node = find_before_innermost(parser.tree, range, startof, function(t, n, _)
183 return not t.d or t.is_empty and n == 1 end)
184 return node and node.start, node
185 end,
187 start_float_after = function(range, up_empty_list)
188 if up_empty_list then
189 local _, parent = sexp_at(range)
190 if parent.is_empty then
191 return parent.finish + 1, parent
194 local node = find_after_innermost(parser.tree, range, startof, function(t, n, max_n)
195 return not t.d or t.is_empty and n == max_n end)
196 return node and node.start, node
197 end,
199 finish_float_before = function(range, up_empty_list)
200 if up_empty_list then
201 local _, parent = sexp_at(range)
202 if parent.is_empty then
203 return parent.start, parent
206 local node = find_before_innermost(parser.tree, range, finishof, function(t, n, _)
207 return not t.d or t.is_empty and n == 1 end)
208 return node and node.finish + 1, node
209 end,
211 finish_float_after = function(range, up_empty_list)
212 if up_empty_list then
213 local _, parent = sexp_at(range)
214 if parent.is_empty then
215 return parent.finish + 1, parent
218 local node = find_after_innermost(parser.tree, range, finishof, function(t, n, max_n)
219 return not t.d or t.is_empty and n == max_n end)
220 return node and node.finish + 1, node
221 end,
223 escaped_at = function(range)
224 local node = parser.escaped.around(range)
225 -- TODO: distinguish between doublequoted strings, chars, line comments, and block comments
226 -- and use approprite offset comparisons for each
227 return node and range.start > node.start and range.finish <= node.finish and node
228 end,
230 prev_start_wrapped = function(range)
231 local node, parent = parser.tree.sexp_at(range)
232 if in_quasilist(node, range) then parent = node end
233 local pstart = parent.start and (parent.start + (parent.p and #parent.p or 0) + #parent.d)
234 local bol = bol_at(range.start)
235 local prev_wrapped = (parent.is_list or parent.is_root) and parent.find_before(range,
236 function(t) return t.start < bol and t.finish > bol end,
237 function(t) return t.finish < bol
238 end)
239 local prev_start = prev_wrapped and prev_wrapped.start or bol
240 pstart = pstart or prev_start
241 return math.max(pstart, prev_start)
242 end,
244 next_finish_wrapped = function(range)
245 local node, parent = parser.tree.sexp_at(range)
246 if in_quasilist(node, range) then parent = node end
247 local pfinish = parent.finish and parent.finish + 1 - #parser.opposite[parent.d]
248 local eol = eol_at(range.start)
249 local next_wrapped = (parent.is_list or parent.is_root) and parent.find_after(range,
250 function(t) return t.start < eol and t.finish > eol end,
251 function(t) return t.start > eol
252 end)
253 local next_finish = next_wrapped and next_wrapped.finish + 1 or eol
254 -- XXX: second return value, to be used for safe line-commenting:
255 local next_start_on_line = math.min(pfinish or range.start, next_wrapped and next_wrapped.start or eol)
256 return math.min(pfinish or next_finish, next_finish), next_start_on_line < eol and next_start_on_line or nil
257 end,
259 paragraph_start_before = function(range, prev_line)
260 local prev, n = parser.tree.before(range.start, startof, is_comment)
261 if not prev_line then
262 return prev and prev.start
264 local pprev = n and parser.tree[n - 1]
265 local has_eol = pprev and pprev.is_comment and parser.opposite[pprev.d] == "\n"
266 local adjacent = pprev and prev and prev.start - (pprev.finish + (has_eol and 0 or 1)) <= 1
267 return prev and prev.start - ((adjacent or prev.start == 0) and 0 or 1)
268 end,
270 paragraph_finish_after = function(range, next_line)
271 local node = parser.tree.around(range)
272 local nxt, n = parser.tree.find_after(node or range, function(t)
273 return not t.is_comment and t.finish > range.finish
274 end)
275 return nxt and nxt.finish + 1 + (next_line and parser.tree[n + 1] and parser.tree[n + 1].indent and 1 or 0)
276 end,
278 beginning_of_quasilist = function(range)
279 local _, parent = sexp_at(range)
280 return parent.start and (parent.start + (parent.p and #parent.p or 0) + #parent.d)
281 end,
283 end_of_quasilist = function(range)
284 local _, parent = sexp_at(range)
285 return parent.finish and parent.finish + 1 - #parser.opposite[parent.d]
286 end,
288 repl_line_begin = function(range)
289 local _, parent = parser.tree.sexp_at(range)
290 if not parent.is_root then return end
291 local prev_indent = parent.find_before(range, function(t) return t.indent end)
292 return prev_indent and prev_indent.start
293 end,
295 wider_than = function(range)
296 local node, parent = parser.tree.sexp_at(range)
297 if not node and parent.is_root then return end
298 local pstart, pfinish
299 if in_quasilist(node, range) then
300 parent = node
301 node = node.word_at(range)
303 if not parent.is_root then
304 pstart, pfinish = parent.start + (parent.p and #parent.p or 0) + #parent.d,
305 parent.finish + 1 - #parser.opposite[parent.d]
307 local same_as_inner = range.start == pstart and range.finish == pfinish
308 local same_as_node = node and range.start == node.start and range.finish - 1 == node.finish
309 if node and not same_as_node then
310 return node.start, node.finish + 1
311 elseif not same_as_inner then
312 return pstart, pfinish
314 return parent.start, parent.finish + 1
315 end,
317 sexp_at = sexp_at,
318 paragraph_at = parser.tree.around,
319 goto_path = parser.tree.goto_path,
320 sexp_path = parser.tree.sexp_path,
321 find_after = parser.tree.find_after,
322 find_before = parser.tree.find_before,
326 return M