KinoftheFlames

Untitled

Sep 23rd, 2012
92
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 18.89 KB | None | 0 0
  1. --[[
  2.  
  3. ed v0.3
  4. by KinoftheFlames
  5.  
  6. ed is an IDE intended to replace the native file editor "edit".
  7. The goal of this program is to make editting lua programs for ComputerCraft as fluid as possible.
  8.  
  9. For more info: http://www.computercraft.info/forums2/index.php?/topic/4270-wip-in-game-ideedit-program/
  10.  
  11. CONTROLS:
  12. F3 - Save
  13. F4 - Exit
  14. F5 - Run
  15. F6 - Run with arguments
  16.  
  17. CHANGELOG for v0.3:
  18. 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.
  19.  
  20. - Added status bar (optional) with the following information (which is all optional):
  21.    -- Current line number
  22.    -- Total line count (off by default)
  23.    -- Insert/overwrite toggle display
  24. - 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.
  25.    
  26. ]]
  27.  
  28. --get filepath
  29. local arg = { ... }
  30. if #arg == 0 then
  31.     print("Syntax: editor <path>")
  32.     error()
  33. end
  34.  
  35. --debug
  36. local bDebug = true --display debugging info if true
  37. local sDebugText = "" --debug text shown if debugging is on
  38.  
  39. --constants
  40. local SCREEN_ORIGIN = 1
  41. local SCREEN_WIDTH, SCREEN_HEIGHT = term.getSize()
  42.  
  43. --settings
  44. local bInsert = true --insert text if true, override text if false
  45. local bShowLineNum = true --toggle visibility of line numbers
  46. local bShowLineNumSep = true --toggle visibility of line numbers seperator
  47. local bShowStatusBar = true --toggle visibility of the line of information at the bottom of the screen
  48.     --toggle visibility of individual status information
  49.     local bStatus_lineNum = true
  50.     local bStatus_lineCount = false
  51.     local bStatus_insert = true
  52. local bShowMessages = true --toggle displaying of messages at the bottom of the screen
  53. local nMinLineDigitsVisible = 1 --[POS] defines how much area for the line numbers is displayed at minimum (program will expand space as needed)
  54. local nSpacesPerTab = 3 --[POS] defines how many visual spaces are in a tab
  55.  
  56. --flags
  57. local bUnsavedChanges = false --tracks whether the file in the editor has been changed since last saved, helps with exit before saving confirmation
  58. local bConfirmingExit = false --if the user tries to exit without saving, this requires them to try again before succeeding
  59.  
  60. --variables
  61. local bRunning = true --false when the program exits
  62. local sFilepath = arg[1] --path to the file being editted
  63. local tFile = { } --table of lines for the file being editted
  64. local xMin, yMin = SCREEN_ORIGIN, SCREEN_ORIGIN --min values for file display
  65. local xMax, yMax = SCREEN_WIDTH, SCREEN_HEIGHT --max values for file display
  66. local xFile, yFile = 1, 1 --the location of the cursor in the text file
  67. local xScroll, yScroll = 0, 0 --amount file is scrolled on screen
  68. local sStatus = "" --text displayed for the status bar at the bottom of the screen
  69. local sMessage = "" --message displayed at the bottom of the screen until next input
  70. local sArguments = "" --stores the last arguments used, bringing them up again when run w/ arguments is called
  71. local sError = "" --stores error information from failed program execution for debugging
  72.  
  73.  
  74.  
  75. ------------------------
  76. --    FILE HANDLING   --
  77. ------------------------
  78.  
  79. --load a file from hdd into editor
  80. function load()
  81.     --error handling file
  82.     if fs.exists(sFilepath) then
  83.         if fs.isDir(sFilepath) then
  84.             print("Cannot read directory as a file.")
  85.             error() --exit program
  86.         else
  87.             --load file
  88.             local file = fs.open(sFilepath, "r")
  89.             while true do
  90.                 local line = file.readLine()
  91.                 if line == nil then --if reached end of file
  92.                     break end
  93.                
  94.                 table.insert(tFile, line)
  95.             end
  96.             file.close()
  97.         end
  98.     else
  99.         table.insert(tFile, "")
  100.     end
  101. end
  102.  
  103. --save editor's text to a file
  104. function save()
  105.     sMessage = "SAVING..."
  106.     draw()
  107.    
  108.     if bUnsavedChanges then --only save if needed (saves on CPU time)
  109.         file = fs.open(sFilepath, "w")
  110.         for i,line in ipairs(tFile) do
  111.             file.writeLine(line) end
  112.         file.close()
  113.     end
  114.    
  115.     sMessage = "SAVED"
  116.     bUnsavedChanges = false
  117. end
  118.  
  119.  
  120.  
  121. ------------------------
  122. --       VISUAL       --
  123. ------------------------
  124.  
  125. --scrolls the screen with the cursor
  126. function scroll()
  127.     alignCursor()
  128.     local x,y = term.getCursorPos()
  129.    
  130.     if y > yMax then
  131.         yScroll = yScroll + (y - yMax)
  132.     elseif y < yMin then
  133.         yScroll = yScroll - (yMin - y)
  134.     end
  135.     if x > xMax then
  136.         xScroll = xScroll + (x - xMax)
  137.     elseif x < xMin then
  138.         xScroll = xScroll - (xMin - x)
  139.     end
  140. end
  141.  
  142. --brings the visual cursor to the location of the cursor in file
  143. function alignCursor()
  144.     local charsLeftOfCursor = string.sub(tFile[yFile], 1, xFile-1) --chars left of cursor
  145.     local dummy, numTabs = string.gsub(charsLeftOfCursor, "\t", "\t") --gets the number of tabs
  146.    
  147.     local x = xMin + (xFile - 1) - xScroll + numTabs * (nSpacesPerTab - 1)
  148.     local y = yMin + (yFile - 1) - yScroll
  149.    
  150.     term.setCursorPos(x, y)
  151. end
  152.  
  153. --moves cursor around
  154. function shiftCursor(xShift, yShift)
  155.     --format input for error handling
  156.     if yFile + yShift < 1 then
  157.         yShift = -(yFile - 1)
  158.     elseif yFile + yShift > #tFile then
  159.         yShift = #tFile - yFile
  160.     end
  161.        
  162.    
  163.     --handle left/right movement
  164.     if xFile == 1 and xShift < 0 then --if at start of line and moving left, move to end of above line (if exists)
  165.         if yFile ~= 1 then
  166.             xFile = #tFile[yFile-1] + 1
  167.             yFile = yFile - 1
  168.         end
  169.     elseif xFile > #tFile[yFile] and xShift > 0 then --if at end of line and moving right, move to start of next line (if exists)
  170.         if yFile ~= #tFile then
  171.             xFile = 1
  172.             yFile = yFile + 1
  173.         end
  174.     else --horizontal only movement
  175.         xFile = xFile + xShift--normal move
  176.     end
  177.    
  178.     --handle up/down movement
  179.     if yFile == 1 and yShift < 0 then --if at first line and moving up, move to start of line
  180.         xFile = 1
  181.     elseif yFile == #tFile and yShift > 0 then --if at last line and moving down, move to end of line
  182.         xFile = #tFile[yFile] + 1
  183.     else --vertical only movement
  184.         yFile = yFile + yShift --normal move
  185.        
  186.         --if the cursor is past the end of the last character after having moved, move it to the last character
  187.         if xFile > #tFile[yFile] then
  188.             xFile = 1 + #tFile[yFile]
  189.         end
  190.     end
  191. end
  192.  
  193. --move cursor to the start of the line
  194. function shiftCursorHome()
  195.     xFile = 1
  196. end
  197.  
  198. --move cursor to the end of the line (end of text)
  199. function shiftCursorEnd()
  200.     xFile = 1 + #tFile[yFile]
  201. end
  202.  
  203. --adjusts borders of editable area depending on settings and situational factors
  204. function applyBoundrySettings()
  205.     if bShowLineNum then
  206.         local chars = #tostring(#tFile) --characters in line num area = num of digits of last line
  207.         if nMinLineDigitsVisible > chars then --unless there is a minimum character count to enforce which is greater
  208.             chars = nMinLineDigitsVisible end
  209.         xMin = chars + 2 --adjust boundry for editable area
  210.     else
  211.         xMin = SCREEN_ORIGIN end
  212.    
  213.     if bShowStatusBar then
  214.         yMax = SCREEN_HEIGHT - 1
  215.     else
  216.         yMax = SCREEN_HEIGHT end
  217. end
  218.  
  219.  
  220.  
  221. ------------------------
  222. --      DRAWING       --
  223. ------------------------
  224.  
  225. --redraws entire screen
  226. function draw()
  227.     --clear screen
  228.     term.clear()
  229.    
  230.     --draw line numbers
  231.     if bShowLineNum then
  232.         drawLineNum() end
  233.    
  234.     --draw file text
  235.     for i=yMin,yMax do
  236.         if tFile[i+yScroll] ~= nil then --if haven't reached end of file
  237.             term.setCursorPos(xMin, i)
  238.             local modifiedLine = string.gsub(tFile[i+yScroll], "\t", string.rep(" ", nSpacesPerTab)) --replace visual line with defined number of spaces
  239.             term.write(string.sub(modifiedLine, 1 + xScroll, xMax - xMin + 1 + xScroll)) --only draw visible text
  240.         end
  241.     end
  242.    
  243.     --draw status bar
  244.     if bShowStatusBar then
  245.         drawStatusBar() end
  246.    
  247.     --draw message
  248.     if bShowMessages then
  249.         drawMessage() end
  250.    
  251.     --draw debug
  252.     if bDebug then
  253.         drawDebug(sDebugText) end
  254.    
  255.     --place visual cursor in correct position
  256.     alignCursor()
  257. end
  258.  
  259. --draws line numbers and seperator
  260. function drawLineNum()
  261.     applyBoundrySettings()
  262.     local chars = #tostring(#tFile) --characters in line num area = num of digits of last line
  263.     if nMinLineDigitsVisible > chars then --unless there is a minimum character count to enforce which is greater
  264.         chars = nMinLineDigitsVisible end
  265.    
  266.     for i=yMin,yMax do
  267.         local lineNum = i+yScroll --line number of file line being drawn
  268.         if tFile[lineNum] ~= nil then --don't draw line numbers for lines that dont exists in the file
  269.             local numSpaces = chars - #tostring(lineNum) --spaces required to right-align numbers
  270.            
  271.             --draw finally
  272.             term.setCursorPos(SCREEN_ORIGIN, i)
  273.             term.write(string.rep(" ", numSpaces)) --spaces before line number
  274.             term.write(tostring(lineNum)) --line number
  275.             if bShowLineNumSep then
  276.                 term.write("|") end --seperator --TODO: add customizable line seperator (single char)
  277.         end
  278.     end
  279.    
  280.     --sDebugText = sDebugText .. " xMin:"..xMin
  281. end
  282.  
  283. --draw bar with information at the bottom of the screen
  284. function drawStatusBar()
  285.     sStatus = "--< " --paint me like one of your french girls
  286.    
  287.     local charsLeft = SCREEN_WIDTH - 8 --characters available to print
  288.     local nextAdd = "" --next info to add to status bar
  289.    
  290.     --line cursor is on
  291.     if bStatus_lineNum then
  292.         nextAdd = "Ln " .. yFile --cur line
  293.         if bStatus_lineCount then --line count
  294.             nextAdd = nextAdd .. "/" .. #tFile end
  295.        
  296.         charsLeft = addStatus(nextAdd, charsLeft)
  297.     elseif bStatus_lineCount then
  298.         nextAdd = #tFile .. " lines" --line count
  299.         charsLeft = addStatus(nextAdd, charsLeft)
  300.     end
  301.    
  302.     --INS for insert text, OVR for overwrite text
  303.     if bStatus_insert then
  304.         if bInsert then
  305.             nextAdd = "INS"
  306.         else
  307.             nextAdd = "OVR" end
  308.         charsLeft = addStatus(nextAdd, charsLeft)
  309.     end
  310.    
  311.     sStatus = sStatus .. string.rep(" ", charsLeft) --filler
  312.     sStatus = sStatus .. " >--" --paint me like one of your french girls
  313.    
  314.     --draw the bar
  315.     term.setCursorPos(SCREEN_ORIGIN,SCREEN_HEIGHT)
  316.     term.write(sStatus)
  317. end
  318.  
  319. --push next status on to the status text
  320. function addStatus(sText, nCharsLeft)
  321.     if nCharsLeft >= #sText + 2 then --if status can fit wholly on line
  322.         if #sStatus <= 4 then --if first status added
  323.             sStatus = sStatus .. sText
  324.             nCharsLeft = nCharsLeft - #sText --reduce remaining space for statuses
  325.         else
  326.             sStatus = sStatus .. "  " .. sText
  327.             nCharsLeft = nCharsLeft - (#sText + 2) --reduce remaining space for statuses
  328.         end
  329.     end
  330.     return nCharsLeft
  331. end
  332.  
  333. --draw general message at the bottom of the screen for one loop
  334. function drawMessage()
  335.     if sMessage == "" then
  336.         return end
  337.    
  338.     --truncates message
  339.     sMessage = string.sub(sMessage, 1, SCREEN_WIDTH - 8)
  340.    
  341.     --add dashes on the side to draw attention
  342.     local fillerDrawn = ((SCREEN_WIDTH - #sMessage)/2)-2 --num dashes on one side
  343.    
  344.     if fillerDrawn < 2 then
  345.         fillerDrawn = 2 end
  346.    
  347.     sMessage = string.rep("-", fillerDrawn) .. "< " .. sMessage .. " >" .. string.rep("-", fillerDrawn + 1)
  348.    
  349.     --display message
  350.     term.setCursorPos(SCREEN_ORIGIN,SCREEN_HEIGHT) --bottom left
  351.     term.write(sMessage)
  352.    
  353.     sMessage = "" --clear message
  354. end
  355.  
  356. --display debug info in bottom right
  357. function drawDebug(sText)
  358.     term.setCursorPos(SCREEN_WIDTH + 1 - #sText, SCREEN_HEIGHT) --set cursor at bottom rightmost while still showing all text
  359.     term.write(sText)
  360. end
  361.  
  362.  
  363.  
  364. ------------------------
  365. --      DEBUGGING     --
  366. ------------------------
  367.  
  368. --run the program being edited
  369. function run(bArguments)
  370.     local tArguments = {}
  371.    
  372.     --get arugments for program execution
  373.     if bArguments then
  374.         --draw seperator dashes below argument input
  375.         term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN+1)
  376.         term.write(string.rep("-", SCREEN_WIDTH))
  377.        
  378.         --clear top line and get arguments
  379.         term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN)
  380.         term.clearLine()
  381.         term.write("Arguments: ")
  382.         sArguments = read(nil, sArguments)
  383.        
  384.         --format arguments (stolen from shell)
  385.         for match in string.gmatch(sArguments, "[^ \t]+") do --grabs each argument, space/tab delimitted
  386.             table.insert(tArguments, match)
  387.         end
  388.     end
  389.    
  390.     --save() --save so its running the program in the editor
  391.     term.clear()
  392.     term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN)
  393.     --local progSuccess = shell.run(sFilepath, unpack(tArguments)) --OLD WAY (calling without debugging)
  394.     sError = ""
  395.     local progSuccess = runCatchErrors(table.concat(tFile, "\n"), unpack(tArguments))
  396.    
  397.     --formats error info
  398.     if sError and sError ~= "" then
  399.         local v, errorStart = string.find(sError, "]:")
  400.         sError = string.sub(sError, errorStart+1)
  401.     end
  402.    
  403.     --display whether program succeeded or failed, then prompt to continue
  404.     if progSuccess then
  405.         sMessage = "SUCCESS - Press any key"
  406.     else
  407.         sMessage = "FAILED - Press any key" end
  408.     drawMessage()
  409.    
  410.     os.pullEvent("key")
  411.     os.sleep(0) --if the key pressed produces a "char" event, this prevents it from going to the next input loop
  412.    
  413.     initializeSystem() --reset program systerm-oriented settings (in case they've been changed)
  414.    
  415.     --moves cursor to error line and display error message
  416.     if sError and sError ~= "" then
  417.         local v1, v2 = string.find(sError, "%d+") --get error line
  418.         local errorLine = tonumber(string.sub(sError, v1, v2))
  419.         xFile = 1
  420.         yFile = errorLine
  421.         sMessage = sError
  422.     end
  423. end
  424.  
  425. --specialized function to run a file and catch the runtime errors
  426. --Courtesy of faubiguy
  427. function runCatchErrors( sProgram, ... ) --sProgram is a string containing the current text. Any other arguments are arguments to the program.
  428.     local tArgs = { ... }
  429.     local fnFile, err = loadstring( sProgram )
  430.     if fnFile then
  431.         local tEnv = {["shell"] = shell}
  432.         setmetatable( tEnv, { __index = _G } )
  433.         setfenv( fnFile, tEnv )
  434.         local ok, err = pcall( function()
  435.                 fnFile( unpack( tArgs ) )
  436.         end )
  437.         if not ok then
  438.                 if err and err ~= "" then
  439.                         print( err )
  440.                         sError = err -- sError is the variable where you want the error
  441.                 end
  442.                 return false
  443.         end
  444.                 sError = "" -- error value set to "" on successful execution
  445.         return true
  446.     end
  447.     if err and err ~= "" then
  448.                 print( err )
  449.                 sError = err -- Again replace sError with the variable where you want the error
  450.         end
  451.     return false
  452. end
  453.  
  454.  
  455. ------------------------
  456. --        OTHER       --
  457. ------------------------
  458.  
  459. --inserts characters the user types in
  460. function insertText(sText)
  461.     local removeChar = 0
  462.     if not bInsert then --overwrite the next char instead of inserting if settings say so
  463.         removeChar = 1 end
  464.    
  465.     --insert/overwrite text
  466.     tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. sText .. string.sub(tFile[yFile], xFile + removeChar)
  467.    
  468.     shiftCursor(#sText, 0)
  469.     bUnsavedChanges = true
  470. end
  471.  
  472.  
  473.  
  474. ------------------------
  475. --        CORE        --
  476. ------------------------
  477.  
  478. --initializes system settings (this is called at startup and after program execution)
  479. function initializeSystem()
  480.     term.setCursorBlink(true)
  481.     alignCursor()
  482. end
  483.  
  484. --intialization
  485. function intialize()
  486.     load() --load file
  487.    
  488.     --setup screen
  489.     applyBoundrySettings()
  490.     draw()
  491.     initializeSystem()
  492. end
  493.  
  494. --handles user input
  495. function input()
  496.     --wait for key input
  497.     local e, p1 = os.pullEvent()
  498.    
  499.     --reset exit w/ unsaved changes confirmation if the user presses any other input than exit
  500.     if bConfirmingExit == true and e == "key" and p1 == keys.f4 then
  501.         exit()
  502.     else
  503.         bConfirmingExit = false end
  504.    
  505.     if e == "char" then --handles character input
  506.         insertText(p1)
  507.     elseif e == "key" then -- handles non-character input
  508.         --DONT USE F2, F10, F11, ALT, ESC
  509.        
  510.         --functions
  511.         if p1 == keys.f1 then --menu
  512.             sMessage = sError
  513.             --sMessage = "F1 will be a menu in a future build"
  514.         elseif p1 == keys.f3 then --save
  515.             save()
  516.         elseif p1 == keys.f4 then --exit
  517.             exit()
  518.         elseif p1 == keys.f5 then --run program in editor
  519.             run(false)
  520.         elseif p1 == keys.f6 then --run with arguments
  521.             run(true)
  522.        
  523.         --movement
  524.         elseif p1 == keys.up then --up
  525.             shiftCursor(0, -1)
  526.         elseif p1 == keys.down then --down
  527.             shiftCursor(0, 1)
  528.         elseif p1 == keys.left then --left
  529.             shiftCursor(-1, 0)
  530.         elseif p1 == keys.right then --right
  531.             shiftCursor(1, 0)
  532.         elseif p1 == keys.home then --home
  533.             shiftCursorHome()
  534.         elseif p1 == keys["end"] then --end
  535.             shiftCursorEnd()
  536.         elseif p1 == keys.pageUp then --pasge up
  537.             shiftCursor(0, -(yMax-yMin))
  538.         elseif p1 == keys.pageDown then --page down
  539.             shiftCursor(0, yMax-yMin)
  540.        
  541.         --removing characters
  542.         elseif p1 == keys.backspace then --backspace
  543.             if not (yFile == 1 and xFile == 1) then --if not at the first character in the file
  544.                 if xFile == 1 then --if at the start of a line, concatenate line above with this line
  545.                     shiftCursor(0,-1)
  546.                     shiftCursorEnd()
  547.                    
  548.                     tFile[yFile] = tFile[yFile] .. tFile[yFile+1] --concat lines
  549.                     table.remove(tFile, yFile+1) --remove second line
  550.                 else --otherwise just delete one character
  551.                     shiftCursor(-1,0)
  552.                     tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. string.sub(tFile[yFile], xFile+1)
  553.                 end
  554.                 bUnsavedChanges = true
  555.             end
  556.         elseif p1 == keys.delete then --delete
  557.             if not (yFile == #tFile and xFile == #tFile[yFile] + 1) then --if not at the last character in the file
  558.                 if xFile > #tFile[yFile] then --if cursor is at the end of the line, concatenate with line below
  559.                     tFile[yFile] = tFile[yFile] .. tFile[yFile+1] --concat lines
  560.                     table.remove(tFile, yFile+1) --remove second line
  561.                 else --otherwise just delete one character
  562.                     tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. string.sub(tFile[yFile], xFile+1)
  563.                 end
  564.                 bUnsavedChanges = true
  565.             end
  566.        
  567.         --adding whitespace
  568.         elseif p1 == keys.enter then --enter
  569.             if xFile <= #tFile[yFile] then --if cursor is between characters
  570.                 table.insert(tFile, yFile+1, string.sub(tFile[yFile], xFile)) --move remainder of characters to a new line
  571.                 tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) --and delete the moved characters from the previous line
  572.             else --cursor at the end of the line
  573.                 table.insert(tFile, yFile+1, "")
  574.             end
  575.             shiftCursorHome()
  576.             shiftCursor(0,1)
  577.             bUnsavedChanges = true
  578.         elseif p1 == keys.tab then --tab
  579.             insertText("\t")
  580.             bUnsavedChanges = true
  581.            
  582.         --insert
  583.         elseif p1 == keys.insert then
  584.             bInsert = not bInsert
  585.        
  586.         end
  587.     end
  588. end
  589.  
  590. --main loop
  591. function mainLoop()
  592.     while bRunning do
  593.         --clear debug info
  594.         sDebugText = ""
  595.        
  596.         input() --get input and respond
  597.        
  598.         --debug info
  599.         local x,y = term.getCursorPos()
  600.         --sDebugText = sDebugText .. " Running:" ..tostring(bRunning)
  601.         --sDebugText = sDebugText .. " File:"..xFile..","..yFile .." Scrn:"..x..","..y
  602.        
  603.         scroll() --shift screen to show cursor
  604.         draw() --update screen
  605.     end
  606. end
  607.  
  608. --exit program
  609. function exit()
  610.     if bUnsavedChanges and not bConfirmingExit then --if there are unsaved changes, and this warning hasnt displayed yet
  611.         sMessage = "UNSAVED CHANGES - Press F4 to exit"
  612.         bConfirmingExit = true
  613.     else
  614.         bRunning = false end
  615. end
  616.  
  617. --have the program exit cleanly and tidily
  618. function exitCleanup()
  619.     term.clear()
  620.     term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN)
  621. end
  622.  
  623. intialize()
  624. mainLoop()
  625. exitCleanup()
Add Comment
Please, Sign In to add comment