View difference between Paste ID: maXLmT7Y and p2vghsaW
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" )
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,
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()
268+
	Delete = function()
269-
		if bReadOnly then
269+
		shell.run("delete", sPath)
270-
			sStatus = "Access denied"
270+
271
	end,
272-
			local ok, err = save( sPath )
272+
273-
			if ok then
273+
274-
				sStatus="Saved to "..sPath
274+
275
			sStatus = "No printer attached"
276-
				sStatus="Error saving to "..sPath
276+
277
		end
278
279
		local nPage = 0
280
		local sName = fs.getName( sPath )
281
		if printer.getInkLevel() < 1 then
282
			sStatus = "Printer out of ink"
283
			return
284
		elseif printer.getPaperLevel() < 1 then
285
			sStatus = "Printer out of paper"
286
			return
287
		end
288
289
		local screenTerminal = term.current()
290
		local printerTerminal = {
291
			getCursorPos = printer.getCursorPos,
292
			setCursorPos = printer.setCursorPos,
293
			getSize = printer.getPageSize,
294
			write = printer.write,
295
		}
296
		printerTerminal.scroll = function()
297
			if nPage == 1 then
298
				printer.setPageTitle( sName.." (page "..nPage..")" )			
299
			end
300
			
301
			while not printer.newPage()	do
302
				if printer.getInkLevel() < 1 then
303
					sStatus = "Printer out of ink, please refill"
304
				elseif printer.getPaperLevel() < 1 then
305
					sStatus = "Printer out of paper, please refill"
306
				else
307
					sStatus = "Printer output tray full, please empty"
308
				end
309
	
310
				term.redirect( screenTerminal )
311
				redrawMenu()
312
				term.redirect( printerTerminal )
313
				
314
				local timer = os.startTimer(0.5)
315
				sleep(0.5)
316
			end
317
318
			nPage = nPage + 1
319
			if nPage == 1 then
320
				printer.setPageTitle( sName )
321
			else
322
				printer.setPageTitle( sName.." (page "..nPage..")" )
323
			end
324
		end
325
		
326
		bMenu = false
327
		term.redirect( printerTerminal )
328
		local ok, error = pcall( function()
329
			term.scroll()
330
			for n, sLine in ipairs( tLines ) do
331
				print( sLine )
332
			end
333
		end )
334
        term.redirect( screenTerminal )
335
		if not ok then
336
			print( error )
337
		end
338
		
339
		while not printer.endPage() do
340
			sStatus = "Printer output tray full, please empty"
341
			redrawMenu()
342
			sleep( 0.5 )
343
		end
344
		bMenu = true
345
			
346
		if nPage > 1 then
347
			sStatus = "Printed "..nPage.." Pages"
348
		else
349
			sStatus = "Printed 1 Page"
350
		end
351
		redrawMenu()
352
	end,
353
	Exit = function()
354
		local ok, err = save( sPath )
355
            if ok then
356
                sStatus="Saved to "..sPath
357
            else
358
                sStatus="Error saving to "..sPath
359
            end
360
		bRunning = false
361
	end,
362
	Run = function()
363
	    local sTempPath = "/.temp"
364
        local ok, err = save( sTempPath )
365
        if ok then
366
            local nTask = shell.openTab( sTempPath )
367
            if nTask then
368
                shell.switchTab( nTask )
369
            else
370
                sStatus="Error starting Task"
371
            end
372
            fs.delete( sTempPath )
373
        else
374
            sStatus="Error saving to "..sTempPath
375
        end
376
		redrawMenu()
377
    end
378
}
379
380
local function doMenuItem( _n )
381
	tMenuFuncs[tMenuItems[_n]]()
382
	if bMenu then
383
		bMenu = false
384
		term.setCursorBlink( true )
385
	end
386
	redrawMenu()
387
end
388
389
local function setCursor( newX, newY )
390
    local oldX, oldY = x, y
