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