better wording
[lisp-parkour.git] / input.lua
blobb2addf9edb1a8f078829fca9da898b34b5c20bf9
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 comment_start = parser.opposite["\n"]
99 local function open_comment(range, seek, char)
100 local escaped = walker.escaped_at(range)
101 if escaped and escaped.is_char then return true end
102 local datum, sexp, parent
103 if not (escaped and range.start > escaped.start) then
104 sexp, parent = walker.sexp_at(range, true)
105 datum = sexp and sexp.p and sexp.p:find("^#") and range.start == sexp.start + 1
106 local _, next_safe = walker.next_finish_wrapped(range)
107 local eol = eol_at(range.start) -- XXX: must be before the write. The vis eol_at wrapper is leaky
108 local bol = bol_at(range.start) -- XXX: must be before the write. The vis eol_at wrapper is leaky
109 if next_safe and not datum then
110 write(next_safe, parser.opposite[char])
112 if not datum then
113 local prev = parent.before(range.start, finishof)
114 local nxt = parent.after(range.start, startof)
115 local on_empty_line = not sexp and
116 ((prev and prev.finish or parent.start or range.start - 1) < bol)
117 and ((nxt and nxt.start or parent.finish or range.start + 1) > eol)
118 if on_empty_line or next_safe == range.start and next_safe ~= parent.finish then
119 if parent.is_root then
120 char = char:rep(3) .. " "
121 else
122 char = "\n" .. char:rep(2) .. " "
124 elseif nxt and nxt.start > eol or range.start == eol then
125 local tabwidth = 8
126 local margin_column = 40
127 local last_col = (not nxt and prev.finish or eol) - bol
128 local margin = math.ceil((margin_column - last_col) / tabwidth)
129 char = (margin > 0 and string.rep("\t", margin) or " ")..char
133 write(range.start, char)
134 -- if parent[#parent + 1] is nil, we are at EOF
135 if not parent or not parent.is_root or parent.is_parsed(range.start) or parent[#parent + 1] then
136 parser.tree.rewind(range.start - (datum and 1 or 0))
138 range.start = range.start + #char
139 range.finish = range.start
140 if not escaped and not parent.is_root then
141 range.start = edit.refmt_at(parent, range)
143 seek(range.start)
144 return true
147 return function(range, seek, char, shiftless, auto_square)
148 if shiftless then
149 char = swap[char] or char
151 local handler =
152 d1:match(char) and open or
153 char == '"' and open or
154 parser.opposite["|"] and char == "|" and open or
155 D2:match(char) and close or
156 char == " " and spacekey or
157 char == "\n" and enterkey or
158 char == comment_start and open_comment
159 if handler then
160 return handler(range, seek, char, shiftless, auto_square)
161 else
162 if parser.tree.is_parsed(range.start) then
163 parser.tree.rewind(range.start)
165 if shiftless and char:find"[%[%]]" then
166 write(range.start, char)
167 seek(range.start + 1)
168 return true
174 function M.new(parser, d1, D2, walker, edit, write, eol_at, bol_at)
175 return {
176 insert = make_insert(parser, d1, D2, walker, edit, write, eol_at, bol_at),
180 return M