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