Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- ed v0.3
- by KinoftheFlames
- ed is an IDE intended to replace the native file editor "edit".
- The goal of this program is to make editting lua programs for ComputerCraft as fluid as possible.
- For more info: http://www.computercraft.info/forums2/index.php?/topic/4270-wip-in-game-ideedit-program/
- CONTROLS:
- F3 - Save
- F4 - Exit
- F5 - Run
- F6 - Run with arguments
- CHANGELOG for v0.3:
- I've added some more customizable options for the UI, which you can change by editting the settings variables at the top of ed's file.
- - Added insertion/overwrite toggle
- - Added status bar (optional) with the following information (which is all optional):
- -- Current line number
- -- Total line count (off by default)
- -- Insert/overwrite toggle display
- - Added debugging. The file no longer needs to be force-saved to run it, and if there are any run-time errors ed will move the cursor to the error line and display the error.
- - You can now run ed from ed as recursively as you'd like and everything should work properly (yo dawg!)
- - Added auto-tabbing
- - Trying to edit files in non-existant directories now creates those directories
- Future features:
- - Find and replace
- ]]
- --argument handling
- local arg = { ... }
- if #arg == 0 then --verify argument exists
- print("Syntax: editor <path>")
- error()
- end
- if fs.isDir(arg[1]) then --verift not a directory
- print("Error: cannot edit a directory as a file")
- error()
- end
- --debug
- local bDebug = true --display debugging info if true
- local sDebugText = "" --debug text shown if debugging is on
- --constants
- local SCREEN_ORIGIN = 1
- local SCREEN_WIDTH, SCREEN_HEIGHT = term.getSize()
- local TIMER_INTERVAL = 300.0 --in seconds (DO NOT SET TO 0, that would be infinite)
- --settings
- local bInsert = true --insert text if true, override text if false
- local bShowLineNum = true --toggle visibility of line numbers
- local bShowLineNumSep = true --toggle visibility of line numbers seperator
- local bShowStatusBar = true --toggle visibility of the line of information at the bottom of the screen
- --toggle visibility of individual status information
- local bStatus_lineNum = true
- local bStatus_lineCount = false
- local bStatus_insert = true
- local bShowMessages = true --toggle displaying of messages at the bottom of the screen
- local nMinLineDigitsVisible = 1 --[POS] defines how much area for the line numbers is displayed at minimum (program will expand space as needed)
- local nSpacesPerTab = 3 --[POS] defines how many visual spaces are in a tab
- --flags
- local bUnsavedChanges = false --tracks whether the file in the editor has been changed since last saved, helps with exit before saving confirmation
- local bConfirmingExit = false --if the user tries to exit without saving, this requires them to try again before succeeding
- --variables
- local bRunning = true --false when the program exits
- local sFilepath = arg[1] --path to the file being editted
- local tFile = { } --table of lines for the file being editted
- local xMin, yMin = SCREEN_ORIGIN, SCREEN_ORIGIN --min values for file display
- local xMax, yMax = SCREEN_WIDTH, SCREEN_HEIGHT --max values for file display
- local xFile, yFile = 1, 1 --the location of the cursor in the text file
- local xScroll, yScroll = 0, 0 --amount file is scrolled on screen
- local sStatus = "" --text displayed for the status bar at the bottom of the screen
- local sMessage = "" --message displayed at the bottom of the screen until next input
- local sArguments = "" --stores the last arguments used, bringing them up again when run w/ arguments is called
- local sError = "" --stores error information from failed program execution for debugging
- local tTimer --timer for main loop and time-based events
- ------------------------
- -- FILE HANDLING --
- ------------------------
- --load a file from hdd into editor
- function load()
- --error handling file
- if fs.exists(sFilepath) then
- --load file
- local file = fs.open(sFilepath, "r")
- while true do
- local line = file.readLine()
- if line == nil then --if reached end of file
- break end
- table.insert(tFile, line)
- end
- file.close()
- else
- table.insert(tFile, "")
- end
- end
- --save editor's text to a file
- function save()
- sMessage = "SAVING..."
- draw()
- if bUnsavedChanges then --only save if needed (saves on CPU time)
- --create directory if it doesn't exist
- local dir = string.sub(sFilepath, 1, #sFilepath - #fs.getName(sFilepath))
- if not fs.exists(dir) then
- fs.makeDir(dir) end
- --write to file
- local file = fs.open(sFilepath, "w")
- for i,line in ipairs(tFile) do
- file.writeLine(line) end
- file.close()
- end
- sMessage = "SAVED to " .. sFilepath
- bUnsavedChanges = false
- end
- ------------------------
- -- VISUAL --
- ------------------------
- --scrolls the screen with the cursor
- function scroll()
- alignCursor()
- local x,y = term.getCursorPos()
- if y > yMax then
- yScroll = yScroll + (y - yMax)
- elseif y < yMin then
- yScroll = yScroll - (yMin - y)
- end
- if x > xMax then
- xScroll = xScroll + (x - xMax)
- elseif x < xMin then
- xScroll = xScroll - (xMin - x)
- end
- end
- --brings the visual cursor to the location of the cursor in file
- function alignCursor()
- local charsLeftOfCursor = string.sub(tFile[yFile], 1, xFile-1) --chars left of cursor
- local dummy, numTabs = string.gsub(charsLeftOfCursor, "\t", "\t") --gets the number of tabs
- local x = xMin + (xFile - 1) - xScroll + numTabs * (nSpacesPerTab - 1)
- local y = yMin + (yFile - 1) - yScroll
- term.setCursorPos(x, y)
- end
- --moves cursor around
- function shiftCursor(xShift, yShift)
- --format input for error handling
- if yFile + yShift < 1 then
- yShift = -(yFile - 1)
- elseif yFile + yShift > #tFile then
- yShift = #tFile - yFile
- end
- --handle left/right movement
- if xFile == 1 and xShift < 0 then --if at start of line and moving left, move to end of above line (if exists)
- if yFile ~= 1 then
- xFile = #tFile[yFile-1] + 1
- yFile = yFile - 1
- end
- elseif xFile > #tFile[yFile] and xShift > 0 then --if at end of line and moving right, move to start of next line (if exists)
- if yFile ~= #tFile then
- xFile = 1
- yFile = yFile + 1
- end
- else --horizontal only movement
- xFile = xFile + xShift--normal move
- end
- --handle up/down movement
- if yFile == 1 and yShift < 0 then --if at first line and moving up, move to start of line
- xFile = 1
- elseif yFile == #tFile and yShift > 0 then --if at last line and moving down, move to end of line
- xFile = #tFile[yFile] + 1
- else --vertical only movement
- yFile = yFile + yShift --normal move
- --if the cursor is past the end of the last character after having moved, move it to the last character
- if xFile > #tFile[yFile] then
- xFile = 1 + #tFile[yFile]
- end
- end
- end
- --move cursor to the start of the line
- function shiftCursorHome()
- xFile = 1
- end
- --move cursor to the end of the line (end of text)
- function shiftCursorEnd()
- xFile = 1 + #tFile[yFile]
- end
- --adjusts borders of editable area depending on settings and situational factors
- function applyBoundrySettings()
- if bShowLineNum then
- local chars = #tostring(#tFile) --characters in line num area = num of digits of last line
- if nMinLineDigitsVisible > chars then --unless there is a minimum character count to enforce which is greater
- chars = nMinLineDigitsVisible end
- xMin = chars + 2 --adjust boundry for editable area
- else
- xMin = SCREEN_ORIGIN end
- if bShowStatusBar then
- yMax = SCREEN_HEIGHT - 1
- else
- yMax = SCREEN_HEIGHT end
- end
- ------------------------
- -- DRAWING --
- ------------------------
- --redraws entire screen
- function draw()
- --clear screen
- term.clear()
- --draw line numbers
- if bShowLineNum then
- drawLineNum() end
- --draw file text
- for i=yMin,yMax do
- if tFile[i+yScroll] ~= nil then --if haven't reached end of file
- term.setCursorPos(xMin, i)
- local modifiedLine = string.gsub(tFile[i+yScroll], "\t", string.rep(" ", nSpacesPerTab)) --replace visual line with defined number of spaces
- term.write(string.sub(modifiedLine, 1 + xScroll, xMax - xMin + 1 + xScroll)) --only draw visible text
- end
- end
- --draw status bar
- if bShowStatusBar then
- drawStatusBar() end
- --draw message
- if bShowMessages then
- drawMessage() end
- --draw debug
- if bDebug then
- drawDebug(sDebugText) end
- --place visual cursor in correct position
- alignCursor()
- end
- --draws line numbers and seperator
- function drawLineNum()
- applyBoundrySettings()
- local chars = #tostring(#tFile) --characters in line num area = num of digits of last line
- if nMinLineDigitsVisible > chars then --unless there is a minimum character count to enforce which is greater
- chars = nMinLineDigitsVisible end
- for i=yMin,yMax do
- local lineNum = i+yScroll --line number of file line being drawn
- if tFile[lineNum] ~= nil then --don't draw line numbers for lines that dont exists in the file
- local numSpaces = chars - #tostring(lineNum) --spaces required to right-align numbers
- --draw finally
- term.setCursorPos(SCREEN_ORIGIN, i)
- term.write(string.rep(" ", numSpaces)) --spaces before line number
- term.write(tostring(lineNum)) --line number
- if bShowLineNumSep then
- term.write("|") end --seperator --TODO: add customizable line seperator (single char)
- end
- end
- --sDebugText = sDebugText .. " xMin:"..xMin
- end
- --draw bar with information at the bottom of the screen
- function drawStatusBar()
- sStatus = "--< " --paint me like one of your french girls
- local charsLeft = SCREEN_WIDTH - 8 --characters available to print
- local nextAdd = "" --next info to add to status bar
- --line cursor is on
- if bStatus_lineNum then
- nextAdd = "Ln " .. yFile --cur line
- if bStatus_lineCount then --line count
- nextAdd = nextAdd .. "/" .. #tFile end
- charsLeft = addStatus(nextAdd, charsLeft)
- elseif bStatus_lineCount then
- nextAdd = #tFile .. " lines" --line count
- charsLeft = addStatus(nextAdd, charsLeft)
- end
- --INS for insert text, OVR for overwrite text
- if bStatus_insert then
- if bInsert then
- nextAdd = "INS"
- else
- nextAdd = "OVR" end
- charsLeft = addStatus(nextAdd, charsLeft)
- end
- sStatus = sStatus .. string.rep(" ", charsLeft) --filler
- sStatus = sStatus .. " >--" --paint me like one of your french girls
- --draw the bar
- term.setCursorPos(SCREEN_ORIGIN,SCREEN_HEIGHT)
- term.write(sStatus)
- end
- --push next status on to the status text
- function addStatus(sText, nCharsLeft)
- if nCharsLeft >= #sText + 2 then --if status can fit wholly on line
- if #sStatus <= 4 then --if first status added
- sStatus = sStatus .. sText
- nCharsLeft = nCharsLeft - #sText --reduce remaining space for statuses
- else
- sStatus = sStatus .. " " .. sText
- nCharsLeft = nCharsLeft - (#sText + 2) --reduce remaining space for statuses
- end
- end
- return nCharsLeft
- end
- --draw general message at the bottom of the screen for one loop
- function drawMessage()
- if sMessage == "" then
- return end
- --truncates message
- sMessage = string.sub(sMessage, 1, SCREEN_WIDTH - 8)
- --add dashes on the side to draw attention
- local fillerDrawn = ((SCREEN_WIDTH - #sMessage)/2)-2 --num dashes on one side
- if fillerDrawn < 2 then
- fillerDrawn = 2 end
- local printedText = string.rep("-", fillerDrawn) .. "< " .. sMessage .. " >" .. string.rep("-", fillerDrawn + 1)
- --display message
- term.setCursorPos(SCREEN_ORIGIN,SCREEN_HEIGHT) --bottom left
- term.write(printedText)
- end
- --display debug info in bottom right
- function drawDebug(sText)
- term.setCursorPos(SCREEN_WIDTH + 1 - #sText, SCREEN_HEIGHT) --set cursor at bottom rightmost while still showing all text
- term.write(sText)
- end
- --draw menu
- function drawMenu()
- drawMenu_bg()
- os.pullEvent()
- end
- --the background for the menu
- function drawMenu_bg()
- local line = ""
- --draw top line
- line = "o"..string.rep("-", SCREEN_WIDTH-2).."o"
- term.setCursorPos(SCREEN_ORIGIN, SCREEN_ORIGIN)
- term.write(line)
- --draw inbetween lines
- for i=SCREEN_ORIGIN+1,SCREEN_HEIGHT-1 do
- line = "|"..string.rep(" ", SCREEN_WIDTH-2).."|"
- term.setCursorPos(SCREEN_ORIGIN, i)
- term.write(line)
- end
- --draw bottom line
- line = "O"..string.rep("-", SCREEN_WIDTH-2).."O"
- term.setCursorPos(SCREEN_ORIGIN, SCREEN_HEIGHT)
- term.write(line)
- end
- ------------------------
- -- DEBUGGING --
- ------------------------
- --run the program being edited
- function run(bArguments)
- local tArguments = {}
- --get arugments for program execution
- if bArguments then
- --draw seperator dashes below argument input
- term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN+1)
- term.write(string.rep("-", SCREEN_WIDTH))
- --clear top line and get arguments
- term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN)
- term.clearLine()
- term.write("Arguments: ")
- sArguments = read(nil, sArguments)
- --format arguments (stolen from shell)
- for match in string.gmatch(sArguments, "[^ \t]+") do --grabs each argument, space/tab delimitted
- table.insert(tArguments, match)
- end
- end
- --save() --save so its running the program in the editor
- term.clear()
- term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN)
- --local progSuccess = shell.run(sFilepath, unpack(tArguments)) --OLD WAY (calling without debugging)
- sError = ""
- local progSuccess = runCatchErrors(table.concat(tFile, "\n"), unpack(tArguments))
- --formats error info
- if sError and sError ~= "" then
- local s1, s2 = string.find(sError, "%d+:") --the editor
- local errorStart, v = string.find(string.sub(sError, s2), "%d+:") --the executed code
- if errorStart ~= nil then
- sError = string.sub(string.sub(sError, s2), errorStart) --removed editor pre-error text
- else
- sError = string.sub(sError, s1) --removed executed pre-error text (name and colon)
- end
- end
- --display whether program succeeded or failed, then prompt to continue
- if progSuccess then
- sMessage = "SUCCESS - Press any key"
- else
- sMessage = "FAILED - Press any key" end
- drawMessage()
- while true do
- e, p1 = os.pullEvent()
- if e == "timer" and p1 == tTimer then
- tTimer = os.startTimer(TIMER_INTERVAL) --reset timer
- elseif e == "key" then
- break end
- end
- os.sleep(0) --if the key pressed produces a "char" event, this prevents it from going to the next input loop
- initializeSystem() --reset program systerm-oriented settings (in case they've been changed)
- if sError and sError ~= "" then --moves cursor to error line and display error message
- local v1, v2 = string.find(sError, "%d+") --get error line
- local errorLine = tonumber(string.sub(sError, v1, v2))
- xFile = 1
- yFile = errorLine
- sMessage = sError
- else --no error
- sMessage = "" end
- end
- --specialized function to run a file and catch the runtime errors
- --Courtesy of faubiguy
- function runCatchErrors( sProgram, ... ) --sProgram is a string containing the current text. Any other arguments are arguments to the program.
- local tArgs = { ... }
- local fnFile, err = loadstring( sProgram )
- if fnFile then
- local tEnv = {["shell"] = shell}
- setmetatable( tEnv, { __index = _G } )
- setfenv( fnFile, tEnv )
- local ok, err = pcall( function()
- fnFile( unpack( tArgs ) )
- end )
- if not ok then
- if err and err ~= "" then
- print( err )
- sError = err -- sError is the variable where you want the error
- end
- return false
- end
- sError = "" -- error value set to "" on successful execution
- return true
- end
- if err and err ~= "" then
- print( err )
- sError = err -- Again replace sError with the variable where you want the error
- end
- return false
- end
- ------------------------
- -- TYPING --
- ------------------------
- --inserts characters the user types in
- function insertText(sText)
- local removeChar = 0
- if not bInsert then --overwrite the next char instead of inserting if settings say so
- removeChar = 1 end
- --insert/overwrite text
- tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. sText .. string.sub(tFile[yFile], xFile + removeChar)
- shiftCursor(#sText, 0)
- bUnsavedChanges = true
- end
- --key press: enter
- function key_enter()
- if xFile <= #tFile[yFile] then --if cursor is between characters
- table.insert(tFile, yFile+1, string.sub(tFile[yFile], xFile)) --move remainder of characters to a new line
- tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) --and delete the moved characters from the previous line
- else --cursor at the end of the line
- table.insert(tFile, yFile+1, "")
- end
- --move cursor to begining of line and down
- shiftCursorHome()
- shiftCursor(0,1)
- --auto insert tabs equal to number on last line
- local v, numTabs = string.find(tFile[yFile-1], "[\t]+")
- if v ~= 1 or numTabs == nil then --tabs found, but not at the start of the line
- numTabs = 0 end
- insertText(string.rep("\t", numTabs))
- bUnsavedChanges = true
- end
- --key press: backspace
- function key_backspace()
- if not (yFile == 1 and xFile == 1) then --if not at the first character in the file
- if xFile == 1 then --if at the start of a line, concatenate line above with this line
- shiftCursor(0,-1)
- shiftCursorEnd()
- tFile[yFile] = tFile[yFile] .. tFile[yFile+1] --concat lines
- table.remove(tFile, yFile+1) --remove second line
- else --otherwise just delete one character
- shiftCursor(-1,0)
- tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. string.sub(tFile[yFile], xFile+1)
- end
- bUnsavedChanges = true
- end
- end
- --key press: delete
- function key_delete()
- if not (yFile == #tFile and xFile == #tFile[yFile] + 1) then --if not at the last character in the file
- if xFile > #tFile[yFile] then --if cursor is at the end of the line, concatenate with line below
- tFile[yFile] = tFile[yFile] .. tFile[yFile+1] --concat lines
- table.remove(tFile, yFile+1) --remove second line
- else --otherwise just delete one character
- tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. string.sub(tFile[yFile], xFile+1)
- end
- bUnsavedChanges = true
- end
- end
- ------------------------
- -- OTHER --
- ------------------------
- --top level menu accessed via F1
- function menu()
- drawMenu()
- end
- ------------------------
- -- CORE --
- ------------------------
- --initializes system settings (this is called at startup and after program execution)
- function initializeSystem()
- term.setCursorBlink(true)
- alignCursor()
- end
- --intialization
- function intialize()
- load() --load file
- --setup screen
- applyBoundrySettings()
- draw()
- initializeSystem()
- end
- --handles user input
- function input()
- --wait for key input
- local e, p1 = os.pullEvent()
- --timer input
- if e == "timer" and p1 == tTimer then --main loop interval timer
- tick()
- else --user input (IMPORTANT distinction)
- sMessage = "" --clear message
- --EXIT if confirmed, otherwise reset confirmation flag
- if bConfirmingExit == true and e == "key" and p1 == keys.f4 then
- exit()
- else
- bConfirmingExit = false end
- --character input
- if e == "char" then
- insertText(p1)
- --non-character, key input
- elseif e == "key" then
- --DONT USE F2, F10, F11, ALT, ESC
- --functions
- if p1 == keys.f1 then --menu
- menu()
- elseif p1 == keys.f3 then --save
- save()
- elseif p1 == keys.f4 then --exit
- exit()
- elseif p1 == keys.f5 then --run program in editor
- run(false)
- elseif p1 == keys.f6 then --run with arguments
- run(true)
- --movement
- elseif p1 == keys.up then --up
- shiftCursor(0, -1)
- elseif p1 == keys.down then --down
- shiftCursor(0, 1)
- elseif p1 == keys.left then --left
- shiftCursor(-1, 0)
- elseif p1 == keys.right then --right
- shiftCursor(1, 0)
- elseif p1 == keys.home then --home
- shiftCursorHome()
- elseif p1 == keys["end"] then --end
- shiftCursorEnd()
- elseif p1 == keys.pageUp then --pasge up
- shiftCursor(0, -(yMax-yMin))
- elseif p1 == keys.pageDown then --page down
- shiftCursor(0, yMax-yMin)
- --removing characters
- elseif p1 == keys.backspace then --backspace
- key_backspace()
- elseif p1 == keys.delete then --delete
- key_delete()
- --adding whitespace
- elseif p1 == keys.enter then --enter
- key_enter()
- elseif p1 == keys.tab then --tab
- insertText("\t")
- bUnsavedChanges = true
- --insert
- elseif p1 == keys.insert then
- bInsert = not bInsert
- end
- end
- end
- end
- --main timer interval actions
- function tick()
- --do interval stuff
- --insertText("HI")
- --sMessage = "TIMER HIT!"
- tTimer = os.startTimer(TIMER_INTERVAL) --reset timer
- end
- --main loop
- function mainLoop()
- tTimer = os.startTimer(TIMER_INTERVAL) --start interval timer
- while bRunning do
- sDebugText = "" --clear debug info
- input() --get input and respond
- --debug info
- local x,y = term.getCursorPos()
- --sDebugText = sDebugText .. " Running:" ..tostring(bRunning)
- --sDebugText = sDebugText .. " File:"..xFile..","..yFile .." Scrn:"..x..","..y
- scroll() --shift screen to show cursor
- draw() --update screen
- end
- end
- --exit program
- function exit()
- if bUnsavedChanges and not bConfirmingExit then --if there are unsaved changes, and this warning hasnt displayed yet
- sMessage = "UNSAVED CHANGES - Press F4 to exit"
- bConfirmingExit = true
- else
- bRunning = false end
- end
- --have the program exit cleanly and tidily
- function exitCleanup()
- term.clear()
- term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN)
- end
- intialize()
- mainLoop()
- exitCleanup()
Advertisement
Add Comment
Please, Sign In to add comment