fix an issue with multi-line lists:
[lisp-parkour.git] / input.lua
blob2189064f6e36d3d29c4e7eb2b726ecd45b22784c
1 local M = {}
3 local swap = {["("] = "[", [")"] = "]", ["["] = "(", ["]"] = ")"}
5 local function startof(node) return node.start end
6 local function finishof(node) return node.finish end
8 local function make_insert(parser, d1, D2, walker, edit, write, eol_at, bol_at)
10 local function open(range, seek, opening, shiftless, auto_square)
11 local escaped = walker.escaped_at(range)
12 if escaped and range.start > escaped.start then
13 if opening == escaped.d then
14 seek(escaped.finish + 1)
15 return true
16 elseif escaped.is_char then
17 return true
18 end
19 write(range.start, opening)
20 seek(range.start + #opening)
21 parser.tree.rewind(escaped.start)
22 else
23 local _, parent = walker.sexp_at(range)
24 -- TODO: make it work even when before the first existing element on the electric line:
25 local on_electric_line = parent.finish and parent.finish > eol_at(range.finish)
26 and not parent.find_after(range, function(t) return t.indent end)
27 local newpos = edit.insert_pair(range, opening, shiftless, auto_square)
28 seek(edit.refmt_at(parent, {start = newpos, finish = newpos}, on_electric_line) or newpos)
29 end
30 return true
31 end
33 local function close(range, seek, kind)
34 local pos = range.start
35 local escaped = walker.escaped_at(range)
36 if escaped then
37 write(pos, kind)
38 seek(pos + 1)
39 parser.tree.rewind(escaped.start)
40 else
41 local _, parent = walker.sexp_at(range)
42 if parent and parent.is_list then
43 range = {start = parent.finish, finish = parent.finish}
44 local newpos = edit.refmt_at(parent, range)
45 seek(newpos + 1)
46 parser.tree.rewind(parent.start)
47 end
48 end
49 return true
50 end
52 local function spacekey(range, seek, char)
53 local _, parent = walker.sexp_at(range)
54 -- if parent[#parent + 1] is nil, we are at EOF
55 if not parent.is_root or parent.is_parsed(range.start) or parent[#parent + 1] then
56 parser.tree.rewind(parent.start or range.start)
57 end
58 if not parent.is_list or parent.is_empty then return end
59 local after_last = parent[#parent].finish and range.start > parent[#parent].finish
60 local bol = bol_at(range.start)
61 local eol = eol_at(range.start)
62 local prev = parent.before(range.start, finishof)
63 local nxt = parent.after(range.start, startof)
64 local on_empty_line = ((prev and prev.finish or parent.start) < bol)
65 and ((nxt and nxt.start or parent.finish) > eol)
66 local at_eol = range.start == eol
67 local newpos
68 if not on_empty_line then
69 write(range.start, char)
70 newpos = range.start + #char
71 _, parent = walker.sexp_at(range)
72 else
73 newpos = bol - 1
74 end
75 if not at_eol or on_empty_line then
76 newpos = edit.refmt_at(parent, {start = newpos, finish = newpos}, after_last and not on_empty_line) or newpos
77 if on_empty_line then
78 parser.tree.rewind(newpos)
79 write(newpos, char)
80 newpos = newpos + #char
81 end
82 end
83 seek(newpos)
84 return true
85 end
87 local function enterkey(range, seek)
88 local _, parent = walker.sexp_at(range)
89 local newpos = edit.newline(parent, range)
90 if newpos then
91 seek(newpos)
92 return true
93 end
94 end
96 local function semicolon(range, seek, char)
97 local escaped = walker.escaped_at(range)
98 if escaped and escaped.is_char then return true end
99 local datum, sexp, parent
100 if not (escaped and range.start > escaped.start) then
101 sexp, parent = walker.sexp_at(range, true)
102 datum = sexp and sexp.p and sexp.p:find("^#") and range.start == sexp.start + 1
103 local _, next_safe = walker.next_finish_wrapped(range)
104 local eol = eol_at(range.start) -- XXX: must be before the write; the vis eol_at wrapper is leaky
105 local bol = bol_at(range.start) -- XXX: must be before the write; the vis eol_at wrapper is leaky
106 if next_safe and not datum then
107 write(next_safe, parser.opposite[char])
109 if not datum then
110 local prev = parent.before(range.start, finishof)
111 local nxt = parent.after(range.start, startof)
112 local on_empty_line = parent.is_list
113 and ((prev and prev.finish or parent.start) < bol)
114 and ((nxt and nxt.start or parent.finish) > eol)
115 if on_empty_line or next_safe == range.start and next_safe ~= parent.finish then
116 if parent.is_root then
117 char = ";;; "
118 else
119 char = "\n;; "
121 elseif nxt and nxt.start > eol or range.start == eol then
122 local tabwidth = 8
123 local margin_column = 40
124 local last_col = (not nxt and prev.finish or eol) - bol
125 local margin = math.ceil((margin_column - last_col) / tabwidth)
126 char = (margin > 0 and string.rep("\t", margin) or " ")..char
130 write(range.start, char)
131 -- if parent[#parent + 1] is nil, we are at EOF
132 if not parent or not parent.is_root or parent.is_parsed(range.start) or parent[#parent + 1] then
133 parser.tree.rewind(range.start - (datum and 1 or 0))
135 range.start = range.start + #char
136 range.finish = range.start
137 if not escaped and not parent.is_root then
138 range.start = edit.refmt_at(parent, range) or range.start
140 seek(range.start)
141 return true
144 return function(range, seek, char, shiftless, auto_square)
145 if shiftless then
146 char = swap[char] or char
148 local handler =
149 d1:match(char) and open or
150 char == '"' and open or
151 parser.opposite['|'] and char == '|' and open or
152 D2:match(char) and close or
153 char == " " and spacekey or
154 char == "\n" and enterkey or
155 char == ";" and semicolon
156 if handler then
157 return handler(range, seek, char, shiftless, auto_square)
158 else
159 if parser.tree.is_parsed(range.start) then
160 parser.tree.rewind(range.start)
162 if shiftless and char:find'[%[%]]' then
163 write(range.start, char)
164 seek(range.start + 1)
165 return true
171 function M.new(parser, d1, D2, walker, edit, write, eol_at, bol_at)
172 return {
173 insert = make_insert(parser, d1, D2, walker, edit, write, eol_at, bol_at),
177 return M