1 -- SPDX-License-Identifier: GPL-3.0-or-later
2 -- © 2020 Georgi Kirilov
6 local function startof(node
) return node
.start
end
7 local function finishof(node
) return node
.finish
end
9 local function make_insert(parser
, d1
, D2
, walker
, edit
, write, eol_at
, bol_at
)
11 local function open(range
, seek, opening
, auto_square
)
12 local escaped
= walker
.escaped_at(range
)
13 if escaped
and range
.start
> escaped
.start
then
14 if opening
== escaped
.d
then
15 seek(escaped
.finish
+ 1)
17 elseif escaped
.is_char
then
20 write(range
.start
, opening
)
21 seek(range
.start
+ #opening
)
22 parser
.tree
.rewind(escaped
.start
)
24 local _
, parent
= walker
.sexp_at(range
)
25 -- TODO: make it work even when before the first existing element on the electric line:
26 local on_electric_line
= parent
.finish
and parent
.finish
> eol_at(range
.finish
)
27 and not parent
.find_after(range
, function(t
) return t
.indent
end)
28 local newpos
= edit
.insert_pair(range
, opening
, auto_square
)
29 seek(edit
.refmt_at(parent
, {start
= newpos
, finish
= newpos
}, on_electric_line
))
34 local function close(range
, seek, kind
)
35 local pos
= range
.start
36 local escaped
= walker
.escaped_at(range
)
40 parser
.tree
.rewind(escaped
.start
)
42 local _
, parent
= walker
.sexp_at(range
)
43 if parent
and parent
.is_list
then
44 range
= {start
= parent
.finish
, finish
= parent
.finish
}
45 local newpos
= edit
.refmt_at(parent
, range
)
47 parser
.tree
.rewind(parent
.start
)
53 local function spacekey(range
, seek, char
)
54 local sexp
, parent
= walker
.sexp_at(range
)
55 -- if parent[#parent + 1] is nil, we are at EOF
56 if not parent
.is_root
or parent
.is_parsed(range
.start
) or parent
[#parent
+ 1] then
57 parser
.tree
.rewind(parent
.start
or range
.start
)
59 if not parent
.is_list
or parent
.is_empty
then return end
60 local after_last
= parent
[#parent
].finish
and range
.start
> parent
[#parent
].finish
61 local bol
= bol_at(range
.start
)
62 local eol
= eol_at(range
.start
)
63 local prev
= parent
.before(range
.start
, finishof
)
64 local nxt
= parent
.after(range
.start
, startof
)
65 local on_empty_line
= not sexp
and ((prev
and prev
.finish
or parent
.start
) < bol
)
66 and ((nxt
and nxt
.start
or parent
.finish
) > eol
)
67 local at_eol
= range
.start
== eol
69 if not on_empty_line
then
71 write(range
.start
, char
)
72 newpos
= range
.start
+ #char
73 _
, parent
= walker
.sexp_at(range
)
77 if not at_eol
or on_empty_line
then
78 newpos
= edit
.refmt_at(parent
, {start
= newpos
, finish
= newpos
}, after_last
and not on_empty_line
)
80 parser
.tree
.rewind(newpos
)
82 newpos
= newpos
+ #char
89 local function enterkey(range
, seek)
90 local _
, parent
= walker
.sexp_at(range
)
91 local newpos
= edit
.newline(parent
, range
)
98 local comment_start
= parser
.opposite
["\n"]
100 local function open_comment(range
, seek, char
)
101 local escaped
= walker
.escaped_at(range
)
102 if escaped
and escaped
.is_char
then return true end
103 local datum
, sexp
, parent
, descend_into_next_comment
, stay_out
104 if not (escaped
and range
.start
> escaped
.start
) then
105 sexp
, parent
= walker
.sexp_at(range
, true)
106 datum
= sexp
and sexp
.p
and sexp
.p
:find("^#") and range
.start
== sexp
.start
+ 1
107 local _
, next_safe
= walker
.next_finish_wrapped(range
)
108 local eol
= eol_at(range
.start
) -- XXX: must be before the write. The vis eol_at wrapper is leaky
109 local bol
= bol_at(range
.start
) -- XXX: must be before the write. The vis eol_at wrapper is leaky
110 if next_safe
and not datum
then
111 write(next_safe
, parser
.opposite
[char
])
114 local prev
= parent
.before(range
.start
, finishof
)
115 local nxt
= parent
.after(range
.start
, startof
)
116 local pstart
= parent
.start
or 0
117 local no_sexp_to_the_left
=
118 not prev
and (pstart
< bol
or parent
.is_root
) or
119 sexp
and (sexp
.indent
and range
.start
== sexp
.start
or sexp
.is_line_comment
) or
120 not sexp
and prev
and prev
.finish
< bol
121 local comment_out_at
=
122 (not sexp
or sexp
.is_line_comment
) and nxt
and nxt
.finish
< eol
and nxt
.start
or
123 sexp
and range
.start
<= sexp
.finish
and sexp
.finish
< eol
and range
.start
124 if no_sexp_to_the_left
then
125 if sexp
and sexp
.is_line_comment
and range
.start
== sexp
.start
then
126 descend_into_next_comment
= sexp
.start
+ #sexp
.d
128 if nxt
and nxt
.is_line_comment
and nxt
.start
< eol
then
129 descend_into_next_comment
= nxt
.start
+ #nxt
.d
131 char
= char
:rep(parent
.is_root
and 3 or 2) .. " "
135 if (not sexp
or range
.start
== sexp
.finish
+ 1) and
136 nxt
and nxt
.is_line_comment
and not nxt
.indent
then
137 descend_into_next_comment
= nxt
.start
+ #nxt
.d
139 local comment_column
= 40
140 local last_col
= range
.start
- bol
+ 1
141 local padding
= math
.max(1, comment_column
- last_col
)
142 char
= string.rep(" ", padding
)..char
145 if not descend_into_next_comment
and comment_out_at
then
146 stay_out
= comment_out_at
151 range
.start
= stay_out
153 if not descend_into_next_comment
then
154 write(range
.start
, char
)
156 -- if parent[#parent + 1] is nil, we are at EOF
157 if not parent
or not parent
.is_root
or parent
.is_parsed(range
.start
) or parent
[#parent
+ 1] then
158 parser
.tree
.rewind(range
.start
- (datum
and 1 or 0))
160 if descend_into_next_comment
then
161 range
.start
= descend_into_next_comment
162 elseif not stay_out
then
163 range
.start
= range
.start
+ #char
165 range
.finish
= range
.start
166 if not descend_into_next_comment
and not escaped
and not parent
.is_root
then
167 range
.start
= edit
.refmt_at(parent
, range
)
173 return function(range
, seek, char
, auto_square
)
175 d1
:match(char
) and open
or
176 char
== '"' and open
or
177 parser
.opposite
["|"] and char
== "|" and open
or
178 D2
:match(char
) and close
or
179 char
== " " and spacekey
or
180 char
== "\n" and enterkey
or
181 char
== comment_start
and open_comment
183 return handler(range
, seek, char
, auto_square
)
185 if parser
.tree
.is_parsed(range
.start
) then
186 parser
.tree
.rewind(range
.start
)
192 function M
.new(parser
, d1
, D2
, walker
, edit
, write, eol_at
, bol_at
)
194 insert
= make_insert(parser
, d1
, D2
, walker
, edit
, write, eol_at
, bol_at
),