391
    x, y = newX, newY
392
	local screenX = x - scrollX
393
	local screenY = y - scrollY
394
	
395
	local bRedraw = false
396
	if screenX < 1 then
397
		scrollX = x - 1
398
		screenX = 1
399
		bRedraw = true
400
	elseif screenX > w then
401
		scrollX = x - w
402
		screenX = w
403
		bRedraw = true
404
	end
405
	
406
	if screenY < 1 then
407
		scrollY = y - 1
408
		screenY = 1
409
		bRedraw = true
410
	elseif screenY > h-1 then
411
		scrollY = y - (h-1)
412
		screenY = h-1
413
		bRedraw = true
414
	end
415
416
	recomplete()
417
	if bRedraw then
418
		redrawText()
419
	elseif y ~= oldY then
420
	    redrawLine( oldY )
421
	    redrawLine( y )
422
	else
423
	    redrawLine( y )
424
	end
425
	term.setCursorPos( screenX, screenY )
426
427
	redrawMenu()
428
end
429
430
-- Actual program functionality begins
431
load(sPath)
432
433
term.setBackgroundColour( bgColour )
434
term.clear()
435
term.setCursorPos(x,y)
436
term.setCursorBlink( true )
437
438
recomplete()
439
redrawText()
440
redrawMenu()
441
442
local function acceptCompletion()
443
    if nCompletion then
444
        -- Find the common prefix of all the other suggestions which start with the same letter as the current one
445
        local sCompletion = tCompletions[ nCompletion ]
446
        local sFirstLetter = string.sub( sCompletion, 1, 1 )
447
        local sCommonPrefix = sCompletion
448
        for n=1,#tCompletions do
449
            local sResult = tCompletions[n]
450
            if n ~= nCompletion and string.find( sResult, sFirstLetter, 1, true ) == 1 then
451
                while #sCommonPrefix > 1 do
452
                    if string.find( sResult, sCommonPrefix, 1, true ) == 1 then
453
                        break
454
                    else
