View difference between Paste ID: 8H1Yc6Sd and maXLmT7Y
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.green
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 = {}
45
if not bReadOnly then
46
    table.insert( tMenuItems, "Delete" )
47
end
48
table.insert( tMenuItems, "Exit" )
49
50
local sStatus = "Press Ctrl to access menu"
51
if string.len( sStatus ) > w - 5 then
52
    sStatus = "Press Ctrl for menu"
53
end
54
55
local function load( _sPath )
56
	tLines = {}
57
	if fs.exists( _sPath ) then
58
		local file = io.open( _sPath, "r" )
59
		local sLine = file:read()
60
		while sLine do
61
			table.insert( tLines, sLine )
62
			sLine = file:read()
63
		end
64
		file:close()
65
	end
66
	
67
	if #tLines == 0 then
68
		table.insert( tLines, "" )
69
	end
70
end
71
72
local function save( _sPath )
73
	-- Create intervening folder
74
	local sDir = _sPath:sub(1, _sPath:len() - fs.getName(_sPath):len() )
75
	if not fs.exists( sDir ) then
76
		fs.makeDir( sDir )
77
	end
78
79
	-- Save
80
	local file = nil
81
	local function innerSave()
82
		file = fs.open( _sPath, "w" )
83
		if file then
84
			for n, sLine in ipairs( tLines ) do
85
				file.write( sLine .. "\n" )
86
			end
87
		else
88
			error( "Failed to open ".._sPath )
89
		end
90
	end
91
	
92
	local ok, err = pcall( innerSave )
93
	if file then 
94
		file.close()
95
	end
96
	return ok, err
