ShEdit - An editor with proper syntax highlighting!
Description
It isn't an IDE, but it's simple, gets the job (uh, most jobs) done, and works well. I actually use this editor more than I use external editors!It's a modification of the stock edit program, with ton...
Installation
Copy one of these commands into your ComputerCraft terminal:
Pastebin:
pastebin get guNvJcZ9 shedit_-_an_editor_with_proper_syntax_highlighting!wget:
wget https://pastebin.com/raw/guNvJcZ9 shedit_-_an_editor_with_proper_syntax_highlighting!Archive:
wget https://cc.shobie.xyz/cc/get/pb-guNvJcZ9 shedit_-_an_editor_with_proper_syntax_highlighting!
Quick Install:
wget https://cc.shobie.xyz/cc/get/pb-guNvJcZ9 ShEdit - An editor with proper syntax highlighting!
Usage
Run the program after downloading
Tags
Source
View Original SourceCode Preview
if not fs.exists('lex') then
shell.run('pastebin get edyuQ5xY lex')
end
local lexSuccess = os.loadAPI('lex')
if not lexSuccess or not lex or not lex.lex then
print('A valid lexer is required (file `lex` seems to be broken or not a lexer)')
return
end
-- Get file to edit
local tArgs = { ... }
if #tArgs == 0 then
print( "Usage: shedit <path>" )
return
end
-- Error checking
local sPath = shell.resolve( tArgs[1] )
local bReadOnly = fs.isReadOnly( sPath )
if fs.exists( sPath ) and fs.isDir( sPath ) then
print( "Cannot edit a directory." )
return
end
local x,y = 1,1
local markX, markY = 1, 1
local w,h = term.getSize()
local scrollX, scrollY = 0,0
local tLines = {}
local bRunning = true
-- Colors
local highlightColor, keywordColor, commentColor, textColor, bgColor, stringColor
if term.isColor() then
bgColor = colors.black
textColor = colors.white
highlightColor = colors.yellow
keywordColor = colors.yellow
commentColor = colors.green
stringColor = colors.red
else
bgColor = colors.black
textColor = colors.white
highlightColor = colors.white
keywordColor = colors.white
commentColor = colors.white
stringColor = colors.white
end
-- Menus
local bMenu = false
local nMenuItem = 1
local tMenuItems = {}
if not bReadOnly then
table.insert( tMenuItems, "Save" )
end
if shell.openTab then
table.insert( tMenuItems, "Run" )
end
if peripheral.find( "printer" ) then
table.insert( tMenuItems, "Print" )
end
table.insert( tMenuItems, "Exit" )
table.insert(tMenuItems, 'Tools')
table.insert(tMenuItems, 'Find')
table.insert(tMenuItems, 'Jump')
table.insert(tMenuItems, 'Copy')
table.insert(tMenuItems, 'VPaste')
local sStatus = "Press Ctrl to access menu"
if string.len( sStatus ) > w - 5 then
sStatus = "Press Ctrl for menu"
end
local function load( _sPath )
tLines = {}
if fs.exists( _sPath ) then
local file = io.open( _sPath, "r" )
local sLine = file:read()
while sLine do
table.insert( tLines, sLine )
sLine = file:read()
end
file:close()
end
if #tLines == 0 then
table.insert( tLines, "" )
end
end
local function save( _sPath )
-- Create intervening folder
local sDir = _sPath:sub(1, _sPath:len() - fs.getName(_sPath):len() )
if not fs.exists( sDir ) then
fs.makeDir( sDir )
end
-- Save
local file = nil
local function innerSave()
file = fs.open( _sPath, "w" )
if file then
for n, sLine in ipairs( tLines ) do
file.write( sLine .. "\n" )
end
else
error( "Failed to open ".._sPath )
end
end
local ok, err = pcall( innerSave )
if file then
file.close()
end
return ok, err
end
local tKeywords = {
["and"] = true,
["break"] = true,
["do"] = true,
["else"] = true,
["elseif"] = true,
["end"] = true,
["false"] = true,
["for"] = true,
["function"] = true,
["if"] = true,
["in"] = true,
["local"] = true,
["nil"] = true,
["not"] = true,
["or"] = true,
["repeat"] = true,
["return"] = true,
["then"] = true,
["true"] = true,
["until"]= true,
["while"] = true,
}
local function tryWrite( sLine, regex, color )
local match = string.match( sLine, regex )
if match then
if type(color) == "number" then
term.setTextColor( color )
else
term.setTextColor( color(match) )
end
term.write( match )
term.setTextColor( textColor )
return string.sub( sLine, string.len(match) + 1 )
end
return nil
end
--[[local function writeHighlighted( sLine )
while string.len(sLine) > 0 do
sLine =
tryWrite( sLine, "^%-%-%[%[.-%]%]", commentColor ) or
tryWrite( sLine, "^%-%-.*", commentColor ) or
tryWrite( sLine, "^\"\"", stringColor ) or
tryWrite( sLine, "^\".-[^\\]\"", stringColor ) or
tryWrite( sLine, "^\'\'", stringColor ) or
tryWrite( sLine, "^\'.-[^\\]\'", stringColor ) or
tryWrite( sLine, "^%[%[.-%]%]", stringColor ) or
tryWrite( sLine, "^[%w_]+", function( match )
if tKeywords[ match ] then
return keywordColor
end
return textColor
end ) or
tryWrite( sLine, "^[^%w_]", textColor )
end
end]]
local apis = {
-- tables
bit = true,
bit32 = true,
bitop = true,
colors = true,
colours = true,
coroutine = true,
disk = true,
fs = true,
gps = true,
help = true,
http = true,
io = true,
keys = true,
math = true,
os = true,
paintutils = true,
parallel = true,
peripheral = true,
rednet = true,
redstone = true,
rs = true,
settings = true,
shell = true,
socket = true,
string = true,
table = true,
term = true,
textutils = true,
vector = true,
window = true,
-- functions
assert = true,
collectgarbage = true,
dofile = true,
error = true,
getfenv = true,
getmetatable = true,
ipairs = true,
loadfile = true,
loadstring = true,
module = true,
next = true,
pairs = true,
pcall = true,
print = true,
rawequal = true,
rawget = true,
rawset = true,
require = true,
select = true,
setfenv = true,
setmetatable = true,
tonumber = true,
tostring = true,
type = true,
unpack = true,
xpcall = true,
printError = true,
write = true
}
local function onUpdate()
tokenized = lex.lex(table.concat(tLines, '\n'))
end
local function markExists()
if markX ~= x or markY ~= y then
return true
else
return false
end
end
local function isMarked(newX, newY)
if (newY > markY and newY < y) or (newY > y and newY < markY) then
return true
end
if newY == markY and newY == y then
if markX > x and newX >= x and newX < markX then
return true
elseif markX < x and newX >= markX and newX < x then
return true
end
elseif newY == markY then
if newX < markX and y < markY then
return true
elseif newX >= markX and y > markY then
return true
end
elseif newY == y then
if newX < x and y > markY then
return true
elseif newX >= x and y < markY then
return true
end
end
return false
end
local function getMarks()
local msx, msy
local mex, mey
if markY == y then
if markX > x then
msx = x
msy = y
mex = markX
mey = markY
else
msx = markX
msy = markY
mex = x
mey = y
end
else
if markY > y then
msx = x
msy = y
mex = markX
mey = markY
else
msx = markX
msy = markY
mex = x
mey = y
end
end
return msx, msy, mex, mey
end
local function getCharDisplay(char)
if char == '\t' then
return ' '
else
return char
end
end
local function getCharWidth(char)
return getCharDisplay(char):len()
end
local function writeHighlighted(lineNr)
local tokens = tokenized[lineNr] or {}
local textColor = term.getTextColor()
local bgColor = term.getBackgroundColor()
local setX, setY = term.getCursorPos()
local msx, msy, mex, mey = getMarks()
if lineNr < mey and lineNr >= msy then
term.setBackgroundColor(colors.white)
end
term.clearLine()
term.setCursorPos(setX, setY)
for t = 1, #tokens do
local token = tokens[t]
local color = colors.white
if token.type == 'keyword' or token.type == 'operator' then
color = colors.yellow
elseif token.type == 'number' or token.type == 'value' then
color = colors.purple
elseif token.type == 'comment' then
color = colors.green
elseif token.type == 'ident' and apis[token.data] then
color = colors.lightGray
elseif token.type == 'string' then
color = colors.cyan
elseif token.type == 'escape' then
color = colors.purple
elseif token.type == 'unidentified' then
color = colors.red
end
for p = 1, #token.data do
local sub = getCharDisplay(token.data:sub(p, p))
local cX, cY = p + token.posFirst - 1, lineNr
if (cY == msy and cY == mey and cX >= msx and cX < mex) or (cY == msy and cY ~= mey and cX >= msx) or (cY == mey and cY ~= msy and cX < mex) or (cY > msy and cY < mey) then
term.setTextColor(colors.black)
term.setBackgroundColor(color)
else
term.setBackgroundColor(colors.black)
term.setTextColor(color)
end
term.write(sub)
end
end
term.setTextColor(textColor)
term.setBackgroundColor(bgColor)
end
local tCompletions
local nCompletion
local tCompleteEnv = _ENV
local function complete( sLine )
if settings.get( "edit.autocomplete" ) then
local nStartPos = string.find( sLine, "[a-zA-Z0-9_%.]+{{code}}quot; )
if nStartPos then
sLine = string.sub( sLine, nStartPos )
end
if #sLine > 0 then
return textutils.complete( sLine, tCompleteEnv )
end
end
return nil
end
local function recomplete()
local sLine = tLines[y]
if not bMenu and not bReadOnly and x == string.len(sLine) + 1 then
tCompletions = complete( sLine )
if tCompletions and #tCompletions > 0 then
nCompletion = 1
else
nCompletion = nil
end
else
tCompletions = nil
nCompletion = nil
end
end
local function writeCompletion( sLine )
if nCompletion then
local sCompletion = tCompletions[ nCompletion ]
term.setTextColor( colors.white )
term.setBackgroundColor( colors.gray )
term.write( sCompletion )
term.setTextColor( textColor )
term.setBackgroundColor( bgColor )
end
end
local function getCursorX(lineNr)
local line = tLines[lineNr]
local cx = 1
for i = 1, x - 1 do
cx = cx + getCharWidth(line:sub(i, i))
end
return cx
end
local function cxToPos(lineNr, cx)
local line = tLines[lineNr]
local num = 1
local pos = 1
for i = 1, #line do
amt = getCharWidth(line:sub(i, i))
if num + amt > cx then
return pos
else
num = num + amt
pos = pos + 1
end
end
return pos
end
local function redrawText()
local cursorX, cursorY = x, y
for y=1,h-1 do
term.setCursorPos( 1 - scrollX, y )
term.clearLine()
local sLine = tLines[ y + scrollY ]
if sLine ~= nil then
--writeHighlighted( sLine )
writeHighlighted(y + scrollY)
if cursorY == y + scrollY and cursorX == #sLine + 1 then
writeCompletion()
end
end
end
local cx = getCursorX(y)
term.setCursorPos( cx - scrollX, y - scrollY )
end
--[[local function redrawLine(_nY)
local sLine = tLines[_nY]
if sLine then
term.setCursorPos( 1 - scrollX, _nY - scrollY )
term.clearLine()
--writeHighlighted( sLine )
writeHighlighted(_nY)
if _nY == y and x == #sLine + 1 then
writeCompletion()
end
term.setCursorPos( x - scrollX, _nY - scrollY )
end
end]]
local redrawLine = redrawText
local function redrawMenu()
-- Clear line
term.setCursorPos( 1, h )
term.clearLine()
-- Draw line numbers
--[[term.setCursorPos( w - string.len( "Ln "..y ) + 1, h )
term.setTextColor( highlightColor )
term.write( "Ln " )
term.setTextColor( textColor )
term.write( y )]]
if not bMenu then
-- Draw status
term.setTextColor( highlightColor )
term.write( sStatus )
term.setTextColor( textColor )
term.setCursorPos(w - ('[' .. x .. ':' .. y .. ']:[' .. markX .. ':' .. markY .. ']'):len() + 1, h)
term.setTextColor(highlightColor)
term.write('[')
term.setTextColor(textColor)
term.write(x)
term.setTextColor(highlightColor)
term.write(':')
term.setTextColor(textColor)
term.write(y)
term.setTextColor(highlightColor)
term.write(']:[')
term.setTextColor(textColor)
term.write(markX)
term.setTextColor(highlightColor)
term.write(':')
term.setTextColor(textColor)
term.write(markY)
term.setTextColor(highlightColor)
term.write(']')
term.setTextColor(textColor)
else
-- Draw menu
term.setTextColor( textColor )
for nItem,sItem in pairs( tMenuItems ) do
if nItem == nMenuItem then
term.setTextColor( highlightColor )
term.write( "[" )
term.setTextColor( textColor )
term.write( sItem )
term.setTextColor( highlightColor )
term.write( "]" )
term.setTextColor( textColor )
else
term.write( " "..sItem.." " )
end
end
end
-- Reset cursor
term.setCursorPos( getCursorX(y) - scrollX, y - scrollY )
end
local function setCursor( newX, newY )
local oldX, oldY = x, y
x, y = newX, newY
local screenX = getCursorX(y) - scrollX
local screenY = y - scrollY
local bRedraw = false
if screenX < 1 then
scrollX = getCursorX(y) - 1
screenX = 1
bRedraw = true
elseif screenX > w then
scrollX = scrollX + (screenX - w)
screenX = w
bRedraw = true
end
if screenY < 1 then
scrollY = y - 1
screenY = 1
bRedraw = true
elseif screenY > h-1 then
scrollY = y - (h-1)
screenY = h-1
bRedraw = true
end
recomplete()
if bRedraw then
redrawText()
elseif y ~= oldY then
redrawLine( oldY )
redrawLine( y )
else
redrawLine( y )
end
term.setCursorPos( screenX, screenY )
redrawMenu()
end
local function setCursorMark(newX, newY)
markX, markY = newX, newY
setCursor(newX, newY)
end
local function deleteMarked()
if markExists() then
local msx, msy, mex, mey = getMarks()
if msy == mey then
local line = tLines[mey]
tLines[mey] = line:sub(1, msx - 1) .. line:sub(mex)
else
if mey - msy > 1 then
for i = 1, mey - msy - 1 do
table.remove(tLines, msy + 1)
end
end
local line = tLines[msy]
local line2 = tLines[msy + 1]
tLines[msy] = line:sub(1, msx - 1) .. line2:sub(mex)
table.remove(tLines, msy + 1)
end
setCursorMark(msx, msy)
end
end
local function getMarked()
if markExists() then
local msx, msy, mex, mey = getMarks()
if msy == mey then
return tLines[msy]:sub(msx, mex - 1)
else
local marked = tLines[msy]:sub(msx)
if mey - msy > 1 then
for i = msy + 1, mey - 1 do
marked = marked .. '\n' .. tLines[i]
end
end
marked = marked .. '\n' .. tLines[mey]:sub(1, mex - 1)
return marked
end
end
end
local toolsFuncs = {
{
'Convert 2 spaces to tabs',
function()
for i = 1, #tLines do
local line = tLines[i]
local count = 0
while true do
if line:sub(count * 2 + 1, count * 2 + 2) == ' ' then
count = count + 1
else
break
end
end
tLines[i] = ('\t'):rep(count) .. line:sub(count * 2 + 1)
end
end
}, {
'Convert tabs to 2 spaces',
function()
for i = 1, #tLines do
local line = tLines[i]
local count = 0
while true do
if line:sub(count + 1, count + 1) == '\t' then
count = count + 1
else
break
end
end
tLines[i] = (' '):rep(count) .. line:sub(count + 1)
end
end
}
}
-- for testing scrolling, etc in the tools menu
--[[for i = 1, 50 do
table.insert(toolsFuncs, {'Do nothing ' .. tostring(i), function() end})
end]]
local findHistory = {}
local jumpHistory = {}
local vClipboard = ''
local tMenuFuncs = {
Save = function()
if bReadOnly then
sStatus = "Access denied"
else
local ok, err = save( sPath )
if ok then
sStatus="Saved to "..sPath
else
sStatus="Error saving to "..sPath
end
end
redrawMenu()
end,
Print = function()
local printer = peripheral.find( "printer" )
if not printer then
sStatus = "No printer attached"
return
end
local nPage = 0
local sName = fs.getName( sPath )
if printer.getInkLevel() < 1 then
sStatus = "Printer out of ink"
return
elseif printer.getPaperLevel() < 1 then
sStatus = "Printer out of paper"
return
end
local screenTerminal = term.current()
local printerTerminal = {
getCursorPos = printer.getCursorPos,
setCursorPos = printer.setCursorPos,
getSize = printer.getPageSize,
write = printer.write,
}
printerTerminal.scroll = function()
if nPage == 1 then
printer.setPageTitle( sName.." (page "..nPage..")" )
end
while not printer.newPage() do
if printer.getInkLevel() < 1 then
sStatus = "Printer out of ink, please refill"
elseif printer.getPaperLevel() < 1 then
sStatus = "Printer out of paper, please refill"
else
sStatus = "Printer output tray full, please empty"
end
term.redirect( screenTerminal )
redrawMenu()
term.redirect( printerTerminal )
local timer = os.startTimer(0.5)
sleep(0.5)
end
nPage = nPage + 1
if nPage == 1 then
printer.setPageTitle( sName )
else
printer.setPageTitle( sName.." (page "..nPage..")" )
end
end
bMenu = false
term.redirect( printerTerminal )
local ok, error = pcall( function()
term.scroll()
for n, sLine in ipairs( tLines ) do
print( sLine )
end
end )
term.redirect( screenTerminal )
if not ok then
print( error )
end
while not printer.endPage() do
sStatus = "Printer output tray full, please empty"
redrawMenu()
sleep( 0.5 )
end
bMenu = true
if nPage > 1 then
sStatus = "Printed "..nPage.." Pages"
else
sStatus = "Printed 1 Page"
end
redrawMenu()
end,
Exit = function()
bRunning = false
end,
Run = function()
local sTempPath = "/.temp"
local ok, err = save( sTempPath )
if ok then
local nTask = shell.openTab( sTempPath )
if nTask then
shell.switchTab( nTask )
else
sStatus="Error starting Task"
end
fs.delete( sTempPath )
else
sStatus="Error saving to "..sTempPath
end
redrawMenu()
end,
Tools = function()
bMenu = false
redrawText()
redrawMenu()
local bgColor = term.getBackgroundColor()
paintutils.drawFilledBox(2, 2, w - 1, h - 1, colors.gray)
term.setCursorPos(3, 3)
term.write('ShEdit Tools')
local scrollY = 0
local height = h - 8
local offset = 5
local selected = 1
local run = false
term.setTextColor(colors.lightGray)
term.setCursorPos(3, offset + height + 1)
term.write('Arrows to select. Enter to run. Ctrl to cancel.')
while true do
term.setBackgroundColor(colors.gray)
term.setTextColor(colors.lightGray)
local toWrite = '(' .. tostring(selected) .. '/' .. tostring(#toolsFuncs) .. ')'
term.setCursorPos(w - toWrite:len() - 1, 3)
term.write(toWrite)
term.setTextColor(colors.white)
for i = 1, height do
local index = i + scrollY
term.setTextColor(colors.white)
if selected == index then
term.setTextColor(keywordColor)
end
if index <= #toolsFuncs then
paintutils.drawLine(2, offset + i - 1, w - 1, offset + i - 1, color)
term.setCursorPos(3, offset + index - scrollY - 1)
if selected == index then
term.write('> ')
else
term.write(' ')
end
term.write(toolsFuncs[index][1])
end
end
local evt, arg1, arg2, arg3 = os.pullEvent()
local clampScroll = false
if evt == 'key' then
clampScroll = true
if arg1 == keys.up then
if selected == 1 then
selected = #toolsFuncs
else
selected = selected - 1
end
elseif arg1 == keys.down then
if selected == #toolsFuncs then
selected = 1
else
selected = selected + 1
end
elseif arg1 == keys.pageUp then
selected = math.max(1, math.min(#toolsFuncs, selected - height))
elseif arg1 == keys.pageDown then
selected = math.max(1, math.min(#toolsFuncs, selected + height))
elseif arg1 == keys.enter then
run = true
break
elseif arg1 == keys.leftCtrl then
break
else
clampScroll = false
end
elseif evt == 'mouse_click' then
if arg1 == 1 then
if arg2 > 1 and arg2 < w then
if arg3 >= offset and arg3 < offset + height then
local index = arg3 - offset + scrollY + 1
if index <= #toolsFuncs then
selected = index
run = true
break
end
end
end
end
elseif evt == 'mouse_scroll' then
if arg2 > 1 and arg2 < w then
if arg3 >= offset and arg3 < offset + height then
scrollY = math.max(0, math.min(#toolsFuncs - height, scrollY + arg1))
end
end
end
if clampScroll == true then
if selected > height + scrollY then
scrollY = selected - height
elseif selected <= scrollY then
scrollY = selected - 1
end
end
end
term.setBackgroundColor(bgColor)
redrawText()
bMenu = true
if run then
if not bReadOnly then
bMenu = false
redrawMenu()
toolsFuncs[selected][2]()
onUpdate()
redrawText()
bMenu = true
else
sStatus = 'File is read-only'
end
end
end,
Find = function()
term.setCursorPos(1, h)
term.clearLine()
term.setTextColor(highlightColor)
term.write('Find: ')
term.setTextColor(textColor)
local text = read(nil, findHistory)
table.insert(findHistory, text)
local fLine, fPos
local found = false
for i = y, #tLines do
local searchText = tLines[i]
if i == y then
searchText = searchText:sub(x + 1)
end
local searchPat = ''
local escapedChars = {
['%'] = true, ['.'] = true, ['('] = true, [')'] = true,
['%'] = true, ['+'] = true, ['-'] = true, ['*'] = true,
['?'] = true, ['['] = true, ['^'] = true, ['{{code}}#039;] = true
}
for i = 1, #text do
local char = text:sub(i, i)
if escapedChars[char] == true then
searchPat = searchPat .. '%'
end
searchPat = searchPat .. char
end
local pos = searchText:find(searchPat)
if pos then
fLine = i
fPos = pos
if i == y then
fPos = fPos + x
end
found = true
break
end
end
if not found then
sStatus = 'Found no matches'
else
setCursorMark(fPos, fLine)
setCursor(fPos + text:len(), fLine)
sStatus = 'Found a match on line ' .. tostring(fLine)
end
redrawText()
end,
Jump = function()
term.setCursorPos(1, h)
term.clearLine()
term.setTextColor(highlightColor)
term.write('Jump: ')
term.setTextColor(textColor)
local toJump = read(nil, jumpHistory)
table.insert(jumpHistory, toJump)
local num = tonumber(toJump)
if not num then
sStatus = 'Invalid line'
else
if num % 1 == 0 then
if num >= 1 and num <= #tLines then
setCursorMark(1, num)
sStatus = 'Successfully jumped to line ' .. toJump
else
sStatus = 'Line is not in the range 1 - ' .. tostring(#tLines)
end
else
sStatus = 'Line must be an integer'
end
end
redrawText()
end,
Copy = function()
vClipboard = getMarked()
local lines = 1
for i = 1, #vClipboard do
if vClipboard:sub(i, i) == '\n' then
lines = lines + 1
end
end
sStatus = 'Copied ' .. tostring(lines) .. ' lines'
end,
VPaste = function()
if not bReadOnly then
deleteMarked()
local lines = 1
for i = 1, #vClipboard do
local char = vClipboard:sub(i, i)
if char == '\n' then
lines = lines + 1
local sLine = tLines[y]
tLines[y] = string.sub(sLine,1,x-1)
table.insert( tLines, y+1, string.sub(sLine,x) )
x = 1
y = y + 1
else
tLines[y] = tLines[y]:sub(1, x - 1) .. char .. tLines[y]:sub(x)
x = x + 1
end
end
setCursorMark(x, y)
onUpdate()
redrawText()
sStatus = 'Pasted ' .. tostring(lines) .. ' lines'
else
sStatus = 'File is read-only'
end
end
}
local function doMenuItem( _n )
tMenuFuncs[tMenuItems[_n]]()
if bMenu then
bMenu = false
term.setCursorBlink( true )
end
redrawMenu()
end
-- Actual program functionality begins
load(sPath)
term.setBackgroundColor( bgColor )
term.clear()
term.setCursorPos(getCursorX(y), y)
term.setCursorBlink( true )
recomplete()
onUpdate()
redrawText()
redrawMenu()
local function acceptCompletion()
if nCompletion then
-- Append the completion
local sCompletion = tCompletions[ nCompletion ]
tLines[y] = tLines[y] .. sCompletion
onUpdate()
setCursorMark( x + string.len( sCompletion ), y )
end
end
local holdingShift = false
-- Handle input
while bRunning do
local sEvent, param, param2, param3 = os.pullEvent()
if sEvent == "key" then
local oldX, oldY = x, y
if param == keys.up then
local cursorSet = setCursorMark
if holdingShift then
cursorSet = setCursor
end
-- Up
if not bMenu then
if nCompletion then
-- Cycle completions
nCompletion = nCompletion - 1
if nCompletion < 1 then
nCompletion = #tCompletions
end
redrawLine(y)
elseif y > 1 then
-- Move cursor up
--[[cursorSet(
math.min( x, string.len( tLines[y - 1] ) + 1 ),
y - 1
)]]
local cx = getCursorX(y)
local pos = cxToPos(y - 1, cx)
cursorSet(pos, y - 1)
else
cursorSet(1, y)
end
end
elseif param == keys.down then
local cursorSet = setCursorMark
if holdingShift then
cursorSet = setCursor
end
-- Down
if not bMenu then
-- Move cursor down
if nCompletion then
-- Cycle completions
nCompletion = nCompletion + 1
if nCompletion > #tCompletions then
nCompletion = 1
end
redrawLine(y)
elseif y < #tLines then
-- Move cursor down
--[[cursorSet(
math.min( x, string.len( tLines[y + 1] ) + 1 ),
y + 1
)]]
local cx = getCursorX(y)
local pos = cxToPos(y + 1, cx)
cursorSet(pos, y + 1)
else
cursorSet(#tLines[y] + 1, y)
end
end
elseif param == keys.tab then
-- Tab
if not bMenu and not bReadOnly then
if nCompletion and x == string.len(tLines[y]) + 1 then
-- Accept autocomplete
acceptCompletion()
else
local msx, msy, mex, mey = getMarks()
for i = msy, mey do
local line = tLines[i]
if holdingShift then
-- Unindent line
if line:sub(1, 1) == '\t' then
line = line:sub(2)
end
else
-- Indent line
if markExists() then
line = '\t' .. line
else
line = line:sub(1, x - 1) .. '\t' .. line:sub(x)
end
end
tLines[i] = line
end
if holdingShift then
markX = math.max(1, markX - 1)
setCursor(math.max(1, x - 1), y)
else
markX = markX + 1
setCursor(x + 1, y)
end
onUpdate()
redrawText()
end
end
elseif param == keys.pageUp then
local cursorSet = setCursorMark
if holdingShift then
cursorSet = setCursor
end
-- Page Up
if not bMenu then
-- Move up a page
local newY
if y - (h - 1) >= 1 then
newY = y - (h - 1)
else
newY = 1
end
cursorSet(
math.min( x, string.len( tLines[newY] ) + 1 ),
newY
)
end
elseif param == keys.pageDown then
local cursorSet = setCursorMark
if holdingShift then
cursorSet = setCursor
end
-- Page Down
if not bMenu then
-- Move down a page
local newY
if y + (h - 1) <= #tLines then
newY = y + (h - 1)
else
newY = #tLines
end
local newX = math.min( x, string.len( tLines[newY] ) + 1 )
cursorSet( newX, newY )
end
elseif param == keys.home then
local cursorSet = setCursorMark
if holdingShift then
cursorSet = setCursor
end
-- Home
if not bMenu then
-- Move cursor to the beginning
if x > 1 then
cursorSet(1,y)
end
end
elseif param == keys["end"] then
local cursorSet = setCursorMark
if holdingShift then
cursorSet = setCursor
end
-- End
if not bMenu then
-- Move cursor to the end
local nLimit = string.len( tLines[y] ) + 1
if x < nLimit then
cursorSet( nLimit, y )
end
end
elseif param == keys.left then
local cursorSet = setCursorMark
if holdingShift then
cursorSet = setCursor
end
-- Left
if not bMenu then
if x > 1 then
-- Move cursor left
cursorSet( x - 1, y )
elseif x==1 and y>1 then
cursorSet( string.len( tLines[y-1] ) + 1, y - 1 )
end
else
-- Move menu left
nMenuItem = nMenuItem - 1
if nMenuItem < 1 then
nMenuItem = #tMenuItems
end
redrawMenu()
end
elseif param == keys.right then
local cursorSet = setCursorMark
if holdingShift then
cursorSet = setCursor
end
-- Right
if not bMenu then
local nLimit = string.len( tLines[y] ) + 1
if x < nLimit then
-- Move cursor right
cursorSet( x + 1, y )
elseif nCompletion and x == string.len(tLines[y]) + 1 then
-- Accept autocomplete
acceptCompletion()
elseif x==nLimit and y<#tLines then
-- Go to next line
cursorSet( 1, y + 1 )
end
else
-- Move menu right
nMenuItem = nMenuItem + 1
if nMenuItem > #tMenuItems then
nMenuItem = 1
end
redrawMenu()
end
elseif param == keys.delete then
-- Delete
if not bMenu and not bReadOnly then
if markExists() then
deleteMarked()
else
local nLimit = string.len( tLines[y] ) + 1
if x < nLimit then
local sLine = tLines[y]
tLines[y] = string.sub(sLine,1,x-1) .. string.sub(sLine,x+1)
elseif y<#tLines then
tLines[y] = tLines[y] .. tLines[y+1]
table.remove( tLines, y+1 )
end
end
onUpdate()
recomplete()
redrawText()
end
elseif param == keys.backspace then
-- Backspace
if not bMenu and not bReadOnly then
if markExists() then
deleteMarked()
else
if x > 1 then
-- Remove character
local sLine = tLines[y]
tLines[y] = string.sub(sLine,1,x-2) .. string.sub(sLine,x)
setCursorMark( x - 1, y )
elseif y > 1 then
-- Remove newline
local sPrevLen = string.len( tLines[y-1] )
tLines[y-1] = tLines[y-1] .. tLines[y]
table.remove( tLines, y )
setCursorMark( sPrevLen + 1, y - 1 )
end
end
onUpdate()
recomplete()
redrawText()
end
elseif param == keys.enter then
-- Enter
if not bMenu and not bReadOnly then
deleteMarked()
-- Newline
local sLine = tLines[y]
local _,tabs=string.find(sLine,"^[\t]+")
if not tabs then
tabs=0
end
tLines[y] = string.sub(sLine,1,x-1)
table.insert( tLines, y+1, string.rep('\t',tabs)..string.sub(sLine,x) )
onUpdate()
setCursorMark( tabs + 1, y + 1 )
redrawText()
elseif bMenu then
-- Menu selection
doMenuItem( nMenuItem )
end
elseif (param == keys.leftCtrl or param == keys.rightCtrl or param == keys.rightAlt) and not param2 then
-- Menu toggle
bMenu = not bMenu
if bMenu then
term.setCursorBlink( false )
else
term.setCursorBlink( true )
end
redrawMenu()
elseif param == 42 then
holdingShift = true
end
elseif sEvent == 'key_up' then
if param == 42 then
holdingShift = false
end
elseif sEvent == "char" then
if not bMenu and not bReadOnly then
deleteMarked()
-- Input text
local sLine = tLines[y]
tLines[y] = string.sub(sLine,1,x-1) .. param .. string.sub(sLine,x)
onUpdate()
setCursorMark( x + 1, y )
elseif bMenu then
-- Select menu items
for n,sMenuItem in ipairs( tMenuItems ) do
if string.lower(string.sub(sMenuItem,1,1)) == string.lower(param) then
doMenuItem( n )
break
end
end
end
elseif sEvent == "paste" then
if not bMenu and not bReadOnly then
-- Input text
--[[local sLine = tLines[y]
tLines[y] = string.sub(sLine,1,x-1) .. param .. string.sub(sLine,x)
onUpdate()
setCursorMark( x + string.len( param ), y )]]
--[[deleteMarked()
local newX, newY = x, y
for i = 1, #param do
local char = param:sub(i, i)
if char == '\n' then
local rest = tLines[newY]:sub(newX)
tLines[newY] = tLines[newY]:sub(1, newX - 1)
table.insert(tLines, rest, newY + 1)
newY = newY + 1
newX = 1
else
local line = tLines[newY]
line = line:sub(1, newX - 1) .. char .. line:sub(newX)
tLines[newY] = line
newX = newX + 1
end
end
onUpdate()
setCursorMark(newX, newY)]]
local oldvClipboard = vClipboard
vClipboard = param
tMenuFuncs.VPaste()
redrawMenu()
vClipboard = oldvClipboard
end
elseif sEvent == "mouse_click" then
local cursorSet = setCursorMark
if holdingShift then
cursorSet = setCursor
end
if not bMenu then
if param == 1 then
-- Left click
local cx,cy = param2, param3
if cy < h then
local tx, ty = cx + scrollX, cy + scrollY
local newX, newY
if ty <= #tLines then
newY = math.min(#tLines, math.max(ty, 1))
tx = cxToPos(ty, tx)
newX = math.min(tLines[newY]:len() + 1, math.max(1, tx))
else
newY = #tLines
newX = #tLines[newY] + 1
end
cursorSet(newX, newY)
end
end
end
elseif sEvent == "mouse_drag" then
if not bMenu then
if param == 1 then
-- Left click
local cx,cy = param2, param3
if cy < h then
local tx, ty = cx + scrollX, cy + scrollY
local newX, newY
if ty <= #tLines then
newY = math.min(#tLines, math.max(ty, 1))
tx = cxToPos(ty, tx)
newX = math.min(tLines[newY]:len() + 1, math.max(1, tx))
else
newY = #tLines
newX = #tLines[newY] + 1
end
setCursor(newX, newY)
end
end
end
elseif sEvent == "mouse_scroll" then
if not bMenu then
if param == -1 then
-- Scroll up
if scrollY > 0 then
-- Move cursor up
scrollY = scrollY - 1
redrawText()
end
elseif param == 1 then
-- Scroll down
local nMaxScroll = #tLines - (h-1)
if scrollY < nMaxScroll then
-- Move cursor down
scrollY = scrollY + 1
redrawText()
end
end
end
elseif sEvent == "term_resize" then
w,h = term.getSize()
setCursor( x, y )
redrawMenu()
redrawText()
end
end
-- Cleanup
term.clear()
term.setCursorBlink( false )
term.setCursorPos( 1, 1 )