]> git.lizzy.rs Git - cutie.git/blob - init.lua
Create LICENSE
[cutie.git] / init.lua
1 local posix = {
2         termio = require("posix.termio"),
3         poll = require("posix.poll"),
4         unistd = require("posix.unistd"),
5 }
6
7 local cutie = {
8         esc = string.char(27) .. "[",
9         terminal_size = nil,
10         buffer = "",
11         input = EventTarget()
12 }
13
14 -- colors
15
16 local function color_from_hue(hue)
17         local h = hue / 60
18         local x = (1 - math.abs(h % 2 - 1))
19
20         local i = math.floor(h)
21
22         if i == 0 then
23                 return {1, x, 0}
24         elseif i == 1 then
25                 return {x, 1, 0}
26         elseif i == 2 then
27                 return {0, 1, x}
28         elseif i == 3 then
29                 return {0, x, 1}
30         elseif i == 4 then
31                 return {x, 0, 1}
32         else
33                 return {1, 0, x}
34         end
35 end
36
37 function cutie.to_color(color)
38         local t = type(color)
39
40         if t == "number" then
41                 return color_from_hue(color)
42         elseif t == "string" then
43                 local color = color:gsub("#", "")
44
45                 return {
46                         tonumber(color:sub(1, 2), 16) / 255,
47                         tonumber(color:sub(3, 4), 16) / 255,
48                         tonumber(color:sub(5, 6), 16) / 255,
49                 }
50         else
51                 return color
52         end
53 end
54
55 function make_color(color, bg)
56         color = cutie.to_color(color)
57
58         return cutie.esc
59                 .. (bg and "48" or "38") .. ";2"
60                 .. ";" .. math.clamp(math.floor(color[1] * 255), 0, 255)
61                 .. ";" .. math.clamp(math.floor(color[2] * 255), 0, 255)
62                 .. ";" .. math.clamp(math.floor(color[3] * 255), 0, 255)
63                 .. "m"
64 end
65
66 function cutie.color(color)
67         return make_color(color, false)
68 end
69
70 function cutie.background_color(color)
71         return make_color(color, true)
72 end
73
74 function cutie.set_color(color)
75         cutie.render(cutie.color(color))
76 end
77
78 function cutie.set_background(color)
79         cutie.render(cutie.background_color(color))
80 end
81
82 -- simple effects
83
84 cutie.bold = cutie.esc .. "1m"
85 cutie.no_effects = cutie.esc .. "0m"
86
87 function cutie.set_bold()
88         cutie.render(cutie.bold)
89 end
90
91 function cutie.clear_effects()
92         cutie.render(cutie.no_effects)
93 end
94
95 function cutie.move_cursor(x, y)
96         cutie.render_escape(math.floor(y) .. ";" .. math.floor(x) .. "H")
97 end
98
99 function cutie.set_alternate_buffer(enabled)
100         cutie.render_escape("?1049" .. (enabled and "h" or "l"))
101 end
102
103 function cutie.set_cursor_shown(enabled)
104         cutie.render_escape("?25" .. (enabled and "h" or "l"))
105 end
106
107 -- input
108
109 function cutie.set_canon_input(enabled)
110         local termios = posix.termio.tcgetattr(0)
111
112         if enabled then
113                 termios.lflag = bit32.bor(termios.lflag,
114                         posix.termio.ICANON,
115                         posix.termio.ECHO
116                 )
117         else
118                 termios.lflag = bit32.band(termios.lflag, bit32.bnot(bit32.bor(
119                         posix.termio.ICANON,
120                         posix.termio.ECHO
121                 )))
122         end
123
124         posix.termio.tcsetattr(0, posix.termio.TCSANOW, termios)
125 end
126
127 function cutie.set_input_buffer(enabled)
128         lua_async.poll_functions[cutie.poll_input] = enabled or nil
129
130         cutie.input.buffer = enabled and "" or nil
131         cutie.input.history = enabled and {} or nil
132         cutie.input.cursor = nil
133 end
134
135 local function getchar()
136         return posix.unistd.read(0, 1)
137 end
138
139 function cutie.poll_input()
140         local pfd = {[0] = {events = {IN = true}}}
141         posix.poll.poll(pfd, 0)
142
143         if pfd[0].revents and pfd[0].revents.IN then
144                 local char = getchar()
145
146                 if char == "\n" then
147                         if input ~= "" then
148                                 cutie.input:dispatchEvent(Event("input", {input = cutie.input.buffer}))
149                                 table.insert(cutie.input.history, cutie.input.buffer)
150                                 cutie.input.buffer = ""
151                                 cutie.input.cursor = nil
152                         end
153                 elseif char == cutie.esc:sub(1, 1) then
154                         local char2 = getchar()
155
156                         if char2 == cutie.esc:sub(2, 2) then
157                                 local char3 = getchar()
158
159                                 if char3 == "A" or char3 == "B" then
160                                         cutie.input.cursor = (cutie.input.cursor or #cutie.input.history + 1) + (char3 == "A" and -1 or 1)
161
162                                         if cutie.input.cursor > #cutie.input.history then
163                                                 cutie.input.cursor = #cutie.input.history + 1
164                                         end
165
166                                         if cutie.input.cursor < 1 then
167                                                 cutie.input.cursor = 1
168                                         end
169
170                                         cutie.input.buffer = cutie.input.history[cutie.input.cursor] or ""
171                                 end
172                         end
173                 elseif char == string.char(127) then
174                         cutie.input.buffer = cutie.input.buffer:sub(1, #cutie.input.buffer - 1)
175                 else
176                         cutie.input.buffer = cutie.input.buffer .. char
177                 end
178         end
179 end
180
181 -- rendering
182
183 function cutie.clear_screen()
184         cutie.render_escape("2J")
185 end
186
187 function cutie.empty_screen()
188         cutie.move_cursor(1, 1)
189
190         local size = cutie.get_terminal_size()
191         local str = (string.rep(" ", size[1]) .. "\n"):rep(size[2])
192         str = str:sub(1, #str - 1)
193
194         cutie.render(str)
195 end
196
197 function cutie.render(text)
198         cutie.buffer = cutie.buffer .. text
199 end
200
201 function cutie.render_escape(text)
202         cutie.render(cutie.esc .. text)
203 end
204
205 function cutie.render_at(array, x, y)
206         for i, line in ipairs(array) do
207                 cutie.move_cursor(x + 1, y + i)
208                 cutie.render(line)
209         end
210 end
211
212 function cutie.get_dimensions(array)
213         local width = 0
214
215         for _, line in pairs(array) do
216                 width = math.max(width, #line)
217         end
218
219         return {width, #array}
220 end
221
222 function cutie.flush_buffer()
223         io.write(cutie.buffer)
224         io.stdout:flush()
225         cutie.buffer = ""
226 end
227
228 function cutie.box(size)
229         return {
230                 "┌" .. string.rep("─", size[1]) .. "┐", string.rep(
231                 "│" .. string.rep(" ", size[1]) .. "│", size[2]),
232                 "└" .. string.rep("─", size[1]) .. "┘",
233         }
234 end
235
236 -- terminal size
237
238 function cutie.handle_resize()
239         local pf = io.popen("echo -ne \"cols\\nlines\" | tput -S", "r")
240         local size = pf:read("*all"):split("\n")
241         pf:close()
242         cutie.terminal_size = size
243 end
244
245 function cutie.get_terminal_size()
246         return cutie.terminal_size
247 end
248
249 return cutie