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
]
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
19 return node
.word_at(range
), node
23 local function escaped_at(range
)
24 local node
= parser
.escaped
.around(range
)
25 -- TODO: distinguish between doublequoted strings, chars, line comments, and block comments
26 -- and use approprite offset comparisons for each
27 return node
and range
.start
> node
.start
and range
.finish
<= node
.finish
and node
30 local function find_before_innermost(t
, range
, key
, pred
)
31 if t
.is_root
or (t
.d
and t
.start
< range
.start
) then
32 if t
.d
and not t
.is_list
then
34 local start
= keypos
== startof(t
)
35 local finish
= keypos
== finishof(t
)
38 newpos
= t
.finish_before(range
.finish
)
40 newpos
= t
.start_before(range
.start
)
43 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
44 if word
and pred(word
) then
50 local _
, nearest_n
= t
.before(range
.start
, startof
)
52 for n
= nearest_n
, 1, -1 do
54 if child
.d
and not child
.is_empty
then
55 local ret
= find_before_innermost(child
, range
, key
, pred
)
59 elseif key(child
) < key(range
) and pred(child
, n
, #t
) then
64 if not t
.is_root
and pred(t
) then
70 local function find_after_innermost(t
, range
, key
, pred
)
71 if t
.is_root
or (t
.d
and t
.finish
>= range
.finish
) then
72 if t
.d
and not t
.is_list
then
74 local start
= keypos
== startof(t
)
75 local finish
= keypos
== finishof(t
)
78 newpos
= t
.finish_after(math
.max(t
.start
+ #t
.d
, range
.finish
))
80 newpos
= range
.finish
< t
.start
and t
.start
+ #t
.d
or t
.start_after(range
.start
)
83 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
84 if word
and pred(word
) then
90 local _
, nearest_n
= t
.after(range
.finish
, finishof
)
92 for n
= nearest_n
, #t
do
94 if child
.d
and not child
.is_empty
then
95 local ret
= find_after_innermost(child
, range
, key
, pred
)
99 elseif key(child
) >= key(range
) and pred(child
, n
, #t
) then
104 if not t
.is_root
and pred(t
) then
112 start_before
= function(range
, skip
)
113 local _
, parent
= parser
.tree
.sexp_at(range
)
114 local node
= parent
.before(range
.start
, startof
, skip
)
115 return node
and startof(node
)
118 start_after
= function(range
, skip
)
119 local _
, parent
= parser
.tree
.sexp_at(range
)
120 local node
= parent
.after(range
.finish
, startof
, skip
)
121 return node
and startof(node
)
124 finish_before
= function(range
, skip
)
125 local _
, parent
= parser
.tree
.sexp_at(range
)
126 local node
= parent
.before(range
.start
, finishof
, skip
)
127 return node
and finishof(node
) + 1
130 finish_after
= function(range
, skip
)
131 local _
, parent
= parser
.tree
.sexp_at(range
)
132 local node
= parent
.after(range
.finish
, finishof
, skip
)
133 return node
and finishof(node
) + 1
136 start_down_after
= function(range
, skip
)
137 local node
, parent
= parser
.tree
.sexp_at(range
)
138 if in_quasilist(node
, range
) then return end
139 if node
and node
.p
then
140 -- XXX: if we are past a prefix start, this will prevent skipping over its list
141 range
.start
= node
.start
143 local next_list
= parent
.find_after(range
, function(t
) return t
.is_list
end)
145 local first
= next_list
.after(range
.finish
, startof
, skip
)
149 return next_list
.start
+ (next_list
.p
and #next_list
.p
or 0) + 1
154 start_up_before
= function(range
)
155 local _
, parent
= sexp_at(range
)
156 return parent
.d
and parent
.start
159 finish_down_before
= function(range
, skip
)
160 local node
, parent
= parser
.tree
.sexp_at(range
)
161 if in_quasilist(node
, range
) then return end
162 local prev_list
= parent
.find_before(range
, function(t
) return t
.is_list
end)
164 local last
= prev_list
.before(range
.start
, finishof
, skip
)
166 return (last
.finish
or last
.p
and last
.start
+ #last
.p
) + 1, prev_list
168 return prev_list
.finish
, prev_list
173 finish_up_after
= function(range
)
174 local _
, parent
= sexp_at(range
)
175 return parent
.d
and parent
.finish
+ 1
178 indented_before
= function(range
)
179 return find_before_innermost(parser
.tree
, range
, startof
, function(t
) return t
.indent
end)
182 start_float_before
= function(range
, up_empty_list
)
183 if up_empty_list
then
184 local _
, parent
= sexp_at(range
)
185 if parent
.is_empty
then
186 return parent
.start
, parent
189 local node
= find_before_innermost(parser
.tree
, range
, startof
, function(t
, n
, _
)
190 return not t
.d
or t
.is_empty
and n
== 1 end)
191 return node
and node
.start
, node
194 finish_float_after
= function(range
, up_empty_list
)
195 if up_empty_list
then
196 local _
, parent
= sexp_at(range
)
197 if parent
.is_empty
then
198 return parent
.finish
+ 1, parent
201 local node
= find_after_innermost(parser
.tree
, range
, finishof
, function(t
, n
, max_n
)
202 return not t
.d
or t
.is_empty
and n
== max_n
end)
203 return node
and node
.finish
+ 1, node
206 start_word_after
= function(t
, range
)
207 local newpos
= t
.start_after(range
.start
)
209 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
210 return word
and word
.start
212 local _
, parent
, n
= sexp_at(range
, true)
213 local cur
, nxt
= parent
[n
], parent
[n
+ 1]
214 if nxt
and cur
.is_line_comment
and nxt
.is_line_comment
then
215 return nxt
.start_after(range
.start
)
220 finish_word_after
= function(t
, range
)
221 local newpos
= t
.finish_after(math
.max(t
.start
+ #t
.d
, range
.finish
))
223 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
224 return word
and word
.finish
+ 1
226 local _
, parent
, n
= sexp_at(range
, true)
227 local cur
, nxt
= parent
[n
], parent
[n
+ 1]
228 if nxt
and cur
.is_line_comment
and nxt
.is_line_comment
then
229 return nxt
.finish_after(range
.start
)
234 start_word_before
= function(t
, range
)
235 local newpos
= t
.start_before(range
.start
)
237 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
238 return word
and word
.start
240 local _
, parent
, n
= sexp_at(range
, true)
241 local cur
, prev
= parent
[n
], parent
[n
- 1]
242 if prev
and cur
.is_line_comment
and prev
.is_line_comment
then
243 return prev
.start_before(range
.start
)
248 finish_word_before
= function(t
, range
)
249 local newpos
= t
.finish_before(range
.finish
)
251 local word
= t
.word_at({start
= newpos
, finish
= newpos
})
252 return word
and word
.finish
+ 1
254 local _
, parent
, n
= sexp_at(range
, true)
255 local cur
, prev
= parent
[n
], parent
[n
- 1]
256 if prev
and cur
.is_line_comment
and prev
.is_line_comment
then
257 return prev
.finish_before(range
.start
)
262 prev_start_wrapped
= function(range
)
263 local node
, parent
= parser
.tree
.sexp_at(range
)
264 if in_quasilist(node
, range
) then parent
= node
end
265 local pstart
= parent
.start
and (parent
.start
+ (parent
.p
and #parent
.p
or 0) + #parent
.d
)
266 local bol
= bol_at(range
.start
)
267 local prev_wrapped
= (parent
.is_list
or parent
.is_root
) and parent
.find_before(range
,
268 function(t
) return t
.start
< bol
and t
.finish
> bol
end,
269 function(t
) return t
.finish
< bol
271 local prev_start
= prev_wrapped
and prev_wrapped
.start
or bol
272 pstart
= pstart
or prev_start
273 return math
.max(pstart
, prev_start
)
276 next_finish_wrapped
= function(range
)
277 local node
, parent
= parser
.tree
.sexp_at(range
)
278 if in_quasilist(node
, range
) then parent
= node
end
279 local pfinish
= parent
.finish
and parent
.finish
+ 1 - #parser
.opposite
[parent
.d
]
280 local eol
= eol_at(range
.start
)
281 local next_wrapped
= (parent
.is_list
or parent
.is_root
) and parent
.find_after(range
,
282 function(t
) return t
.start
< eol
and t
.finish
> eol
end,
283 function(t
) return t
.start
> eol
285 local next_finish
= next_wrapped
and next_wrapped
.finish
+ 1 or eol
286 -- XXX: second return value, to be used for safe line-commenting:
287 local next_start_on_line
= math
.min(pfinish
or range
.start
, next_wrapped
and next_wrapped
.start
or eol
)
288 return math
.min(pfinish
or next_finish
, next_finish
), next_start_on_line
< eol
and next_start_on_line
or nil
291 paragraph_start_before
= function(range
, prev_line
)
292 local prev
, n
= parser
.tree
.before(range
.start
, startof
, is_comment
)
293 if not prev_line
then
294 return prev
and prev
.start
296 local pprev
= n
and parser
.tree
[n
- 1]
297 local has_eol
= pprev
and pprev
.is_line_comment
298 local adjacent
= pprev
and prev
and prev
.start
- (pprev
.finish
+ (has_eol
and 0 or 1)) <= 1
299 return prev
and prev
.start
- ((adjacent
or prev
.start
== 0) and 0 or 1)
302 paragraph_finish_after
= function(range
, next_line
)
303 local node
= parser
.tree
.around(range
)
304 local nxt
, n
= parser
.tree
.find_after(node
or range
, function(t
)
305 return not t
.is_comment
and t
.finish
> range
.finish
307 return nxt
and nxt
.finish
+ 1 + (next_line
and parser
.tree
[n
+ 1] and parser
.tree
[n
+ 1].indent
and 1 or 0)
310 beginning_of_quasilist
= function(range
)
311 local _
, parent
= sexp_at(range
)
312 return parent
.start
and (parent
.start
+ (parent
.p
and #parent
.p
or 0) + #parent
.d
)
315 end_of_quasilist
= function(range
)
316 local _
, parent
= sexp_at(range
)
317 return parent
.finish
and parent
.finish
+ 1 - #parser
.opposite
[parent
.d
]
320 repl_line_begin
= function(range
)
321 local _
, parent
= parser
.tree
.sexp_at(range
)
322 if not parent
.is_root
then return end
323 local prev_indent
= parent
.find_before(range
, function(t
) return t
.indent
end)
324 return prev_indent
and prev_indent
.start
327 wider_than
= function(range
)
328 local node
, parent
= parser
.tree
.sexp_at(range
)
329 if not node
and parent
.is_root
then return end
330 local pstart
, pfinish
331 if in_quasilist(node
, range
) then
333 node
= node
.word_at(range
)
335 if not parent
.is_root
then
336 pstart
, pfinish
= parent
.start
+ (parent
.p
and #parent
.p
or 0) + #parent
.d
,
337 parent
.finish
+ 1 - #parser
.opposite
[parent
.d
]
339 local same_as_inner
= range
.start
== pstart
and range
.finish
== pfinish
340 local same_as_node
= node
and range
.start
== node
.start
and range
.finish
- 1 == node
.finish
341 if node
and not same_as_node
then
342 return node
.start
, node
.finish
+ 1
343 elseif not same_as_inner
then
344 return pstart
, pfinish
346 return parent
.start
, parent
.finish
+ 1
349 -- opening: nil - bracketed list, false - any quasilist
350 quasilist_at
= function(range
, opening
)
351 local lcd
= parser
.opposite
["\n"]
352 if opening
and opening
:match('[%' .. lcd
.. '%"]') then
353 local escaped
= escaped_at(range
)
354 return escaped
and escaped
.d
:find("^"..opening
) and escaped
356 local _
, nodes
= parser
.tree
.sexp_path(range
)
358 local up
= #nodes
- 1
362 until not parent
or (opening
== false or not opening
or parent
.d
== opening
)
368 escaped_at
= escaped_at
,
369 paragraph_at
= parser
.tree
.around
,
370 goto_path
= parser
.tree
.goto_path
,
371 sexp_path
= parser
.tree
.sexp_path
,
372 find_after
= parser
.tree
.find_after
,
373 find_before
= parser
.tree
.find_before
,