3 -- XXX: in Lua 5.2 unpack() was moved into table
4 local unpack
= unpack
or table.unpack
6 local function startof(node
) return node
.start
end
7 local function finishof(node
) return node
.finish
end
8 local function is_comment(node
) return node
.is_comment
end
10 function M
.new(parser
, walker
, fmt
, write, delete
, eol_at
)
12 local function normalize_spacing(start
, delta
, list
)
14 table.insert(list
, {start
+ delta
, -delta
})
16 table.insert(list
, {start
, -delta
})
21 local function refmt_list(parent
, base_indent
, padj
, deltas
, keep_electric_space
)
24 local pstart
= parent
.start
+ (parent
.p
and #parent
.p
or 0) + #parent
.d
25 if parent
.is_empty
then
26 normalize_spacing(pstart
, parent
.finish
- pstart
, deltas
)
29 local last_distinguished
= fmt
:last_distinguished(parent
)
30 for i
, s
in ipairs(parent
) do
32 adj
= s
.indent
and 0 or adj
33 if i
== 2 and last_distinguished
== 1 and not s
.is_list
and parent
[1].text
== "let" then
34 -- s is the name of a named let. account for it:
35 last_distinguished
= last_distinguished
+ 1
37 local prev
= parent
[i
- 1]
38 local has_eol
= s
.is_comment
and parser
.opposite
[s
.d
] == "\n"
39 if not s
.indent
and not (prev
and (s
.d
== "|" or prev
.d
== "|")) and not has_eol
then
40 -- remove/add leading spaces
41 local off
= prev
and prev
.finish
+ 1 or pstart
42 adj
= adj
+ normalize_spacing(off
, s
.start
- off
- (prev
and 1 or 0), deltas
)
44 local nxt
= parent
[i
+ 1]
45 local is_last
= not nxt
46 if (is_last
and not keep_electric_space
) or nxt
and nxt
.indent
then
47 -- clean up trailing spaces
48 local off
= is_last
and parent
.finish
- (has_eol
and i
< #parent
and 1 or 0) or nxt
.start
- nxt
.indent
- 1
49 local finish
= s
.finish
+ (has_eol
and i
< #parent
and 0 or 1)
50 normalize_spacing(finish
, off
- finish
, deltas
)
53 delta
= base_indent
- s
.indent
54 if last_distinguished
and i
> 1 then
56 if i
- 1 <= last_distinguished
then
60 -- remove leading space, if any
61 delta
= -(s
.start
- pstart
)
62 elseif -- align further arguments below the second one
63 parent
.d
== "(" -- [] and {} contain data, so no arguments
64 and not parent
[1].is_string
-- a string can't have arguments
65 --and not parent[1].is_list -- wrong, GNU Emacs compatible behaviour
67 if i
> 2 and not parent
[2].indent
then
68 delta
= base_indent
+ parent
[1].finish
+ 2 - pstart
- s
.indent
- op_adj
70 if delta
< 0 then delta
= math
.max(-s
.indent
, delta
) end
73 table.insert(deltas
, {s
.start
, delta
})
81 local nearest_indented
= s
.indent
and s
or parent
.find_before(s
, function(t
) return t
.indent
end)
82 local parent_column
= nearest_indented
and
83 nearest_indented
.indent
+ (s
.start
+ (s
.p
and #s
.p
or 0) + #s
.d
) - nearest_indented
.start
84 or base_indent
+ (s
.start
+ (s
.p
and #s
.p
or 0) + #s
.d
) - pstart
85 refmt_list(s
, parent_column
- adj
, padj
+ adj
, deltas
, keep_electric_space
)
91 local function refmt_at(scope
, range
, keep_electric_space
)
92 if not range
or not scope
or scope
.is_root
then return end
93 local parent
= walker
.sexp_at(scope
, true)
94 if not (parent
and parent
.is_list
) then return end
95 local _
, parent_at
, m
= walker
.sexp_at(range
, true)
97 _
, m
= parent_at
.after(range
.start
, startof
)
102 local base
= parent_at
[m
] or parent_at
103 local path
= walker
.sexp_path(base
)
104 local after_start
, at_pfinish
106 local s
= parent_at
[#parent_at
]
107 local has_eol
= s
.is_comment
and parser
.opposite
[s
.d
] == "\n"
108 if range
.start
>= s
.finish
+ 1 + (has_eol
and 1 or 0) then
111 after_start
= range
.start
< base
.start
and 0 or range
.start
- base
.start
114 after_start
= (parent_at
.p
and #parent_at
.p
or 0) + #parent_at
.d
116 local indented_parent
= parent
.indent
and parent
or parent
.is_list
and walker
.indented_before(parent
)
117 local parent_column
= indented_parent
and (indented_parent
.indent
+ parent
.start
- indented_parent
.start
+
118 (parent
.p
and #parent
.p
or 0) + #parent
.d
) or
120 local deltas
= refmt_list(parent
, parent_column
, 0, {}, keep_electric_space
)
121 table.sort(deltas
, function(d1
, d2
) return d1
[1] > d2
[1] end)
122 for _
, pair
in ipairs(deltas
) do
123 local offset
, delta
= unpack(pair
)
125 write(offset
, string.rep(" ", delta
))
126 elseif delta
< 0 then
127 delete(offset
+ delta
, -delta
)
130 parser
.tree
.rewind(parent
.start
or 0)
132 local sexp
, parentng
= walker
.goto_path(path
)
135 return parentng
.finish
137 return sexp
.start
+ after_start
144 local function splice(pos
, sexps
, skip
, backwards
, action
)
146 local sexp
= walker
.sexp_at(skip
, true)
147 local start
= sexp
.start
+ (sexp
.p
and #sexp
.p
or 0)
148 local is_line_comment
= sexp
.is_comment
and parser
.opposite
[sexp
.d
] == "\n"
149 -- XXX: don't splice empty line comments _yet_; see _join_or_splice
150 local tosplice
= action
.splice
or action
.wrap
or not is_line_comment
and sexp
.is_empty
151 local opening
= (sexp
.p
or '')..sexp
.d
152 local closing
= parser
.opposite
[sexp
.d
]
153 local real_start
= tosplice
and sexp
.start
or start
+ #sexp
.d
154 local splice_closing
= closing
~= "\n" or sexp
.is_empty
155 local real_finish
= sexp
.finish
+ 1 - (tosplice
and splice_closing
and 0 or #closing
)
156 local first
= backwards
and
157 {start
= real_start
, finish
= math
.max(pos
, start
+ #sexp
.d
)} or
158 {start
= real_start
, finish
= sexp
.is_empty
and pos
or start
+ #sexp
.d
}
159 local second
= backwards
and
160 {start
= sexp
.is_empty
and pos
or sexp
.finish
+ 1 - #closing
, finish
= real_finish
} or
161 {start
= math
.min(pos
, sexp
.finish
+ 1 - #closing
), finish
= real_finish
}
166 local ndeleted
= first
.finish
- first
.start
+ second
.finish
- second
.start
168 sexps
.rewind(sexp
.start
)
171 return first
.finish
- first
.start
, spliced
, opening
, closing
174 -- If you try to delete some part of indentation, this function joins the current line with
175 -- the previous one, unless the latter is a line comment.
177 -- a) extending or shrinking the range that is to be deleted
178 -- b) returning a truthy value to trick pick_out to glide the cursor, but refmt to restore the deleted spaces
180 local function delete_indentation(range
, pos
)
181 local node
, parent
= walker
.sexp_at(range
)
182 if node
or not parent
.is_list
then return end
183 local prev
= parent
.before(pos
, finishof
)
184 local nxt
= parent
.after(pos
, startof
)
185 if prev
and prev
.finish
>= range
.start
or nxt
and nxt
.start
< range
.finish
then return end
186 local backwards
= pos
== range
.finish
188 local empty_line_after_comment
= prev
and prev
.is_comment
and parser
.opposite
[prev
.d
] == "\n"
189 and (not nxt
or nxt
.start
> eol_at(pos
))
190 if nxt
and nxt
.indent
then
191 if backwards
and prev
then
193 range
.finish
= range
.start
196 range
.start
= prev
.finish
+ (empty_line_after_comment
and 0 or 1)
200 if not nxt
and prev
then
201 range
.start
= prev
.finish
+ 1
202 range
.finish
= parent
.finish
203 elseif nxt
and nxt
.start
== range
.finish
and (not prev
or prev
.finish
+ 1 == range
.start
) then
204 if prev
and prev
.d
or nxt
and nxt
.d
then
206 range
.finish
= range
.start
208 range
.start
= range
.finish
215 local function pick_out(range
, pos
, action
)
216 local sexps
= parser
.tree
217 local skips
= sexps
.unbalanced_delimiters(range
)
218 -- handle splice and kill-splice of forms and strings:
220 local sexp
= walker
.sexp_at(skips
[1], true)
221 local backward_splice
= skips
[1].opening
and pos
>= sexp
.start
+ (sexp
.p
and #sexp
.p
or 0) + #sexp
.d
222 and range
.start
>= sexp
.start
223 local forward_splice
= skips
[1].closing
and pos
<= sexp
.finish
+ 1 - #parser
.opposite
[sexp
.d
]
224 and range
.finish
<= sexp
.finish
+ 1
225 if backward_splice
or forward_splice
then
226 return splice(backward_splice
and range
.finish
or range
.start
, sexps
, sexp
, backward_splice
, action
)
229 local node
, parent
= walker
.sexp_at({start
= range
.finish
, finish
= range
.finish
})
230 local drop_eol
= node
and node
.finish
+ 1 == range
.finish
and node
.is_comment
and parser
.opposite
[node
.d
] == "\n"
231 local _
, par
= walker
.sexp_at({start
= range
.start
, finish
= range
.start
})
232 local operator_changed
= par
[1] and par
[1].finish
>= range
.start
233 local refmt
= #skips
== 0 and delete_indentation(range
, pos
) or operator_changed
and 0
234 table.sort(skips
, function(a
, b
) return a
.start
< b
.start
end)
235 table.insert(skips
, {start
= range
.finish
- (drop_eol
and 1 or 0)})
236 table.insert(skips
, 1, {finish
= range
.start
})
237 local ndeleted
= drop_eol
and 1 or 0
238 for i
= #skips
- 1, 1, -1 do
239 local region
= {start
= skips
[i
].finish
, finish
= skips
[i
+ 1].start
}
240 if skips
[i
].closing
and skips
[i
+ 1].opening
then
241 -- leave out some of the space between adjacent lists
242 local _
, rparent
= walker
.sexp_at(region
)
243 local nxt
= rparent
.after(region
.start
, startof
)
244 region
.start
= nxt
and nxt
.start
or region
.start
248 ndeleted
= ndeleted
+ (region
.finish
- region
.start
)
251 -- if parent[#parent + 1] is nil, we are at EOF
252 if ndeleted
> 0 and (not parent
.is_root
or parent
.is_parsed(range
.start
) or parent
[#parent
+ 1]) then
253 sexps
.rewind(range
.start
)
255 return ndeleted
- (refmt
or 0), nil, nil, nil, refmt
258 local function raise_sexp(range
, pos
)
259 local sexp
, parent
= walker
.sexp_at(range
, true)
260 if sexp
and parent
and parent
.is_list
then
261 delete(sexp
.finish
+ 1, parent
.finish
- sexp
.finish
)
262 delete(parent
.start
, sexp
.start
- parent
.start
)
263 parser
.tree
.rewind(parent
.start
)
264 range
.start
= parent
.start
+ pos
- sexp
.start
265 range
.finish
= range
.start
266 local _
, nodes
= walker
.sexp_path(range
)
267 local grandparent
= nodes
[#nodes
- 2]
268 return grandparent
and refmt_at(grandparent
, range
) or range
.start
272 local function slurp_sexp(range
, forward
)
273 local _
, parent
= walker
.sexp_at(range
, true)
274 local seeker
= forward
and walker
.finish_after
or walker
.start_before
275 if not parent
or not parent
.is_list
then return range
.start
end
276 local r
= {start
= parent
.start
, finish
= parent
.finish
+ 1}
277 local newpos
= seeker(r
, is_comment
)
278 if not newpos
then return range
.start
end
279 local opening
= (parent
.p
or '')..parent
.d
280 local closing
= parser
.opposite
[parent
.d
]
281 local delimiter
= forward
and closing
or opening
283 write(newpos
, delimiter
)
285 delete(forward
and parent
.finish
or parent
.start
, #delimiter
)
287 write(newpos
, delimiter
)
289 parser
.tree
.rewind(math
.min(parent
.start
, newpos
))
290 local _
, parentng
= walker
.sexp_at(range
, true)
291 return refmt_at(parentng
, range
)
294 local function barf_sexp(range
, forward
)
295 local _
, parent
, m
= walker
.sexp_at(range
, true)
296 local seeker
= forward
and walker
.finish_before
or walker
.start_after
297 -- TODO: barfing out of strings requires calling the parser on them
298 if not parent
or not parent
.is_list
then return range
.start
end
299 local opening
= (parent
.p
or '')..parent
.d
300 local pstart
= parent
.start
+ #opening
301 local r
= {start
= forward
and parent
.finish
- 1 or pstart
, finish
= forward
and parent
.finish
or pstart
+ 1}
302 local newpos
= seeker(r
, is_comment
) or forward
and pstart
or parent
.finish
303 if not newpos
then return range
.start
end
304 local closing
= parser
.opposite
[parent
.d
]
305 local delimiter
= forward
and closing
or opening
307 write(newpos
, delimiter
)
309 delete(forward
and parent
.finish
or parent
.start
, #delimiter
)
311 write(newpos
, delimiter
)
313 parser
.tree
.rewind(math
.min(parent
.start
, newpos
))
314 local barfed_cursor_backward
= m
== 1 and not forward
315 local barfed_cursor_forward
= m
== #parent
and forward
316 range
.start
= range
.start
+ #delimiter
* (barfed_cursor_backward
and -1 or barfed_cursor_forward
and 1 or 0)
317 local barfed_cursor
= barfed_cursor_forward
or barfed_cursor_backward
318 local _
, nodes
= walker
.sexp_path(range
)
319 local parentng
, grandparent
= nodes
[#nodes
- 1], nodes
[#nodes
- 2]
320 local scope
= barfed_cursor
and parentng
and not parentng
.is_root
and parentng
or grandparent
321 return refmt_at(scope
, range
) or range
.start
324 local function splice_sexp(range
, _
, no_refmt
)
325 local _
, parent
= walker
.sexp_at(range
)
326 if not parent
or not parent
.d
then return end
327 local opening
= (parent
.p
or '')..parent
.d
328 local closing
= parser
.opposite
[parent
.d
]
329 local finish
= parent
.finish
+ 1 - #closing
330 if closing
~= "\n" then
331 delete(finish
, #closing
)
333 -- TODO: (un)escape special characters, if necessary
334 delete(parent
.start
, parent
.is_empty
and (finish
- parent
.start
) or #opening
)
335 parser
.tree
.rewind(parent
.start
)
336 range
.start
= range
.start
- #opening
337 range
.finish
= range
.start
338 local _
, parentng
= walker
.sexp_at(range
, true)
339 return not no_refmt
and refmt_at(parentng
, range
) or range
.start
342 local function rewrap(parent
, kind
)
343 local pstart
= parent
.start
+ #((parent
.p
or '')..parent
.d
) - 1
344 delete(parent
.finish
, 1)
345 write(parent
.finish
, parser
.opposite
[kind
])
346 delete(pstart
, #parent
.d
)
348 parser
.tree
.rewind(parent
.start
)
351 local function cycle_wrap(range
, pos
)
352 local _
, parent
= walker
.sexp_at(range
)
353 if not parent
or not parent
.is_list
then return end
354 local next_kind
= {["("] = "[", ["["] = "{", ["{"] = "("}
355 rewrap(parent
, next_kind
[parent
.d
])
359 local function split_sexp(range
)
360 local _
, parent
= walker
.sexp_at(range
)
361 if not (parent
and parent
.d
) then return end
362 local new_finish
, new_start
363 if parent
.is_list
then
364 local prev
= parent
.before(range
.start
, finishof
, is_comment
)
365 new_finish
= prev
and prev
.finish
+ 1
366 -- XXX: do not skip comments here, so they end up in the second list
367 -- and are not separated from their target expression:
368 local nxt
= new_finish
and parent
.after(new_finish
, startof
)
369 new_start
= nxt
and nxt
.start
371 new_start
= range
.start
372 new_finish
= range
.start
374 if not (new_start
and new_finish
) then return end
375 local opening
= (parent
.p
or '')..parent
.d
376 local closing
= parser
.opposite
[parent
.d
]
377 write(new_start
, opening
)
378 local sep
= parser
.opposite
[parent
.d
] == "\n" and "" -- line comments already have a separator
379 or new_finish
== new_start
and " " -- only add a separator if there was none before
381 write(new_finish
, closing
..sep
)
382 parser
.tree
.rewind(parent
.start
)
383 range
.start
= new_start
+ (parent
.is_list
and 0 or #opening
+ #closing
)
384 range
.finish
= range
.start
385 local _
, nodes
= walker
.sexp_path(range
)
386 local parentng
, grandparent
= nodes
[#nodes
- 1], nodes
[#nodes
- 2]
387 local scope
= parentng
and not parentng
.is_root
and parentng
or grandparent
388 return refmt_at(scope
, range
) or range
.start
391 local function join_sexps(range
)
392 local node
, parent
= walker
.sexp_at(range
, true)
393 local first
= node
and node
.finish
+ 1 == range
.start
and node
or parent
.before(range
.start
, finishof
)
394 local second
= first
~= node
and node
or parent
.after(range
.start
, startof
)
395 if not (first
and second
and first
.d
and first
.indent
and second
.indent
and
396 (first
.d
== second
.d
or
397 -- join line comments even when their delimiters differ slightly
398 -- (different number of semicolons, existence/lack of a space after them)
399 parser
.opposite
[first
.d
] == parser
.opposite
[second
.d
])) then
402 local opening
= (second
.p
or '')..second
.d
403 local closing
= parser
.opposite
[first
.d
]
405 if not first
.is_list
then
406 pos
= first
.finish
+ 1 - #closing
407 delete(pos
, second
.start
+ #opening
- pos
)
409 delete(second
.start
, #opening
)
410 delete(first
.finish
, #closing
)
411 pos
= second
.start
- #closing
413 parser
.tree
.rewind(first
.start
)
415 range
.finish
= range
.start
416 local _
, nodes
= walker
.sexp_path(range
)
417 local parentng
, grandparent
= nodes
[#nodes
- 1], nodes
[#nodes
- 2]
418 local scope
= parentng
and not parentng
.is_root
and parentng
or grandparent
419 return refmt_at(scope
, range
) or range
.start
422 local function delete_splicing(range
, pos
, splicing
, delete_and_yank
)
423 local action
= {kill
= true, wrap
= splicing
, splice
= splicing
, func
= delete_and_yank
}
424 local sexp
, parent
, n
= walker
.sexp_at(range
, true)
425 local ndeleted
, spliced
= pick_out(range
, pos
, action
)
426 local closing
= spliced
and parser
.opposite
[sexp
.d
]
427 local inner_list_len
= spliced
and sexp
.finish
- sexp
.start
+ 1 - #sexp
.d
- #closing
428 local range_len
= range
.finish
- range
.start
429 local backwards
= pos
== range
.finish
430 local whole_object
= sexp
and
431 (sexp
.start
== range
.start
and sexp
.finish
+ 1 == range
.finish
432 or spliced
and (inner_list_len
<= (backwards
and range_len
+ ndeleted
or range_len
)))
433 local in_head_atom
= sexp
and not sexp
.d
and n
== 1 and #parent
> 1
434 local in_whitespace
= not sexp
435 if whole_object
or in_whitespace
or in_head_atom
then
436 local cur
= whole_object
and sexp
.start
or range
.start
437 -- if parent[#parent + 1] is nil, we are at EOF
438 if not parent
.is_root
or parent
.is_parsed(cur
) or parent
[#parent
+ 1] then
439 parser
.tree
.rewind(cur
)
441 local r
= {start
= cur
, finish
= cur
}
442 local _
, parentng
= walker
.sexp_at(r
, true)
443 local newpos
= refmt_at(parentng
, r
)
446 return spliced
and (backwards
and sexp
.start
or range
.start
- ndeleted
)
447 or not backwards
and ndeleted
<= 0 and range
.finish
- ndeleted
451 local function transpose(range
, first
, second
)
452 if not (first
and second
) then return end
453 local copy1
= first
.text
454 local copy2
= second
.text
455 delete(second
.start
, second
.finish
+ 1 - second
.start
)
456 write(second
.start
, copy1
)
457 delete(first
.start
, first
.finish
+ 1 - first
.start
)
458 write(first
.start
, copy2
)
459 parser
.tree
.rewind(first
.start
)
460 range
.start
= second
.finish
+ 1
461 range
.finish
= range
.start
462 local _
, parentng
= walker
.sexp_at(range
, true)
463 return refmt_at(parentng
, range
) or range
.start
466 local function transpose_sexps(range
)
467 local node
, parent
= walker
.sexp_at(range
, true)
468 local first
= node
and node
.finish
+ 1 == range
.start
and node
or parent
.before(range
.start
, finishof
)
469 local second
= first
~= node
and node
or parent
.after(range
.start
, startof
)
470 return transpose(range
, first
, second
)
473 local function transpose_words(range
)
474 local _
, first
= walker
.start_float_before(range
)
475 local _
, second
= walker
.finish_float_after(range
)
476 if first
and second
and first
.start
== second
.start
then
477 _
, first
= walker
.start_float_before(first
)
479 return transpose(range
, first
, second
)
482 local function transpose_chars(range
)
483 local node
, parent
, i
= walker
.sexp_at(range
)
484 local nxt
= i
and parent
[i
+ 1]
485 local pstart
= not parent
.is_root
and parent
.start
+ (parent
.p
and #parent
.p
or 0) + #parent
.d
486 local pfinish
= not parent
.is_root
and parent
.finish
487 local npref
= node
and node
.p
and node
.start
+ #node
.p
488 -- only allow transposing while inside atoms/words and prefixes
489 if node
and ((npref
and (range
.start
<= npref
and range
.start
> node
.start
) or
490 node
.d
and range
.start
> node
.finish
+ 1) or not node
.d
and (not pstart
or range
.start
> pstart
)) then
491 local start
= range
.start
-
492 ((range
.start
== pfinish
or range
.start
== npref
or
493 range
.start
== node
.finish
+ 1 and (parent
.is_list
or parent
.is_root
) and (not nxt
or nxt
.indent
)) and 1 or 0)
494 local str_start
= start
- node
.start
+ 1
495 local char
= node
.text
:sub(str_start
, str_start
)
497 write(start
- 1, #char
> 0 and char
or " ")
498 parser
.tree
.rewind(start
)
499 local in_head_atom
= i
== 1 and #parent
> 1
500 return parent
.is_list
and in_head_atom
501 and refmt_at(parent
, {start
= start
+ 1, finish
= start
+ 1})
506 local function _join_or_splice(parent
, n
, range
, pos
)
507 local sexp
= parent
[n
]
508 local nxt
= parent
[n
+ 1]
509 local is_last
= not nxt
or parser
.opposite
[nxt
.d
] ~= "\n"
510 local newpos
= not (is_last
and sexp
.is_empty
) and join_sexps(range
)
511 if not newpos
and sexp
.is_empty
then
512 newpos
= splice_sexp({start
= pos
, finish
= pos
}, nil, true)
517 local function delete_nonsplicing(range
, pos
, delete_maybe_yank
)
518 local action
= {kill
= true, wrap
= false, splice
= false, func
= delete_maybe_yank
}
519 local ndeleted
, spliced
, opening
, _
, refmt
= pick_out(range
, pos
, action
)
520 local backwards
= pos
== range
.finish
523 return pos
- ndeleted
525 if ndeleted
== 0 then
526 local closing
= parser
.opposite
[opening
]
527 if closing
== "\n" then
528 local sexp
, parent
, n
= walker
.sexp_at(range
)
529 if pos
== sexp
.start
+ #sexp
.d
and backwards
then
530 return _join_or_splice(parent
, n
, range
, pos
)
531 elseif pos
== sexp
.finish
then
532 local r
= {start
= pos
+ #closing
, finish
= pos
+ #closing
}
533 return _join_or_splice(parent
, n
, r
, pos
)
537 return backwards
and (range
.finish
- ndeleted
) or range
.start
540 local newpos
= backwards
and range
.start
or (range
.finish
- ndeleted
)
541 local r
= {start
= newpos
, finish
= newpos
}
543 -- since the range can cross list boundaries, find which of the lists at both ends
544 -- of the range is larger, and reformat it:
546 _
, p1
= walker
.sexp_at({start
= range
.start
, finish
= range
.start
}, true)
547 if p1
.is_root
or not (p1
.start
< range
.start
and p1
.finish
> range
.finish
- ndeleted
) then
548 _
, p2
= walker
.sexp_at({start
= range
.finish
- ndeleted
, finish
= range
.finish
- ndeleted
}, true)
550 return refmt_at(p2
and not p2
.is_root
and p2
or p1
, r
, true)
556 local function insert_pair(range
, delimiter
, shiftless
, auto_square
)
557 local indices
, nodes
= walker
.sexp_path(range
)
558 local sexp
= nodes
[#nodes
]
559 local right_after_prefix
= sexp
and sexp
.p
and range
.start
== sexp
.start
+ #sexp
.p
560 -- XXX: here I assume that # is a valid prefix for the dialect
561 local mb_closing
= (delimiter
== "|") and right_after_prefix
and parser
.opposite
[sexp
.p
.. delimiter
]
562 local closing
= mb_closing
or parser
.opposite
[delimiter
]
563 local squarewords
= fmt
.squarewords
564 if squarewords
and not right_after_prefix
565 and not (closing
== "]" and shiftless
and not auto_square
)
566 and (auto_square
or squarewords
.not_optional
)
567 and not fmt
:adjust_bracket_p(indices
, nodes
, range
) then
568 delimiter
, closing
= "[", "]"
570 write(range
.finish
, closing
)
571 write(range
.start
, delimiter
)
572 if right_after_prefix
or parser
.tree
.is_parsed(range
.start
) then
573 parser
.tree
.rewind(right_after_prefix
and sexp
.start
or range
.start
)
575 return range
.start
+ #delimiter
578 local function make_wrap(kind
)
579 return function(range
, pos
, auto_square
)
581 local function _wrap(r
)
582 insert_pair(r
, opening
, false, auto_square
)
584 local action
= {kill
= false, wrap
= false, splice
= false, func
= _wrap
}
585 pick_out(range
, pos
, action
)
586 parser
.tree
.rewind(range
.start
)
587 local _
, parentng
= walker
.sexp_at(range
, true)
588 range
.start
= range
.start
+ #opening
589 return refmt_at(parentng
, range
) or range
.start
593 local function newline(parent
, range
)
594 local line_comment
= parent
.d
and parser
.opposite
[parent
.d
] == "\n"
595 -- do not autoextend margin comments:
596 if line_comment
and range
.start
< parent
.finish
+ (parent
.indent
and 1 or 0) then
597 local newpos
= split_sexp(range
)
602 if not parent
.is_list
and not line_comment
then
603 -- if parent[#parent + 1] is nil, we are at EOF
604 if not parent
.is_root
or parent
.is_parsed(range
.start
) or parent
[#parent
+ 1] then
605 parser
.tree
.rewind(parent
.start
or range
.start
)
609 if not parent
.is_list
then
611 _
, parent
= walker
.sexp_at(parent
, true)
613 local last_nonblank_in_list
= not parent
.is_empty
and parent
[#parent
].finish
- (line_comment
and 1 or 0)
614 local nxt
= parent
.after(range
.start
, startof
)
615 local last_on_line
= not nxt
or nxt
and nxt
.indent
and nxt
.start
> range
.start
616 local margin
= nxt
and not nxt
.indent
and nxt
.is_comment
and parser
.opposite
[nxt
.d
] == "\n" and nxt
.finish
617 local after_last
= last_nonblank_in_list
and range
.start
> last_nonblank_in_list
618 local placeholder
= "asdf"
619 local newpos
= margin
or range
.start
620 if not parent
.is_empty
then
621 write(newpos
, "\n"..((after_last
or last_on_line
or margin
) and placeholder
or ""))
623 parser
.tree
.rewind(parent
.start
or newpos
)
624 -- move the cursor onto the placeholder, so refmt_at can restore the position:
625 local r
= {start
= newpos
, finish
= newpos
}
626 newpos
= walker
.start_after(r
) or newpos
627 local rangeng
= {start
= newpos
, finish
= newpos
}
628 newpos
= refmt_at(parent
, rangeng
) or rangeng
.start
630 local _
, parentng
= walker
.sexp_at({start
= newpos
, finish
= newpos
}, true)
631 local autoindent
= parentng
[#parentng
].indent
632 write(parentng
.finish
, "\n"..string.rep(" ", autoindent
or 0))
634 if after_last
or last_on_line
or margin
then
635 delete(newpos
, #placeholder
)
637 parser
.tree
.rewind(newpos
)
641 local function close_and_newline(range
)
642 local _
, parent
= walker
.sexp_at(range
)
643 if parent
and not parent
.is_root
then
644 local r
= {start
= parent
.finish
, finish
= parent
.finish
}
645 local newpos
= refmt_at(parent
, r
)
646 r
= {start
= (newpos
or r
.finish
) + 1, finish
= (newpos
or r
.finish
) + 1}
647 local list
, parentng
= walker
.sexp_at(r
)
648 newpos
= newline(parentng
, {start
= list
.finish
+ 1, finish
= list
.finish
+ 1})
650 newpos
= list
.finish
+ 1
658 local function meta_doublequote(range
, pos
, auto_square
)
659 local escaped
= walker
.escaped_at(range
)
661 local _
, parent
= walker
.sexp_at(range
)
662 local newpos
= insert_pair(range
, '"', false, auto_square
)
663 return refmt_at(parent
, {start
= newpos
, finish
= newpos
}) or newpos
664 elseif escaped
.is_string
then
665 return close_and_newline(range
)
671 delete_splicing
= delete_splicing
,
672 delete_nonsplicing
= delete_nonsplicing
,
675 raise_sexp
= raise_sexp
,
676 slurp_sexp
= slurp_sexp
,
677 barf_sexp
= barf_sexp
,
678 splice_sexp
= splice_sexp
,
679 wrap_round
= make_wrap("("),
680 meta_doublequote
= meta_doublequote
,
681 insert_pair
= insert_pair
,
683 close_and_newline
= close_and_newline
,
684 cycle_wrap
= cycle_wrap
,
685 split_sexp
= split_sexp
,
686 join_sexps
= join_sexps
,
687 transpose_sexps
= transpose_sexps
,
688 transpose_words
= transpose_words
,
689 transpose_chars
= transpose_chars
,