KinoftheFlames

Untitled

Sep 21st, 2012
96
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.50 KB | None | 0 0
  1. --[[
  2.  
  3. editor (tentative name) v0.2
  4. by KinoftheFlames
  5.  
  6. This 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.  
  16. CHANGELOG for v0.2:
  17. - Refactored and simplified code to reduce possible errors
  18. - Added message display at the bottom for general information (e.g. program saved, program execution success/failure)
  19. - Added exit without saving confirmation
  20. - Added in-editor program execution (F5)
  21. - Added tab support (more annoying to implement than you'd think...)
  22. - Added page up and page down support
  23. - Changed exit code to return true if exiting normally or false if due to an error
  24.  
  25. ]]
  26.  
  27. --get filepath
  28. local arg = { ... }
  29. if #arg == 0 then
  30.     print("Syntax: editor <path>")
  31.     error()
  32. end
  33.  
  34. --debug
  35. local bDebug = false --display debugging info if true
  36. local sDebugText = "" --debug text shown if debugging is on
  37.  
  38. --settings
  39. local bInsert = true --insert text if true, override text if false
  40. local bShowLineNum = true --toggle visibility of line numbers
  41. local bShowLineNumSep = true --toggle visibility of line numbers seperator
  42. local bShowMessages = true --toggle displaying of messages at the bottom of the screen
  43. local nMinLineDigitsVisible = 1 --[POS] defines how much area for the line numbers is displayed at minimum (program will expand space as needed)
  44. local nSpacesPerTab = 3 --[POS] defines how many visual spaces are in a tab
  45.  
  46. --flags
  47. local bUnsavedChanges = false --tracks whether the file in the editor has been changed since last saved, helps with exit before saving confirmation
  48. local bConfirmingExit = false --if the user tries to exit without saving, this requires them to try again before succeeding
  49.  
  50. --variables
  51. local bRunning = true --false when the program exits
  52. local sFilepath = arg[1] --path to the file being editted
  53. local tFile = { } --table of lines for the file being editted
  54. local xMin, yMin = 1, 1 --min values for file display
  55. local xMax, yMax = term.getSize() --max values for file display
  56. local xFile, yFile = 1, 1 --the location of the cursor in the text file
  57. local xScroll, yScroll = 0, 0 --amount file is scrolled on screen
  58. local sMessage = "" --message displayed at the bottom of the screen until next input
  59.  
  60.  
  61.  
  62. ------------------------
  63. --    FILE HANDLING   --
  64. ------------------------
  65.  
  66. --load a file from hdd into editor
  67. function load()
  68.     --error handling file
  69.     if fs.exists(sFilepath) then
  70.         if fs.isDir(sFilepath) then
  71.             print("Cannot read directory as a file.")
  72.             error() --exit program
  73.         else
  74.             --load file
  75.             local file = fs.open(sFilepath, "r")
  76.             while true do
  77.                 local line = file.readLine()
  78.                 if line == nil then --if reached end of file
  79.                     break end
  80.                
  81.                 table.insert(tFile, line)
  82.             end
  83.             file.close()
  84.         end
  85.     else
  86.         table.insert(tFile, "")
  87.     end
  88. end
  89.  
  90. --save editor's text to a file
  91. function save()
  92.     file = fs.open(sFilepath, "w")
  93.     for i,line in ipairs(tFile) do
  94.         file.writeLine(line) end
  95.     file.close()
  96.    
  97.     sMessage = "SAVED"
  98.     bUnsavedChanges = false
  99. end
  100.  
  101.  
  102.  
  103. ------------------------
  104. --       VISUAL       --
  105. ------------------------
  106.  
  107. --scrolls the screen with the cursor
  108. function scroll()
  109.     alignCursor()
  110.     local x,y = term.getCursorPos()
  111.    
  112.     if y > yMax then
  113.         yScroll = yScroll + (y - yMax)
  114.     elseif y < yMin then
  115.         yScroll = yScroll - (yMin - y)
  116.     end
  117.     if x > xMax then
  118.         xScroll = xScroll + (x - xMax)
  119.     elseif x < xMin then
  120.         xScroll = xScroll - (xMin - x)
  121.     end
  122. end
  123.  
  124. --brings the visual cursor to the location of the cursor in file
  125. function alignCursor()
  126.     local charsLeftOfCursor = string.sub(tFile[yFile], 1, xFile-1) --chars left of cursor
  127.     local dummy, numTabs = string.gsub(charsLeftOfCursor, "\t", "\t") --gets the number of tabs
  128.    
  129.     local x = xMin + (xFile - 1) - xScroll + numTabs * (nSpacesPerTab - 1)
  130.     local y = yMin + (yFile - 1) - yScroll
  131.    
  132.     term.setCursorPos(x, y)
  133. end
  134.  
  135. --moves cursor around
  136. function shiftCursor(xShift, yShift)
  137.     --format input for error handling
  138.     if yFile + yShift < 1 then
  139.         yShift = -(yFile - 1)
  140.     elseif yFile + yShift > #tFile then
  141.         yShift = #tFile - yFile
  142.     end
  143.        
  144.    
  145.     --handle left/right movement
  146.     if xFile == 1 and xShift < 0 then --if at start of line and moving left, move to end of above line (if exists)
  147.         if yFile ~= 1 then
  148.             xFile = #tFile[yFile-1] + 1
  149.             yFile = yFile - 1
  150.         end
  151.     elseif xFile > #tFile[yFile] and xShift > 0 then --if at end of line and moving right, move to start of next line (if exists)
  152.         if yFile ~= #tFile then
  153.             xFile = 1
  154.             yFile = yFile + 1
  155.         end
  156.     else --horizontal only movement
  157.         xFile = xFile + xShift--normal move
  158.     end
  159.    
  160.     --handle up/down movement
  161.     if yFile == 1 and yShift < 0 then --if at first line and moving up, move to start of line
  162.         xFile = 1
  163.     elseif yFile == #tFile and yShift > 0 then --if at last line and moving down, move to end of line
  164.         xFile = #tFile[yFile] + 1
  165.     else --vertical only movement
  166.         yFile = yFile + yShift --normal move
  167.        
  168.         --if the cursor is past the end of the last character after having moved, move it to the last character
  169.         if xFile > #tFile[yFile] then
  170.             xFile = 1 + #tFile[yFile]
  171.         end
  172.     end
  173. end
  174.  
  175. --move cursor to the start of the line
  176. function shiftCursorHome()
  177.     xFile = 1
  178. end
  179.  
  180. --move cursor to the end of the line (end of text)
  181. function shiftCursorEnd()
  182.     xFile = 1 + #tFile[yFile]
  183. end
  184.  
  185. --adjusts borders of editable area depending on settings and situational factors
  186. function applyBoundrySettings()
  187.     if bShowLineNum then
  188.         local chars = #tostring(#tFile) --characters in line num area = num of digits of last line
  189.         if nMinLineDigitsVisible > chars then --unless there is a minimum character count to enforce which is greater
  190.             chars = nMinLineDigitsVisible end
  191.         xMin = chars + 2 --adjust boundry for editable area
  192.     else
  193.         xMin = 1 end
  194. end
  195.  
  196.  
  197.  
  198. ------------------------
  199. --      DRAWING       --
  200. ------------------------
  201.  
  202. --redraws entire screen
  203. function draw()
  204.     --clear screen
  205.     term.clear()
  206.    
  207.     --draw line numbers
  208.     if bShowLineNum then
  209.         drawLineNum() end
  210.    
  211.     --draw file text
  212.     for i=yMin,yMax do
  213.         if tFile[i+yScroll] ~= nil then --if haven't reached end of file
  214.             term.setCursorPos(xMin, i)
  215.             local modifiedLine = string.gsub(tFile[i+yScroll], "\t", string.rep(" ", nSpacesPerTab)) --replace visual line with defined number of spaces
  216.             term.write(string.sub(modifiedLine, 1 + xScroll, xMax - xMin + 1 + xScroll)) --only draw visible text
  217.         end
  218.     end
  219.    
  220.     --draw message
  221.     if bShowMessages then
  222.         drawMessage() end
  223.    
  224.     --draw debug
  225.     if bDebug then
  226.         drawDebug(sDebugText) end
  227.    
  228.     --place visual cursor in correct position
  229.     alignCursor()
  230. end
  231.  
  232. --draws line numbers and seperator
  233. function drawLineNum()
  234.     applyBoundrySettings()
  235.     local chars = #tostring(#tFile) --characters in line num area = num of digits of last line
  236.     if nMinLineDigitsVisible > chars then --unless there is a minimum character count to enforce which is greater
  237.         chars = nMinLineDigitsVisible end
  238.    
  239.     for i=yMin,yMax do
  240.         local lineNum = i+yScroll --line number of file line being drawn
  241.         if tFile[lineNum] ~= nil then --don't draw line numbers for lines that dont exists in the file
  242.             local numSpaces = chars - #tostring(lineNum) --spaces required to right-align numbers
  243.            
  244.             --draw finally
  245.             term.setCursorPos(1, i)
  246.             term.write(string.rep(" ", numSpaces)) --spaces before line number
  247.             term.write(lineNum) --line number
  248.             if bShowLineNumSep then
  249.                 term.write("|") end --seperator --TODO: add customizable line seperator (single char)
  250.         end
  251.     end
  252.    
  253.     --sDebugText = sDebugText .. " xMin:"..xMin
  254. end
  255.  
  256. --draw general message at the bottom of the screen for one loop
  257. function drawMessage()
  258.     if sMessage == "" then
  259.         return end
  260.    
  261.     --add dashes on the side to draw attention
  262.     local openSpace = xMax - #sMessage --openSpace = screenWidth - messageWidth
  263.     local fillerDrawn = (openSpace/2)-1 --dashes drawn on one side of message
  264.    
  265.     if fillerDrawn < 0 then
  266.         fillerDrawn = 0 end
  267.    
  268.     if openSpace % 2 == 0 then
  269.         sMessage = string.rep("-", fillerDrawn) .. " " .. sMessage .. " " .. string.rep("-", fillerDrawn)
  270.     else
  271.         sMessage = string.rep("-", fillerDrawn) .. " " .. sMessage .. "  " .. string.rep("-", fillerDrawn) end
  272.    
  273.     --display message
  274.     term.setCursorPos(1,yMax) --bottom left
  275.     term.write(sMessage)
  276.    
  277.     sMessage = ""
  278. end
  279.  
  280. --display debug info in bottom right
  281. function drawDebug(sText)
  282.     term.setCursorPos(xMax + 1 - #sText, yMax) --set cursor at bottom rightmost while still showing all text
  283.     term.write(sText)
  284. end
  285.  
  286.  
  287.  
  288. ------------------------
  289. --      DEBUGGING     --
  290. ------------------------
  291.  
  292. --run the program being edited
  293. function run()
  294.     save() --save so its running the program in the editor
  295.     term.clear()
  296.     term.setCursorPos(1,1)
  297.     local success = shell.run(sFilepath)
  298.    
  299.     --display whether program succeeded or failed, then prompt to continue
  300.     if success then
  301.         sMessage = "SUCCESS - Press any key"
  302.     else
  303.         sMessage = "FAILED - Press any key" end
  304.     drawMessage()
  305.    
  306.     os.pullEvent("key")
  307.     os.sleep(0) --if the key pressed produces a "char" event, this prevents it from going to the next input loop
  308.    
  309.     sMessage = "" --clear any messages generated by the run process
  310. end
  311.  
  312.  
  313.  
  314. ------------------------
  315. --        OTHER       --
  316. ------------------------
  317.  
  318. --inserts characters the user types in
  319. function insertText(sText)
  320.     tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. sText .. string.sub(tFile[yFile], xFile)
  321.     shiftCursor(#sText, 0)
  322.     bUnsavedChanges = true
  323. end
  324.  
  325.  
  326.  
  327. ------------------------
  328. --        CORE        --
  329. ------------------------
  330.  
  331. --intialization
  332. function intialize()
  333.     load() --load file
  334.    
  335.     --setup screen
  336.     applyBoundrySettings()
  337.     draw()
  338.     term.setCursorBlink(true)
  339. end
  340.  
  341. --handles user input
  342. function input()
  343.     --wait for key input
  344.     local e, p1 = os.pullEvent()
  345.    
  346.     --reset exit w/ unsaved changes confirmation if the user presses any other input than exit
  347.     if bConfirmingExit == true and e == "key" and p1 == keys.f4 then
  348.         exit()
  349.     else
  350.         bConfirmingExit = false end
  351.    
  352.     if e == "char" then --handles character input
  353.         insertText(p1)
  354.     elseif e == "key" then -- handles non-character input
  355.         --DONT USE F2, F10, F11, ALT, ESC
  356.        
  357.         --functional keys
  358.         if p1 == keys.f1 then --menu
  359.            
  360.         elseif p1 == keys.f3 then --save
  361.             save()
  362.         elseif p1 == keys.f4 then --exit
  363.             exit()
  364.         elseif p1 == keys.f5 then --run program in editor
  365.             run()
  366.        
  367.         --arrow keys
  368.         elseif p1 == keys.up then
  369.             shiftCursor(0, -1)
  370.         elseif p1 == keys.down then
  371.             shiftCursor(0, 1)
  372.         elseif p1 == keys.left then
  373.             shiftCursor(-1, 0)
  374.         elseif p1 == keys.right then
  375.             shiftCursor(1, 0)
  376.        
  377.         --home, end, page up, page down
  378.         elseif p1 == keys.home then --home
  379.             shiftCursorHome()
  380.         elseif p1 == keys["end"] then --end
  381.             shiftCursorEnd()
  382.         elseif p1 == keys.pageUp then --pasge up
  383.             shiftCursor(0, -(yMax-yMin))
  384.         elseif p1 == keys.pageDown then --page down
  385.             shiftCursor(0, yMax-yMin)
  386.        
  387.         --backspace, delete
  388.         elseif p1 == keys.backspace then --backspace
  389.             if not (yFile == 1 and xFile == 1) then --if not at the first character in the file
  390.                 if xFile == 1 then --if at the start of a line, concatenate line above with this line
  391.                     shiftCursor(0,-1)
  392.                     shiftCursorEnd()
  393.                    
  394.                     tFile[yFile] = tFile[yFile] .. tFile[yFile+1] --concat lines
  395.                     table.remove(tFile, yFile+1) --remove second line
  396.                 else --otherwise just delete one character
  397.                     shiftCursor(-1,0)
  398.                     tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. string.sub(tFile[yFile], xFile+1)
  399.                 end
  400.                 bUnsavedChanges = true
  401.             end
  402.         elseif p1 == keys.delete then --delete
  403.             if not (yFile == #tFile and xFile == #tFile[yFile] + 1) then --if not at the last character in the file
  404.                 if xFile > #tFile[yFile] then --if cursor is at the end of the line, concatenate with line below
  405.                     tFile[yFile] = tFile[yFile] .. tFile[yFile+1] --concat lines
  406.                     table.remove(tFile, yFile+1) --remove second line
  407.                 else --otherwise just delete one character
  408.                     tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. string.sub(tFile[yFile], xFile+1)
  409.                 end
  410.                 bUnsavedChanges = true
  411.             end
  412.         --enter, tab
  413.         elseif p1 == keys.enter then --enter
  414.             if xFile <= #tFile[yFile] then --if cursor is between characters
  415.                 table.insert(tFile, yFile+1, string.sub(tFile[yFile], xFile)) --move remainder of characters to a new line
  416.                 tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) --and delete the moved characters from the previous line
  417.             else --cursor at the end of the line
  418.                 table.insert(tFile, yFile+1, "")
  419.             end
  420.             shiftCursorHome()
  421.             shiftCursor(0,1)
  422.             bUnsavedChanges = true
  423.         elseif p1 == keys.tab then --tab
  424.             insertText("\t")
  425.             bUnsavedChanges = true
  426.         end
  427.     end
  428. end
  429.  
  430. --main loop
  431. function mainLoop()
  432.     while bRunning do
  433.         --clear debug info
  434.         sDebugText = ""
  435.        
  436.         input() --get input and respond
  437.        
  438.         --debug info
  439.         local x,y = term.getCursorPos()
  440.         sDebugText = sDebugText .. " File:"..xFile..","..yFile .." Scrn:"..x..","..y
  441.        
  442.         scroll() --shift screen to show cursor
  443.         draw() --update screen
  444.     end
  445. end
  446.  
  447. --exit program
  448. function exit()
  449.     if bUnsavedChanges and not bConfirmingExit then --if there are unsaved changes, and this warning hasnt displayed yet
  450.         sMessage = "UNSAVED CHANGES - Press F4 to exit"
  451.         bConfirmingExit = true
  452.     else
  453.         bRunning = false end
  454. end
  455.  
  456. --have the program exit cleanly and tidily
  457. function exitCleanup()
  458.     term.clear()
  459.     term.setCursorPos(1,1)
  460. end
  461.  
  462. intialize()
  463. mainLoop()
  464. exitCleanup()
Advertisement
Add Comment
Please, Sign In to add comment