SHOW:
|
|
- or go back to the newest paste.
1 | local fs = require("filesystem") | |
2 | local keyboard = require("keyboard") | |
3 | local shell = require("shell") | |
4 | local term = require("term") -- TODO use tty and cursor position instead of global area and gpu | |
5 | local text = require("text") | |
6 | local hl = require("highlighter") --https://pastebin.com/KNmLY5vX /lib/highlighter.lua | |
7 | ||
8 | local unicode = require("unicode") | |
9 | ||
10 | local copybuffer = "" | |
11 | local xcopy = 0 | |
12 | local ycopy = 0 | |
13 | ||
14 | if not term.isAvailable() then | |
15 | return | |
16 | end | |
17 | local gpu = term.gpu() | |
18 | local args, options = shell.parse(...) | |
19 | if #args == 0 then | |
20 | io.write("Usage: edit <filename>") | |
21 | return | |
22 | end | |
23 | ||
24 | local filename = shell.resolve(args[1]) | |
25 | local file_parentpath = fs.path(filename) | |
26 | ||
27 | if fs.exists(file_parentpath) and not fs.isDirectory(file_parentpath) then | |
28 | io.stderr:write(string.format("Not a directory: %s\n", file_parentpath)) | |
29 | return 1 | |
30 | end | |
31 | ||
32 | local readonly = options.r or fs.get(filename) == nil or fs.get(filename).isReadOnly() | |
33 | ||
34 | if fs.isDirectory(filename) then | |
35 | io.stderr:write("file is a directory\n") | |
36 | return 1 | |
37 | elseif not fs.exists(filename) and readonly then | |
38 | io.stderr:write("file system is read only\n") | |
39 | return 1 | |
40 | end | |
41 | ||
42 | local function loadConfig() | |
43 | -- Try to load user settings. | |
44 | local env = {} | |
45 | local config = loadfile("/home/etc/edit.cfg", nil, env) | |
46 | if config then | |
47 | pcall(config) | |
48 | end | |
49 | -- Fill in defaults. | |
50 | env.keybinds = env.keybinds or { | |
51 | left = {{"left"}}, | |
52 | right = {{"right"}}, | |
53 | up = {{"up"}}, | |
54 | down = {{"down"}}, | |
55 | home = {{"home"}}, | |
56 | eol = {{"end"}}, | |
57 | pageUp = {{"pageUp"}}, | |
58 | pageDown = {{"pageDown"}}, | |
59 | ||
60 | backspace = {{"back"}}, | |
61 | delete = {{"delete"}}, | |
62 | deleteLine = {{"control", "delete"}, {"shift", "delete"}}, | |
63 | copyLine = {{"control", "c"}}, | |
64 | pasteLine = {{"control", "v"}}, | |
65 | newline = {{"enter"}}, | |
66 | ||
67 | save = {{"control", "s"}}, | |
68 | close = {{"control", "w"}}, | |
69 | find = {{"control", "f"}}, | |
70 | findnext = {{"control", "g"}, {"control", "n"}, {"f3"}} | |
71 | } | |
72 | -- Generate config file if it didn't exist. | |
73 | if not config then | |
74 | local root = fs.get("/home") | |
75 | if root and not root.isReadOnly() then | |
76 | fs.makeDirectory("/etc") | |
77 | local f = io.open("/home/etc/edit.cfg", "w") | |
78 | if f then | |
79 | local serialization = require("serialization") | |
80 | for k, v in pairs(env) do | |
81 | f:write(k.."="..tostring(serialization.serialize(v, math.huge)).."\n") | |
82 | end | |
83 | f:close() | |
84 | end | |
85 | end | |
86 | end | |
87 | return env | |
88 | end | |
89 | ||
90 | term.clear() | |
91 | term.setCursorBlink(true) | |
92 | ||
93 | local running = true | |
94 | local buffer = {} | |
95 | local scrollX, scrollY = 0, 0 | |
96 | local config = loadConfig() | |
97 | ||
98 | local getKeyBindHandler -- forward declaration for refind() | |
99 | ||
100 | local function helpStatusText() | |
101 | local function prettifyKeybind(label, command) | |
102 | local keybind = type(config.keybinds) == "table" and config.keybinds[command] | |
103 | if type(keybind) ~= "table" or type(keybind[1]) ~= "table" then return "" end | |
104 | local alt, control, shift, key | |
105 | for _, value in ipairs(keybind[1]) do | |
106 | if value == "alt" then alt = true | |
107 | elseif value == "control" then control = true | |
108 | elseif value == "shift" then shift = true | |
109 | else key = value end | |
110 | end | |
111 | if not key then return "" end | |
112 | return label .. ": [" .. | |
113 | (control and "Ctrl+" or "") .. | |
114 | (alt and "Alt+" or "") .. | |
115 | (shift and "Shift+" or "") .. | |
116 | unicode.upper(key) .. | |
117 | "] " | |
118 | end | |
119 | return prettifyKeybind("Save", "save") .. | |
120 | prettifyKeybind("Close", "close") .. | |
121 | prettifyKeybind("Copy Line", "copyLine") .. | |
122 | prettifyKeybind("Paste Line", "pasteLine") .. | |
123 | prettifyKeybind("Find", "find") | |
124 | end | |
125 | ||
126 | ------------------------------------------------------------------------------- | |
127 | ||
128 | local function setStatus(value) | |
129 | local x, y, w, h = term.getGlobalArea() | |
130 | value = unicode.wlen(value) > w - 10 and unicode.wtrunc(value, w - 9) or value | |
131 | value = text.padRight(value, w - 10) | |
132 | gpu.set(x, y + h - 1, value) | |
133 | end | |
134 | ||
135 | local function getArea() | |
136 | local x, y, w, h = term.getGlobalArea() | |
137 | return x, y, w, h - 1 | |
138 | end | |
139 | ||
140 | local function removePrefix(line, length) | |
141 | if length >= unicode.wlen(line) then | |
142 | return "" | |
143 | else | |
144 | local prefix = unicode.wtrunc(line, length + 1) | |
145 | local suffix = unicode.sub(line, unicode.len(prefix) + 1) | |
146 | length = length - unicode.wlen(prefix) | |
147 | if length > 0 then | |
148 | suffix = (" "):rep(unicode.charWidth(suffix) - length) .. unicode.sub(suffix, 2) | |
149 | end | |
150 | return suffix | |
151 | end | |
152 | end | |
153 | ||
154 | local function lengthToChars(line, length) | |
155 | if length > unicode.wlen(line) then | |
156 | return unicode.len(line) + 1 | |
157 | else | |
158 | local prefix = unicode.wtrunc(line, length) | |
159 | return unicode.len(prefix) + 1 | |
160 | end | |
161 | end | |
162 | ||
163 | ||
164 | local function isWideAtPosition(line, x) | |
165 | local index = lengthToChars(line, x) | |
166 | if index > unicode.len(line) then | |
167 | return false, false | |
168 | end | |
169 | local prefix = unicode.sub(line, 1, index) | |
170 | local char = unicode.sub(line, index, index) | |
171 | --isWide, isRight | |
172 | return unicode.isWide(char), unicode.wlen(prefix) == x | |
173 | end | |
174 | ||
175 | local function drawLine(x, y, w, h, lineNr) | |
176 | local yLocal = lineNr - scrollY | |
177 | if yLocal > 0 and yLocal <= h then | |
178 | local str = removePrefix(buffer[lineNr] or "", scrollX) | |
179 | str = unicode.wlen(str) > w and unicode.wtrunc(str, w + 1) or str | |
180 | str = text.padRight(str, w) | |
181 | --gpu.set(x, y - 1 + lineNr - scrollY, str) | |
182 | hl.put(x, y - 1 + lineNr - scrollY, str) | |
183 | end | |
184 | end | |
185 | ||
186 | local function getCursor() | |
187 | local cx, cy = term.getCursor() | |
188 | return cx + scrollX, cy + scrollY | |
189 | end | |
190 | ||
191 | local function line() | |
192 | local cbx, cby = getCursor() | |
193 | return buffer[cby] | |
194 | end | |
195 | ||
196 | local function getNormalizedCursor() | |
197 | local cbx, cby = getCursor() | |
198 | local wide, right = isWideAtPosition(buffer[cby], cbx) | |
199 | if wide and right then | |
200 | cbx = cbx - 1 | |
201 | end | |
202 | return cbx, cby | |
203 | end | |
204 | ||
205 | local function setCursor(nbx, nby) | |
206 | local x, y, w, h = getArea() | |
207 | nby = math.max(1, math.min(#buffer, nby)) | |
208 | ||
209 | local ncy = nby - scrollY | |
210 | if ncy > h then | |
211 | term.setCursorBlink(false) | |
212 | local sy = nby - h | |
213 | local dy = math.abs(scrollY - sy) | |
214 | scrollY = sy | |
215 | if h > dy then | |
216 | gpu.copy(x, y + dy, w, h - dy, 0, -dy) | |
217 | end | |
218 | for lineNr = nby - (math.min(dy, h) - 1), nby do | |
219 | drawLine(x, y, w, h, lineNr) | |
220 | end | |
221 | elseif ncy < 1 then | |
222 | term.setCursorBlink(false) | |
223 | local sy = nby - 1 | |
224 | local dy = math.abs(scrollY - sy) | |
225 | scrollY = sy | |
226 | if h > dy then | |
227 | gpu.copy(x, y, w, h - dy, 0, dy) | |
228 | end | |
229 | for lineNr = nby, nby + (math.min(dy, h) - 1) do | |
230 | drawLine(x, y, w, h, lineNr) | |
231 | end | |
232 | end | |
233 | term.setCursor(term.getCursor(), nby - scrollY) | |
234 | ||
235 | nbx = math.max(1, math.min(unicode.wlen(line()) + 1, nbx)) | |
236 | local wide, right = isWideAtPosition(line(), nbx) | |
237 | local ncx = nbx - scrollX | |
238 | if ncx > w or (ncx + 1 > w and wide and not right) then | |
239 | term.setCursorBlink(false) | |
240 | scrollX = nbx - w + ((wide and not right) and 1 or 0) | |
241 | for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do | |
242 | drawLine(x, y, w, h, lineNr) | |
243 | end | |
244 | elseif ncx < 1 or (ncx - 1 < 1 and wide and right) then | |
245 | term.setCursorBlink(false) | |
246 | scrollX = nbx - 1 - ((wide and right) and 1 or 0) | |
247 | for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do | |
248 | drawLine(x, y, w, h, lineNr) | |
249 | end | |
250 | end | |
251 | term.setCursor(nbx - scrollX, nby - scrollY) | |
252 | --update with term lib | |
253 | nbx, nby = getCursor() | |
254 | gpu.set(x + w - 10, y + h, text.padLeft(string.format("%d,%d", nby, nbx), 10)) | |
255 | end | |
256 | ||
257 | local function highlight(bx, by, length, enabled) | |
258 | local x, y, w, h = getArea() | |
259 | local cx, cy = bx - scrollX, by - scrollY | |
260 | cx = math.max(1, math.min(w, cx)) | |
261 | cy = math.max(1, math.min(h, cy)) | |
262 | length = math.max(1, math.min(w - cx, length)) | |
263 | ||
264 | local fg, fgp = gpu.getForeground() | |
265 | local bg, bgp = gpu.getBackground() | |
266 | if enabled then | |
267 | gpu.setForeground(bg, bgp) | |
268 | gpu.setBackground(fg, fgp) | |
269 | end | |
270 | local indexFrom = lengthToChars(buffer[by], bx) | |
271 | local value = unicode.sub(buffer[by], indexFrom) | |
272 | if unicode.wlen(value) > length then | |
273 | value = unicode.wtrunc(value, length + 1) | |
274 | end | |
275 | -- gpu.set(x - 1 + cx, y - 1 + cy, value) | |
276 | hl.put(x - 1 + cx, y - 1 + cy, value) | |
277 | if enabled then | |
278 | gpu.setForeground(fg, fgp) | |
279 | gpu.setBackground(bg, bgp) | |
280 | end | |
281 | end | |
282 | ||
283 | local function home() | |
284 | local cbx, cby = getCursor() | |
285 | setCursor(1, cby) | |
286 | end | |
287 | ||
288 | local function ende() | |
289 | local cbx, cby = getCursor() | |
290 | setCursor(unicode.wlen(line()) + 1, cby) | |
291 | end | |
292 | ||
293 | local function left() | |
294 | local cbx, cby = getNormalizedCursor() | |
295 | if cbx > 1 then | |
296 | local wideTarget, rightTarget = isWideAtPosition(line(), cbx - 1) | |
297 | if wideTarget and rightTarget then | |
298 | setCursor(cbx - 2, cby) | |
299 | else | |
300 | setCursor(cbx - 1, cby) | |
301 | end | |
302 | return true -- for backspace | |
303 | elseif cby > 1 then | |
304 | setCursor(cbx, cby - 1) | |
305 | ende() | |
306 | return true -- again, for backspace | |
307 | end | |
308 | end | |
309 | ||
310 | local function right(n) | |
311 | n = n or 1 | |
312 | local cbx, cby = getNormalizedCursor() | |
313 | local be = unicode.wlen(line()) + 1 | |
314 | local wide, right = isWideAtPosition(line(), cbx + n) | |
315 | if wide and right then | |
316 | n = n + 1 | |
317 | end | |
318 | if cbx + n <= be then | |
319 | setCursor(cbx + n, cby) | |
320 | elseif cby < #buffer then | |
321 | setCursor(1, cby + 1) | |
322 | end | |
323 | end | |
324 | ||
325 | local function up(n) | |
326 | n = n or 1 | |
327 | local cbx, cby = getCursor() | |
328 | if cby > 1 then | |
329 | setCursor(cbx, cby - n) | |
330 | end | |
331 | end | |
332 | ||
333 | local function down(n) | |
334 | n = n or 1 | |
335 | local cbx, cby = getCursor() | |
336 | if cby < #buffer then | |
337 | setCursor(cbx, cby + n) | |
338 | end | |
339 | end | |
340 | ||
341 | local function delete(fullRow) | |
342 | local cx, cy = term.getCursor() | |
343 | local cbx, cby = getCursor() | |
344 | local x, y, w, h = getArea() | |
345 | local function deleteRow(row) | |
346 | local content = table.remove(buffer, row) | |
347 | local rcy = cy + (row - cby) | |
348 | if rcy <= h then | |
349 | gpu.copy(x, y + rcy, w, h - rcy, 0, -1) | |
350 | drawLine(x, y, w, h, row + (h - rcy)) | |
351 | end | |
352 | return content | |
353 | end | |
354 | if fullRow then | |
355 | term.setCursorBlink(false) | |
356 | if #buffer > 1 then | |
357 | deleteRow(cby) | |
358 | else | |
359 | buffer[cby] = "" | |
360 | gpu.fill(x, y - 1 + cy, w, 1, " ") | |
361 | end | |
362 | setCursor(1, cby) | |
363 | elseif cbx <= unicode.wlen(line()) then | |
364 | term.setCursorBlink(false) | |
365 | local index = lengthToChars(line(), cbx) | |
366 | buffer[cby] = unicode.sub(line(), 1, index - 1) .. | |
367 | unicode.sub(line(), index + 1) | |
368 | drawLine(x, y, w, h, cby) | |
369 | elseif cby < #buffer then | |
370 | term.setCursorBlink(false) | |
371 | local append = deleteRow(cby + 1) | |
372 | buffer[cby] = buffer[cby] .. append | |
373 | drawLine(x, y, w, h, cby) | |
374 | else | |
375 | return | |
376 | end | |
377 | setStatus(helpStatusText()) | |
378 | end | |
379 | ||
380 | local function insert(value) | |
381 | if not value or unicode.len(value) < 1 then | |
382 | return | |
383 | end | |
384 | term.setCursorBlink(false) | |
385 | local cx, cy = term.getCursor() | |
386 | local cbx, cby = getCursor() | |
387 | local x, y, w, h = getArea() | |
388 | local index = lengthToChars(line(), cbx) | |
389 | buffer[cby] = unicode.sub(line(), 1, index - 1) .. | |
390 | value .. | |
391 | unicode.sub(line(), index) | |
392 | drawLine(x, y, w, h, cby) | |
393 | right(unicode.wlen(value)) | |
394 | setStatus(helpStatusText()) | |
395 | end | |
396 | ||
397 | local function enter() | |
398 | term.setCursorBlink(false) | |
399 | local cx, cy = term.getCursor() | |
400 | local cbx, cby = getCursor() | |
401 | local x, y, w, h = getArea() | |
402 | local index = lengthToChars(line(), cbx) | |
403 | table.insert(buffer, cby + 1, unicode.sub(buffer[cby], index)) | |
404 | buffer[cby] = unicode.sub(buffer[cby], 1, index - 1) | |
405 | drawLine(x, y, w, h, cby) | |
406 | if cy < h then | |
407 | if cy < h - 1 then | |
408 | gpu.copy(x, y + cy, w, h - (cy + 1), 0, 1) | |
409 | end | |
410 | drawLine(x, y, w, h, cby + 1) | |
411 | end | |
412 | setCursor(1, cby + 1) | |
413 | setStatus(helpStatusText()) | |
414 | end | |
415 | ||
416 | local findText = "" | |
417 | ||
418 | local function find() | |
419 | local x, y, w, h = getArea() | |
420 | local cx, cy = term.getCursor() | |
421 | local cbx, cby = getCursor() | |
422 | local ibx, iby = cbx, cby | |
423 | while running do | |
424 | if unicode.len(findText) > 0 then | |
425 | local sx, sy | |
426 | for syo = 1, #buffer do -- iterate lines with wraparound | |
427 | sy = (iby + syo - 1 + #buffer - 1) % #buffer + 1 | |
428 | sx = string.find(buffer[sy], findText, syo == 1 and ibx or 1, true) | |
429 | if sx and (sx >= ibx or syo > 1) then | |
430 | break | |
431 | end | |
432 | end | |
433 | if not sx then -- special case for single matches | |
434 | sy = iby | |
435 | sx = string.find(buffer[sy], findText, nil, true) | |
436 | end | |
437 | if sx then | |
438 | sx = unicode.wlen(string.sub(buffer[sy], 1, sx - 1)) + 1 | |
439 | cbx, cby = sx, sy | |
440 | setCursor(cbx, cby) | |
441 | highlight(cbx, cby, unicode.wlen(findText), true) | |
442 | end | |
443 | end | |
444 | term.setCursor(7 + unicode.wlen(findText), h + 1) | |
445 | setStatus("Find: " .. findText) | |
446 | ||
447 | local _, address, char, code = term.pull("key_down") | |
448 | if address == term.keyboard() then | |
449 | local handler, name = getKeyBindHandler(code) | |
450 | highlight(cbx, cby, unicode.wlen(findText), false) | |
451 | if name == "newline" then | |
452 | break | |
453 | elseif name == "close" then | |
454 | handler() | |
455 | elseif name == "backspace" then | |
456 | findText = unicode.sub(findText, 1, -2) | |
457 | elseif name == "find" or name == "findnext" then | |
458 | ibx = cbx + 1 | |
459 | iby = cby | |
460 | elseif not keyboard.isControl(char) then | |
461 | findText = findText .. unicode.char(char) | |
462 | end | |
463 | end | |
464 | end | |
465 | setCursor(cbx, cby) | |
466 | setStatus(helpStatusText()) | |
467 | end | |
468 | ||
469 | ------------------------------------------------------------------------------- | |
470 | ||
471 | local keyBindHandlers = { | |
472 | left = left, | |
473 | right = right, | |
474 | up = up, | |
475 | down = down, | |
476 | home = home, | |
477 | eol = ende, | |
478 | pageUp = function() | |
479 | local x, y, w, h = getArea() | |
480 | up(h - 1) | |
481 | end, | |
482 | pageDown = function() | |
483 | local x, y, w, h = getArea() | |
484 | down(h - 1) | |
485 | end, | |
486 | ||
487 | backspace = function() | |
488 | if not readonly and left() then | |
489 | delete() | |
490 | end | |
491 | end, | |
492 | delete = function() | |
493 | if not readonly then | |
494 | delete() | |
495 | end | |
496 | end, | |
497 | deleteLine = function() | |
498 | if not readonly then | |
499 | delete(true) | |
500 | end | |
501 | end, | |
502 | newline = function() | |
503 | if not readonly then | |
504 | enter() | |
505 | end | |
506 | end, | |
507 | copyLine = function() | |
508 | setStatus("Copy line") | |
509 | xcopy, ycopy = getCursor() | |
510 | copybuffer=line() | |
511 | highlight(0, ycopy, unicode.len(copybuffer), true) | |
512 | end, | |
513 | pasteLine = function() | |
514 | if (unicode.len(copybuffer)>0) then | |
515 | insert(copybuffer) | |
516 | highlight(0, ycopy, unicode.len(copybuffer), false) | |
517 | end | |
518 | end, | |
519 | save = function() | |
520 | if readonly then return end | |
521 | local new = not fs.exists(filename) | |
522 | local backup | |
523 | if not new then | |
524 | backup = filename .. "~" | |
525 | for i = 1, math.huge do | |
526 | if not fs.exists(backup) then | |
527 | break | |
528 | end | |
529 | backup = filename .. "~" .. i | |
530 | end | |
531 | fs.copy(filename, backup) | |
532 | end | |
533 | if not fs.exists(file_parentpath) then | |
534 | fs.makeDirectory(file_parentpath) | |
535 | end | |
536 | local f, reason = io.open(filename, "w") | |
537 | if f then | |
538 | local chars, firstLine = 0, true | |
539 | for _, line in ipairs(buffer) do | |
540 | if not firstLine then | |
541 | line = "\n" .. line | |
542 | end | |
543 | firstLine = false | |
544 | f:write(line) | |
545 | chars = chars + unicode.len(line) | |
546 | end | |
547 | f:close() | |
548 | local format | |
549 | if new then | |
550 | format = [["%s" [New] %dL,%dC written]] | |
551 | else | |
552 | format = [["%s" %dL,%dC written]] | |
553 | end | |
554 | setStatus(string.format(format, fs.name(filename), #buffer, chars)) | |
555 | else | |
556 | setStatus(reason) | |
557 | end | |
558 | if not new then | |
559 | fs.remove(backup) | |
560 | end | |
561 | end, | |
562 | close = function() | |
563 | -- TODO ask to save if changed | |
564 | running = false | |
565 | end, | |
566 | find = function() | |
567 | findText = "" | |
568 | find() | |
569 | end, | |
570 | findnext = find | |
571 | } | |
572 | ||
573 | getKeyBindHandler = function(code) | |
574 | if type(config.keybinds) ~= "table" then return end | |
575 | -- Look for matches, prefer more 'precise' keybinds, e.g. prefer | |
576 | -- ctrl+del over del. | |
577 | local result, resultName, resultWeight = nil, nil, 0 | |
578 | for command, keybinds in pairs(config.keybinds) do | |
579 | if type(keybinds) == "table" and keyBindHandlers[command] then | |
580 | for _, keybind in ipairs(keybinds) do | |
581 | if type(keybind) == "table" then | |
582 | local alt, control, shift, key = false, false, false | |
583 | for _, value in ipairs(keybind) do | |
584 | if value == "alt" then alt = true | |
585 | elseif value == "control" then control = true | |
586 | elseif value == "shift" then shift = true | |
587 | else key = value end | |
588 | end | |
589 | local keyboardAddress = term.keyboard() | |
590 | if (alt == not not keyboard.isAltDown(keyboardAddress)) and | |
591 | (control == not not keyboard.isControlDown(keyboardAddress)) and | |
592 | (shift == not not keyboard.isShiftDown(keyboardAddress)) and | |
593 | code == keyboard.keys[key] and | |
594 | #keybind > resultWeight | |
595 | then | |
596 | resultWeight = #keybind | |
597 | resultName = command | |
598 | result = keyBindHandlers[command] | |
599 | end | |
600 | end | |
601 | end | |
602 | end | |
603 | end | |
604 | return result, resultName | |
605 | end | |
606 | ||
607 | ------------------------------------------------------------------------------- | |
608 | ||
609 | local function onKeyDown(char, code) | |
610 | local handler = getKeyBindHandler(code) | |
611 | if handler then | |
612 | handler() | |
613 | elseif readonly and code == keyboard.keys.q then | |
614 | running = false | |
615 | elseif not readonly then | |
616 | if not keyboard.isControl(char) then | |
617 | insert(unicode.char(char)) | |
618 | elseif unicode.char(char) == "\t" then | |
619 | insert(" ") | |
620 | end | |
621 | end | |
622 | end | |
623 | ||
624 | local function onClipboard(value) | |
625 | value = value:gsub("\r\n", "\n") | |
626 | local cbx, cby = getCursor() | |
627 | local start = 1 | |
628 | local l = value:find("\n", 1, true) | |
629 | if l then | |
630 | repeat | |
631 | local line = string.sub(value, start, l - 1) | |
632 | line = text.detab(line, 2) | |
633 | insert(line) | |
634 | enter() | |
635 | start = l + 1 | |
636 | l = value:find("\n", start, true) | |
637 | until not l | |
638 | end | |
639 | insert(string.sub(value, start)) | |
640 | end | |
641 | ||
642 | local function onClick(x, y) | |
643 | setCursor(x + scrollX, y + scrollY) | |
644 | end | |
645 | ||
646 | local function onScroll(direction) | |
647 | local cbx, cby = getCursor() | |
648 | setCursor(cbx, cby - direction * 12) | |
649 | end | |
650 | ||
651 | ------------------------------------------------------------------------------- | |
652 | ||
653 | do | |
654 | local f = io.open(filename) | |
655 | if f then | |
656 | local x, y, w, h = getArea() | |
657 | local chars = 0 | |
658 | for line in f:lines() do | |
659 | table.insert(buffer, line) | |
660 | chars = chars + unicode.len(line) | |
661 | if #buffer <= h then | |
662 | drawLine(x, y, w, h, #buffer) | |
663 | end | |
664 | end | |
665 | f:close() | |
666 | if #buffer == 0 then | |
667 | table.insert(buffer, "") | |
668 | end | |
669 | local format | |
670 | if readonly then | |
671 | format = [["%s" [readonly] %dL,%dC]] | |
672 | else | |
673 | format = [["%s" %dL,%dC]] | |
674 | end | |
675 | setStatus(string.format(format, fs.name(filename), #buffer, chars)) | |
676 | else | |
677 | table.insert(buffer, "") | |
678 | setStatus(string.format([["%s" [New File] ]], fs.name(filename))) | |
679 | end | |
680 | setCursor(1, 1) | |
681 | end | |
682 | ||
683 | while running do | |
684 | local event, address, arg1, arg2, arg3 = term.pull() | |
685 | if address == term.keyboard() or address == term.screen() then | |
686 | local blink = true | |
687 | if event == "key_down" then | |
688 | onKeyDown(arg1, arg2) | |
689 | elseif event == "clipboard" and not readonly then | |
690 | onClipboard(arg1) | |
691 | elseif event == "touch" or event == "drag" then | |
692 | local x, y, w, h = getArea() | |
693 | arg1 = arg1 - x + 1 | |
694 | arg2 = arg2 - y + 1 | |
695 | if arg1 >= 1 and arg2 >= 1 and arg1 <= w and arg2 <= h then | |
696 | onClick(arg1, arg2) | |
697 | end | |
698 | elseif event == "scroll" then | |
699 | onScroll(arg3) | |
700 | else | |
701 | blink = false | |
702 | end | |
703 | if blink then | |
704 | term.setCursorBlink(true) | |
705 | end | |
706 | end | |
707 | end | |
708 | ||
709 | term.clear() | |
710 | term.setCursorBlink(true) |