455
                        sCommonPrefix = string.sub( sCommonPrefix, 1, #sCommonPrefix - 1 )
456
                    end
457
                end
458
            end
459
        end
460
461
        -- Append this string
462
        tLines[y] = tLines[y] .. sCommonPrefix
463
        setCursor( x + string.len( sCommonPrefix ), y )
464
    end
465
end
466
467
-- Handle input
468
while bRunning do
469
	local sEvent, param, param2, param3 = os.pullEvent()
470
	if sEvent == "key" then
471
	    local oldX, oldY = x, y
472
		if param == keys.up then
473
			-- Up
474
			if not bMenu then
475
			    if nCompletion then
476
			        -- Cycle completions
477
                    nCompletion = nCompletion - 1
478
                    if nCompletion < 1 then
479
                        nCompletion = #tCompletions
480
                    end
481
                    redrawLine(y)
482
483
				elseif y > 1 then
484
					-- Move cursor up
485
					setCursor(
486
					    math.min( x, string.len( tLines[y - 1] ) + 1 ),
487
					    y - 1
488
					)
489
				end
490
			end
491
492
		elseif param == keys.down then
493
			-- Down
494
			if not bMenu then
495
				-- Move cursor down
496
			    if nCompletion then
497
			        -- Cycle completions
498
                    nCompletion = nCompletion + 1
499
                    if nCompletion > #tCompletions then
500
                        nCompletion = 1
501
                    end
502
                    redrawLine(y)
503
504
				elseif y < #tLines then
505
				    -- Move cursor down
506
					setCursor(
507
                        math.min( x, string.len( tLines[y + 1] ) + 1 ),
508
                        y + 1
509
                    )
510
				end
511
			end
512
513
		elseif param == keys.tab then
514
			-- Tab
515
			if not bMenu and not bReadOnly then
516
			    if nCompletion and x == string.len(tLines[y]) + 1 then
517
			        -- Accept autocomplete
518
                    acceptCompletion()
519
                else
520
                    -- Indent line
521
                    local sLine = tLines[y]
522
                    tLines[y] = string.sub(sLine,1,x-1) .. "  " .. string.sub(sLine,x)
523
                    setCursor( x + 2, y )
524
                end
525
			end
526
527
		elseif param == keys.pageUp then
528
			-- Page Up
529
			if not bMenu then
530
				-- Move up a page
531
				local newY
532
				if y - (h - 1) >= 1 then
533
					newY = y - (h - 1)
534
				else
535
				    newY = 1
536
				end
537
                setCursor(
538
				    math.min( x, string.len( tLines[newY] ) + 1 ),
539
				    newY
540
				)
541
			end
542
543
		elseif param == keys.pageDown then
544
			-- Page Down
545
			if not bMenu then
546
				-- Move down a page
547
				local newY
548
				if y + (h - 1) <= #tLines then
549
					newY = y + (h - 1)
550
				else
551
					newY = #tLines
552
				end
553
				local newX = math.min( x, string.len( tLines[newY] ) + 1 )
554
				setCursor( newX, newY )
555
			end
556
557
		elseif param == keys.home then
558
			-- Home
559
			if not bMenu then
560
				-- Move cursor to the beginning
561
				if x > 1 then
562
                    setCursor(1,y)
563
                end
564
			end
565
566
		elseif param == keys["end"] then
567
			-- End
568
			if not bMenu then
569
				-- Move cursor to the end
570
				local nLimit = string.len( tLines[y] ) + 1
571
				if x < nLimit then
572
    				setCursor( nLimit, y )
573
    		    end
574
			end
575
576
		elseif param == keys.left then
577
			-- Left
578
			if not bMenu then
579
				if x > 1 then
580
					-- Move cursor left
581
    				setCursor( x - 1, y )
582
				elseif x==1 and y>1 then
583
    				setCursor( string.len( tLines[y-1] ) + 1, y - 1 )
584
				end
585
			else
586
				-- Move menu left
587
				nMenuItem = nMenuItem - 1
588
				if nMenuItem < 1 then
589
					nMenuItem = #tMenuItems
590
				end
591
				redrawMenu()
592
			end
593
594
		elseif param == keys.right then
595
			-- Right
596
			if not bMenu then
597
			    local nLimit = string.len( tLines[y] ) + 1
598
				if x < nLimit then
599
					-- Move cursor right
600
					setCursor( x + 1, y )
601
			    elseif nCompletion and x == string.len(tLines[y]) + 1 then
602
                    -- Accept autocomplete
603
                    acceptCompletion()
604
				elseif x==nLimit and y<#tLines then
605
				    -- Go to next line
606
				    setCursor( 1, y + 1 )
607
				end
608
			else
609
				-- Move menu right
610
				nMenuItem = nMenuItem + 1
611
				if nMenuItem > #tMenuItems then
612
					nMenuItem = 1
613
				end
614
				redrawMenu()
615
			end
616
617
		elseif param == keys.delete then
618
			-- Delete
619
			if not bMenu and not bReadOnly then
620
			    local nLimit = string.len( tLines[y] ) + 1
621
				if x < nLimit then
622
					local sLine = tLines[y]
623
					tLines[y] = string.sub(sLine,1,x-1) .. string.sub(sLine,x+1)
624
					recomplete()
625
					redrawLine(y)
626
				elseif y<#tLines then
627
					tLines[y] = tLines[y] .. tLines[y+1]
628
					table.remove( tLines, y+1 )
629
					recomplete()
630
					redrawText()
631
				end
632
			end
633
634
		elseif param == keys.backspace then
635
			-- Backspace
636
			if not bMenu and not bReadOnly then
637
				if x > 1 then
638
					-- Remove character
639
					local sLine = tLines[y]
640
					tLines[y] = string.sub(sLine,1,x-2) .. string.sub(sLine,x)
641
			        setCursor( x - 1, y )
642
				elseif y > 1 then
643
					-- Remove newline
644
					local sPrevLen = string.len( tLines[y-1] )
645
					tLines[y-1] = tLines[y-1] .. tLines[y]
646
					table.remove( tLines, y )
647
					setCursor( sPrevLen + 1, y - 1 )
648
					redrawText()
649
				end
650
			end
651
652
		elseif param == keys.enter then
653
			-- Enter
654
			if not bMenu and not bReadOnly then
655
				-- Newline
656
				local sLine = tLines[y]
657
				local _,spaces=string.find(sLine,"^[ ]+")
658
				if not spaces then
659
					spaces=0
660
				end
661
				tLines[y] = string.sub(sLine,1,x-1)
662
				table.insert( tLines, y+1, string.rep(' ',spaces)..string.sub(sLine,x) )
663
				setCursor( spaces + 1, y + 1 )
664
				redrawText()
665
666
			elseif bMenu then
667
				-- Menu selection
668
				doMenuItem( nMenuItem )
669
670
			end
671
672
		elseif param == keys.leftCtrl or param == keys.rightCtrl or param == keys.rightAlt then
673
			-- Menu toggle
674
			bMenu = not bMenu
675
			if bMenu then
676
				term.setCursorBlink( false )
677
			else
678
				term.setCursorBlink( true )
679
			end
680
			redrawMenu()
681
682
		end
683
		
684
	elseif sEvent == "char" then
685
		if not bMenu and not bReadOnly then
686
			-- Input text
687
			local sLine = tLines[y]
688
			tLines[y] = string.sub(sLine,1,x-1) .. param .. string.sub(sLine,x)
689
			setCursor( x + 1, y )
690
691
		elseif bMenu then
692
			-- Select menu items
693
			for n,sMenuItem in ipairs( tMenuItems ) do
694
				if string.lower(string.sub(sMenuItem,1,1)) == string.lower(param) then
695
					doMenuItem( n )
696
					break
697
				end
698
			end
699
		end
700
701
	elseif sEvent == "paste" then
702
		if not bMenu and not bReadOnly then
703
			-- Input text
704
			local sLine = tLines[y]
705
			tLines[y] = string.sub(sLine,1,x-1) .. param .. string.sub(sLine,x)
706
			setCursor( x + string.len( param ), y )
707
		end
708
		
709
	elseif sEvent == "mouse_click" then
710
		if not bMenu then
711
			if param == 1 then
712
				-- Left click
713
				local cx,cy = param2, param3
714
				if cy < h then
715
					local newY = math.min( math.max( scrollY + cy, 1 ), #tLines )
716
					local newX = math.min( math.max( scrollX + cx, 1 ), string.len( tLines[newY] ) + 1 )
717
					setCursor( newX, newY )
718
				end
719
			end
720
		end
721
		
722
	elseif sEvent == "mouse_scroll" then
723
		if not bMenu then
724
			if param == -1 then
725
				-- Scroll up
726
				if scrollY > 0 then
727
					-- Move cursor up
728
					scrollY = scrollY - 1
729
					redrawText()
730
				end
731
			
732
			elseif param == 1 then
733
				-- Scroll down
734
				local nMaxScroll = #tLines - (h-1)
735
				if scrollY < nMaxScroll then
736
					-- Move cursor down
737
					scrollY = scrollY + 1
738
					redrawText()
739
				end
740
				
741
			end
742
		end
743
744
	elseif sEvent == "term_resize" then
745
	    w,h = term.getSize()
746
        setCursor( x, y )
747
        redrawMenu()
748
        redrawText()
749
750
	end
751
end
752
753
-- Cleanup
754
term.clear()
755
term.setCursorBlink( false )
756
term.setCursorPos( 1, 1 )