SHOW:
|
|
- or go back to the newest paste.
1 | -- Get file to edit | |
2 | local tArgs = { ... } | |
3 | if #tArgs == 0 then | |
4 | print( "Usage: edit <path>" ) | |
5 | return | |
6 | end | |
7 | ||
8 | -- Error checking | |
9 | local sPath = shell.resolve( tArgs[1] ) | |
10 | local bReadOnly = fs.isReadOnly( sPath ) | |
11 | if fs.exists( sPath ) and fs.isDir( sPath ) then | |
12 | print( "Cannot edit a directory." ) | |
13 | return | |
14 | end | |
15 | ||
16 | local x,y = 1,1 | |
17 | local w,h = term.getSize() | |
18 | local scrollX, scrollY = 0,0 | |
19 | ||
20 | local tLines = {} | |
21 | local bRunning = true | |
22 | ||
23 | -- Colours | |
24 | local highlightColour, keywordColour, commentColour, textColour, bgColour | |
25 | if term.isColour() then | |
26 | bgColour = colours.black | |
27 | textColour = colours.white | |
28 | highlightColour = colours.yellow | |
29 | keywordColour = colours.yellow | |
30 | commentColour = colours.lime | |
31 | stringColour = colours.red | |
32 | else | |
33 | bgColour = colours.black | |
34 | textColour = colours.white | |
35 | highlightColour = colours.white | |
36 | keywordColour = colours.white | |
37 | commentColour = colours.white | |
38 | stringColour = colours.white | |
39 | end | |
40 | ||
41 | -- Menus | |
42 | local bMenu = false | |
43 | local nMenuItem = 1 | |
44 | local tMenuItems = {"Save", "Exit", "Print"} | |
45 | local sStatus = "Press Ctrl to access menu" | |
46 | ||
47 | local function load(_sPath) | |
48 | tLines = {} | |
49 | if fs.exists( _sPath ) then | |
50 | local file = io.open( _sPath, "r" ) | |
51 | local sLine = file:read() | |
52 | while sLine do | |
53 | table.insert( tLines, sLine ) | |
54 | sLine = file:read() | |
55 | end | |
56 | file:close() | |
57 | end | |
58 | ||
59 | if #tLines == 0 then | |
60 | table.insert( tLines, "" ) | |
61 | end | |
62 | end | |
63 | ||
64 | local function save( _sPath ) | |
65 | -- Create intervening folder | |
66 | local sDir = sPath:sub(1, sPath:len() - fs.getName(sPath):len() ) | |
67 | if not fs.exists( sDir ) then | |
68 | fs.makeDir( sDir ) | |
69 | end | |
70 | ||
71 | -- Save | |
72 | local file = nil | |
73 | local function innerSave() | |
74 | file = fs.open( _sPath, "w" ) | |
75 | if file then | |
76 | for n, sLine in ipairs( tLines ) do | |
77 | file.write( sLine .. "\n" ) | |
78 | end | |
79 | else | |
80 | error( "Failed to open ".._sPath ) | |
81 | end | |
82 | end | |
83 | ||
84 | local ok = pcall( innerSave ) | |
85 | if file then | |
86 | file.close() | |
87 | end | |
88 | return ok | |
89 | end | |
90 | ||
91 | local tKeywords = { | |
92 | ["and"] = true, | |
93 | ["break"] = true, | |
94 | ["do"] = true, | |
95 | ["else"] = true, | |
96 | ["elseif"] = true, | |
97 | ["end"] = true, | |
98 | ["false"] = true, | |
99 | ["for"] = true, | |
100 | ["function"] = true, | |
101 | ["if"] = true, | |
102 | ["in"] = true, | |
103 | ["local"] = true, | |
104 | ["nil"] = true, | |
105 | ["not"] = true, | |
106 | ["or"] = true, | |
107 | ["repeat"] = true, | |
108 | ["return"] = true, | |
109 | ["then"] = true, | |
110 | ["true"] = true, | |
111 | ["until"]= true, | |
112 | ["while"] = true, | |
113 | } | |
114 | ||
115 | local function tryWrite( sLine, regex, colour ) | |
116 | local match = string.match( sLine, regex ) | |
117 | if match then | |
118 | if type(colour) == "number" then | |
119 | term.setTextColour( colour ) | |
120 | else | |
121 | term.setTextColour( colour(match) ) | |
122 | end | |
123 | term.write( match ) | |
124 | term.setTextColour( textColour ) | |
125 | return string.sub( sLine, string.len(match) + 1 ) | |
126 | end | |
127 | return nil | |
128 | end | |
129 | ||
130 | local function writeHighlighted( sLine ) | |
131 | while string.len(sLine) > 0 do | |
132 | sLine = | |
133 | tryWrite( sLine, "^%-%-%[%[.-%]%]", commentColour ) or | |
134 | tryWrite( sLine, "^%-%-.*", commentColour ) or | |
135 | tryWrite( sLine, "^\".-[^\\]\"", stringColour ) or | |
136 | tryWrite( sLine, "^\'.-[^\\]\'", stringColour ) or | |
137 | tryWrite( sLine, "^%[%[.-%]%]", stringColour ) or | |
138 | tryWrite( sLine, "^[%w_]+", function( match ) | |
139 | if tKeywords[ match ] then | |
140 | return keywordColour | |
141 | end | |
142 | return textColour | |
143 | end ) or | |
144 | tryWrite( sLine, "^[^%w_]", textColour ) | |
145 | end | |
146 | end | |
147 | ||
148 | local function redrawText() | |
149 | for y=1,h-1 do | |
150 | term.setCursorPos( 1 - scrollX, y ) | |
151 | term.clearLine() | |
152 | ||
153 | local sLine = tLines[ y + scrollY ] | |
154 | if sLine ~= nil then | |
155 | writeHighlighted( sLine ) | |
156 | end | |
157 | end | |
158 | term.setCursorPos( x - scrollX, y - scrollY ) | |
159 | end | |
160 | ||
161 | local function redrawLine(_nY) | |
162 | local sLine = tLines[_nY] | |
163 | term.setCursorPos( 1 - scrollX, _nY - scrollY ) | |
164 | term.clearLine() | |
165 | writeHighlighted( sLine ) | |
166 | term.setCursorPos( x - scrollX, _nY - scrollY ) | |
167 | end | |
168 | ||
169 | local function setLeftStatus() | |
170 | end | |
171 | ||
172 | local function redrawMenu() | |
173 | term.setCursorPos( 1, h ) | |
174 | term.clearLine() | |
175 | ||
176 | local sLeft, sRight | |
177 | local nLeftColour, nLeftHighlight1, nLeftHighlight2 | |
178 | if bMenu then | |
179 | local sMenu = "" | |
180 | for n,sItem in ipairs( tMenuItems ) do | |
181 | if n == nMenuItem then | |
182 | nLeftHighlight1 = sMenu:len() + 1 | |
183 | nLeftHighlight2 = sMenu:len() + sItem:len() + 2 | |
184 | end | |
185 | sMenu = sMenu.." "..sItem.." " | |
186 | end | |
187 | sLeft = sMenu | |
188 | nLeftColour = textColour | |
189 | else | |
190 | sLeft = sStatus | |
191 | nLeftColour = highlightColour | |
192 | end | |
193 | ||
194 | -- Left goes last so that it can overwrite the line numbers. | |
195 | sRight = "Ln "..y | |
196 | term.setTextColour( highlightColour ) | |
197 | term.setCursorPos( w-sRight:len() + 1, h ) | |
198 | term.write(sRight) | |
199 | ||
200 | sRight = tostring(y) | |
201 | term.setTextColour( textColour ) | |
202 | term.setCursorPos( w-sRight:len() + 1, h ) | |
203 | term.write(sRight) | |
204 | ||
205 | if sLeft then | |
206 | term.setCursorPos( 1, h ) | |
207 | term.setTextColour( nLeftColour ) | |
208 | term.write(sLeft) | |
209 | if nLeftHighlight1 then | |
210 | term.setTextColour( highlightColour ) | |
211 | term.setCursorPos( nLeftHighlight1, h ) | |
212 | term.write( "[" ) | |
213 | term.setCursorPos( nLeftHighlight2, h ) | |
214 | term.write( "]" ) | |
215 | end | |
216 | term.setTextColour( textColour ) | |
217 | end | |
218 | ||
219 | -- Cursor highlights selection | |
220 | term.setCursorPos( x - scrollX, y - scrollY ) | |
221 | end | |
222 | ||
223 | local tMenuFuncs = { | |
224 | Save=function() | |
225 | if bReadOnly then | |
226 | sStatus = "Access denied" | |
227 | else | |
228 | local ok, err = save( sPath ) | |
229 | if ok then | |
230 | sStatus="Saved to "..sPath | |
231 | else | |
232 | sStatus="Error saving to "..sPath | |
233 | end | |
234 | end | |
235 | redrawMenu() | |
236 | end, | |
237 | Print=function() | |
238 | local sPrinterSide = nil | |
239 | for n,sName in ipairs(peripheral.getNames()) do | |
240 | if peripheral.isPresent(sName) and peripheral.getType(sName) == "printer" then | |
241 | sPrinterSide = sName | |
242 | break | |
243 | end | |
244 | end | |
245 | ||
246 | if not sPrinterSide then | |
247 | sStatus = "No printer attached" | |
248 | return | |
249 | end | |
250 | ||
251 | local nPage = 0 | |
252 | local sName = fs.getName( sPath ) | |
253 | local printer = peripheral.wrap(sPrinterSide) | |
254 | if printer.getInkLevel() < 1 then | |
255 | sStatus = "Printer out of ink" | |
256 | return | |
257 | elseif printer.getPaperLevel() < 1 then | |
258 | sStatus = "Printer out of paper" | |
259 | return | |
260 | end | |
261 | ||
262 | local terminal = { | |
263 | getCursorPos = printer.getCursorPos, | |
264 | setCursorPos = printer.setCursorPos, | |
265 | getSize = printer.getPageSize, | |
266 | write = printer.write, | |
267 | } | |
268 | terminal.scroll = function() | |
269 | if nPage == 1 then | |
270 | printer.setPageTitle( sName.." (page "..nPage..")" ) | |
271 | end | |
272 | ||
273 | while not printer.newPage() do | |
274 | if printer.getInkLevel() < 1 then | |
275 | sStatus = "Printer out of ink, please refill" | |
276 | elseif printer.getPaperLevel() < 1 then | |
277 | sStatus = "Printer out of paper, please refill" | |
278 | else | |
279 | sStatus = "Printer output tray full, please empty" | |
280 | end | |
281 | ||
282 | term.restore() | |
283 | redrawMenu() | |
284 | term.redirect( terminal ) | |
285 | ||
286 | local timer = os.startTimer(0.5) | |
287 | sleep(0.5) | |
288 | end | |
289 | ||
290 | nPage = nPage + 1 | |
291 | if nPage == 1 then | |
292 | printer.setPageTitle( sName ) | |
293 | else | |
294 | printer.setPageTitle( sName.." (page "..nPage..")" ) | |
295 | end | |
296 | end | |
297 | ||
298 | bMenu = false | |
299 | term.redirect( terminal ) | |
300 | local ok, error = pcall( function() | |
301 | term.scroll() | |
302 | for n, sLine in ipairs( tLines ) do | |
303 | print( sLine ) | |
304 | end | |
305 | end ) | |
306 | term.restore() | |
307 | if not ok then | |
308 | print( error ) | |
309 | end | |
310 | ||
311 | while not printer.endPage() do | |
312 | sStatus = "Printer output tray full, please empty" | |
313 | redrawMenu() | |
314 | sleep( 0.5 ) | |
315 | end | |
316 | bMenu = true | |
317 | ||
318 | if nPage > 1 then | |
319 | sStatus = "Printed "..nPage.." Pages" | |
320 | else | |
321 | sStatus = "Printed 1 Page" | |
322 | end | |
323 | redrawMenu() | |
324 | end, | |
325 | Exit=function() | |
326 | bRunning = false | |
327 | end | |
328 | } | |
329 | ||
330 | local function doMenuItem( _n ) | |
331 | tMenuFuncs[tMenuItems[_n]]() | |
332 | if bMenu then | |
333 | bMenu = false | |
334 | term.setCursorBlink( true ) | |
335 | end | |
336 | redrawMenu() | |
337 | end | |
338 | ||
339 | local function setCursor( x, y ) | |
340 | local screenX = x - scrollX | |
341 | local screenY = y - scrollY | |
342 | ||
343 | local bRedraw = false | |
344 | if screenX < 1 then | |
345 | scrollX = x - 1 | |
346 | screenX = 1 | |
347 | bRedraw = true | |
348 | elseif screenX > w then | |
349 | scrollX = x - w | |
350 | screenX = w | |
351 | bRedraw = true | |
352 | end | |
353 | ||
354 | if screenY < 1 then | |
355 | scrollY = y - 1 | |
356 | screenY = 1 | |
357 | bRedraw = true | |
358 | elseif screenY > h-1 then | |
359 | scrollY = y - (h-1) | |
360 | screenY = h-1 | |
361 | bRedraw = true | |
362 | end | |
363 | ||
364 | if bRedraw then | |
365 | redrawText() | |
366 | end | |
367 | term.setCursorPos( screenX, screenY ) | |
368 | ||
369 | -- Statusbar now pertains to menu, it would probably be safe to redraw the menu on every key event. | |
370 | redrawMenu() | |
371 | end | |
372 | ||
373 | -- Actual program functionality begins | |
374 | load(sPath) | |
375 | ||
376 | term.setBackgroundColour( bgColour ) | |
377 | term.clear() | |
378 | term.setCursorPos(x,y) | |
379 | term.setCursorBlink( true ) | |
380 | ||
381 | redrawText() | |
382 | redrawMenu() | |
383 | ||
384 | -- Handle input | |
385 | while bRunning do | |
386 | local sEvent, param, param2, param3 = os.pullEvent() | |
387 | if sEvent == "key" then | |
388 | if param == keys.up then | |
389 | -- Up | |
390 | if not bMenu then | |
391 | if y > 1 then | |
392 | -- Move cursor up | |
393 | y = y - 1 | |
394 | x = math.min( x, string.len( tLines[y] ) + 1 ) | |
395 | setCursor( x, y ) | |
396 | end | |
397 | end | |
398 | elseif param == keys.down then | |
399 | -- Down | |
400 | if not bMenu then | |
401 | -- Move cursor down | |
402 | if y < #tLines then | |
403 | y = y + 1 | |
404 | x = math.min( x, string.len( tLines[y] ) + 1 ) | |
405 | setCursor( x, y ) | |
406 | end | |
407 | end | |
408 | elseif param == keys.tab then | |
409 | -- Tab | |
410 | if not bMenu then | |
411 | local sLine = tLines[y] | |
412 | ||
413 | -- Indent line | |
414 | -- IN CASE OF INSERT TAB IN PLACE: | |
415 | -- tLines[y] = string.sub(sLine,1,x-1) .. " " .. string.sub(sLine,x) | |
416 | tLines[y]=" "..tLines[y] | |
417 | x = x + 2 | |
418 | setCursor( x, y ) | |
419 | redrawLine(y) | |
420 | end | |
421 | elseif param == keys.pageUp then | |
422 | -- Page Up | |
423 | if not bMenu then | |
424 | -- Move up a page | |
425 | local sx,sy=term.getSize() | |
426 | y=y-sy-1 | |
427 | if y<1 then y=1 end | |
428 | x = math.min( x, string.len( tLines[y] ) + 1 ) | |
429 | setCursor( x, y ) | |
430 | end | |
431 | elseif param == keys.pageDown then | |
432 | -- Page Down | |
433 | if not bMenu then | |
434 | -- Move down a page | |
435 | local sx,sy=term.getSize() | |
436 | if y<#tLines-sy-1 then | |
437 | y = y+sy-1 | |
438 | else | |
439 | y = #tLines | |
440 | end | |
441 | x = math.min( x, string.len( tLines[y] ) + 1 ) | |
442 | setCursor( x, y ) | |
443 | end | |
444 | elseif param == keys.home then | |
445 | -- Home | |
446 | if not bMenu then | |
447 | -- Move cursor to the beginning | |
448 | x=1 | |
449 | setCursor(x,y) | |
450 | end | |
451 | elseif param == keys["end"] then | |
452 | -- End | |
453 | if not bMenu then | |
454 | -- Move cursor to the end | |
455 | x = string.len( tLines[y] ) + 1 | |
456 | setCursor(x,y) | |
457 | end | |
458 | elseif param == keys.left then | |
459 | -- Left | |
460 | if not bMenu then | |
461 | if x > 1 then | |
462 | -- Move cursor left | |
463 | x = x - 1 | |
464 | elseif x==1 and y>1 then | |
465 | x = string.len( tLines[y-1] ) + 1 | |
466 | y = y - 1 | |
467 | end | |
468 | setCursor( x, y ) | |
469 | else | |
470 | -- Move menu left | |
471 | nMenuItem = nMenuItem - 1 | |
472 | if nMenuItem < 1 then | |
473 | nMenuItem = #tMenuItems | |
474 | end | |
475 | redrawMenu() | |
476 | end | |
477 | elseif param == keys.right then | |
478 | -- Right | |
479 | if not bMenu then | |
480 | if x < string.len( tLines[y] ) + 1 then | |
481 | -- Move cursor right | |
482 | x = x + 1 | |
483 | elseif x==string.len( tLines[y] ) + 1 and y<#tLines then | |
484 | x = 1 | |
485 | y = y + 1 | |
486 | end | |
487 | setCursor( x, y ) | |
488 | else | |
489 | -- Move menu right | |
490 | nMenuItem = nMenuItem + 1 | |
491 | if nMenuItem > #tMenuItems then | |
492 | nMenuItem = 1 | |
493 | end | |
494 | redrawMenu() | |
495 | end | |
496 | elseif param == keys.delete then | |
497 | -- Delete | |
498 | if not bMenu then | |
499 | if x < string.len( tLines[y] ) + 1 then | |
500 | local sLine = tLines[y] | |
501 | tLines[y] = string.sub(sLine,1,x-1) .. string.sub(sLine,x+1) | |
502 | redrawLine(y) | |
503 | elseif y<#tLines then | |
504 | tLines[y] = tLines[y] .. tLines[y+1] | |
505 | table.remove( tLines, y+1 ) | |
506 | redrawText() | |
507 | redrawMenu() | |
508 | end | |
509 | end | |
510 | elseif param == keys.backspace then | |
511 | -- Backspace | |
512 | if not bMenu then | |
513 | if x > 1 then | |
514 | -- Remove character | |
515 | local sLine = tLines[y] | |
516 | tLines[y] = string.sub(sLine,1,x-2) .. string.sub(sLine,x) | |
517 | redrawLine(y) | |
518 | ||
519 | x = x - 1 | |
520 | setCursor( x, y ) | |
521 | elseif y > 1 then | |
522 | -- Remove newline | |
523 | local sPrevLen = string.len( tLines[y-1] ) | |
524 | tLines[y-1] = tLines[y-1] .. tLines[y] | |
525 | table.remove( tLines, y ) | |
526 | redrawText() | |
527 | ||
528 | x = sPrevLen + 1 | |
529 | y = y - 1 | |
530 | setCursor( x, y ) | |
531 | end | |
532 | end | |
533 | elseif param == keys.enter then | |
534 | -- Enter | |
535 | if not bMenu then | |
536 | -- Newline | |
537 | local sLine = tLines[y] | |
538 | local _,spaces=string.find(sLine,"^[ ]+") | |
539 | if not spaces then | |
540 | spaces=0 | |
541 | end | |
542 | tLines[y] = string.sub(sLine,1,x-1) | |
543 | table.insert( tLines, y+1, string.rep(' ',spaces)..string.sub(sLine,x) ) | |
544 | redrawText() | |
545 | ||
546 | x = spaces+1 | |
547 | y = y + 1 | |
548 | setCursor( x, y ) | |
549 | else | |
550 | -- Menu selection | |
551 | doMenuItem( nMenuItem ) | |
552 | end | |
553 | elseif param == keys.leftCtrl or param == keys.rightCtrl then | |
554 | -- Menu toggle | |
555 | bMenu = not bMenu | |
556 | if bMenu then | |
557 | term.setCursorBlink( false ) | |
558 | nMenuItem = 1 | |
559 | else | |
560 | term.setCursorBlink( true ) | |
561 | end | |
562 | redrawMenu() | |
563 | end | |
564 | ||
565 | elseif sEvent == "char" then | |
566 | if not bMenu then | |
567 | -- Input text | |
568 | local sLine = tLines[y] | |
569 | tLines[y] = string.sub(sLine,1,x-1) .. param .. string.sub(sLine,x) | |
570 | redrawLine(y) | |
571 | ||
572 | x = x + string.len( param ) | |
573 | setCursor( x, y ) | |
574 | else | |
575 | -- Select menu items | |
576 | for n,sMenuItem in ipairs( tMenuItems ) do | |
577 | if string.lower(string.sub(sMenuItem,1,1)) == string.lower(param) then | |
578 | doMenuItem( n ) | |
579 | break | |
580 | end | |
581 | end | |
582 | end | |
583 | ||
584 | elseif sEvent == "mouse_click" then | |
585 | if not bMenu then | |
586 | if param == 1 then | |
587 | -- Left click | |
588 | local cx,cy = param2, param3 | |
589 | if cy < h then | |
590 | y = math.min( math.max( scrollY + cy, 1 ), #tLines ) | |
591 | x = math.min( math.max( scrollX + cx, 1 ), string.len( tLines[y] ) + 1 ) | |
592 | setCursor( x, y ) | |
593 | end | |
594 | end | |
595 | end | |
596 | ||
597 | elseif sEvent == "mouse_scroll" then | |
598 | if not bMenu then | |
599 | if param == -1 then | |
600 | -- Scroll up | |
601 | if scrollY > 0 then | |
602 | -- Move cursor up | |
603 | scrollY = scrollY - 1 | |
604 | redrawText() | |
605 | end | |
606 | ||
607 | elseif param == 1 then | |
608 | -- Scroll down | |
609 | local nMaxScroll = #tLines - (h-1) | |
610 | if scrollY < nMaxScroll then | |
611 | -- Move cursor down | |
612 | scrollY = scrollY + 1 | |
613 | redrawText() | |
614 | end | |
615 | ||
616 | end | |
617 | end | |
618 | end | |
619 | end | |
620 | ||
621 | -- Cleanup | |
622 | term.clear() | |
623 | term.setCursorBlink( false ) | |
624 | term.setCursorPos( 1, 1 ) |