KinoftheFlames

Untitled

Sep 24th, 2012
105
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 29.88 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 insertion/overwrite toggle
  21. - Added status bar (optional) with the following information (which is all optional):
  22.    -- Current line number
  23.    -- Total line count (off by default)
  24.    -- Insert/overwrite toggle display
  25. - 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.
  26. - You can now run ed from ed as recursively as you'd like and everything should work properly (yo dawg!)
  27. - Added auto-tabbing
  28. - Trying to edit files in non-existant directories now creates those directories
  29. - Added settings menu, you can finally customize the UI without editing the code!.
  30.    -- Press F1 to access it, then use the arrow keys and space/enter to interact.
  31. - Settings will automatically save on menu exit and load with the program - change it once and you're good!
  32.  
  33.  
  34. Future features:
  35. - Find and replace
  36. - Goto line
  37.  
  38. ]]
  39.  
  40. --argument handling
  41. local arg = { ... }
  42. if #arg == 0 then --verify argument exists
  43.     print("Syntax: editor <path>")
  44.     error()
  45. end
  46. if fs.isDir(arg[1]) then --verift not a directory
  47.     print("Error: cannot edit a directory as a file")
  48.     error()
  49. end
  50.  
  51. --debug
  52. local bDebug = true --display debugging info if true
  53. local bClearDebug = false --determines if debug is cleared from screen every loop or not
  54. local sDebugText = "" --debug text shown if debugging is on
  55.  
  56. --constants
  57. local SCREEN_ORIGIN = 1 --top left of screen
  58. local SCREEN_WIDTH, SCREEN_HEIGHT = term.getSize() --width and height of screen
  59. local TIMER_INTERVAL = 300.0 --in seconds (DO NOT SET TO 0, that would be infinite)
  60. local SETTINGS_FILEPATH = ".ed_settings" --where user settings are located
  61.  
  62. --settings (defaults)
  63. local bShowLineNum = true --toggle visibility of line numbers
  64. local bShowLineNumSep = true --toggle visibility of line numbers seperator
  65. local bShowStatusBar = true --toggle visibility of the line of information at the bottom of the screen
  66.     --toggle visibility of individual status information
  67.     local bStatus_lineNum = true
  68.     local bStatus_lineCount = false
  69.     local bStatus_insert = true
  70. local bShowMessages = true --toggle displaying of messages at the bottom of the screen
  71. local nMinLineDigitsVisible = 1 --[POS] defines how much area for the line numbers is displayed at minimum (program will expand space as needed)
  72. local nSpacesPerTab = 3 --[POS] defines how many visual spaces are in a tab
  73.  
  74. --toggles
  75. local bInsert = true --insert text if true, override text if false
  76.  
  77. --flags
  78. local bUnsavedChanges = false --tracks whether the file in the editor has been changed since last saved, helps with exit before saving confirmation
  79. local bConfirmingExit = false --if the user tries to exit without saving, this requires them to try again before succeeding
  80.  
  81. --variables
  82. local bRunning = true --false when the program exits
  83. local sFilepath = arg[1] --path to the file being editted
  84. local tFile = { } --table of lines for the file being editted
  85. local xMin, yMin = SCREEN_ORIGIN, SCREEN_ORIGIN --min values for file display
  86. local xMax, yMax = SCREEN_WIDTH, SCREEN_HEIGHT --max values for file display
  87. local xFile, yFile = 1, 1 --the location of the cursor in the text file
  88. local xScroll, yScroll = 0, 0 --amount file is scrolled on screen
  89. local sStatus = "" --text displayed for the status bar at the bottom of the screen
  90. local sMessage = "" --message displayed at the bottom of the screen until next input
  91. local sArguments = "" --stores the last arguments used, bringing them up again when run w/ arguments is called
  92. local sError = "" --stores error information from failed program execution for debugging
  93. local tTimer --timer for main loop and time-based events
  94.  
  95. ------------------------
  96. --      EXTERNAL      --
  97. ------------------------
  98.  
  99. --converts a string to a boolean
  100. function toboolean(value)
  101.     if value == "true" then return true end
  102.     if value == "false" then return false end
  103. end
  104.  
  105.  
  106.  
  107. ------------------------
  108. --    FILE HANDLING   --
  109. ------------------------
  110.  
  111. --load a file from hdd into editor
  112. function load()
  113.     --error handling file
  114.     if fs.exists(sFilepath) then
  115.         --load file
  116.         local file = fs.open(sFilepath, "r")
  117.         while true do
  118.             local line = file.readLine()
  119.             if line == nil then --if reached end of file
  120.                 break end
  121.            
  122.             table.insert(tFile, line)
  123.         end
  124.         file.close()
  125.     else
  126.         table.insert(tFile, "")
  127.     end
  128. end
  129.  
  130. --save editor's text to a file
  131. function save()
  132.     sMessage = "SAVING..."
  133.     draw()
  134.    
  135.     if bUnsavedChanges then --only save if needed (saves on CPU time)
  136.         --create directory if it doesn't exist
  137.         local dir = string.sub(sFilepath, 1, #sFilepath - #fs.getName(sFilepath))
  138.         if not fs.exists(dir) then
  139.             fs.makeDir(dir) end
  140.        
  141.         --write to file
  142.         local file = fs.open(sFilepath, "w")
  143.         for i,line in ipairs(tFile) do
  144.             file.writeLine(line) end
  145.         file.close()
  146.     end
  147.    
  148.     sMessage = "SAVED to " .. sFilepath
  149.     bUnsavedChanges = false
  150. end
  151.  
  152. function loadSettings()
  153.     if fs.exists(SETTINGS_FILEPATH) and not fs.isDir(SETTINGS_FILEPATH) then
  154.         file = fs.open(SETTINGS_FILEPATH, "r")
  155.        
  156.         --load settings
  157.         while true do
  158.             local line = file.readLine()
  159.             if line == nil then --EOF
  160.                 break end
  161.             local v1, v2 = string.find(line, " = ") --finds delimitter location
  162.             local var = string.sub(line, 1, v1-1) --name of variable
  163.             local value = string.sub(line, v2+1) --value of variable
  164.            
  165.             if var == "bShowLineNum" then bShowLineNum = toboolean(value) end
  166.             if var == "bShowLineNumSep" then bShowLineNumSep = toboolean(value) end
  167.             if var == "bShowStatusBar" then bShowStatusBar = toboolean(value) end
  168.             if var == "bStatus_lineNum" then bStatus_lineNum = toboolean(value) end
  169.             if var == "bStatus_lineCount" then bStatus_lineCount = toboolean(value) end
  170.             if var == "bStatus_insert" then bStatus_insert = toboolean(value) end
  171.             if var == "bShowMessages" then bShowMessages = toboolean(value) end
  172.             if var == "nMinLineDigitsVisible" then nMinLineDigitsVisible = tonumber(value) end
  173.             if var == "nSpacesPerTab" then nSpacesPerTab = tonumber(value) end
  174.         end
  175.        
  176.         file.close()
  177.     end
  178. end
  179.  
  180. function saveSettings()
  181.     if fs.isDir(SETTINGS_FILEPATH) then --if a dir with settings filepath exists, delete it (sorry!)
  182.         fs.delete(SETTINGS_FILEPATH)
  183.     end
  184.    
  185.     file = fs.open(SETTINGS_FILEPATH, "w")
  186.    
  187.     --save settings
  188.     file.writeLine("bShowLineNum = "..tostring(bShowLineNum))
  189.     file.writeLine("bShowLineNumSep = "..tostring(bShowLineNumSep))
  190.     file.writeLine("bShowStatusBar = "..tostring(bShowStatusBar))
  191.     file.writeLine("bStatus_lineNum = "..tostring(bStatus_lineNum))
  192.     file.writeLine("bStatus_lineCount = "..tostring(bStatus_lineCount))
  193.     file.writeLine("bStatus_insert = "..tostring(bStatus_insert))
  194.     file.writeLine("bShowMessages = "..tostring(bShowMessages))
  195.     file.writeLine("nMinLineDigitsVisible = "..tostring(nMinLineDigitsVisible))
  196.     file.writeLine("nSpacesPerTab = "..tostring(nSpacesPerTab))
  197.    
  198.     file.close()
  199.     sMessage = "Settings saved"
  200. end
  201.  
  202.  
  203. ------------------------
  204. --       VISUAL       --
  205. ------------------------
  206.  
  207. --scrolls the screen with the cursor
  208. function scroll()
  209.     alignCursor()
  210.     local x,y = term.getCursorPos()
  211.    
  212.     if y > yMax then
  213.         yScroll = yScroll + (y - yMax)
  214.     elseif y < yMin then
  215.         yScroll = yScroll - (yMin - y)
  216.     end
  217.     if x > xMax then
  218.         xScroll = xScroll + (x - xMax)
  219.     elseif x < xMin then
  220.         xScroll = xScroll - (xMin - x)
  221.     end
  222. end
  223.  
  224. --brings the visual cursor to the location of the cursor in file
  225. function alignCursor()
  226.     local charsLeftOfCursor = string.sub(tFile[yFile], 1, xFile-1) --chars left of cursor
  227.     local dummy, numTabs = string.gsub(charsLeftOfCursor, "\t", "\t") --gets the number of tabs
  228.    
  229.     local x = xMin + (xFile - 1) - xScroll + numTabs * (nSpacesPerTab - 1)
  230.     local y = yMin + (yFile - 1) - yScroll
  231.    
  232.     term.setCursorPos(x, y)
  233. end
  234.  
  235. --moves cursor around
  236. function shiftCursor(xShift, yShift)
  237.     --format input for error handling
  238.     if yFile + yShift < 1 then
  239.         yShift = -(yFile - 1)
  240.     elseif yFile + yShift > #tFile then
  241.         yShift = #tFile - yFile
  242.     end
  243.        
  244.    
  245.     --handle left/right movement
  246.     if xFile == 1 and xShift < 0 then --if at start of line and moving left, move to end of above line (if exists)
  247.         if yFile ~= 1 then
  248.             xFile = #tFile[yFile-1] + 1
  249.             yFile = yFile - 1
  250.         end
  251.     elseif xFile > #tFile[yFile] and xShift > 0 then --if at end of line and moving right, move to start of next line (if exists)
  252.         if yFile ~= #tFile then
  253.             xFile = 1
  254.             yFile = yFile + 1
  255.         end
  256.     else --horizontal only movement
  257.         xFile = xFile + xShift--normal move
  258.     end
  259.    
  260.     --handle up/down movement
  261.     if yFile == 1 and yShift < 0 then --if at first line and moving up, move to start of line
  262.         xFile = 1
  263.     elseif yFile == #tFile and yShift > 0 then --if at last line and moving down, move to end of line
  264.         xFile = #tFile[yFile] + 1
  265.     else --vertical only movement
  266.         yFile = yFile + yShift --normal move
  267.        
  268.         --if the cursor is past the end of the last character after having moved, move it to the last character
  269.         if xFile > #tFile[yFile] then
  270.             xFile = 1 + #tFile[yFile]
  271.         end
  272.     end
  273. end
  274.  
  275. --move cursor to the start of the line
  276. function shiftCursorHome()
  277.     xFile = 1
  278. end
  279.  
  280. --move cursor to the end of the line (end of text)
  281. function shiftCursorEnd()
  282.     xFile = 1 + #tFile[yFile]
  283. end
  284.  
  285. --adjusts borders of editable area depending on settings and situational factors
  286. function applyBoundrySettings()
  287.     if bShowLineNum then
  288.         local chars = #tostring(#tFile) --characters in line num area = num of digits of last line
  289.         if nMinLineDigitsVisible > chars then --unless there is a minimum character count to enforce which is greater
  290.             chars = nMinLineDigitsVisible end
  291.         xMin = chars + 2 --adjust boundry for editable area
  292.     else
  293.         xMin = SCREEN_ORIGIN end
  294.    
  295.     if bShowStatusBar then
  296.         yMax = SCREEN_HEIGHT - 1
  297.     else
  298.         yMax = SCREEN_HEIGHT end
  299. end
  300.  
  301.  
  302.  
  303. ------------------------
  304. --      DRAWING       --
  305. ------------------------
  306.  
  307. --redraws entire screen
  308. function draw()
  309.     --clear screen
  310.     term.clear()
  311.    
  312.     --draw line numbers
  313.     if bShowLineNum then
  314.         drawLineNum() end
  315.    
  316.     --draw file text
  317.     for i=yMin,yMax do
  318.         if tFile[i+yScroll] ~= nil then --if haven't reached end of file
  319.             term.setCursorPos(xMin, i)
  320.             local modifiedLine = string.gsub(tFile[i+yScroll], "\t", string.rep(" ", nSpacesPerTab)) --replace visual line's tab characters with defined num of spaces
  321.             term.write(string.sub(modifiedLine, 1 + xScroll, xMax - xMin + 1 + xScroll)) --only draw visible text
  322.         end
  323.     end
  324.    
  325.     --draw status bar
  326.     if bShowStatusBar then
  327.         drawStatusBar() end
  328.    
  329.     --draw message
  330.     if bShowMessages then
  331.         drawMessage() end
  332.    
  333.     --draw debug
  334.     if bDebug then
  335.         drawDebug(sDebugText) end
  336.    
  337.     --place visual cursor in correct position
  338.     alignCursor()
  339. end
  340.  
  341. --draws line numbers and seperator
  342. function drawLineNum()
  343.     applyBoundrySettings()
  344.     local chars = #tostring(#tFile) --characters in line num area = num of digits of last line
  345.     if nMinLineDigitsVisible > chars then --unless there is a minimum character count to enforce which is greater
  346.         chars = nMinLineDigitsVisible end
  347.    
  348.     for i=yMin,yMax do
  349.         local lineNum = i+yScroll --line number of file line being drawn
  350.         if tFile[lineNum] ~= nil then --don't draw line numbers for lines that dont exists in the file
  351.             local numSpaces = chars - #tostring(lineNum) --spaces required to right-align numbers
  352.            
  353.             --draw finally
  354.             term.setCursorPos(SCREEN_ORIGIN, i)
  355.             term.write(string.rep(" ", numSpaces)) --spaces before line number
  356.             term.write(tostring(lineNum)) --line number
  357.             if bShowLineNumSep then
  358.                 term.write("|") end --seperator --TODO: add customizable line seperator (single char)
  359.         end
  360.     end
  361.    
  362.     --sDebugText = sDebugText .. " xMin:"..xMin
  363. end
  364.  
  365. --draw bar with information at the bottom of the screen
  366. function drawStatusBar()
  367.     sStatus = "--< " --paint me like one of your french girls
  368.    
  369.     local charsLeft = SCREEN_WIDTH - 8 --characters available to print
  370.     local nextAdd = "" --next info to add to status bar
  371.    
  372.     --line cursor is on
  373.     if bStatus_lineNum then
  374.         nextAdd = "Ln " .. yFile --cur line
  375.         if bStatus_lineCount then --line count
  376.             nextAdd = nextAdd .. "/" .. #tFile end
  377.        
  378.         charsLeft = addStatus(nextAdd, charsLeft)
  379.     elseif bStatus_lineCount then
  380.         nextAdd = #tFile .. " lines" --line count
  381.         charsLeft = addStatus(nextAdd, charsLeft)
  382.     end
  383.    
  384.     --INS for insert text, OVR for overwrite text
  385.     if bStatus_insert then
  386.         if bInsert then
  387.             nextAdd = "INS"
  388.         else
  389.             nextAdd = "OVR" end
  390.         charsLeft = addStatus(nextAdd, charsLeft)
  391.     end
  392.    
  393.     sStatus = sStatus .. string.rep(" ", charsLeft) --filler
  394.     sStatus = sStatus .. " >--" --paint me like one of your french girls
  395.    
  396.     --draw the bar
  397.     term.setCursorPos(SCREEN_ORIGIN,SCREEN_HEIGHT)
  398.     term.write(sStatus)
  399. end
  400.  
  401. --push next status on to the status text
  402. function addStatus(sText, nCharsLeft)
  403.     if nCharsLeft >= #sText + 2 then --if status can fit wholly on line
  404.         if #sStatus <= 4 then --if first status added
  405.             sStatus = sStatus .. sText
  406.             nCharsLeft = nCharsLeft - #sText --reduce remaining space for statuses
  407.         else
  408.             sStatus = sStatus .. "  " .. sText
  409.             nCharsLeft = nCharsLeft - (#sText + 2) --reduce remaining space for statuses
  410.         end
  411.     end
  412.     return nCharsLeft
  413. end
  414.  
  415. --draw general message at the bottom of the screen for one loop
  416. function drawMessage()
  417.     if sMessage == "" then
  418.         return end
  419.    
  420.     --truncates message
  421.     sMessage = string.sub(sMessage, 1, SCREEN_WIDTH - 8)
  422.    
  423.     --add dashes on the side to draw attention
  424.     local fillerDrawn = ((SCREEN_WIDTH - #sMessage)/2)-2 --num dashes on one side
  425.    
  426.     if fillerDrawn < 2 then
  427.         fillerDrawn = 2 end
  428.    
  429.     local printedText = string.rep("-", fillerDrawn) .. "< " .. sMessage .. " >" .. string.rep("-", fillerDrawn + 1)
  430.    
  431.     --display message
  432.     term.setCursorPos(SCREEN_ORIGIN,SCREEN_HEIGHT) --bottom left
  433.     term.write(printedText)
  434. end
  435.  
  436. --display debug info in bottom right
  437. function drawDebug(sText)
  438.     term.setCursorPos(SCREEN_WIDTH + 1 - #sText, SCREEN_HEIGHT) --set cursor at bottom rightmost while still showing all text
  439.     term.write(sText)
  440. end
  441.  
  442. --draw menu
  443. function drawMenu(tMenu, nSel)
  444.     --ALL HARD CODED POSITIONS ATM
  445.     local xText,yText = SCREEN_ORIGIN+2, SCREEN_ORIGIN+2
  446.     local xParam, yParam = xText + 40, SCREEN_ORIGIN+2
  447.    
  448.     for i,option in ipairs(tMenu) do
  449.         --write text
  450.         term.setCursorPos(xText, yText + (i-1))
  451.         term.write(option.text)
  452.        
  453.         --write value
  454.         if nSel == i then
  455.             term.setCursorPos(xParam-2, yParam + (i-1))
  456.             term.write("[ " .. tostring(option.value) .. " ]") --draw brackets to indicate selection
  457.         else
  458.             term.setCursorPos(xParam, yParam + (i-1))
  459.             term.write(tostring(option.value))
  460.         end
  461.     end
  462. end
  463.  
  464. --the background for the menu
  465. function drawMenu_bg()
  466.     local line = ""
  467.     --draw top line
  468.     local dashes = string.rep("-", (SCREEN_WIDTH-6)/2)
  469.     line = "o"..dashes.." ed "..dashes
  470.         if SCREEN_WIDTH-6 % 2 ~= 0 then --handling for odd screen widths
  471.             line = line.."-o"
  472.         else
  473.             line = line.."o" end
  474.     term.setCursorPos(SCREEN_ORIGIN, SCREEN_ORIGIN)
  475.     term.write(line)
  476.    
  477.     --draw inbetween lines
  478.     for i=SCREEN_ORIGIN+1,SCREEN_HEIGHT-1 do
  479.         line = "|"..string.rep(" ", SCREEN_WIDTH-2).."|"
  480.         term.setCursorPos(SCREEN_ORIGIN, i)
  481.         term.write(line)
  482.     end
  483.    
  484.     --draw bottom line
  485.     line = "O"..string.rep("-", SCREEN_WIDTH-2).."O"
  486.     term.setCursorPos(SCREEN_ORIGIN, SCREEN_HEIGHT)
  487.     term.write(line)
  488. end
  489.  
  490.  
  491. ------------------------
  492. --      DEBUGGING     --
  493. ------------------------
  494.  
  495. --run the program being edited
  496. function run(bArguments)
  497.     local tArguments = {}
  498.    
  499.     --get arugments for program execution
  500.     if bArguments then
  501.         --draw seperator dashes below argument input
  502.         term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN+1)
  503.         term.write(string.rep("-", SCREEN_WIDTH))
  504.        
  505.         --clear top line and get arguments
  506.         term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN)
  507.         term.clearLine()
  508.         term.write("Arguments: ")
  509.         sArguments = read(nil, sArguments)
  510.        
  511.         --format arguments (stolen from shell)
  512.         for match in string.gmatch(sArguments, "[^ \t]+") do --grabs each argument, space/tab delimitted
  513.             table.insert(tArguments, match)
  514.         end
  515.     end
  516.    
  517.     --save() --save so its running the program in the editor
  518.     term.clear()
  519.     term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN)
  520.     --local progSuccess = shell.run(sFilepath, unpack(tArguments)) --OLD WAY (calling without debugging)
  521.     sError = ""
  522.     local progSuccess = runCatchErrors(table.concat(tFile, "\n"), unpack(tArguments))
  523.    
  524.     --formats error info
  525.     if sError and sError ~= "" then
  526.         local s1, s2 = string.find(sError, "%d+:") --the editor
  527.         local errorStart, v = string.find(string.sub(sError, s2), "%d+:") --the executed code
  528.         if errorStart ~= nil then
  529.             sError = string.sub(string.sub(sError, s2), errorStart) --removed editor pre-error text
  530.         else
  531.             sError = string.sub(sError, s1) --removed executed pre-error text (name and colon)
  532.         end
  533.     end
  534.    
  535.     --display whether program succeeded or failed, then prompt to continue
  536.     if progSuccess then
  537.         sMessage = "SUCCESS - Press any key"
  538.     else
  539.         sMessage = "FAILED - Press any key" end
  540.     drawMessage()
  541.    
  542.     while true do
  543.         e, p1 = os.pullEvent()
  544.         if e == "timer" and p1 == tTimer then
  545.             tTimer = os.startTimer(TIMER_INTERVAL) --reset timer
  546.         elseif e == "key" then
  547.             break end
  548.     end
  549.     os.sleep(0) --if the key pressed produces a "char" event, this prevents it from going to the next input loop
  550.    
  551.     initializeSystem() --reset program systerm-oriented settings (in case they've been changed)
  552.    
  553.     if sError and sError ~= "" then --moves cursor to error line and display error message
  554.         local v1, v2 = string.find(sError, "%d+") --get error line
  555.         local errorLine = tonumber(string.sub(sError, v1, v2))
  556.         xFile = 1
  557.         yFile = errorLine
  558.         sMessage = sError
  559.     else --no error
  560.         sMessage = "" end
  561. end
  562.  
  563. --specialized function to run a file and catch the runtime errors
  564. --Courtesy of faubiguy
  565. function runCatchErrors( sProgram, ... ) --sProgram is a string containing the current text. Any other arguments are arguments to the program.
  566.     local tArgs = { ... }
  567.     local fnFile, err = loadstring( sProgram )
  568.     if fnFile then
  569.         local tEnv = {["shell"] = shell}
  570.         setmetatable( tEnv, { __index = _G } )
  571.         setfenv( fnFile, tEnv )
  572.         local ok, err = pcall( function()
  573.                 fnFile( unpack( tArgs ) )
  574.         end )
  575.         if not ok then
  576.                 if err and err ~= "" then
  577.                         print( err )
  578.                         sError = err -- sError is the variable where you want the error
  579.                 end
  580.                 return false
  581.         end
  582.                 sError = "" -- error value set to "" on successful execution
  583.         return true
  584.     end
  585.     if err and err ~= "" then
  586.                 print( err )
  587.                 sError = err -- Again replace sError with the variable where you want the error
  588.         end
  589.     return false
  590. end
  591.  
  592.  
  593. ------------------------
  594. --       TYPING       --
  595. ------------------------
  596.  
  597. --inserts characters the user types in
  598. function insertText(sText)
  599.     local removeChar = 0
  600.     if not bInsert then --overwrite the next char instead of inserting if settings say so
  601.         removeChar = 1 end
  602.    
  603.     --insert/overwrite text
  604.     tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. sText .. string.sub(tFile[yFile], xFile + removeChar)
  605.    
  606.     shiftCursor(#sText, 0)
  607.     bUnsavedChanges = true
  608. end
  609.  
  610. --key press: enter
  611. function key_enter()
  612.     if xFile <= #tFile[yFile] then --if cursor is between characters
  613.         table.insert(tFile, yFile+1, string.sub(tFile[yFile], xFile)) --move remainder of characters to a new line
  614.         tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) --and delete the moved characters from the previous line
  615.     else --cursor at the end of the line
  616.         table.insert(tFile, yFile+1, "")
  617.     end
  618.    
  619.     --move cursor to begining of line and down
  620.     shiftCursorHome()
  621.     shiftCursor(0,1)
  622.    
  623.     --auto insert tabs equal to number on last line
  624.     local v, numTabs = string.find(tFile[yFile-1], "[\t]+")
  625.     if v ~= 1 or numTabs == nil then --tabs found, but not at the start of the line
  626.         numTabs = 0 end
  627.     insertText(string.rep("\t", numTabs))
  628.    
  629.     bUnsavedChanges = true
  630. end
  631.  
  632. --key press: backspace
  633. function key_backspace()
  634.     if not (yFile == 1 and xFile == 1) then --if not at the first character in the file
  635.         if xFile == 1 then --if at the start of a line, concatenate line above with this line
  636.             shiftCursor(0,-1)
  637.             shiftCursorEnd()
  638.            
  639.             tFile[yFile] = tFile[yFile] .. tFile[yFile+1] --concat lines
  640.             table.remove(tFile, yFile+1) --remove second line
  641.         else --otherwise just delete one character
  642.             shiftCursor(-1,0)
  643.             tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. string.sub(tFile[yFile], xFile+1)
  644.         end
  645.         bUnsavedChanges = true
  646.     end
  647. end
  648.  
  649. --key press: delete
  650. function key_delete()
  651.     if not (yFile == #tFile and xFile == #tFile[yFile] + 1) then --if not at the last character in the file
  652.         if xFile > #tFile[yFile] then --if cursor is at the end of the line, concatenate with line below
  653.             tFile[yFile] = tFile[yFile] .. tFile[yFile+1] --concat lines
  654.             table.remove(tFile, yFile+1) --remove second line
  655.         else --otherwise just delete one character
  656.             tFile[yFile] = string.sub(tFile[yFile], 1, xFile-1) .. string.sub(tFile[yFile], xFile+1)
  657.         end
  658.         bUnsavedChanges = true
  659.     end
  660. end
  661.  
  662.  
  663.  
  664. ------------------------
  665. --        MENU        --
  666. ------------------------
  667.  
  668. function menuInit_settings()
  669.     local opShowLineNum = { text = "Show line numbers",
  670.                             type = "bool",
  671.                             param = nil,
  672.                             value = bShowLineNum }
  673.     local opShowLineNumSep = {  text = "Show line number seperator",
  674.                                 type = "bool",
  675.                                 param = nil,
  676.                                 value = bShowLineNumSep }
  677.     local opShowStatusBar = {   text = "Show status bar",
  678.                                 type = "bool",
  679.                                 param = nil,
  680.                                 value = bShowStatusBar }
  681.         local opStatus_lineNum = {  text = "Show status - line number",
  682.                                     type = "bool",
  683.                                     param = nil,
  684.                                     value = bStatus_lineNum }
  685.         local opStatus_lineCount = {    text = "Show status - line count",
  686.                                         type = "bool",
  687.                                         param = nil,
  688.                                         value = bStatus_lineCount }
  689.         local opStatus_insert = {   text = "Show status - insert",
  690.                                     type = "bool",
  691.                                     param = nil,
  692.                                     value = bStatus_insert }
  693.     local opShowMessages = {    text = "Show messages",
  694.                                 type = "bool",
  695.                                 param = nil,
  696.                                 value = bShowMessages }
  697.     local opMinLineDigitsVisible = {    text = "Min line num characters shown",
  698.                                         type = "number",
  699.                                         param = { 1, nil },
  700.                                         value = nMinLineDigitsVisible }
  701.     local opSpacesPerTab = {    text = "Spaces per tab",
  702.                                 type = "number",
  703.                                 param = { 1, nil },
  704.                                 value = nSpacesPerTab }
  705.    
  706.     local menu = {  opShowLineNum,
  707.                     opShowLineNumSep,
  708.                     opShowStatusBar,
  709.                     opStatus_lineNum,
  710.                     opStatus_lineCount,
  711.                     opStatus_insert,
  712.                     opShowMessages,
  713.                     opMinLineDigitsVisible,
  714.                     opSpacesPerTab }
  715.    
  716.     return menu
  717. end
  718.  
  719. --top level menu accessed via F1
  720. function menu()
  721.     os.sleep(0) --prevents runover input from accessing the menu
  722.     term.setCursorBlink(false) --HARDCODED
  723.     local tMenu = menuInit_settings() --HARDCODED intializing values
  724.    
  725.     nSel = 1 --index of selected element
  726.     opBack = {  text = "",
  727.                 type = "exit",
  728.                 param = nil,
  729.                 value = "BACK" }
  730.     table.insert(tMenu, opBack)
  731.    
  732.     --draw
  733.     drawMenu_bg()
  734.     drawMenu(tMenu, nSel)
  735.    
  736.     local bRunning = true
  737.     while bRunning do
  738.         --input
  739.         local e, p1 = os.pullEvent()
  740.         if e == "timer" and p1 == tTimer then --main loop interval timer
  741.             tick()
  742.         else --user input (IMPORTANT distinction)
  743.             if e == "key" then --key input
  744.                 if p1 == keys.up then --UP
  745.                     if nSel > 1 then
  746.                         nSel = nSel - 1 --move selection up
  747.                     else
  748.                         nSel = #tMenu end --move selection to bottom
  749.                        
  750.                 elseif p1 == keys.down then --DOWN
  751.                     if nSel < #tMenu then
  752.                         nSel = nSel + 1 --move selection down
  753.                     else
  754.                         nSel = 1 end --move selection to top
  755.                        
  756.                 elseif p1 == keys.left then --LEFT
  757.                     if tMenu[nSel].type == "bool" then --if bool
  758.                         tMenu[nSel].value = not tMenu[nSel].value --flip bool value
  759.                     elseif tMenu[nSel].type == "number" then --if number
  760.                         if tMenu[nSel].param[1] == nil then --if no min then decrease number
  761.                             tMenu[nSel].value = tMenu[nSel].value - 1
  762.                         elseif tMenu[nSel].value > tMenu[nSel].param[1] then --if num is above min then decrease number
  763.                             tMenu[nSel].value = tMenu[nSel].value - 1 end
  764.                     elseif tMenu[nSel].type == "table" then --if table
  765.                        
  766.                     end
  767.                 elseif p1 == keys.right then --RIGHT
  768.                     if tMenu[nSel].type == "bool" then --if bool
  769.                         tMenu[nSel].value = not tMenu[nSel].value --flip bool value
  770.                     elseif tMenu[nSel].type == "number" then --if number
  771.                         if tMenu[nSel].param[2] == nil then --if no max then increase number
  772.                             tMenu[nSel].value = tMenu[nSel].value + 1
  773.                         elseif tMenu[nSel].value < tMenu[nSel].param[2] then --if num is below max then increase number
  774.                             tMenu[nSel].value = tMenu[nSel].value + 1 end
  775.                     elseif tMenu[nSel].type == "table" then --if table
  776.                        
  777.                     end
  778.                
  779.                 elseif p1 == keys.enter then --ENTER
  780.                     if tMenu[nSel].type == "exit" then --exit menu
  781.                         bRunning = false
  782.                     elseif tMenu[nSel].type == "bool" then --flip bool value
  783.                         tMenu[nSel].value = not tMenu[nSel].value
  784.                     end
  785.                
  786.                 elseif p1 == keys.space then --SPACE
  787.                     if tMenu[nSel].type == "exit" then --exit menu
  788.                         bRunning = false
  789.                     elseif tMenu[nSel].type == "bool" then --flip bool value
  790.                         tMenu[nSel].value = not tMenu[nSel].value
  791.                     end
  792.                
  793.                 elseif p1 == keys.f1 then --F1
  794.                     bRunning = false
  795.                
  796.                 elseif p1 == keys.f4 then --F4
  797.                     bRunning = false
  798.                 end
  799.             end
  800.         end
  801.        
  802.         --draw
  803.         drawMenu_bg()
  804.         drawMenu(tMenu, nSel)
  805.     end
  806.    
  807.     --hardcoded grabbing values
  808.     bShowLineNum = tMenu[1].value
  809.     bShowLineNumSep = tMenu[2].value
  810.     bShowStatusBar = tMenu[3].value
  811.     bStatus_lineNum = tMenu[4].value
  812.     bStatus_lineCount = tMenu[5].value
  813.     bStatus_insert = tMenu[6].value
  814.     bShowMessages = tMenu[7].value
  815.     nMinLineDigitsVisible = tMenu[8].value
  816.     nSpacesPerTab = tMenu[9].value
  817.    
  818.     saveSettings()
  819.    
  820.     term.setCursorBlink(true) --HARDCODED (asumes top layer)
  821.     applyBoundrySettings() --HARDCODED (asumes top layer)
  822.    
  823.     os.sleep(0) --prevent carryover input
  824. end
  825.  
  826.  
  827. ------------------------
  828. --        CORE        --
  829. ------------------------
  830.  
  831. --initializes system settings (this is called at startup and after program execution)
  832. function initializeSystem()
  833.     term.setCursorBlink(true)
  834.     alignCursor()
  835. end
  836.  
  837. --intialization
  838. function intialize()
  839.     load() --load file
  840.     loadSettings() --loads user settings
  841.    
  842.     --setup screen
  843.     applyBoundrySettings()
  844.     draw()
  845.     initializeSystem()
  846. end
  847.  
  848. --handles user input
  849. function input()
  850.     --wait for key input
  851.     local e, p1 = os.pullEvent()
  852.    
  853.     --timer input
  854.     if e == "timer" and p1 == tTimer then --main loop interval timer
  855.         tick()
  856.     else --user input (IMPORTANT distinction)
  857.         sMessage = "" --clear message
  858.        
  859.         --EXIT if confirmed, otherwise reset confirmation flag
  860.         if bConfirmingExit == true and e == "key" and p1 == keys.f4 then
  861.             exit()
  862.         else
  863.             bConfirmingExit = false end
  864.        
  865.         --character input
  866.         if e == "char" then
  867.             insertText(p1)
  868.        
  869.         --non-character, key input
  870.         elseif e == "key" then
  871.             --DONT USE F2, F10, F11, ALT, ESC
  872.            
  873.             --functions
  874.             if p1 == keys.f1 then --menu
  875.                 menu()
  876.             elseif p1 == keys.f3 then --save
  877.                 save()
  878.             elseif p1 == keys.f4 then --exit
  879.                 exit()
  880.             elseif p1 == keys.f5 then --run program in editor
  881.                 run(false)
  882.             elseif p1 == keys.f6 then --run with arguments
  883.                 run(true)
  884.                
  885.             --movement
  886.             elseif p1 == keys.up then --up
  887.                 shiftCursor(0, -1)
  888.             elseif p1 == keys.down then --down
  889.                 shiftCursor(0, 1)
  890.             elseif p1 == keys.left then --left
  891.                 shiftCursor(-1, 0)
  892.             elseif p1 == keys.right then --right
  893.                 shiftCursor(1, 0)
  894.             elseif p1 == keys.home then --home
  895.                 shiftCursorHome()
  896.             elseif p1 == keys["end"] then --end
  897.                 shiftCursorEnd()
  898.             elseif p1 == keys.pageUp then --pasge up
  899.                 shiftCursor(0, -(yMax-yMin))
  900.             elseif p1 == keys.pageDown then --page down
  901.                 shiftCursor(0, yMax-yMin)
  902.            
  903.             --removing characters
  904.             elseif p1 == keys.backspace then --backspace
  905.                 key_backspace()
  906.             elseif p1 == keys.delete then --delete
  907.                 key_delete()
  908.            
  909.             --adding whitespace
  910.             elseif p1 == keys.enter then --enter
  911.                 key_enter()
  912.             elseif p1 == keys.tab then --tab
  913.                 insertText("\t")
  914.                 bUnsavedChanges = true
  915.                
  916.             --insert
  917.             elseif p1 == keys.insert then
  918.                 bInsert = not bInsert
  919.            
  920.             end
  921.         end
  922.     end
  923. end
  924.  
  925. --main timer interval actions
  926. function tick()
  927.     --do interval stuff
  928.     --insertText("HI")
  929.     --sMessage = "TIMER HIT!"
  930.    
  931.     tTimer = os.startTimer(TIMER_INTERVAL) --reset timer
  932. end
  933.  
  934. --main loop
  935. function mainLoop()
  936.     tTimer = os.startTimer(TIMER_INTERVAL) --start interval timer
  937.    
  938.     while bRunning do
  939.         if bClearDebug then
  940.             sDebugText = "" end --clear debug info
  941.        
  942.         input() --get input and respond
  943.        
  944.         --debug info
  945.         local x,y = term.getCursorPos()
  946.         --sDebugText = sDebugText .. " Running:" ..tostring(bRunning)
  947.         --sDebugText = sDebugText .. " File:"..xFile..","..yFile .." Scrn:"..x..","..y
  948.        
  949.         scroll() --shift screen to show cursor
  950.         draw() --update screen
  951.     end
  952. end
  953.  
  954. --exit program
  955. function exit()
  956.     if bUnsavedChanges and not bConfirmingExit then --if there are unsaved changes, and this warning hasnt displayed yet
  957.         sMessage = "UNSAVED CHANGES - Press F4 to exit"
  958.         bConfirmingExit = true
  959.     else
  960.         bRunning = false end
  961. end
  962.  
  963. --have the program exit cleanly and tidily
  964. function exitCleanup()
  965.     term.clear()
  966.     term.setCursorPos(SCREEN_ORIGIN,SCREEN_ORIGIN)
  967. end
  968.  
  969. intialize()
  970. mainLoop()
  971. exitCleanup()
Add Comment
Please, Sign In to add comment