97
end
98
99
local tKeywords = {
100-
	["and"] = true,
100+
101-
	["break"] = true,
101+
102-
	["do"] = true,
102+
103-
	["else"] = true,
103+
104-
	["elseif"] = true,
104+
105-
	["end"] = true,
105+
106-
	["false"] = true,
106+
107-
	["for"] = true,
107+
108-
	["function"] = true,
108+
109-
	["if"] = true,
109+
110-
	["in"] = true,
110+
111-
	["local"] = true,
111+
112-
	["nil"] = true,
112+
113-
	["not"] = true,
113+
114-
	["or"] = true,
114+
115-
	["repeat"] = true,
115+
116-
	["return"] = true,
116+
117-
	["then"] = true,
117+
118-
	["true"] = true,
118+
119-
	["until"]= true,
119+
120-
	["while"] = true,
120+
121
			tryWrite( sLine, "^%-%-.*", commentColour ) or
122
			tryWrite( sLine, "^\".-[^\\]\"", stringColour ) or
123
			tryWrite( sLine, "^\'.-[^\\]\'", stringColour ) or
124
			tryWrite( sLine, "^%[%[.-%]%]", stringColour ) or
125
			tryWrite( sLine, "^[%w_]+", function( match )
126
				if tKeywords[ match ] then
127
					return keywordColour
128
				end
129
				return textColour
130
			end ) or
131
			tryWrite( sLine, "^[^%w_]", textColour )
132
	end
133
end
134
135
local tCompletions
136
local nCompletion
137
138
local tCompleteEnv = _ENV
139
local function complete( sLine )
140
    local nStartPos = string.find( sLine, "[a-zA-Z0-9_%.]+$" )
141
    if nStartPos then
142
        sLine = string.sub( sLine, nStartPos )
143
    end
144
    if #sLine > 0 then
145
        return textutils.complete( sLine, tCompleteEnv )
146
    end
147
    return nil
148
end
149
150
local function recomplete()
151
    local sLine = tLines[y]
152
    if not bMenu and not bReadOnly and x == string.len(sLine) + 1 then
153
        tCompletions = complete( sLine )
154
        if tCompletions and #tCompletions > 0 then
155
            nCompletion = 1
156
        else
157
            nCompletion = nil
158
        end
159
    else
160
        tCompletions = nil
161
        nCompletion = nil
162
    end
163
end
164
165
local function writeCompletion( sLine )
166
    if nCompletion then
167
        local sCompletion = tCompletions[ nCompletion ]
168
        term.setTextColor( colours.white )
169
        term.setBackgroundColor( colours.grey )
170
        term.write( sCompletion )
171
        term.setTextColor( textColour )
172
        term.setBackgroundColor( bgColour )
173
    end
174
end
175
176
local function redrawText()
177
    local cursorX, cursorY = x, y
178
	for y=1,h-1 do
179
		term.setCursorPos( 1 - scrollX, y )
180
		term.clearLine()
181
182
		local sLine = tLines[ y + scrollY ]
183
		if sLine ~= nil then
184
			writeHighlighted( sLine )
185
            if cursorY == y and cursorX == #sLine + 1 then
186
                writeCompletion()
187
            end
188
		end
189
	end
190
	term.setCursorPos( x - scrollX, y - scrollY )
191
end
192
193
local function redrawLine(_nY)
194
	local sLine = tLines[_nY]
195
	if sLine then
196
        term.setCursorPos( 1 - scrollX, _nY - scrollY )
197
        term.clearLine()
198
        writeHighlighted( sLine )
199
        if _nY == y and x == #sLine + 1 then
200
            writeCompletion()
201
        end
202
        term.setCursorPos( x - scrollX, _nY - scrollY )
203
    end
204
end
205
206
local function redrawMenu()
207
    -- Clear line
208
    term.setCursorPos( 1, h )
209
	term.clearLine()
210
211
    -- Draw line numbers
212
    term.setCursorPos( w - string.len( "Ln "..y ) + 1, h )
213
    term.setTextColour( highlightColour )
214
    term.write( "Ln " )
215
    term.setTextColour( textColour )
216
    term.write( y )
217
218
    term.setCursorPos( 1, h )
219
	if bMenu then
220
        -- Draw menu
221
		term.setTextColour( textColour )
222
		for nItem,sItem in pairs( tMenuItems ) do
223
			if nItem == nMenuItem then
224
				term.setTextColour( highlightColour )
225
				term.write( "[" )
226
				term.setTextColour( textColour )
227
				term.write( sItem )
228
				term.setTextColour( highlightColour )
229
				term.write( "]" )
230
        		term.setTextColour( textColour )
231
			else
232
				term.write( " "..sItem.." " )
233
			end
234
		end
235
    else
236
        -- Draw status
237
        term.setTextColour( highlightColour )
238
        term.write( sStatus )
239
        term.setTextColour( textColour )
240
    end
241
242
	-- Reset cursor
243
	term.setCursorPos( x - scrollX, y - scrollY )
244
end
245
246
local tMenuFuncs = { 
247
	Delete = function()
248
		shell.run("delete", sPath)
249
		bRunning = false
250
	end,
251
	Print = function()
252
		local printer = peripheral.find( "printer" )
253
		if not printer then
254
			sStatus = "No printer attached"
255
			return
256
		end
257
258
		local nPage = 0
259
		local sName = fs.getName( sPath )
260
		if printer.getInkLevel() < 1 then
261
			sStatus = "Printer out of ink"
262
			return
263
		elseif printer.getPaperLevel() < 1 then
264
			sStatus = "Printer out of paper"
265
			return
266
		end
267
268
		local screenTerminal = term.current()
269
		local printerTerminal = {
270
			getCursorPos = printer.getCursorPos,
271
			setCursorPos = printer.setCursorPos,
272
			getSize = printer.getPageSize,
273
			write = printer.write,
274
		}
275
		printerTerminal.scroll = function()
276
			if nPage == 1 then
277
				printer.setPageTitle( sName.." (page "..nPage..")" )			
278
			end
279
			
280
			while not printer.newPage()	do
281
				if printer.getInkLevel() < 1 then
282
					sStatus = "Printer out of ink, please refill"
283
				elseif printer.getPaperLevel() < 1 then
284
					sStatus = "Printer out of paper, please refill"
285
				else
286
					sStatus = "Printer output tray full, please empty"
287
				end
288
	
289
				term.redirect( screenTerminal )
290
				redrawMenu()
291
				term.redirect( printerTerminal )
292
				
293
				local timer = os.startTimer(0.5)
294
				sleep(0.5)
295
			end
296
297
			nPage = nPage + 1
298
			if nPage == 1 then
299
				printer.setPageTitle( sName )
300
			else
301
				printer.setPageTitle( sName.." (page "..nPage..")" )
302
			end
303
		end
304
		
305
		bMenu = false
306
		term.redirect( printerTerminal )
307
		local ok, error = pcall( function()
308
			term.scroll()
309
			for n, sLine in ipairs( tLines ) do
310
				print( sLine )
311
			end
312
		end )
313
        term.redirect( screenTerminal )
314
		if not ok then
315
			print( error )
316
		end
317
		
318
		while not printer.endPage() do
319
			sStatus = "Printer output tray full, please empty"
320
			redrawMenu()
321
			sleep( 0.5 )
322
		end
323
		bMenu = true
324
			
325
		if nPage > 1 then
326
			sStatus = "Printed "..nPage.." Pages"
327
		else
328
			sStatus = "Printed 1 Page"
329
		end
330
		redrawMenu()
331
	end,
332
	Exit = function()
333
		bRunning = false
334
	end,
335
	Run = function()
336
	    local sTempPath = "/.temp"
337
        local ok, err = save( sTempPath )
338
        if ok then
339
            local nTask = shell.openTab( sTempPath )
340
            if nTask then
341
                shell.switchTab( nTask )
342
            else
343
                sStatus="Error starting Task"
344
            end
345
            fs.delete( sTempPath )
346
        else
347
            sStatus="Error saving to "..sTempPath
348
        end
349
		redrawMenu()
350
    end
351
}
352
353
local function doMenuItem( _n )
354-
		local ok, err = save( sPath )
354+
355-
            if ok then
355+
356-
                sStatus="Saved to "..sPath
356+
357
		term.setCursorBlink( true )
358-
                sStatus="Error saving to "..sPath
358+
359
	redrawMenu()
360
end
361
362
local function setCursor( newX, newY )
363
    local oldX, oldY = x, y
364
    x, y = newX, newY
365
	local screenX = x - scrollX
366
	local screenY = y - scrollY
367
	
368
	local bRedraw = false
369
	if screenX < 1 then
370
		scrollX = x - 1
371
		screenX = 1
372
		bRedraw = true
373
	elseif screenX > w then
374
		scrollX = x - w
375
		screenX = w
376
		bRedraw = true
377
	end
378
	
379
	if screenY < 1 then
380
		scrollY = y - 1
381
		screenY = 1
382
		bRedraw = true
383
	elseif screenY > h-1 then
384
		scrollY = y - (h-1)
385
		screenY = h-1
386
		bRedraw = true
387
	end
388
389
	recomplete()
390
	if bRedraw then
391
		redrawText()
392
	elseif y ~= oldY then
393
	    redrawLine( oldY )
394
	    redrawLine( y )
395
	else
396
	    redrawLine( y )
397
	end
398
	term.setCursorPos( screenX, screenY )
399
400
	redrawMenu()
401
end
402
403
-- Actual program functionality begins
404
load(sPath)
405
406
term.setBackgroundColour( bgColour )
407
term.clear()
408
term.setCursorPos(x,y)
409
term.setCursorBlink( true )
410
411
recomplete()
412
redrawText()
413
redrawMenu()
414
415
local function acceptCompletion()
416
    if nCompletion then
417
        -- Find the common prefix of all the other suggestions which start with the same letter as the current one
418
        local sCompletion = tCompletions[ nCompletion ]
419
        local sFirstLetter = string.sub( sCompletion, 1, 1 )
420
        local sCommonPrefix = sCompletion
421
        for n=1,#tCompletions do
422
            local sResult = tCompletions[n]
423
            if n ~= nCompletion and string.find( sResult, sFirstLetter, 1, true ) == 1 then
424
                while #sCommonPrefix > 1 do
425
                    if string.find( sResult, sCommonPrefix, 1, true ) == 1 then
426
                        break
427
                    else
428
                        sCommonPrefix = string.sub( sCommonPrefix, 1, #sCommonPrefix - 1 )
429
                    end
430
                end
431
            end
432
        end
433
434
        -- Append this string
435
        tLines[y] = tLines[y] .. sCommonPrefix
436
        setCursor( x + string.len( sCommonPrefix ), y )
437
    end
438
end
439
440
-- Handle input
441
while bRunning do
442
	local sEvent, param, param2, param3 = os.pullEvent()
443
	if sEvent == "key" then
444
	    local oldX, oldY = x, y
445
		if param == keys.up then
446
			-- Up
447
			if not bMenu then
448
			    if nCompletion then
449
			        -- Cycle completions
450
                    nCompletion = nCompletion - 1
451
                    if nCompletion < 1 then
452
                        nCompletion = #tCompletions
453
                    end
454
                    redrawLine(y)
455
456
				elseif y > 1 then
457
					-- Move cursor up
458
					setCursor(
459
					    math.min( x, string.len( tLines[y - 1] ) + 1 ),
460
					    y - 1
461
					)
462
				end
463
			end
464
465
		elseif param == keys.down then
466
			-- Down
467
			if not bMenu then
468
				-- Move cursor down
469
			    if nCompletion then
470
			        -- Cycle completions
471
                    nCompletion = nCompletion + 1
472
                    if nCompletion > #tCompletions then
473
                        nCompletion = 1
474
                    end
475
                    redrawLine(y)
476
477
				elseif y < #tLines then
478
				    -- Move cursor down
479
					setCursor(
480
                        math.min( x, string.len( tLines[y + 1] ) + 1 ),
481
                        y + 1
482
                    )
483
				end
484
			end
485
486
		elseif param == keys.tab then
487
			-- Tab
488
			if not bMenu and not bReadOnly then
489
			    if nCompletion and x == string.len(tLines[y]) + 1 then
490
			        -- Accept autocomplete
491
                    acceptCompletion()
492
                else
493
                    -- Indent line
494
                    local sLine = tLines[y]
495
                    tLines[y] = string.sub(sLine,1,x-1) .. "  " .. string.sub(sLine,x)
496
                    setCursor( x + 2, y )
497
                end
498
			end
499
500
		elseif param == keys.pageUp then
501
			-- Page Up
502
			if not bMenu then
503
				-- Move up a page
504
				local newY
505
				if y - (h - 1) >= 1 then
506
					newY = y - (h - 1)
507
				else
508
				    newY = 1
509
				end
510
                setCursor(
511
				    math.min( x, string.len( tLines[newY] ) + 1 ),
512
				    newY
513
				)
514
			end
515
516
		elseif param == keys.pageDown then
517
			-- Page Down
518
			if not bMenu then
519
				-- Move down a page
520
				local newY
521
				if y + (h - 1) <= #tLines then
522
					newY = y + (h - 1)
523
				else
524
					newY = #tLines
525
				end
526
				local newX = math.min( x, string.len( tLines[newY] ) + 1 )
527
				setCursor( newX, newY )
528
			end
529
530
		elseif param == keys.home then
531
			-- Home
532
			if not bMenu then
533
				-- Move cursor to the beginning
534
				if x > 1 then
535
                    setCursor(1,y)
536
                end
537
			end
538
539
		elseif param == keys["end"] then
540
			-- End
541
			if not bMenu then
542
				-- Move cursor to the end
543
				local nLimit = string.len( tLines[y] ) + 1
544
				if x < nLimit then
545
    				setCursor( nLimit, y )
546
    		    end
547
			end
548
549
		elseif param == keys.left then
550
			-- Left
551
			if not bMenu then
552
				if x > 1 then
553
					-- Move cursor left
554
    				setCursor( x - 1, y )
555
				elseif x==1 and y>1 then
556
    				setCursor( string.len( tLines[y-1] ) + 1, y - 1 )
557
				end
558
			else
559
				-- Move menu left
560
				nMenuItem = nMenuItem - 1
561
				if nMenuItem < 1 then
562
					nMenuItem = #tMenuItems
563
				end
564
				redrawMenu()
565
			end
566
567
		elseif param == keys.right then
568
			-- Right
569
			if not bMenu then
570
			    local nLimit = string.len( tLines[y] ) + 1
571
				if x < nLimit then
572
					-- Move cursor right
573
					setCursor( x + 1, y )
574
			    elseif nCompletion and x == string.len(tLines[y]) + 1 then
575
                    -- Accept autocomplete
576
                    acceptCompletion()
577
				elseif x==nLimit and y<#tLines then
578
				    -- Go to next line
579
				    setCursor( 1, y + 1 )
580
				end
581
			else
582
				-- Move menu right
583
				nMenuItem = nMenuItem + 1
584
				if nMenuItem > #tMenuItems then
585
					nMenuItem = 1
586
				end
587
				redrawMenu()
588
			end
589
590
		elseif param == keys.delete then
591
			-- Delete
592
			if not bMenu and not bReadOnly then
593
			    local nLimit = string.len( tLines[y] ) + 1
594
				if x < nLimit then
595
					local sLine = tLines[y]
596
					tLines[y] = string.sub(sLine,1,x-1) .. string.sub(sLine,x+1)
597
					recomplete()
598
					redrawLine(y)
599
				elseif y<#tLines then
600
					tLines[y] = tLines[y] .. tLines[y+1]
601
					table.remove( tLines, y+1 )
602
					recomplete()
603
					redrawText()
604
				end
605
			end
606
607
		elseif param == keys.backspace then
608
			-- Backspace
609
			if not bMenu and not bReadOnly then
610
				if x > 1 then
611
					-- Remove character
612
					local sLine = tLines[y]
613
					tLines[y] = string.sub(sLine,1,x-2) .. string.sub(sLine,x)
614
			        setCursor( x - 1, y )
615
				elseif y > 1 then
616
					-- Remove newline
617
					local sPrevLen = string.len( tLines[y-1] )
618
					tLines[y-1] = tLines[y-1] .. tLines[y]
619
					table.remove( tLines, y )
620
					setCursor( sPrevLen + 1, y - 1 )
621
					redrawText()
622
				end
623
			end
624
625
		elseif param == keys.enter then
626
			-- Enter
627
			if not bMenu and not bReadOnly then
628
				-- Newline
629
				local sLine = tLines[y]
630
				local _,spaces=string.find(sLine,"^[ ]+")
631
				if not spaces then
632
					spaces=0
633
				end
634
				tLines[y] = string.sub(sLine,1,x-1)
635
				table.insert( tLines, y+1, string.rep(' ',spaces)..string.sub(sLine,x) )
636
				setCursor( spaces + 1, y + 1 )
637
				redrawText()
638
639
			elseif bMenu then
640
				-- Menu selection
641
				doMenuItem( nMenuItem )
642
643
			end
644
645
		elseif param == keys.leftCtrl or param == keys.rightCtrl or param == keys.rightAlt then
646
			-- Menu toggle
647
			bMenu = not bMenu
648
			if bMenu then
649
				term.setCursorBlink( false )
650
			else
651
				term.setCursorBlink( true )
652
			end
653
			redrawMenu()
654
655
		end
656
		
657
	elseif sEvent == "char" then
658
		if not bMenu and not bReadOnly then
659
			-- Input text
660
			local sLine = tLines[y]
661
			tLines[y] = string.sub(sLine,1,x-1) .. param .. string.sub(sLine,x)
662
			setCursor( x + 1, y )
663
664
		elseif bMenu then
665
			-- Select menu items
666
			for n,sMenuItem in ipairs( tMenuItems ) do
667
				if string.lower(string.sub(sMenuItem,1,1)) == string.lower(param) then
668
					doMenuItem( n )
669
					break
670
				end
671
			end
672
		end
673
674
	elseif sEvent == "paste" then
675
		if not bMenu and not bReadOnly then
676
			-- Input text
677
			local sLine = tLines[y]
678
			tLines[y] = string.sub(sLine,1,x-1) .. param .. string.sub(sLine,x)
679
			setCursor( x + string.len( param ), y )
680
		end
681
		
682
	elseif sEvent == "mouse_click" then
683
		if not bMenu then
684
			if param == 1 then
685
				-- Left click
686
				local cx,cy = param2, param3
687
				if cy < h then
688
					local newY = math.min( math.max( scrollY + cy, 1 ), #tLines )
689
					local newX = math.min( math.max( scrollX + cx, 1 ), string.len( tLines[newY] ) + 1 )
690
					setCursor( newX, newY )
691
				end
692
			end
693
		end
694
		
695
	elseif sEvent == "mouse_scroll" then
696
		if not bMenu then
697
			if param == -1 then
698
				-- Scroll up
699
				if scrollY > 0 then
700
					-- Move cursor up
701
					scrollY = scrollY - 1
702
					redrawText()
703
				end
704
			
705
			elseif param == 1 then
706
				-- Scroll down
707
				local nMaxScroll = #tLines - (h-1)
708
				if scrollY < nMaxScroll then
709
					-- Move cursor down
710
					scrollY = scrollY + 1
711
					redrawText()
712
				end
713
				
714
			end
715
		end
716
717
	elseif sEvent == "term_resize" then
718
	    w,h = term.getSize()
719
        setCursor( x, y )
720
        redrawMenu()
721
        redrawText()
722
723
	end
724
end
725
726
-- Cleanup
727
term.clear()
728
term.setCursorBlink( false )
729
term.setCursorPos( 1, 1 )