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