use double-quotes in code
[lisp-parkour.git] / input.lua
blob2fcd434c0e52a40612949d3275281556b6184e9c
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))
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 sexp, 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 = not sexp and ((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 local _
70 write(range.start, char)
71 newpos = range.start + #char
72 _, parent = walker.sexp_at(range)
73 else
74 newpos = bol - 1
75 end
76 if not at_eol or on_empty_line then
77 newpos = edit.refmt_at(parent, {start = newpos, finish = newpos}, after_last and not on_empty_line)
78 if on_empty_line then
79 parser.tree.rewind(newpos)
80 write(newpos, char)
81 newpos = newpos + #char
82 end
83 end
84 seek(newpos)
85 return true
86 end
88 local function enterkey(range, seek)
89 local _, parent = walker.sexp_at(range)
90 local newpos = edit.newline(parent, range)
91 if newpos then
92 seek(newpos)
93 return true
94 end
95 end
97 local function semicolon(range, seek, char)
98 local escaped = walker.escaped_at(range)
99 if escaped and escaped.is_char then return true end
100 local datum, sexp, parent
101 if not (escaped and range.start > escaped.start) then
102 sexp, parent = walker.sexp_at(range, true)
103 datum = sexp and sexp.p and sexp.p:find("^#") and range.start == sexp.start + 1
104 local _, next_safe = walker.next_finish_wrapped(range)
105 local eol = eol_at(range.start) -- XXX: must be before the write; the vis eol_at wrapper is leaky
106 local bol = bol_at(range.start) -- XXX: must be before the write; the vis eol_at wrapper is leaky
107 if next_safe and not datum then
108 write(next_safe, parser.opposite[char])
110 if not datum then
111 local prev = parent.before(range.start, finishof)
112 local nxt = parent.after(range.start, startof)
113 local on_empty_line = not sexp and
114 ((prev and prev.finish or parent.start or range.start - 1) < bol)
115 and ((nxt and nxt.start or parent.finish or range.start + 1) > eol)
116 if on_empty_line or next_safe == range.start and next_safe ~= parent.finish then
117 if parent.is_root then
118 char = ";;; "
119 else
120 char = "\n;; "
122 elseif nxt and nxt.start > eol or range.start == eol then
123 local tabwidth = 8
124 local margin_column = 40
125 local last_col = (not nxt and prev.finish or eol) - bol
126 local margin = math.ceil((margin_column - last_col) / tabwidth)
127 char = (margin > 0 and string.rep("\t", margin) or " ")..char
131 write(range.start, char)
132 -- if parent[#parent + 1] is nil, we are at EOF
133 if not parent or not parent.is_root or parent.is_parsed(range.start) or parent[#parent + 1] then
134 parser.tree.rewind(range.start - (datum and 1 or 0))
136 range.start = range.start + #char
137 range.finish = range.start
138 if not escaped and not parent.is_root then
139 range.start = edit.refmt_at(parent, range)
141 seek(range.start)
142 return true
145 return function(range, seek, char, shiftless, auto_square)
146 if shiftless then
147 char = swap[char] or char
149 local handler =
150 d1:match(char) and open or
151 char == '"' and open or
152 parser.opposite["|"] and char == "|" and open or
153 D2:match(char) and close or
154 char == " " and spacekey or
155 char == "\n" and enterkey or
156 char == ";" and semicolon
157 if handler then
158 return handler(range, seek, char, shiftless, auto_square)
159 else
160 if parser.tree.is_parsed(range.start) then
161 parser.tree.rewind(range.start)
163 if shiftless and char:find"[%[%]]" then
164 write(range.start, char)
165 seek(range.start + 1)
166 return true
172 function M.new(parser, d1, D2, walker, edit, write, eol_at, bol_at)
173 return {
174 insert = make_insert(parser, d1, D2, walker, edit, write, eol_at, bol_at),
178 return M