3 local function finishof(node
) return node
.finish
end
4 local function startof(node
) return node
.start
end
6 function M
.new(parser
, eol_at
, bol_at
)
8 local function in_quasilist(t
, range
)
9 return t
and t
.d
and not t
.is_list
and
10 range
.start
>= t
.start
+ #t
.d
and range
.finish
<= t
.finish
+ 1 - #parser
.opposite
[t
.d
]
13 local function sexp_at(range
, true_sexp
)
14 local node
, parent
, nth
= parser
.tree
.sexp_at(range
)
15 if not in_quasilist(node
, range
) or true_sexp
then
16 return node
, parent
, nth
18 return node
.word_at(range
), node
22 local function escaped_at(range
)
23 local node
= parser
.escaped
.around(range
)
24 -- TODO: distinguish between doublequoted strings, chars, line comments, and block comments
25 -- and use approprite offset comparisons for each
26 return node
and range
.start
> node
.start
and range
.finish
<= node
.finish
and node
29 local function find_before_innermost(t
, range
, key
, pred
)
30 if t
.is_root
or (t
.d
and t
.start
< range
.start
) then
31 if t
.d
and not t
.is_list
then
33 local start
= keypos
== startof(t
)
34 local finish
= keypos
== finishof(t
)
37 newpos
= t
.finish_before(range
.finish
)
39 newpos
= t
.start_before(range
.start
)
42 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
43 if word
and (not pred
or pred(word
)) then
47 return (not pred
or pred(t
)) and t
49 local _
, nearest_n
= t
.before(range
.start
, startof
)
51 for n
= nearest_n
, 1, -1 do
53 if child
.d
and not child
.is_empty
then
54 local ret
= find_before_innermost(child
, range
, key
, pred
)
58 elseif key(child
) < key(range
) and (not pred
or pred(child
, n
, #t
)) then
63 if not t
.is_root
and (not pred
or pred(t
)) then
69 local function find_after_innermost(t
, range
, key
, pred
)
70 if t
.is_root
or (t
.d
and t
.finish
>= range
.finish
) then
71 if t
.d
and not t
.is_list
then
73 local start
= keypos
== startof(t
)
74 local finish
= keypos
== finishof(t
)
77 newpos
= t
.finish_after(math
.max(t
.start
+ #t
.d
, range
.finish
))
79 newpos
= range
.finish
< t
.start
and t
.start
+ #t
.d
or t
.start_after(range
.start
)
82 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
83 if word
and (not pred
or pred(word
)) then
87 return (not pred
or pred(t
)) and t
89 local _
, nearest_n
= t
.after(range
.finish
, finishof
)
91 for n
= nearest_n
, #t
do
93 if child
.d
and not child
.is_empty
then
94 local ret
= find_after_innermost(child
, range
, key
, pred
)
98 elseif key(child
) >= key(range
) and (not pred
or pred(child
, n
, #t
)) then
103 if not t
.is_root
and (not pred
or pred(t
)) then
111 start_before
= function(range
, skip
)
112 local _
, parent
= parser
.tree
.sexp_at(range
)
113 local node
= parent
.before(range
.start
, startof
, skip
)
114 return node
and startof(node
)
117 start_after
= function(range
, skip
)
118 local _
, parent
= parser
.tree
.sexp_at(range
)
119 local node
= parent
.after(range
.finish
, startof
, skip
)
120 return node
and startof(node
)
123 finish_before
= function(range
, skip
)
124 local _
, parent
= parser
.tree
.sexp_at(range
)
125 local node
= parent
.before(range
.start
, finishof
, skip
)
126 return node
and finishof(node
) + 1
129 finish_after
= function(range
, skip
)
130 local _
, parent
= parser
.tree
.sexp_at(range
)
131 local node
= parent
.after(range
.finish
, finishof
, skip
)
132 return node
and finishof(node
) + 1
135 start_down_after
= function(range
, skip
)
136 local node
, parent
= parser
.tree
.sexp_at(range
)
137 if in_quasilist(node
, range
) then return end
138 if node
and node
.p
then
139 -- XXX: if we are past a prefix start, this will prevent skipping over its list
140 range
.start
= node
.start
142 local next_list
= parent
.find_after(range
, function(t
) return t
.is_list
end)
144 local first
= next_list
.after(range
.finish
, startof
, skip
)
148 return next_list
.start
+ (next_list
.p
and #next_list
.p
or 0) + 1
153 start_up_before
= function(range
)
154 local _
, parent
= sexp_at(range
)
155 return parent
.d
and parent
.start
158 finish_down_before
= function(range
, skip
)
159 local node
, parent
= parser
.tree
.sexp_at(range
)
160 if in_quasilist(node
, range
) then return end
161 local prev_list
= parent
.find_before(range
, function(t
) return t
.is_list
end)
163 local last
= prev_list
.before(range
.start
, finishof
, skip
)
165 return (last
.finish
or last
.p
and last
.start
+ #last
.p
) + 1, prev_list
167 return prev_list
.finish
, prev_list
172 finish_up_after
= function(range
)
173 local _
, parent
= sexp_at(range
)
174 return parent
.d
and parent
.finish
+ 1
177 indented_before
= function(range
)
178 return find_before_innermost(parser
.tree
, range
, startof
, function(t
) return t
.indent
end)
181 start_left
= function(range
)
182 local node
= find_before_innermost(parser
.tree
, range
, startof
)
183 return node
and node
.start
186 finish_right
= function(range
)
187 local node
= find_after_innermost(parser
.tree
, range
, finishof
)
188 return node
and node
.finish
+ 1
191 start_float_before
= function(range
, up_empty_list
)
192 if up_empty_list
then
193 local _
, parent
= sexp_at(range
)
194 if parent
.is_empty
then
195 return parent
.start
, parent
198 local node
= find_before_innermost(parser
.tree
, range
, startof
, function(t
, n
, _
)
199 return not t
.d
or t
.is_empty
and n
== 1 end)
200 return node
and node
.start
, node
203 finish_float_after
= function(range
, up_empty_list
)
204 if up_empty_list
then
205 local _
, parent
= sexp_at(range
)
206 if parent
.is_empty
then
207 return parent
.finish
+ 1, parent
210 local node
= find_after_innermost(parser
.tree
, range
, finishof
, function(t
, n
, max_n
)
211 return not t
.d
or t
.is_empty
and n
== max_n
end)
212 return node
and node
.finish
+ 1, node
215 start_word_after
= function(t
, range
)
216 local newpos
= t
.start_after(range
.start
)
218 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
219 return word
and word
.start
221 local _
, parent
, n
= sexp_at(range
, true)
222 local cur
, nxt
= parent
[n
], parent
[n
+ 1]
223 if nxt
and cur
.is_line_comment
and nxt
.is_line_comment
then
224 return nxt
.start_after(range
.start
)
229 finish_word_after
= function(t
, range
)
230 local newpos
= t
.finish_after(math
.max(t
.start
+ #t
.d
, range
.finish
))
232 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
233 return word
and word
.finish
+ 1
235 local _
, parent
, n
= sexp_at(range
, true)
236 local cur
, nxt
= parent
[n
], parent
[n
+ 1]
237 if nxt
and cur
.is_line_comment
and nxt
.is_line_comment
then
238 return nxt
.finish_after(range
.start
)
243 start_word_before
= function(t
, range
)
244 local newpos
= t
.start_before(range
.start
)
246 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
247 return word
and word
.start
249 local _
, parent
, n
= sexp_at(range
, true)
250 local cur
, prev
= parent
[n
], parent
[n
- 1]
251 if prev
and cur
.is_line_comment
and prev
.is_line_comment
then
252 return prev
.start_before(range
.start
)
257 finish_word_before
= function(t
, range
)
258 local newpos
= t
.finish_before(range
.finish
)
260 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
261 return word
and word
.finish
+ 1
263 local _
, parent
, n
= sexp_at(range
, true)
264 local cur
, prev
= parent
[n
], parent
[n
- 1]
265 if prev
and cur
.is_line_comment
and prev
.is_line_comment
then
266 return prev
.finish_before(range
.start
)
271 prev_start_wrapped
= function(range
)
272 local node
, parent
= parser
.tree
.sexp_at(range
)
273 if in_quasilist(node
, range
) then parent
= node
end
274 local pstart
= parent
.start
and (parent
.start
+ (parent
.p
and #parent
.p
or 0) + #parent
.d
)
275 local bol
= bol_at(range
.start
)
276 local prev_wrapped
= (parent
.is_list
or parent
.is_root
) and parent
.find_before(range
,
277 function(t
) return t
.start
< bol
and t
.finish
> bol
end,
278 function(t
) return t
.finish
< bol
280 local prev_start
= prev_wrapped
and prev_wrapped
.start
or bol
281 pstart
= pstart
or prev_start
282 return math
.max(pstart
, prev_start
)
285 next_finish_wrapped
= function(range
)
286 local node
, parent
= parser
.tree
.sexp_at(range
)
287 if in_quasilist(node
, range
) then parent
= node
end
288 local pfinish
= parent
.finish
and parent
.finish
+ 1 - #parser
.opposite
[parent
.d
]
289 local eol
= eol_at(range
.start
)
290 local next_wrapped
= (parent
.is_list
or parent
.is_root
) and parent
.find_after(range
,
291 function(t
) return t
.start
< eol
and t
.finish
> eol
end,
292 function(t
) return t
.start
> eol
294 local next_finish
= next_wrapped
and next_wrapped
.finish
+ 1 or eol
295 -- XXX: second return value, to be used for safe line-commenting:
296 local next_start_on_line
= math
.min(pfinish
or range
.start
, next_wrapped
and next_wrapped
.start
or eol
)
297 return math
.min(pfinish
or next_finish
, next_finish
), next_start_on_line
< eol
and next_start_on_line
or nil
300 quasilist_start
= function(range
)
301 local _
, parent
= sexp_at(range
)
302 return parent
.start
and (parent
.start
+ (parent
.p
and #parent
.p
or 0) + #parent
.d
)
305 quasilist_finish
= function(range
)
306 local _
, parent
= sexp_at(range
)
307 return parent
.finish
and parent
.finish
+ 1 - #parser
.opposite
[parent
.d
]
310 wider_than
= function(range
)
311 local node
, parent
= parser
.tree
.sexp_at(range
)
312 if not node
and parent
.is_root
then return end
313 local pstart
, pfinish
314 if in_quasilist(node
, range
) then
316 node
= node
.word_at(range
)
318 if not parent
.is_root
then
319 pstart
, pfinish
= parent
.start
+ (parent
.p
and #parent
.p
or 0) + #parent
.d
,
320 parent
.finish
+ 1 - #parser
.opposite
[parent
.d
]
322 local same_as_inner
= range
.start
== pstart
and range
.finish
== pfinish
323 local same_as_node
= node
and range
.start
== node
.start
and range
.finish
- 1 == node
.finish
324 if node
and not same_as_node
then
325 return node
.start
, node
.finish
+ 1
326 elseif not same_as_inner
then
327 return pstart
, pfinish
329 return parent
.start
, parent
.finish
+ 1
332 -- opening: nil - bracketed list, false - any quasilist
333 quasilist_at
= function(range
, opening
)
334 local lcd
= parser
.opposite
["\n"]
335 if opening
and opening
:match('[%' .. lcd
.. '%"]') then
336 local escaped
= escaped_at(range
)
337 return escaped
and escaped
.d
:find("^"..opening
) and escaped
339 local _
, nodes
= parser
.tree
.sexp_path(range
)
341 local up
= #nodes
- 1
345 until not parent
or (opening
== false or not opening
or parent
.d
== opening
)
350 repl_line_at
= function(range
)
351 local last
, nl
= parser
.tree
.find_after(range
, function(t
) return t
.indent
end)
353 local node
= parser
.tree
.around(range
)
354 if last
and node
and not node
.is_comment
and last
.start
== node
.start
then
358 until not parser
.tree
[i
] or parser
.tree
[i
].indent
362 local i
= nl
or #parser
.tree
+ 1
365 until parser
.tree
[i
].indent
366 nf
= not parser
.tree
[i
].is_comment
and i
367 nl
= nl
and nl
- 1 or #parser
.tree
369 first
, last
= nf
and parser
.tree
[nf
], parser
.tree
[nl
]
370 if first
and last
then
371 return {start
= first
.start
, finish
= last
.finish
}
376 escaped_at
= escaped_at
,
377 paragraph_at
= parser
.tree
.around
,
378 goto_path
= parser
.tree
.goto_path
,
379 sexp_path
= parser
.tree
.sexp_path
,
380 find_after
= parser
.tree
.find_after
,
381 find_before
= parser
.tree
.find_before
,