Advertisement
tahg

Untitled

Feb 4th, 2014
136
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 41.10 KB | None | 0 0
  1.  
  2. --
  3. -- Lua IDE
  4. -- Made by GravityScore
  5. --
  6.  
  7.  
  8.  
  9.  
  10. -- Variables
  11.  
  12. local version = "2.0"
  13. local args = {...}
  14.  
  15.  
  16. local w, h = term.getSize()
  17. local tabWidth = 2
  18.  
  19. local autosaveInterval = 20
  20. local allowEditorEvent = true
  21. local keyboardShortcutTimeout = 0.4
  22.  
  23.  
  24. local clipboard = nil
  25.  
  26. local languages = {}
  27. local currentLanguage = {}
  28.  
  29.  
  30. local updateURL = "https://raw.github.com/GravityScore/LuaIDE/master/computercraft/ide.lua"
  31. local ideLocation = "/" .. shell.getRunningProgram()
  32. local themeLocation = "/.luaide-theme"
  33.  
  34.  
  35. local theme = {
  36. background = colors.gray,
  37. titleBar = colors.lightGray,
  38.  
  39. top = colors.lightBlue,
  40. bottom = colors.cyan,
  41.  
  42. button = colors.cyan,
  43. buttonHighlighted = colors.lightBlue,
  44.  
  45. editor_lineHighlight = colors.lightBlue,
  46. editor_errorLineHighlight = colors.pink,
  47.  
  48. dangerButton = colors.red,
  49. dangerButtonHighlighted = colors.pink,
  50.  
  51. text = colors.white,
  52. folder = colors.lime,
  53. readOnly = colors.red,
  54. }
  55.  
  56.  
  57.  
  58.  
  59. -- Utilities
  60.  
  61.  
  62. local isAdvanced = function()
  63. return term.isColor and term.isColor()
  64. end
  65.  
  66.  
  67.  
  68. local fill = function(x, y, w, h, text)
  69. if not text then
  70. text = " "
  71. end
  72.  
  73. for a = y, y + h - 1 do
  74. term.setCursorPos(x, a)
  75. term.write(string.rep(text, w))
  76. end
  77. end
  78.  
  79.  
  80. local clear = function()
  81. term.setBackgroundColor(theme.background)
  82. term.setTextColor(theme.text)
  83. term.clear()
  84. end
  85.  
  86.  
  87. local title = function(text)
  88. term.setBackgroundColor(theme.titleBar)
  89. fill(1, 2, w, 3)
  90. term.setCursorPos(2, 3)
  91. term.write(text)
  92. end
  93.  
  94.  
  95. local top = function(...)
  96. local text = {...}
  97. local width = 0
  98. for i, closing in pairs(text) do
  99. width = width < closing:len() and closing:len() or width
  100. end
  101.  
  102. term.setBackgroundColor(theme.top)
  103. fill(1, 6, width + 2, #text + 2)
  104. for i, closing in pairs(text) do
  105. term.setCursorPos(2, 6 + i)
  106. term.write(closing)
  107. end
  108. print("")
  109. print("")
  110. print("")
  111. end
  112.  
  113.  
  114. local bottom = function(...)
  115. local text = {...}
  116. local width = 0
  117. for i, closing in pairs(text) do
  118. width = width < closing:len() and closing:len() or width
  119. end
  120.  
  121. local cx, cy = term.getCursorPos()
  122. term.setBackgroundColor(theme.bottom)
  123. fill(w - width - 1, cy, width + 2, #text + 2)
  124. for i, closing in pairs(text) do
  125. term.setCursorPos(w - closing:len(), cy + i)
  126. term.write(closing)
  127. end
  128. end
  129.  
  130.  
  131. local center = function(text)
  132. local x, y = term.getCursorPos()
  133. term.setCursorPos(math.floor(w / 2 - text:len() / 2) + (text:len() % 2 == 0 and 1 or 0), y)
  134. term.write(text)
  135. term.setCursorPos(1, y + 1)
  136. end
  137.  
  138.  
  139.  
  140. local buttonGrid = function(buttons)
  141. term.setTextColor(theme.text)
  142. if #buttons == 2 then
  143. local y = math.floor(h / 2) - 1
  144.  
  145. buttons[1].x = math.floor(w / 2) - buttons[1].text:len() - 3
  146. buttons[1].y = y
  147.  
  148. buttons[2].x = math.floor(w / 2) + 1
  149. buttons[2].y = y
  150.  
  151. for i = 1, #buttons do
  152. term.setBackgroundColor(theme[buttons[i].id .. (buttons[i].selected and "Highlighted" or "")])
  153. fill(buttons[i].x, buttons[i].y, buttons[i].text:len() + 2, 3)
  154. term.setCursorPos(buttons[i].x + 1, buttons[i].y + 1)
  155. term.write(buttons[i].text)
  156. end
  157. elseif #buttons == 3 then
  158.  
  159. elseif #buttons == 4 then
  160. local y1 = math.floor(h / 2) - 2
  161. local y2 = math.floor(h / 2) + 3
  162.  
  163. buttons[1].x = math.floor(w / 2) - buttons[1].text:len() - 3
  164. buttons[1].y = y1
  165.  
  166. buttons[2].x = math.floor(w / 2) + 1
  167. buttons[2].y = y1
  168.  
  169. buttons[3].x = math.floor(w / 2) - buttons[3].text:len() - 3
  170. buttons[3].y = y2
  171.  
  172. buttons[4].x = math.floor(w / 2) + 1
  173. buttons[4].y = y2
  174.  
  175. for i = 1, #buttons do
  176. buttons[i].w = buttons[i].text:len() + 2
  177. buttons[i].h = 3
  178.  
  179. term.setBackgroundColor(theme[buttons[i].id .. (buttons[i].selected and "Highlighted" or "")])
  180. fill(buttons[i].x, buttons[i].y, buttons[i].text:len() + 2, 3)
  181. term.setCursorPos(buttons[i].x + 1, buttons[i].y + 1)
  182. term.write(buttons[i].text)
  183. end
  184. end
  185. end
  186.  
  187.  
  188.  
  189.  
  190. -- Modified Read
  191.  
  192.  
  193. local modifiedRead = function(properties)
  194. -- Properties:
  195. -- - replaceCharacter
  196. -- - displayLength
  197. -- - maxLength
  198. -- - onEvent
  199. -- - startingText
  200.  
  201. local text = ""
  202. local startX, startY = term.getCursorPos()
  203. local pos = 0
  204.  
  205. if not properties then
  206. properties = {}
  207. end
  208. if properties.displayLength then
  209. properties.displayLength = math.min(properties.displayLength, w - 2)
  210. end
  211. if properties.startingText then
  212. text = properties.startingText
  213. pos = text:len()
  214. end
  215.  
  216. local edit_draw = function(replaceCharacter)
  217. local scroll = 0
  218. if properties.displayLength and pos > properties.displayLength then
  219. scroll = pos - properties.displayLength
  220. end
  221.  
  222. local repl = replaceCharacter or properties.replaceCharacter
  223. term.setTextColor(theme.text)
  224. term.setCursorPos(startX, startY)
  225. if repl then
  226. term.write(string.rep(repl:sub(1, 1), text:len() - scroll))
  227. else
  228. term.write(text:sub(scroll + 1))
  229. end
  230.  
  231. term.setCursorPos(startX + pos - scroll, startY)
  232. end
  233.  
  234. term.setCursorBlink(true)
  235. edit_draw()
  236. while true do
  237. local event, key, x, y, param4, param5 = os.pullEvent()
  238.  
  239. if properties.onEvent then
  240. -- Actions:
  241. -- - exit (bool)
  242. -- - text
  243.  
  244. term.setCursorBlink(false)
  245. local action = properties.onEvent(text, event, key, x, y, param4, param5)
  246. if action then
  247. if action.text then
  248. edit_draw(" ")
  249. text = action.text
  250. if text then
  251. pos = text:len()
  252. end
  253. end if action.exit then
  254. break
  255. end
  256. end
  257. edit_draw()
  258. end
  259.  
  260. term.setCursorBlink(true)
  261. if event == "char" then
  262. local canType = true
  263. if properties.maxLength and text:len() >= properties.maxLength then
  264. canType = false
  265. end
  266.  
  267. if canType then
  268. text = text:sub(1, pos) .. key .. text:sub(pos + 1, -1)
  269. pos = pos + 1
  270. edit_draw()
  271. end
  272. elseif event == "key" then
  273. if key == keys.enter then
  274. break
  275. elseif key == keys.left and pos > 0 then
  276. pos = pos - 1
  277. edit_draw()
  278. elseif key == keys.right and pos < text:len() then
  279. pos = pos + 1
  280. edit_draw()
  281. elseif key == keys.backspace and pos > 0 then
  282. edit_draw(" ")
  283. text = text:sub(1, pos - 1) .. text:sub(pos + 1, -1)
  284. pos = pos - 1
  285. edit_draw()
  286. elseif key == keys.delete and pos < text:len() then
  287. edit_draw(" ")
  288. text = text:sub(1, pos) .. text:sub(pos + 2, -1)
  289. edit_draw()
  290. elseif key == keys.home then
  291. pos = 0
  292. edit_draw()
  293. elseif key == keys["end"] then
  294. pos = text:len()
  295. edit_draw()
  296. end
  297. elseif event == "mouse_click" then
  298. local scroll = 0
  299. if properties.displayLength and pos > properties.displayLength then
  300. scroll = pos - properties.displayLength
  301. end
  302.  
  303. if y == startY and x >= startX and x <= math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then
  304. pos = x - startX + scroll
  305. edit_draw()
  306. elseif y == startY then
  307. if x < startX then
  308. pos = scroll
  309. edit_draw()
  310. elseif x > math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then
  311. pos = text:len()
  312. edit_draw()
  313. end
  314. end
  315. end
  316. end
  317.  
  318. term.setCursorBlink(false)
  319. print("")
  320. return text
  321. end
  322.  
  323.  
  324.  
  325.  
  326. -- Updating
  327.  
  328.  
  329. local update_download = function(url, path)
  330.  
  331. end
  332.  
  333.  
  334. local update = function()
  335.  
  336. end
  337.  
  338.  
  339.  
  340.  
  341. -- Menu
  342.  
  343.  
  344. manageButtonGrid = function(buttons)
  345. buttonGrid(buttons)
  346.  
  347. while true do
  348. local event, key, x, y = os.pullEvent()
  349. if event == "key" then
  350. if key == keys.enter then
  351. return buttons[selection].action
  352. elseif key == keys.up then
  353. if selection > math.floor(#buttons / 2) then
  354. buttons[selection].selected = false
  355. selection = selection - 2
  356. buttons[selection].selected = true
  357. end
  358. elseif key == keys.down then
  359. if selection <= math.floor(#buttons / 2) then
  360. buttons[selection].selected = false
  361. selection = selection + 2
  362. buttons[selection].selected = true
  363. end
  364. elseif key == keys.left then
  365. if selection > 0 then
  366. buttons[selection].selected = false
  367. selection = selection - 1
  368. buttons[selection].selected = true
  369. end
  370. elseif key == keys.right then
  371. if selection < #buttons then
  372. buttons[selection].selected = false
  373. selection = selection + 1
  374. buttons[selection].selected = true
  375. end
  376. end
  377.  
  378. buttonGrid(buttons)
  379. elseif event == "mouse_click" then
  380. for _, button in pairs(buttons) do
  381. if x >= button.x and y >= button.y and x < button.x + button.w and y < button.y + button.h then
  382. return button.action
  383. end
  384. end
  385. end
  386. end
  387. end
  388.  
  389.  
  390. menu_items = function()
  391. local selection = 1
  392. local buttons = {
  393. {text = "New File", id = "button", selected = true, action = "new"},
  394. {text = "Open File", id = "button", selected = false, action = "open"},
  395. {text = "Settings", id = "button", selected = false, action = "settings"},
  396. {text = "Exit", id = "dangerButton", selected = false, action = "exit"},
  397. }
  398.  
  399. clear()
  400. title("Lua IDE : Welcome")
  401. return manageButtonGrid(buttons)
  402. end
  403.  
  404.  
  405.  
  406.  
  407. -- Settings
  408.  
  409.  
  410. settings = function()
  411.  
  412. end
  413.  
  414.  
  415.  
  416.  
  417. -- Files
  418.  
  419.  
  420. currentDirectory = ""
  421. fileScroll = 0
  422.  
  423.  
  424. fileSelect_draw = function()
  425. term.setBackgroundColor(theme.top)
  426. fill(2, 9, w - 2, h - 9)
  427.  
  428. local files = fs.list(currentDirectory)
  429. if currentDirectory ~= "" then
  430. table.insert(files, 1, "Back [..]")
  431. end
  432.  
  433. for i = fileScroll + 1, h - 10 + fileScroll do
  434. local name = files[i]
  435. if name then
  436. term.setCursorPos(3, (i - fileScroll) + 8)
  437. local path = currentDirectory .. "/" .. name
  438. if fs.isReadOnly(path) and name ~= "Back [..]" then
  439. term.setTextColor(theme.readOnly)
  440. term.write(name .. (fs.isDir(path) and "/" or ""))
  441. elseif fs.isDir(path) or name == "Back [..]" then
  442. term.setTextColor(theme.folder)
  443. term.write(name .. (name == "Back [..]" and "" or "/"))
  444. else
  445. term.setTextColor(theme.text)
  446. term.write(name)
  447. end
  448. term.setBackgroundColor(theme.top)
  449. end
  450. end
  451. end
  452.  
  453.  
  454. fileSelect_onEvent = function(text, event, key, x, y)
  455. local files = fs.list(currentDirectory)
  456. if currentDirectory ~= "" then
  457. table.insert(files, 1, "Back [..]")
  458. end
  459.  
  460. edit_draw()
  461. if event == "key" then
  462. if key == keys.up and fileScroll > 0 then
  463. fileScroll = fileScroll - 1
  464. edit_draw()
  465. elseif key == keys.down and fileScroll + (h - 10) < #files then
  466. fileScroll = fileScroll + 1
  467. edit_draw()
  468. elseif key == keys.leftCtrl or key == keys.rightCtrl then
  469. return {text = nil, exit = true}
  470. end
  471.  
  472. elseif event == "mouse_click" then
  473. if x >= 3 and x <= w - 4 and y >= 9 and y <= h - 2 then
  474. local selection = y - 8 + fileScroll
  475. local name = files[selection]
  476.  
  477. if name == "Back [..]" and currentDirectory ~= "" then
  478. currentDirectory = currentDirectory:sub(1, currentDirectory:find(fs.getName(currentDirectory)) - 2)
  479. fileScroll = 0
  480. edit_draw()
  481. return {text = currentDirectory .. "/"}
  482. elseif fs.isDir(currentDirectory .. "/" .. name) then
  483. currentDirectory = currentDirectory .. "/" .. name
  484. fileScroll = 0
  485. edit_draw()
  486. return {text = currentDirectory .. "/"}
  487. elseif fileTypes:find("open") then
  488. return {text = currentDirectory .. "/" .. name, exit = true}
  489. end
  490. end
  491.  
  492. elseif event == "mouse_fileScroll" then
  493. if key > 0 and fileScroll + (h - 10) < #files then
  494. fileScroll = fileScroll + 1
  495. elseif key < 0 and fileScroll > 0 then
  496. fileScroll = fileScroll - 1
  497. end
  498. end
  499. end
  500.  
  501.  
  502. fileSelect_main = function(selectType, callback)
  503. edit_draw()
  504. term.setBackgroundColor(theme.top)
  505. fill(2, 6, w - 2, 3)
  506. term.setCursorPos(3, 7)
  507. term.write("Path: ")
  508.  
  509. if selectType:find("new") then
  510. local path = modifiedRead({startingText = "/", displayLength = w - 4, onEvent = file.select.onEvent})
  511. if path:sub(1, 1) ~= "/" then
  512. path = "/" .. path
  513. end
  514.  
  515. return path
  516.  
  517. elseif selectType:find("open") then
  518. local text = "/"
  519.  
  520. term.setTextColor(theme.text)
  521. term.setCursorPos(9, 7)
  522. term.write(text)
  523.  
  524. while true do
  525. local event, key, x, y = os.pullEvent()
  526. local action = onEvent(text, event, key, x, y)
  527.  
  528. if action and action.text then
  529. text = action.text
  530.  
  531. if text then
  532. term.setTextColor(theme.text)
  533. term.setCursorPos(9, 7)
  534. term.write(string.rep(" ", w - 11))
  535. term.setCursorPos(9, 7)
  536. term.write(text)
  537. end
  538. end
  539.  
  540. if action and action.exit then
  541. return text
  542. end
  543. end
  544. end
  545. end
  546.  
  547.  
  548.  
  549. file_newFile = function()
  550. clear()
  551. title("Lua IDE : New File")
  552. local path = selectFile("new")
  553. if not path then
  554. return "menu"
  555. end
  556.  
  557. return "edit", path
  558. end
  559.  
  560.  
  561. file_openFile = function()
  562. clear()
  563. title("Lua IDE : Open File")
  564. local path = selectFile("open")
  565. if not path then
  566. return "menu"
  567. end
  568.  
  569. return "edit", path
  570. end
  571.  
  572.  
  573.  
  574.  
  575. -- Languages
  576.  
  577.  
  578. languages.lua = {}
  579. languages.brainfuck = {}
  580. languages.none = {}
  581.  
  582.  
  583. languages.lua.keywords = {
  584. ["and"] = "conditional",
  585. ["break"] = "conditional",
  586. ["do"] = "conditional",
  587. ["else"] = "conditional",
  588. ["elseif"] = "conditional",
  589. ["end"] = "conditional",
  590. ["for"] = "conditional",
  591. ["function"] = "conditional",
  592. ["if"] = "conditional",
  593. ["in"] = "conditional",
  594. ["local"] = "conditional",
  595. ["not"] = "conditional",
  596. ["or"] = "conditional",
  597. ["repeat"] = "conditional",
  598. ["return"] = "conditional",
  599. ["then"] = "conditional",
  600. ["until"] = "conditional",
  601. ["while"] = "conditional",
  602.  
  603. ["true"] = "constant",
  604. ["false"] = "constant",
  605. ["nil"] = "constant",
  606.  
  607. ["print"] = "function",
  608. ["write"] = "function",
  609. ["sleep"] = "function",
  610. ["pairs"] = "function",
  611. ["ipairs"] = "function",
  612. ["loadstring"] = "function",
  613. ["loadfile"] = "function",
  614. ["dofile"] = "function",
  615. ["rawset"] = "function",
  616. ["rawget"] = "function",
  617. ["setfenclosing"] = "function",
  618. ["getfenclosing"] = "function",
  619. }
  620.  
  621.  
  622. languages.lua.parseError = function(err)
  623. local parsedErr = {
  624. filename = "unknown",
  625. line = -1,
  626. display = "Unknown!",
  627. err = ""
  628. }
  629.  
  630. if err and err ~= "" then
  631. parsedErr.err = err
  632. if err:find(":") then
  633. parsedErr.filename = err:sub(1, err:find(":") - 1):gsub("^%s*(.-)%s*$", "%1")
  634.  
  635. err = (err:sub(err:find(":") + 1) .. ""):gsub("^%s*(.-)%s*$", "%1") .. ""
  636. if err:find(":") then
  637. parsedErr.line = err:sub(1, err:find(":") - 1)
  638. if tonumber(parsedError.line) then
  639. parsedError.line = tonumber(parsedError.line)
  640. end
  641.  
  642. err = err:sub(err:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. ""
  643. end
  644. end
  645.  
  646. parsedErr.display = err:sub(1, 1):upper() .. err:sub(2, -1) .. "."
  647. end
  648.  
  649. return parsedErr
  650. end
  651.  
  652.  
  653. languages.lua.getCompilerErrors = function(code)
  654. local _, err = loadstring(code)
  655. if err then
  656. local a = err:find("]", 1, true)
  657. if a then
  658. err = "string" .. err:sub(a + 1, -1)
  659. end
  660.  
  661. return languages.lua.parseError(err)
  662. else
  663. return languages.lua.parseError(nil)
  664. end
  665. end
  666.  
  667.  
  668. languages.lua.run = function(path, arguments)
  669. local fn, err = loadfile(path)
  670. setfenclosing(fn, getfenclosing())
  671. if not err then
  672. _, err = pcall(function() fn(unpack(arguments)) end)
  673. end
  674.  
  675. return err
  676. end
  677.  
  678.  
  679.  
  680.  
  681. languages.brainfuck.keywords = {}
  682.  
  683.  
  684. languages.brainfuck.parseError = function(err)
  685. local parsedError = {filename = "unknown", line = -1, display = "Unknown!", err = ""}
  686. if err and err ~= "" then
  687. parsedError.err = err
  688. parsedError.line = err:sub(1, err:find(":") - 1)
  689. if tonumber(parsedError.line) then
  690. parsedError.line = tonumber(parsedError.line)
  691. end
  692.  
  693. err = err:sub(err:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. ""
  694.  
  695. parsedError.display = err:sub(1, 1):upper() .. err:sub(2, -1) .. "."
  696. end
  697.  
  698. return parsedError
  699. end
  700.  
  701.  
  702. languages.brainfuck.mapLoops = function(code)
  703. local loopLocations = {}
  704. local loc = 1
  705. local line = 1
  706.  
  707. for let in string.gmatch(code, ".") do
  708. if let == "[" then
  709. loopLocations[loc] = true
  710. elseif let == "]" then
  711. local found = false
  712. for i = loc, 1, -1 do
  713. if loopLocations[i] == true then
  714. loopLocations[i] = loc
  715. found = true
  716. end
  717. end
  718.  
  719. if not found then
  720. return line .. ": No matching '['"
  721. end
  722. end
  723.  
  724. if let == "\n" then
  725. line = line + 1
  726. end
  727. loc = loc + 1
  728. end
  729.  
  730. return loopLocations
  731. end
  732.  
  733.  
  734. languages.brainfuck.getCompilerErrors = function(code)
  735. local a = languages.brainfuck.mapLoops(code)
  736. if type(a) == "string" then
  737. return languages.brainfuck.parseError(a)
  738. else
  739. return languages.brainfuck.parseError(nil)
  740. end
  741. end
  742.  
  743.  
  744. languages.brainfuck.run = function(path)
  745. local f = io.open(path, "r")
  746. local content = f:read("*a")
  747. f:close()
  748.  
  749. local dataCells = {}
  750. local dataPointer = 1
  751. local instructionPointer = 1
  752.  
  753. local loopLocations = languages.brainfuck.mapLoops(content)
  754. if type(loopLocations) == "string" then
  755. return loopLocations
  756. end
  757.  
  758. local cdp = function()
  759. if not dataCells[tostring(dataPointer)] then
  760. dataCells[tostring(dataPointer)] = 0
  761. end
  762. end
  763.  
  764. while true do
  765. local let = content:sub(instructionPointer, instructionPointer)
  766.  
  767. if let == ">" then
  768. dataPointer = dataPointer + 1
  769. cdp()
  770. elseif let == "<" then
  771. cdp()
  772. dataPointer = dataPointer - 1
  773. cdp()
  774. elseif let == "+" then
  775. cdp()
  776. dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] + 1
  777. elseif let == "-" then
  778. cdp()
  779. dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] - 1
  780. elseif let == "." then
  781. cdp()
  782. if term.getCursorPos() >= w then print("") end
  783. write(string.char(math.max(1, dataCells[tostring(dataPointer)])))
  784. elseif let == "," then
  785. cdp()
  786. term.setCursorBlink(true)
  787. local e, key = os.pullEvent("char")
  788. term.setCursorBlink(false)
  789. dataCells[tostring(dataPointer)] = string.byte(key)
  790.  
  791. if term.getCursorPos() >= w then
  792. print("")
  793. end
  794. write(key)
  795. elseif let == "/" then
  796. cdp()
  797. if term.getCursorPos() >= w then print("") end
  798. write(dataCells[tostring(dataPointer)])
  799. elseif let == "[" and dataCells[tostring(dataPointer)] == 0 then
  800. for k, closing in pairs(loopLocations) do
  801. if k == instructionPointer then
  802. instructionPointer = closing
  803. end
  804. end
  805. elseif let == "]" then
  806. for k, closing in pairs(loopLocations) do
  807. if closing == instructionPointer then
  808. instructionPointer = k - 1
  809. end
  810. end
  811. end
  812.  
  813. instructionPointer = instructionPointer + 1
  814. if instructionPointer > content:len() then
  815. print("")
  816. break
  817. end
  818. end
  819. end
  820.  
  821.  
  822.  
  823.  
  824. languages.none.keywords = {}
  825.  
  826. languages.none.parseError = function(err)
  827. return {filename = "", line = -1, display = "", err = ""}
  828. end
  829.  
  830. languages.none.getCompilerErrors = function(code)
  831. return languages.none.parseError(nil)
  832. end
  833.  
  834. languages.none.run = function(path) end
  835.  
  836.  
  837.  
  838.  
  839. -- File Save Management
  840.  
  841.  
  842. file_save = function(path, lines)
  843. local dir = path:sub(1, path:len() - fs.getName(path):len())
  844. if not fs.exists(dir) then
  845. fs.makeDir(dir)
  846. end
  847.  
  848. if not fs.isDir(path) and not fs.isReadOnly(path) then
  849. local contents = ""
  850. for _, closing in pairs(lines) do
  851. contents = contents .. closing .. "\n"
  852. end
  853.  
  854. local f = io.open(path, "w")
  855. f:write(a)
  856. f:close()
  857. return true
  858. else
  859. return false
  860. end
  861. end
  862.  
  863.  
  864. file_load = function(path)
  865. if not fs.exists(path) then
  866. local dir = path:sub(1, path:len() - fs.getName(path):len())
  867. if not fs.exists(dir) then
  868. fs.makeDir(dir)
  869. end
  870.  
  871. local f = io.open(path, "w")
  872. f:write("")
  873. f:close()
  874. end
  875.  
  876. local lines = {}
  877. if fs.exists(path) and not fs.isDir(path) then
  878. local f = io.open(path, "r")
  879. if f then
  880. local line = f:read("*lines")
  881. while a do
  882. table.insert(lines, line)
  883. line = f:read("*lines")
  884. end
  885. f:close()
  886. end
  887. else
  888. return nil
  889. end
  890.  
  891. if #lines == 0 then
  892. table.insert(lines, "")
  893. end
  894.  
  895. return lines
  896. end
  897.  
  898.  
  899.  
  900.  
  901. -- Clipboard
  902.  
  903.  
  904. local clipboard_cut = function(lines, y)
  905. clipboard = lines[y]
  906. table.remove(lines, y)
  907. return lines
  908. end
  909.  
  910.  
  911. local clipboard_copy = function(lines, y)
  912. clipboard = lines[y]
  913. end
  914.  
  915.  
  916. local clipboard_paste = function(lines, y)
  917. if clipboard then
  918. table.insert(lines, y, clipboard)
  919. end
  920. return lines
  921. end
  922.  
  923.  
  924. local removeLine = function(lines, y)
  925. table.remove(lines, y)
  926. return lines
  927. end
  928.  
  929.  
  930. local clearLine = function(lines, y)
  931. lines[y] = ""
  932. return lines
  933. end
  934.  
  935. local setSyntax = function(lines)
  936. if currentLanguage == languages.brainfuck and lines[1] ~= "-- Syntax: Brainfuck" then
  937. table.insert(lines, 1, "-- Syntax: Brainfuck")
  938. end
  939.  
  940. return lines
  941. end
  942.  
  943.  
  944.  
  945.  
  946. -- Reindenting
  947.  
  948.  
  949. local reindent = function(lines)
  950.  
  951. end
  952.  
  953.  
  954.  
  955.  
  956. -- Editor
  957.  
  958.  
  959. local x = 1
  960. local y = 1
  961. local offsetX = 0
  962. local offsetY = 1
  963. local scrollX = 0
  964. local scrollY = 0
  965.  
  966. local editorWidth = 0
  967. local editorHeight = h - offsetY
  968.  
  969. local lines = {}
  970.  
  971. local autosaveClock = 0
  972. local scrollClock = 0
  973. local liveErrorClock = 0
  974. local hasScrolled = false
  975.  
  976. local displayErrorCode = false
  977. local liveError = currentLanguage.parseError(nil)
  978.  
  979. local liveCompletions = {
  980. ["("] = ")",
  981. ["{"] = "}",
  982. ["["] = "]",
  983. ["\""] = "\"",
  984. ["'"] = "'",
  985. }
  986.  
  987. local standardsCompletions = {
  988. "if%s+.+%s+then%s*$",
  989. "for%s+.+%s+do%s*$",
  990. "while%s+.+%s+do%s*$",
  991. "repeat%s*$",
  992. "function%s+[a-zA-Z_0-9]?\(.*\)%s*$",
  993. "=%s*function%s*\(.*\)%s*$",
  994. "else%s*$",
  995. "elseif%s+.+%s+then%s*$"
  996. }
  997.  
  998.  
  999.  
  1000.  
  1001. -- Menu
  1002.  
  1003.  
  1004. local menu_items = {
  1005. {"File",
  1006. "About",
  1007. "Settings",
  1008. "New File ^+N",
  1009. "Open File ^+O",
  1010. "Save File ^+S",
  1011. "Close ^+W",
  1012. "Print ^+P",
  1013. "Quit ^+Q"
  1014. }, {"Edit",
  1015. "Cut Line ^+X",
  1016. "Copy Line ^+C",
  1017. "Paste Line ^+V",
  1018. "Delete Line",
  1019. "Clear Line"
  1020. }, {"Functions",
  1021. "Go To Line ^+G",
  1022. "Re-Indent ^+I",
  1023. "Set Syntax ^+E",
  1024. "Start of Line ^+<",
  1025. "End of Line ^+>"
  1026. }, {"Run",
  1027. "Run Program ^+R",
  1028. "Run w/ Args ^+Shift+R"
  1029. }
  1030. }
  1031.  
  1032.  
  1033. local menu_shortcuts = {
  1034. -- File
  1035. ["ctrl n"] = "New File ^+N",
  1036. ["ctrl o"] = "Open File ^+O",
  1037. ["ctrl s"] = "Save File ^+S",
  1038. ["ctrl w"] = "Close ^+W",
  1039. ["ctrl p"] = "Print ^+P",
  1040. ["ctrl q"] = "Quit ^+Q",
  1041.  
  1042. -- Edit
  1043. ["ctrl x"] = "Cut Line ^+X",
  1044. ["ctrl c"] = "Copy Line ^+C",
  1045. ["ctrl v"] = "Paste Line ^+V",
  1046.  
  1047. -- Functions
  1048. ["ctrl g"] = "Go To Line ^+G",
  1049. ["ctrl i"] = "Re-Indent ^+I",
  1050. ["ctrl e"] = "Set Syntax ^+E",
  1051. ["ctrl 203"] = "Start of Line ^+<",
  1052. ["ctrl 205"] = "End of Line ^+>",
  1053.  
  1054. -- Run
  1055. ["ctrl r"] = "Run Program ^+R",
  1056. ["ctrl shift r"] = "Run w/ Args ^+Shift+R"
  1057. }
  1058.  
  1059.  
  1060. local menu_functions = {
  1061. -- Return Properties
  1062. -- - action
  1063. -- - lines
  1064. -- - cursorY
  1065.  
  1066. -- File
  1067. ["About"] = function() about() end,
  1068. ["Settings"] = function() return {action = "settings"} end,
  1069. ["New File ^+N"] = function() return {action = "new"} end,
  1070. ["Open File ^+O"] = function() return {action = "open"} end,
  1071. ["Save File ^+S"] = function() end,
  1072. ["Close ^+W"] = function() return {action = "menu_items"} end,
  1073. ["Print ^+P"] = function() end,
  1074. ["Quit ^+Q"] = function() return {action = "exit"} end,
  1075.  
  1076. -- Edit
  1077. ["Cut Line ^+X"] = function(path, lines, y) return {lines = clipboard_cut(lines, y)} end,
  1078. ["Copy Line ^+C"] = function(path, lines, y) clipboard_copy(lines, y) end,
  1079. ["Paste Line ^+V"] = function(path, lines, y) return {lines = clipboard_paste(lines, y)} end,
  1080. ["Delete Line"] = function(path, lines, y) return {lines = removeLine(lines, y)} end,
  1081. ["Clear Line"] = function(path, lines, y) return {lines = clearLine(lines, y)} end,
  1082.  
  1083. -- Functions
  1084. ["Go To Line ^+G"] = function() return {"cursorY" = goto()} end,
  1085. ["Re-Indent ^+I"] = function(path, lines) return {lines = reindent(lines)} end,
  1086. ["Set Syntax ^+E"] = function(path, lines) return {lines = setSyntax(lines)} end,
  1087. ["Start of Line ^+<"] = function() os.queueEvent("key", keys.home) end,
  1088. ["End of Line ^+>"] = function() os.queueEvent("key", keys.end) end,
  1089.  
  1090. -- Run
  1091. ["Run Program ^+R"] = function(path, lines) run(path, lines, false) end,
  1092. ["Run w/ Args ^+Shift+R"] = function(path, lines) run(path, lines, true) end,
  1093. }
  1094.  
  1095.  
  1096.  
  1097.  
  1098. -- Editor Setup
  1099.  
  1100.  
  1101. edit_setup = function(path)
  1102. lines = loadFile(path)
  1103. if not lines then
  1104. return "menu_items"
  1105. end
  1106.  
  1107. if lines[1] == "-- Syntax: Brainfuck" then
  1108. currentLanguage = languages.brainfuck
  1109. end
  1110.  
  1111. x = 1
  1112. y = 1
  1113. offsetX = 0
  1114. offsetY = 1
  1115. scrollX = 0
  1116. scrollY = 0
  1117.  
  1118. editorWidth = 0
  1119. editorHeight = h - offsetY
  1120.  
  1121. autosaveClock = os.clock()
  1122. scrollClock = os.clock()
  1123. liveErrorClock = os.clock()
  1124. hasScrolled = false
  1125.  
  1126. displayErrorCode = false
  1127. liveError = currentLanguage.parseError(nil)
  1128.  
  1129. edit_draw()
  1130. term.setCursorPos(x + offsetX, y + offsetY)
  1131. term.setCursorBlink(true)
  1132. end
  1133.  
  1134.  
  1135.  
  1136.  
  1137. -- Editor Menu
  1138.  
  1139.  
  1140. menu_draw = function(open)
  1141. -- Top row
  1142. term.setCursorPos(1, 1)
  1143. term.setTextColor(theme.text)
  1144. term.setBackgroundColor(theme.backgroundHighlight)
  1145. term.clearLine()
  1146.  
  1147. -- Main menu items
  1148. local padding = 3
  1149. local curX = 0
  1150. for _, item in pairs(menu_items) do
  1151. term.setCursorPos(padding + curX, 1)
  1152. term.write(item[1])
  1153. curX = curX + item[1]:len() + padding
  1154. end
  1155.  
  1156. if open then
  1157. -- Get the main menu item
  1158. local item = {}
  1159. local x = 1
  1160. for _, test in pairs(menu_items) do
  1161. if open == test[1] then
  1162. item = test
  1163. break
  1164. end
  1165.  
  1166. x = x + test[1]:len() + padding
  1167. end
  1168. x = x + 1
  1169.  
  1170. -- Get each item under the main menu item
  1171. local items = {}
  1172. for i = 2, #item do
  1173. table.insert(items, item[i])
  1174. end
  1175.  
  1176. -- Get the maximum length of these items
  1177. local width = 1
  1178. for _, item in pairs(items) do
  1179. if item:len() + 2 > width then
  1180. width = item:len() + 2
  1181. end
  1182. end
  1183.  
  1184. -- Draw items
  1185. fill(x, offsetY + 1, width, #items)
  1186. for i, item in pairs(items) do
  1187. term.setCursorPos(x + 1, i + offsetY)
  1188. term.write(item)
  1189. end
  1190.  
  1191. -- One more row for padding
  1192. term.setCursorPos(x, #items + 2)
  1193. term.write(string.rep(" ", width))
  1194.  
  1195. return items, width
  1196. end
  1197. end
  1198.  
  1199.  
  1200. menu_trigger = function(cx, cy)
  1201. -- Determine clicked menu
  1202. local padding = 3
  1203. local curX = 0
  1204. local clicked = nil
  1205. for _, item in pairs(menu) do
  1206. if cx >= curX + padding and cx <= curX + item[1]:len() + 2 then
  1207. clicked = item[1]
  1208. break
  1209. end
  1210.  
  1211. curX = curX + item[1]:len() + padding
  1212. end
  1213.  
  1214. local menuX = curX + 2
  1215. if not clicked then
  1216. return false
  1217. end
  1218.  
  1219. -- Flash menu item
  1220. term.setCursorBlink(false)
  1221. term.setCursorPos(menuX, 1)
  1222. term.setBackgroundColor(theme.background)
  1223. term.write(string.rep(" ", clicked:len() + 2))
  1224. term.setCursorPos(menuX + 1, 1)
  1225. term.write(clicked)
  1226. sleep(0.1)
  1227.  
  1228. local items, width = drawMenu(clicked)
  1229. local action = nil
  1230. local ox, oy = term.getCursorPos()
  1231.  
  1232. while not action do
  1233. local e, but, x, y = os.pullEvent()
  1234.  
  1235. if e == "mouse_click" then
  1236. -- Click outside menu bounds
  1237. if x < menuX - 1 or x > menuX + width - 1 then
  1238. break
  1239. elseif y > #items + 2 then
  1240. break
  1241. elseif y == 1 then
  1242. break
  1243. end
  1244.  
  1245. for i, v in ipairs(items) do
  1246. if y == i + 1 and x >= menuX and x <= menuX + width - 2 then
  1247. -- Flash
  1248. term.setBackgroundColor(theme.background)
  1249. fill(menuX, y, width, 1)
  1250. term.setCursorPos(menuX + 1, y)
  1251. term.write(v)
  1252. sleep(0.1)
  1253.  
  1254. drawMenu(clicked)
  1255.  
  1256. action = v
  1257. break
  1258. end
  1259. end
  1260. end
  1261. end
  1262.  
  1263. term.setCursorPos(ox, oy)
  1264. term.setCursorBlink(true)
  1265. return action
  1266. end
  1267.  
  1268.  
  1269. menu_executeItem = function(item, path)
  1270. if menu_functions[item] then
  1271. file_save(path, lines)
  1272. local actions = menu_functions[item](path, lines, y)
  1273.  
  1274. term.setCursorBlink(false)
  1275. if actions then
  1276. if actions.action then
  1277. return actions.action
  1278. end
  1279. if actions.lines then
  1280. lines = actions.lines
  1281.  
  1282. if #lines < 1 then
  1283. table.insert(lines, "")
  1284. end
  1285. y = math.min(y, #lines)
  1286. x = math.min(x, lines[y]:len() + 1)
  1287. end
  1288. if actions.cursorY then
  1289. x = 1
  1290. y = math.min(#lines, actions.cursorY)
  1291. edit_setCursorLocation(x, y)
  1292. end
  1293. end
  1294.  
  1295. term.setCursorBlink(true)
  1296. draw()
  1297. term.setCursorPos(x - scrollX + offsetX, y - scrollY + offsetY)
  1298. end
  1299. end
  1300.  
  1301.  
  1302.  
  1303.  
  1304. -- Editor Drawing
  1305.  
  1306.  
  1307. edit_draw = function()
  1308. clear()
  1309. menu_draw()
  1310.  
  1311. offsetX = tostring(#lines):len() + 1
  1312. offsetY = 1
  1313. editorWidth = w - offsetX
  1314. editorHeight = h - 1
  1315.  
  1316. for i = 1, editorHeight do
  1317. local line = lines[scrollY + i]
  1318. if line then
  1319. -- Line number
  1320. local lineNumber = string.rep(" ", offsetX - 1 - tostring(scrollY + i):len()) .. tostring(scrollY + i) .. ":"
  1321.  
  1322. if liveError.line == scrollY + i then
  1323. lineNumber = string.rep(" ", offsetX - 2) .. "!:"
  1324. end
  1325.  
  1326. term.setCursorPos(1, i + offsetY)
  1327. term.setTextColor(theme.text)
  1328.  
  1329. -- Line background
  1330. term.setBackgroundColor(theme.background)
  1331. if scrollY + i == y then
  1332. if scrollY + i == liveError.line and os.clock() - lastEventClock > 3 then
  1333. term.setBackgroundColor(theme.editor_errorLineHighlight)
  1334. else
  1335. term.setBackgroundColor(theme.editor_lineHightlight)
  1336. end
  1337. elseif scrollY + i == liveError.line then
  1338. term.setBackgroundColor(theme.editor_errorLine)
  1339. end
  1340. term.clearLine()
  1341.  
  1342. -- Text
  1343. term.setCursorPos(1 - scrollX + offsetX, i + offsetY)
  1344. if scrollY + i == liveError.line then
  1345. if displayErrorCode then
  1346. term.write(liveError.display)
  1347. else
  1348. term.write(line)
  1349. end
  1350. else
  1351. writeHighlighted(line)
  1352. end
  1353.  
  1354. -- Line numbers
  1355. term.setCursorPos(1, i + offsetY)
  1356. if scrollY + i == y then
  1357. if scrollY + i == liveError.line and os.clock() - lastEventClock > 3 then
  1358. term.setBackgroundColor(theme.editor_errorLine)
  1359. else
  1360. term.setBackgroundColor(theme.editor_lineNumberHighlight)
  1361. end
  1362. elseif scrollY + i == liveError.line then
  1363. term.setBackgroundColor(theme.editor_errorLineHighlight)
  1364. else
  1365. term.setBackgroundColor(theme.editor_lineNumber)
  1366. end
  1367. term.write(lineNumber)
  1368. end
  1369. end
  1370.  
  1371. term.setCursorPos(x - scrollX + offsetX, y - scrollY + offsetY)
  1372. end
  1373.  
  1374.  
  1375. edit_drawLine = function(...)
  1376. local linesToDraw = {...}
  1377. offsetX = tostring(#lines):len() + 1
  1378.  
  1379. for _, lineY in pairs(linesToDraw) do
  1380. local line = lines[lineY]
  1381. if line then
  1382. -- Line number
  1383. local lineNumber = string.rep(" ", offsetX - 1 - tostring(lineY):len()) .. tostring(lineY) .. ":"
  1384.  
  1385. if liveError.line == lineY then
  1386. lineNumber = string.rep(" ", offsetX - 2) .. "!:"
  1387. end
  1388.  
  1389. term.setCursorPos(1, (lineY - scrollY) + offsetY)
  1390. term.setBackgroundColor(theme.background)
  1391.  
  1392. -- Background color
  1393. if lineY == y then
  1394. if lineY == liveError.line and os.clock() - lastEventClock > 3 then
  1395. term.setBackgroundColor(theme.editor_errorLineHighlight)
  1396. else
  1397. term.setBackgroundColor(theme.editor_lineHightlight)
  1398. end
  1399. elseif lineY == liveError.line then
  1400. term.setBackgroundColor(theme.editor_errorLine)
  1401. end
  1402. term.clearLine()
  1403.  
  1404. -- Text
  1405. term.setCursorPos(1 - scrollX + offsetX, (lineY - scrollY) + offsetY)
  1406. if lineY == liveError.line then
  1407. if displayErrorCode then
  1408. term.write(liveError.display)
  1409. else
  1410. term.write(line)
  1411. end
  1412. else
  1413. writeHighlighted(line)
  1414. end
  1415.  
  1416. -- Line Number
  1417. term.setCursorPos(1, (lineY - scrollY) + offsetY)
  1418. if lineY == y then
  1419. if lineY == liveError.line and os.clock() - lastEventClock > 3 then
  1420. term.setBackgroundColor(theme.editor_errorLine)
  1421. else
  1422. term.setBackgroundColor(theme.editor_lineNumberHighlight)
  1423. end
  1424. elseif lineY == liveError.line then
  1425. term.setBackgroundColor(theme.editor_errorLineHighlight])
  1426. else
  1427. term.setBackgroundColor(theme.editor_lineNumber)
  1428. end
  1429. term.write(lineNumber)
  1430. end
  1431. end
  1432.  
  1433. term.setCursorPos(x - scrollX + offsetX, y - scrollY + offsetY)
  1434. end
  1435.  
  1436.  
  1437.  
  1438.  
  1439. -- Editor Event Handling
  1440.  
  1441.  
  1442. edit_setCursorLocation = function(nx, ny)
  1443. local nScrollX, nScrollY = nx - scrollX, ny - scrollY
  1444. local redraw = false
  1445.  
  1446. if nScrollX < 1 then
  1447. scrollX = nx - 1
  1448. nScrollX = 1
  1449. redraw = true
  1450. elseif nScrollX > editorWidth then
  1451. scrollX = nx - editorWidth
  1452. nScrollX = editorWidth
  1453. redraw = true
  1454. end
  1455.  
  1456. if nScrollY < 1 then
  1457. scrollY = y - 1
  1458. nScrollY = 1
  1459. redraw = true
  1460. elseif nScrollY > editorHeight then
  1461. scrollY = y - editorHeight
  1462. nScrollY = editorHeight
  1463. redraw = true
  1464. end
  1465.  
  1466. if redraw or y - scrollY + offsetY < offsetY + 1 then
  1467. edit_draw()
  1468. end
  1469.  
  1470. term.setCursorPos(nScrollX + offsetX, nScrollY + offsetY)
  1471. end
  1472.  
  1473.  
  1474. edit_handleKey = function(key)
  1475. if key == keys.up and y > 1 then
  1476. x = math.min(x, lines[y - 1]:len() + 1)
  1477. y = y - 1
  1478. edit_drawLine(y, y + 1)
  1479. edit_setCursorLocation(x, y)
  1480.  
  1481. elseif key == keys.down and y < #lines then
  1482. x = math.min(x, lines[y + 1]:len() + 1)
  1483. y = y + 1
  1484. edit_drawLine(y, y - 1)
  1485. edit_setCursorLocation(x, y)
  1486.  
  1487. elseif key == keys.left and x > 1 then
  1488. x = x - 1
  1489. edit_setCursorLocation(x, y)
  1490.  
  1491. elseif key == keys.right and x < lines[y]:len() + 1 then
  1492. x = x + 1
  1493. edit_setCursorLocation(x, y)
  1494.  
  1495. elseif (key == keys.enter or key == keys.numPadEnter) then
  1496. if displayErrorCode or y + scrollY - 1 == liveError.line then
  1497. return
  1498. end
  1499.  
  1500. local completion = nil
  1501. for _, completion in pairs(standardsCompletions) do
  1502. if lines[y]:find(completion) and x == #lines[y] + 1 then -- If there is a completion, and the cursor is at the end of the line
  1503. completion = completion
  1504. end
  1505. end
  1506.  
  1507. -- Count the number of spaces at the start of the line
  1508. local _, spaces = lines[y]:find("^[ ]+")
  1509. if not spaces then
  1510. spaces = 0
  1511. end
  1512.  
  1513. if completion then
  1514. table.insert(lines, y + 1, string.rep(" ", spaces + 2))
  1515.  
  1516. -- Insert the second line
  1517. if not completion:find("else", 1, true) and not completion:find("elseif", 1, true) then
  1518. local line = string.rep(" ", spaces)
  1519. if completion:find("repeat", 1, true) then
  1520. line = line .. "until "
  1521. elseif completion:find("{", 1, true) then
  1522. line = line .. "}"
  1523. else
  1524. line = line .. "end"
  1525. end
  1526.  
  1527. table.insert(lines, y + 2, line)
  1528. end
  1529.  
  1530. x = spaces + 3
  1531. y = y + 1
  1532. edit_setCursorLocation(x, y)
  1533. else
  1534. local oldLine = lines[y]
  1535. lines[y] = lines[y]:sub(1, x - 1)
  1536. table.insert(lines, y + 1, string.rep(" ", spaces) .. oldLine:sub(x, -1))
  1537.  
  1538. x = spaces + 1
  1539. y = y + 1
  1540. edit_setCursorLocation(x, y)
  1541. end
  1542.  
  1543. elseif key == keys.backspace then
  1544. if displayErrorCode or y + scrollY - 1 == liveError.line then
  1545. return
  1546. end
  1547.  
  1548. if x > 1 then
  1549. local f = false
  1550. for k, closing in pairs(liveCompletions) do
  1551. if lines[y]:sub(x - 1, x - 1) == k then f = true end
  1552. end
  1553.  
  1554. lines[y] = lines[y]:sub(1, x - 2) .. lines[y]:sub(x + (f and 1 or 0), -1)
  1555. edit_drawLine(y)
  1556. x = x - 1
  1557. edit_setCursorLocation(x, y)
  1558. elseif y > 1 then
  1559. local prevLen = lines[y - 1]:len() + 1
  1560. lines[y - 1] = lines[y - 1] .. lines[y]
  1561. table.remove(lines, y)
  1562. x, y = prevLen, y - 1
  1563.  
  1564. edit_draw()
  1565. edit_setCursorLocation(x, y)
  1566. end
  1567.  
  1568. elseif key == keys.home then
  1569. x = 1
  1570. edit_setCursorLocation(x, y)
  1571.  
  1572. elseif key == keys["end"] then
  1573. x = lines[y]:len() + 1
  1574. edit_setCursorLocation(x, y)
  1575.  
  1576. elseif key == keys.delete then
  1577. if displayErrorCode or y + scrollY - 1 == liveError.line then
  1578. return
  1579. end
  1580.  
  1581. if x < lines[y]:len() + 1 then
  1582. lines[y] = lines[y]:sub(1, x - 1) .. lines[y]:sub(x + 1)
  1583. edit_drawLine(y)
  1584. edit_setCursorLocation(x, y)
  1585. elseif y < #lines then
  1586. lines[y] = lines[y] .. lines[y + 1]
  1587. table.remove(lines, y + 1)
  1588. edit_draw()
  1589. edit_setCursorLocation(x, y)
  1590. end
  1591.  
  1592. elseif key == keys.tab then
  1593. if displayErrorCode or y + scrollY - 1 == liveError.line then
  1594. return
  1595. end
  1596.  
  1597. lines[y] = string.rep(" ", tabWidth) .. lines[y]
  1598. x = x + 2
  1599. edit_drawLine(y)
  1600. edit_setCursorLocation(x, y)
  1601.  
  1602. elseif key == keys.pageUp then
  1603. y = math.min(math.max(y - editorHeight, 1), #lines)
  1604. x = math.min(lines[y]:len() + 1, x)
  1605. edit_setCursorLocation(x, y, true)
  1606.  
  1607. elseif key == keys.pageDown then
  1608. y = math.min(math.max(y + editorHeight, 1), #lines)
  1609. x = math.min(lines[y]:len() + 1, x)
  1610. edit_setCursorLocation(x, y, true)
  1611. end
  1612. end
  1613.  
  1614.  
  1615. edit_handleChar = function(key)
  1616. if displayErrorCode or y + scrollY - 1 == liveError.line then
  1617. return
  1618. end
  1619.  
  1620. -- If we are typing the second character of a live completion (eg the second ")
  1621. local shouldIgnore = false
  1622. for opening, closing in pairs(liveCompletions) do
  1623. if key == closing and lines[y]:find(opening, 1, true) and lines[y]:sub(x, x) == closing then
  1624. shouldIgnore = true
  1625. end
  1626. end
  1627.  
  1628. -- Whether to add the second character of a live completions (eg the second " after typing the first)
  1629. local addOne = nil
  1630. if not shouldIgnore then
  1631. for opening, closing in pairs(liveCompletions) do
  1632. if key == opening and lines[y]:sub(x, x) ~= opening then
  1633. addOne = closing
  1634. end
  1635. end
  1636.  
  1637. lines[y] = lines[y]:sub(1, x - 1) .. key .. (addOne and addOne or "") .. lines[y]:sub(x, -1)
  1638. end
  1639.  
  1640. x = x + key:len()
  1641. edit_drawLine(y)
  1642. edit_setCursorLocation(x, y)
  1643. end
  1644.  
  1645.  
  1646. edit_handleMouseClick = function(button, cx, cy)
  1647. if cy > 1 then
  1648. if cx <= offsetX and cy - offsetY == liveError.line - scrollY then
  1649. -- Trigger showing the live error text
  1650. displayErrorCode = not displayErrorCode
  1651. edit_drawLine(liveError.line)
  1652. else
  1653. local oldy = y
  1654. y = math.min(math.max(scrollY + cy - offsetY, 1), #lines)
  1655. x = math.min(math.max(scrollX + cx - offsetX, 1), lines[y]:len() + 1)
  1656.  
  1657. if oldy ~= y then
  1658. edit_drawLine(oldy, y)
  1659. end
  1660. edit_setCursorLocation(x, y)
  1661. end
  1662. else
  1663. local selectedMenu = triggerMenu(cx, cy)
  1664. if selectedMenu then
  1665. local action = menu_executeItem(selectedMenu, path)
  1666. if action then
  1667. return action
  1668. end
  1669. end
  1670. end
  1671. end
  1672.  
  1673.  
  1674. edit_handleShortcut = function(modifier, key)
  1675. local item = menu_shortcuts[modifier .. " " .. key]
  1676. if item then
  1677. -- Find the parent menu item
  1678. local parent = nil
  1679. local curX = 0
  1680. for i, potentialParent in pairs(menu_items) do
  1681. for _, subItem in pairs(potentialParent) do
  1682. if subItem == item then
  1683. parent = menu_items[i][1]
  1684. break
  1685. end
  1686. end
  1687.  
  1688. curX = curX + potentialParent[1]:len() + 3
  1689. end
  1690. local menuX = curX + 2
  1691.  
  1692. -- Flash parent item
  1693. term.setCursorBlink(false)
  1694. term.setBackgroundColor(colors[theme.background])
  1695. fill(menuX, 1, parent:len() + 2, 1)
  1696. term.setCursorPos(menuX + 1, 1)
  1697. term.write(parent)
  1698. sleep(0.1)
  1699. drawMenu()
  1700.  
  1701. -- Execute
  1702. local action = menu_executeItem(item, path)
  1703. if action then
  1704. return action
  1705. end
  1706. end
  1707. end
  1708.  
  1709.  
  1710. edit_handleMouseScroll = function(direction)
  1711. if direction == -1 and scrollY > 0 then
  1712. scrollY = scrollY - 1
  1713. elseif direction == 1 and scrollY < #lines - editorHeight then
  1714. scrollY = scrollY + 1
  1715. end
  1716.  
  1717. if os.clock() - scrollClock > 0.0005 then
  1718. edit_draw()
  1719. term.setCursorPos(x - scrollX + offsetX, y - scrollY + offsetY)
  1720. end
  1721.  
  1722. scrollClock = os.clock()
  1723. hasScrolled = true
  1724. end
  1725.  
  1726.  
  1727. edit_loop = function()
  1728. local e, key, cx, cy = os.pullEvent()
  1729.  
  1730. if e == "key" and allowEditorEvent then
  1731. edit_handleKey(key)
  1732. elseif e == "char" and allowEditorEvent then
  1733. edit_handleChar(key)
  1734. elseif e == "mouse_click" and key == 1 then
  1735. local action = edit_handleMouseClick(key, cx, cy)
  1736. if action then
  1737. return action
  1738. end
  1739. elseif e == "shortcut" then
  1740. local action = edit_handleShortcut(key, cx)
  1741. if action then
  1742. return action
  1743. end
  1744. elseif e == "mouse_scroll" then
  1745. edit_handleMouseScroll(key)
  1746. end
  1747.  
  1748. if hasScrolled and os.clock() - scrollClock > 0.1 then
  1749. edit_draw()
  1750. term.setCursorPos(x - scrollX + offsetX, y - scrollY + offsetY)
  1751. hasScrolled = false
  1752. end
  1753.  
  1754. if os.clock() - autosaveClock > autosaveInterval then
  1755. saveFile(path, lines)
  1756. autosaveClock = os.clock()
  1757. end
  1758.  
  1759. if os.clock() - liveErrorClock > 1 then
  1760. local previousLiveError = liveError
  1761. liveError = currentLanguage.parseError(nil)
  1762. local code = ""
  1763. for _, closing in pairs(lines) do
  1764. code = code .. closing .. "\n"
  1765. end
  1766.  
  1767. liveError = currentLanguage.getCompilerErrors(code)
  1768. liveError.line = math.min(liveError.line - 2, #lines)
  1769. if liveError ~= previousLiveError then
  1770. edit_draw()
  1771. end
  1772.  
  1773. liveErrorClock = os.clock()
  1774. end
  1775. end
  1776.  
  1777.  
  1778.  
  1779.  
  1780. -- Editor Main
  1781.  
  1782.  
  1783. edit = function(path)
  1784. local action = edit_setup()
  1785. if action then
  1786. return action
  1787. end
  1788.  
  1789. while true do
  1790. local action = edit_loop()
  1791. if action then
  1792. return action
  1793. end
  1794. end
  1795.  
  1796. return "menu"
  1797. end
  1798.  
  1799.  
  1800.  
  1801.  
  1802. -- Main
  1803.  
  1804.  
  1805. local main = function(programArgs)
  1806. local option = "menu"
  1807. local args = nil
  1808.  
  1809. if #programArgs > 0 then
  1810. local path = "/" .. shell.resolve(programArgs[1])
  1811. if fs.isDir(path) then
  1812. print("Cannot edit a directory.")
  1813. return
  1814. else
  1815. option = "edit"
  1816. args = path
  1817. end
  1818. end
  1819.  
  1820.  
  1821. while true do
  1822. if option == "menu" then
  1823. option = menu_items()
  1824. end
  1825.  
  1826. if option == "new" then
  1827. option, args = file_newFile()
  1828. end
  1829.  
  1830. if option == "open" then
  1831. option, args = file_openFile()
  1832. end
  1833.  
  1834. if option == "settings" then
  1835. option = settings()
  1836. end
  1837.  
  1838. if option == "exit" then
  1839. break
  1840. end
  1841.  
  1842. if option == "edit" and args then
  1843. option = edit(args)
  1844. end
  1845. end
  1846. end
  1847.  
  1848.  
  1849. local success, err = pcall(function()
  1850. main(args)
  1851. end)
  1852.  
  1853.  
  1854. term.setCursorBlink(false)
  1855. if err and not err:find("Terminated") then
  1856. clear()
  1857. title("Lua IDE : Crash Report")
  1858. top("Lua IDE encountered an unexpected crash.", "Please report this error to GravityScore.")
  1859.  
  1860. term.setBackgroundColor(theme.background)
  1861. print(err)
  1862. print("")
  1863.  
  1864. bottom("Press any key to exit.")
  1865.  
  1866. os.pullEvent("key")
  1867. os.queueEvent("")
  1868. os.pullEvent()
  1869. end
  1870.  
  1871.  
  1872. term.setBackgroundColor(colors.black)
  1873. term.setTextColor(colors.white)
  1874. term.clear()
  1875. term.setCursorPos(1, 1)
  1876. center("Thank You for Using Lua IDE " .. version)
  1877. center("Made by GravityScore")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement