Advertisement
Parlocameon

LuaIDE Download Source

Dec 19th, 2014
53
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 67.37 KB | None | 0 0
  1. --
  2. -- Lua IDE
  3. -- Made by GravityScore
  4. --
  5.  
  6.  
  7.  
  8.  
  9. -- Variables
  10.  
  11. local arguments = {...}
  12.  
  13. local version = "1.3"
  14.  
  15. local doCursorWrap = true
  16. local tabWidth = 2
  17. local autosaveInterval = 20
  18. local keyboardShortcutTimeout = 0.4
  19. -- local updateURL = "https://raw.github.com/GravityScore/LuaIDE/master/luaide.lua"
  20. local updateURL = "https://raw.github.com/DeGariless/LuaIDE/master/luaide.lua"
  21. local themeLocation = "/.luaide_theme"
  22.  
  23. local w, h = term.getSize()
  24. local ideLocation = "/" .. shell.getRunningProgram()
  25.  
  26. local allowEditorEvent = true
  27. local clipboard = nil
  28. local languages = {}
  29. local currentLanguage = {}
  30.  
  31. local theme = {
  32. background = colors.gray,
  33. titleBar = colors.lightGray,
  34.  
  35. top = colors.lightBlue,
  36. bottom = colors.cyan,
  37.  
  38. button = colors.cyan,
  39. buttonHighlighted = colors.lightBlue,
  40.  
  41. dangerButton = colors.red,
  42. dangerButtonHighlighted = colors.pink,
  43.  
  44. text = colors.white,
  45. folder = colors.lime,
  46. readOnly = colors.red,
  47. }
  48.  
  49. local function isAdvanced()
  50. return term.isColor and term.isColor()
  51. end
  52.  
  53.  
  54. -- -------- Utilities
  55.  
  56. local function modRead(properties)
  57. local w, h = term.getSize()
  58. local defaults = {replaceChar = nil, history = nil, visibleLength = nil, textLength = nil,
  59. liveUpdates = nil, exitOnKey = nil}
  60. if not properties then properties = {} end
  61. for k, v in pairs(defaults) do if not properties[k] then properties[k] = v end end
  62. if properties.replaceChar then properties.replaceChar = properties.replaceChar:sub(1, 1) end
  63. if not properties.visibleLength then properties.visibleLength = w end
  64.  
  65. local sx, sy = term.getCursorPos()
  66. local line = ""
  67. local pos = 0
  68. local historyPos = nil
  69.  
  70. local function redraw(repl)
  71. local scroll = 0
  72. if properties.visibleLength and sx + pos > properties.visibleLength + 1 then
  73. scroll = (sx + pos) - (properties.visibleLength + 1)
  74. end
  75.  
  76. term.setCursorPos(sx, sy)
  77. local a = repl or properties.replaceChar
  78. if a then term.write(string.rep(a, line:len() - scroll))
  79. else term.write(line:sub(scroll + 1, -1)) end
  80. term.setCursorPos(sx + pos - scroll, sy)
  81. end
  82.  
  83. local function sendLiveUpdates(event, ...)
  84. if type(properties.liveUpdates) == "function" then
  85. local ox, oy = term.getCursorPos()
  86. local a, data = properties.liveUpdates(line, event, ...)
  87. if a == true and data == nil then
  88. term.setCursorBlink(false)
  89. return line
  90. elseif a == true and data ~= nil then
  91. term.setCursorBlink(false)
  92. return data
  93. end
  94. term.setCursorPos(ox, oy)
  95. end
  96. end
  97.  
  98. term.setCursorBlink(true)
  99. while true do
  100. local e, but, x, y, p4, p5 = os.pullEvent()
  101.  
  102. if e == "char" then
  103. local s = false
  104. if properties.textLength and line:len() < properties.textLength then s = true
  105. elseif not properties.textLength then s = true end
  106.  
  107. local canType = true
  108. if not properties.grantPrint and properties.refusePrint then
  109. local canTypeKeys = {}
  110. if type(properties.refusePrint) == "table" then
  111. for _, v in pairs(properties.refusePrint) do
  112. table.insert(canTypeKeys, tostring(v):sub(1, 1))
  113. end
  114. elseif type(properties.refusePrint) == "string" then
  115. for char in properties.refusePrint:gmatch(".") do
  116. table.insert(canTypeKeys, char)
  117. end
  118. end
  119. for _, v in pairs(canTypeKeys) do if but == v then canType = false end end
  120. elseif properties.grantPrint then
  121. canType = false
  122. local canTypeKeys = {}
  123. if type(properties.grantPrint) == "table" then
  124. for _, v in pairs(properties.grantPrint) do
  125. table.insert(canTypeKeys, tostring(v):sub(1, 1))
  126. end
  127. elseif type(properties.grantPrint) == "string" then
  128. for char in properties.grantPrint:gmatch(".") do
  129. table.insert(canTypeKeys, char)
  130. end
  131. end
  132. for _, v in pairs(canTypeKeys) do if but == v then canType = true end end
  133. end
  134.  
  135. if s and canType then
  136. line = line:sub(1, pos) .. but .. line:sub(pos + 1, -1)
  137. pos = pos + 1
  138. redraw()
  139. end
  140. elseif e == "key" then
  141. if but == keys.enter then break
  142. elseif but == keys.left then if pos > 0 then pos = pos - 1 redraw() end
  143. elseif but == keys.right then if pos < line:len() then pos = pos + 1 redraw() end
  144. elseif (but == keys.up or but == keys.down) and properties.history then
  145. redraw(" ")
  146. if but == keys.up then
  147. if historyPos == nil and #properties.history > 0 then
  148. historyPos = #properties.history
  149. elseif historyPos > 1 then
  150. historyPos = historyPos - 1
  151. end
  152. elseif but == keys.down then
  153. if historyPos == #properties.history then historyPos = nil
  154. elseif historyPos ~= nil then historyPos = historyPos + 1 end
  155. end
  156.  
  157. if properties.history and historyPos then
  158. line = properties.history[historyPos]
  159. pos = line:len()
  160. else
  161. line = ""
  162. pos = 0
  163. end
  164.  
  165. redraw()
  166. local a = sendLiveUpdates("history")
  167. if a then return a end
  168. elseif but == keys.backspace and pos > 0 then
  169. redraw(" ")
  170. line = line:sub(1, pos - 1) .. line:sub(pos + 1, -1)
  171. pos = pos - 1
  172. redraw()
  173. local a = sendLiveUpdates("delete")
  174. if a then return a end
  175. elseif but == keys.home then
  176. pos = 0
  177. redraw()
  178. elseif but == keys.delete and pos < line:len() then
  179. redraw(" ")
  180. line = line:sub(1, pos) .. line:sub(pos + 2, -1)
  181. redraw()
  182. local a = sendLiveUpdates("delete")
  183. if a then return a end
  184. elseif but == keys["end"] then
  185. pos = line:len()
  186. redraw()
  187. elseif properties.exitOnKey then
  188. if but == properties.exitOnKey or (properties.exitOnKey == "control" and
  189. (but == 29 or but == 157)) then
  190. term.setCursorBlink(false)
  191. return nil
  192. end
  193. end
  194. end
  195. local a = sendLiveUpdates(e, but, x, y, p4, p5)
  196. if a then return a end
  197. end
  198.  
  199. term.setCursorBlink(false)
  200. if line ~= nil then line = line:gsub("^%s*(.-)%s*$", "%1") end
  201. return line
  202. end
  203.  
  204.  
  205. -- -------- Themes
  206.  
  207. local defaultTheme = {
  208. background = "gray",
  209. backgroundHighlight = "lightGray",
  210. prompt = "cyan",
  211. promptHighlight = "lightBlue",
  212. err = "red",
  213. errHighlight = "pink",
  214.  
  215. editorBackground = "gray",
  216. editorLineHightlight = "lightBlue",
  217. editorLineNumbers = "gray",
  218. editorLineNumbersHighlight = "lightGray",
  219. editorError = "pink",
  220. editorErrorHighlight = "red",
  221.  
  222. textColor = "white",
  223. conditional = "yellow",
  224. constant = "orange",
  225. ["function"] = "magenta",
  226. string = "red",
  227. comment = "lime"
  228. }
  229.  
  230. local normalTheme = {
  231. background = "black",
  232. backgroundHighlight = "black",
  233. prompt = "black",
  234. promptHighlight = "black",
  235. err = "black",
  236. errHighlight = "black",
  237.  
  238. editorBackground = "black",
  239. editorLineHightlight = "black",
  240. editorLineNumbers = "black",
  241. editorLineNumbersHighlight = "white",
  242. editorError = "black",
  243. editorErrorHighlight = "black",
  244.  
  245. textColor = "white",
  246. conditional = "white",
  247. constant = "white",
  248. ["function"] = "white",
  249. string = "white",
  250. comment = "white"
  251. }
  252.  
  253. local availableThemes = {
  254. {"Water (Default)", "https://raw.github.com/GravityScore/LuaIDE/master/themes/default.txt"},
  255. {"Fire", "https://raw.github.com/GravityScore/LuaIDE/master/themes/fire.txt"},
  256. {"Sublime Text 2", "https://raw.github.com/GravityScore/LuaIDE/master/themes/st2.txt"},
  257. {"Midnight", "https://raw.github.com/GravityScore/LuaIDE/master/themes/midnight.txt"},
  258. {"TheOriginalBIT", "https://raw.github.com/GravityScore/LuaIDE/master/themes/bit.txt"},
  259. {"Superaxander", "https://raw.github.com/GravityScore/LuaIDE/master/themes/superaxander.txt"},
  260. {"Forest", "https://raw.github.com/GravityScore/LuaIDE/master/themes/forest.txt"},
  261. {"Night", "https://raw.github.com/GravityScore/LuaIDE/master/themes/night.txt"},
  262. {"Original", "https://raw.github.com/GravityScore/LuaIDE/master/themes/original.txt"},
  263. }
  264.  
  265. local function loadTheme(path)
  266. local f = io.open(path)
  267. local l = f:read("*l")
  268. local config = {}
  269. while l ~= nil do
  270. local k, v = string.match(l, "^(%a+)=(%a+)")
  271. if k and v then config[k] = v end
  272. l = f:read("*l")
  273. end
  274. f:close()
  275. return config
  276. end
  277.  
  278. -- Load Theme
  279. if isAdvanced() then theme = defaultTheme
  280. else theme = normalTheme end
  281.  
  282.  
  283. -- -------- Drawing
  284.  
  285. local function centerPrint(text, ny) --TODO fix this function to work with any size screen
  286. if type(text) == "table" then for _, v in pairs(text) do centerPrint(v) end
  287. else
  288. local x, y = term.getCursorPos()
  289. local w, h = term.getSize()
  290. term.setCursorPos(w/2 - text:len()/2 + (#text % 2 == 0 and 1 or 0), ny or y)
  291. print(text)
  292. end
  293. end
  294.  
  295. local function title(t)
  296. term.setTextColor(colors[theme.textColor])
  297. term.setBackgroundColor(colors[theme.background])
  298. term.clear()
  299.  
  300. term.setBackgroundColor(colors[theme.backgroundHighlight])
  301. for i = 2, 4 do term.setCursorPos(1, i) term.clearLine() end
  302. term.setCursorPos(3, 3)
  303. term.write(t)
  304. end
  305.  
  306. local function centerRead(wid, begt)
  307. local function liveUpdate(line, e, but, x, y, p4, p5)
  308. if isAdvanced() and e == "mouse_click" and x >= w/2 - wid/2 and x <= w/2 - wid/2 + 10
  309. and y >= 13 and y <= 15 then
  310. return true, ""
  311. end
  312. end
  313.  
  314. if not begt then begt = "" end
  315. term.setTextColor(colors[theme.textColor])
  316. term.setBackgroundColor(colors[theme.promptHighlight])
  317. for i = 8, 10 do
  318. term.setCursorPos(w/2 - wid/2, i)
  319. term.write(string.rep(" ", wid))
  320. end
  321.  
  322. if isAdvanced() then
  323. term.setBackgroundColor(colors[theme.errHighlight])
  324. for i = 13, 15 do
  325. term.setCursorPos(w/2 - wid/2 + 1, i)
  326. term.write(string.rep(" ", 10))
  327. end
  328. term.setCursorPos(w/2 - wid/2 + 2, 14)
  329. term.write("> Cancel")
  330. end
  331.  
  332. term.setBackgroundColor(colors[theme.promptHighlight])
  333. term.setCursorPos(w/2 - wid/2 + 1, 9)
  334. term.write("> " .. begt)
  335. return modRead({visibleLength = w/2 + wid/2, liveUpdates = liveUpdate})
  336. end
  337.  
  338.  
  339. -- -------- Prompt
  340.  
  341. local function promptMenu(itemList)
  342. local sel = 0
  343. local menuScroll = 1
  344. maxItems = math.floor((h - 5)/3) -- Calculate how many items can fit on the screen
  345. if not isAdvanced() then
  346. sel = 1 -- highlight 1st choice if computer is not touch screen
  347. end
  348.  
  349. local function draw()
  350. term.setTextColor(colors[theme.textColor])
  351. for i = menuScroll, math.min(menuScroll + maxItems - 1, #itemList) do -- draw menu Items
  352. if i == sel then
  353. term.setBackgroundColor(colors[theme.promptHighlight])
  354. else
  355. term.setBackgroundColor(colors[theme.prompt])
  356. end
  357. term.setCursorPos(1, 6 + (i - menuScroll) * 3)
  358. term.clearLine()
  359. term.setCursorPos(1, 7 + (i - menuScroll) * 3)
  360. term.clearLine()
  361. if i == sel then
  362. term.write(" > ")
  363. else
  364. term.write(" - ")
  365. end
  366. term.write(itemList[i])
  367. term.setCursorPos(1, 8 + (i - menuScroll) * 3)
  368. term.clearLine()
  369. end
  370.  
  371. if #itemList > maxItems then -- draw scroll buttons
  372. term.setTextColor(colors[theme.background])
  373. if menuScroll > 1 then
  374. term.setCursorPos(w-1,7)
  375. if sel == menuScroll then
  376. term.setBackgroundColor(colors[theme.promptHighlight])
  377. else
  378. term.setBackgroundColor(colors[theme.prompt])
  379. end
  380. term.write("^")
  381. end
  382. if menuScroll ~= #itemList - maxItems + 1 then
  383. term.setCursorPos(w-1,maxItems * 3 + 4)
  384. if sel == menuScroll + maxItems - 1 then
  385. term.setBackgroundColor(colors[theme.promptHighlight])
  386. else
  387. term.setBackgroundColor(colors[theme.prompt])
  388. end
  389. term.write("V")
  390. end
  391. term.setTextColor(colors[theme.textColor])
  392. end
  393. end -- end of draw() function
  394.  
  395. if h < 12 then
  396. error("screen is not tall enough for these menus") --TODO make menu work for smaller screens (turtles)
  397. else
  398. draw()
  399. sleep(.5)
  400. while true do
  401. local event,but,x,y = os.pullEventRaw()
  402. if event == "mouse_scroll" then -- mouse scroll
  403. menuScroll = math.max(math.min(menuScroll + but, #itemList - maxItems + 1),1)
  404. draw()
  405. elseif event == "key" then
  406. if but == 208 or but == 205 then -- down or right arrow key
  407. if sel < menuScroll or sel > menuScroll + maxItems then
  408. sel = menuScroll
  409. else
  410. sel = math.min(sel + 1, #itemList)
  411. if sel == menuScroll + maxItems then
  412. menuScroll = menuScroll + 1
  413. end
  414. end
  415. draw()
  416. elseif but == 200 or but == 203 then -- up or left arrow key
  417. if sel < menuScroll or sel > menuScroll + maxItems then
  418. sel = menuScroll + maxItems - 1
  419. else
  420. sel = math.max(sel - 1, 1)
  421. if sel < menuScroll then
  422. menuScroll = menuScroll - 1
  423. end
  424. end
  425. draw()
  426. elseif but == 28 and sel ~= 0 then --enter
  427. break
  428. end
  429. elseif event == "mouse_click" and but == 1 then
  430. if y > 6 and y < maxItems * 3 + 6 and x < w - 3 then
  431. sel = math.ceil((y - 5)/3) + menuScroll - 1
  432. draw()
  433. sleep(0.07)
  434. break
  435. elseif x == w - 1 and y == 7 then -- scroll up button
  436. menuScroll = math.max(math.min(menuScroll - 1, #itemList - maxItems + 1),1)
  437. draw()
  438. elseif x == w - 1 and y == maxItems * 3 + 4 then -- scroll down button
  439. menuScroll = math.max(math.min(menuScroll + 1, #itemList - maxItems + 1),1)
  440. draw()
  441. end
  442. elseif e == "terminate" then
  443. return "exit"
  444. end
  445. end
  446. return itemList[sel]
  447. end
  448. end
  449.  
  450.  
  451.  
  452.  
  453. local function prompt(list, dir, isGrid) -- MARKER this is function is currently not used
  454. local function draw(sel)
  455. for i, v in ipairs(list) do
  456. if i == sel then term.setBackgroundColor(v.highlight or colors[theme.promptHighlight])
  457. else term.setBackgroundColor(v.bg or colors[theme.prompt]) end
  458. term.setTextColor(v.tc or colors[theme.textColor])
  459. for i = -1, 1 do
  460. term.setCursorPos(v[2], v[3] + i)
  461. term.write(string.rep(" ", v[1]:len() + 4))
  462. end
  463.  
  464. term.setCursorPos(v[2], v[3])
  465. if i == sel then
  466. term.setBackgroundColor(v.highlight or colors[theme.promptHighlight])
  467. term.write(" > ")
  468. else term.write(" - ") end
  469. term.write(v[1] .. " ")
  470. end
  471. end
  472.  
  473. local key1 = dir == "horizontal" and 203 or 200
  474. local key2 = dir == "horizontal" and 205 or 208
  475. local sel = 1
  476. draw(sel)
  477.  
  478. while true do
  479. local e, but, x, y = os.pullEventRaw()
  480. if e == "key" and but == 28 then
  481. return list[sel][1]
  482. elseif e == "key" and but == key1 and sel > 1 then
  483. sel = sel - 1
  484. draw(sel)
  485. elseif e == "key" and but == key2 and ((err == true and sel < #list - 1) or (sel < #list)) then
  486. sel = sel + 1
  487. draw(sel)
  488. elseif isGrid and e == "key" and but == 203 and sel > 2 then
  489. sel = sel - 2
  490. draw(sel)
  491. elseif isGrid and e == "key" and but == 205 and sel < 3 then
  492. sel = sel + 2
  493. draw(sel)
  494. elseif e == "mouse_click" then
  495. for i, v in ipairs(list) do
  496. if x >= v[2] - 1 and x <= v[2] + v[1]:len() + 3 and y >= v[3] - 1 and y <= v[3] + 1 then
  497. return list[i][1]
  498. end
  499. end
  500. elseif e == "terminate" then
  501. return "exit"
  502. end
  503. end
  504. end
  505.  
  506.  
  507.  
  508. local function scrollingPrompt(list) -- MARKER this is function is currently not used
  509. local function draw(items, sel, loc)
  510. for i, v in ipairs(items) do
  511. local bg = colors[theme.prompt]
  512. local bghigh = colors[theme.promptHighlight]
  513. if v:find("Back") or v:find("Return") then
  514. bg = colors[theme.err]
  515. bghigh = colors[theme.errHighlight]
  516. end
  517.  
  518. if i == sel then term.setBackgroundColor(bghigh)
  519. else term.setBackgroundColor(bg) end
  520. term.setTextColor(colors[theme.textColor])
  521. for x = -1, 1 do
  522. term.setCursorPos(3, (i * 4) + x + 4)
  523. term.write(string.rep(" ", w - 13))
  524. end
  525.  
  526. term.setCursorPos(3, i * 4 + 4)
  527. if i == sel then
  528. term.setBackgroundColor(bghigh)
  529. term.write(" > ")
  530. else term.write(" - ") end
  531. term.write(v .. " ")
  532. end
  533. end
  534.  
  535. local function updateDisplayList(items, loc, len)
  536. local ret = {}
  537. for i = 1, len do
  538. local item = items[i + loc - 1]
  539. if item then table.insert(ret, item) end
  540. end
  541. return ret
  542. end
  543.  
  544. -- Variables
  545. local sel = 1
  546. local loc = 1
  547. local len = 3
  548. local disList = updateDisplayList(list, loc, len)
  549. draw(disList, sel, loc)
  550.  
  551. -- Loop
  552. while true do
  553. local e, key, x, y = os.pullEvent()
  554.  
  555. if e == "mouse_click" then
  556. for i, v in ipairs(disList) do
  557. if x >= 3 and x <= w - 11 and y >= i * 4 + 3 and y <= i * 4 + 5 then return v end
  558. end
  559. elseif e == "key" and key == 200 then
  560. if sel > 1 then
  561. sel = sel - 1
  562. draw(disList, sel, loc)
  563. elseif loc > 1 then
  564. loc = loc - 1
  565. disList = updateDisplayList(list, loc, len)
  566. draw(disList, sel, loc)
  567. end
  568. elseif e == "key" and key == 208 then
  569. if sel < len then
  570. sel = sel + 1
  571. draw(disList, sel, loc)
  572. elseif loc + len - 1 < #list then
  573. loc = loc + 1
  574. disList = updateDisplayList(list, loc, len)
  575. draw(disList, sel, loc)
  576. end
  577. elseif e == "mouse_scroll" then
  578. os.queueEvent("key", key == -1 and 200 or 208)
  579. elseif e == "key" and key == 28 then
  580. return disList[sel]
  581. end
  582. end
  583. end
  584.  
  585. function monitorKeyboardShortcuts()
  586. local ta, tb = nil, nil
  587. local allowChar = false
  588. local shiftPressed = false
  589. while true do
  590. local event, char = os.pullEvent()
  591. if event == "key" and (char == 42 or char == 52) then
  592. shiftPressed = true
  593. tb = os.startTimer(keyboardShortcutTimeout)
  594. elseif event == "key" and (char == 29 or char == 157 or char == 219 or char == 220) then
  595. allowEditorEvent = false
  596. allowChar = true
  597. ta = os.startTimer(keyboardShortcutTimeout)
  598. elseif event == "key" and allowChar then
  599. local name = nil
  600. for k, v in pairs(keys) do
  601. if v == char then
  602. if shiftPressed then os.queueEvent("shortcut", "ctrl shift", k:lower())
  603. else os.queueEvent("shortcut", "ctrl", k:lower()) end
  604. sleep(0.005)
  605. allowEditorEvent = true
  606. end
  607. end
  608. if shiftPressed then os.queueEvent("shortcut", "ctrl shift", char)
  609. else os.queueEvent("shortcut", "ctrl", char) end
  610. elseif event == "timer" and char == ta then
  611. allowEditorEvent = true
  612. allowChar = false
  613. elseif event == "timer" and char == tb then
  614. shiftPressed = false
  615. end
  616. end
  617. end
  618.  
  619.  
  620. -- -------- Saving and Loading
  621.  
  622. local function download(url, path)
  623. for i = 1, 3 do
  624. local response = http.get(url)
  625. if response then
  626. local data = response.readAll()
  627. response.close()
  628. if path then
  629. local f = io.open(path, "w")
  630. f:write(data)
  631. f:close()
  632. end
  633. return true
  634. end
  635. end
  636.  
  637. return false
  638. end
  639.  
  640. local function saveFile(path, lines)
  641. local dir = path:sub(1, path:len() - fs.getName(path):len())
  642. if not fs.exists(dir) then fs.makeDir(dir) end
  643. if not fs.isDir(path) and not fs.isReadOnly(path) then
  644. local a = ""
  645. for _, v in pairs(lines) do a = a .. v .. "\n" end
  646.  
  647. local f = io.open(path, "w")
  648. f:write(a)
  649. f:close()
  650. return true
  651. else return false end
  652. end
  653.  
  654. local function loadFile(path)
  655. if not fs.exists(path) then
  656. local dir = path:sub(1, path:len() - fs.getName(path):len())
  657. if not fs.exists(dir) then fs.makeDir(dir) end
  658. local f = io.open(path, "w")
  659. f:write("")
  660. f:close()
  661. end
  662.  
  663. local l = {}
  664. if fs.exists(path) and not fs.isDir(path) then
  665. local f = io.open(path, "r")
  666. if f then
  667. local a = f:read("*l")
  668. while a do
  669. table.insert(l, a)
  670. a = f:read("*l")
  671. end
  672. f:close()
  673. end
  674. else return nil end
  675.  
  676. if #l < 1 then table.insert(l, "") end
  677. return l
  678. end
  679.  
  680.  
  681. -- -------- Languages
  682.  
  683. languages.lua = {}
  684. languages.brainfuck = {}
  685. languages.none = {}
  686.  
  687. -- Lua
  688.  
  689. languages.lua.helpTips = {
  690. "A function you tried to call doesn't exist.",
  691. "You made a typo.",
  692. "The index of an array is nil.",
  693. "The wrong variable type was passed.",
  694. "A function/variable doesn't exist.",
  695. "You missed an 'end'.",
  696. "You missed a 'then'.",
  697. "You declared a variable incorrectly.",
  698. "One of your variables is mysteriously nil."
  699. }
  700.  
  701. languages.lua.defaultHelpTips = {
  702. 2, 5
  703. }
  704.  
  705. languages.lua.errors = {
  706. ["Attempt to call nil."] = {1, 2},
  707. ["Attempt to index nil."] = {3, 2},
  708. [".+ expected, got .+"] = {4, 2, 9},
  709. ["'end' expected"] = {6, 2},
  710. ["'then' expected"] = {7, 2},
  711. ["'=' expected"] = {8, 2}
  712. }
  713.  
  714. languages.lua.keywords = {
  715. ["and"] = "conditional",
  716. ["break"] = "conditional",
  717. ["do"] = "conditional",
  718. ["else"] = "conditional",
  719. ["elseif"] = "conditional",
  720. ["end"] = "conditional",
  721. ["for"] = "conditional",
  722. ["function"] = "conditional",
  723. ["if"] = "conditional",
  724. ["in"] = "conditional",
  725. ["local"] = "conditional",
  726. ["not"] = "conditional",
  727. ["or"] = "conditional",
  728. ["repeat"] = "conditional",
  729. ["return"] = "conditional",
  730. ["then"] = "conditional",
  731. ["until"] = "conditional",
  732. ["while"] = "conditional",
  733.  
  734. ["true"] = "constant",
  735. ["false"] = "constant",
  736. ["nil"] = "constant",
  737.  
  738. ["print"] = "function",
  739. ["printError"] = "function",
  740. ["error"] = "function",
  741. ["write"] = "function",
  742. ["sleep"] = "function",
  743. ["pairs"] = "function",
  744. ["ipairs"] = "function",
  745. ["loadstring"] = "function",
  746. ["loadfile"] = "function",
  747. ["dofile"] = "function",
  748. ["rawset"] = "function",
  749. ["rawget"] = "function",
  750. ["setfenv"] = "function",
  751. ["getfenv"] = "function",
  752. ["error"] = "function",
  753. }
  754.  
  755. languages.lua.parseError = function(e)
  756. local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""}
  757. if e and e ~= "" then
  758. ret.err = e
  759. if e:find(":") then
  760. ret.filename = e:sub(1, e:find(":") - 1):gsub("^%s*(.-)%s*$", "%1")
  761. -- The "" is needed to circumvent a CC bug
  762. e = (e:sub(e:find(":") + 1) .. ""):gsub("^%s*(.-)%s*$", "%1")
  763. if e:find(":") then
  764. ret.line = e:sub(1, e:find(":") - 1)
  765. e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. ""
  766. end
  767. end
  768. ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "."
  769. end
  770.  
  771. return ret
  772. end
  773.  
  774. languages.lua.getCompilerErrors = function(code)
  775. code = "local function ee65da6af1cb6f63fee9a081246f2fd92b36ef2(...)\n\n" .. code .. "\n\nend"
  776. local fn, err = loadstring(code)
  777. if not err then
  778. local _, e = pcall(fn)
  779. if e then err = e end
  780. end
  781.  
  782. if err then
  783. local a = err:find("]", 1, true)
  784. if a then err = "string" .. err:sub(a + 1, -1) end
  785. local ret = languages.lua.parseError(err)
  786. if tonumber(ret.line) then ret.line = tonumber(ret.line) end
  787. return ret
  788. else return languages.lua.parseError(nil) end
  789. end
  790.  
  791. languages.lua.run = function(path, ar)
  792. local fn, err = loadfile(path)
  793. setfenv(fn, getfenv())
  794. if not err then
  795. _, err = pcall(function() fn(unpack(ar)) end)
  796. end
  797. return err
  798. end
  799.  
  800.  
  801. -- Brainfuck
  802.  
  803. languages.brainfuck.helpTips = {
  804. "Well idk...",
  805. "Isn't this the whole point of the language?",
  806. "Ya know... Not being able to debug it?",
  807. "You made a typo."
  808. }
  809.  
  810. languages.brainfuck.defaultHelpTips = {
  811. 1, 2, 3
  812. }
  813.  
  814. languages.brainfuck.errors = {
  815. ["No matching '['"] = {1, 2, 3, 4}
  816. }
  817.  
  818. languages.brainfuck.keywords = {}
  819.  
  820. languages.brainfuck.parseError = function(e)
  821. local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""}
  822. if e and e ~= "" then
  823. ret.err = e
  824. ret.line = e:sub(1, e:find(":") - 1)
  825. e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. ""
  826. ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "."
  827. end
  828.  
  829. return ret
  830. end
  831.  
  832. languages.brainfuck.mapLoops = function(code)
  833. -- Map loops
  834. local loopLocations = {}
  835. local loc = 1
  836. local line = 1
  837. for let in string.gmatch(code, ".") do
  838. if let == "[" then
  839. loopLocations[loc] = true
  840. elseif let == "]" then
  841. local found = false
  842. for i = loc, 1, -1 do
  843. if loopLocations[i] == true then
  844. loopLocations[i] = loc
  845. found = true
  846. end
  847. end
  848.  
  849. if not found then
  850. return line .. ": No matching '['"
  851. end
  852. end
  853.  
  854. if let == "\n" then line = line + 1 end
  855. loc = loc + 1
  856. end
  857. return loopLocations
  858. end
  859.  
  860. languages.brainfuck.getCompilerErrors = function(code)
  861. local a = languages.brainfuck.mapLoops(code)
  862. if type(a) == "string" then return languages.brainfuck.parseError(a)
  863. else return languages.brainfuck.parseError(nil) end
  864. end
  865.  
  866. languages.brainfuck.run = function(path)
  867. -- Read from file
  868. local f = io.open(path, "r")
  869. local content = f:read("*a")
  870. f:close()
  871.  
  872. -- Define environment
  873. local dataCells = {}
  874. local dataPointer = 1
  875. local instructionPointer = 1
  876.  
  877. -- Map loops
  878. local loopLocations = languages.brainfuck.mapLoops(content)
  879. if type(loopLocations) == "string" then return loopLocations end
  880.  
  881. -- Execute code
  882. while true do
  883. local let = content:sub(instructionPointer, instructionPointer)
  884.  
  885. if let == ">" then
  886. dataPointer = dataPointer + 1
  887. if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  888. elseif let == "<" then
  889. if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  890. dataPointer = dataPointer - 1
  891. if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  892. elseif let == "+" then
  893. if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  894. dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] + 1
  895. elseif let == "-" then
  896. if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  897. dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] - 1
  898. elseif let == "." then
  899. if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  900. if term.getCursorPos() >= w then print("") end
  901. write(string.char(math.max(1, dataCells[tostring(dataPointer)])))
  902. elseif let == "," then
  903. if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  904. term.setCursorBlink(true)
  905. local e, but = os.pullEvent("char")
  906. term.setCursorBlink(false)
  907. dataCells[tostring(dataPointer)] = string.byte(but)
  908. if term.getCursorPos() >= w then print("") end
  909. write(but)
  910. elseif let == "/" then
  911. if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  912. if term.getCursorPos() >= w then print("") end
  913. write(dataCells[tostring(dataPointer)])
  914. elseif let == "[" then
  915. if dataCells[tostring(dataPointer)] == 0 then
  916. for k, v in pairs(loopLocations) do
  917. if k == instructionPointer then instructionPointer = v end
  918. end
  919. end
  920. elseif let == "]" then
  921. for k, v in pairs(loopLocations) do
  922. if v == instructionPointer then instructionPointer = k - 1 end
  923. end
  924. end
  925.  
  926. instructionPointer = instructionPointer + 1
  927. if instructionPointer > content:len() then print("") break end
  928. end
  929. end
  930.  
  931. -- None
  932.  
  933. languages.none.helpTips = {}
  934. languages.none.defaultHelpTips = {}
  935. languages.none.errors = {}
  936. languages.none.keywords = {}
  937.  
  938. languages.none.parseError = function(err)
  939. return {filename = "", line = -1, display = "", err = ""}
  940. end
  941.  
  942. languages.none.getCompilerErrors = function(code)
  943. return languages.none.parseError(nil)
  944. end
  945.  
  946. languages.none.run = function(path) end
  947.  
  948.  
  949. -- Load language
  950. currentLanguage = languages.lua
  951.  
  952.  
  953. -- -------- Run GUI
  954.  
  955. local function viewErrorHelp(e)
  956. title("LuaIDE - Error Help")
  957.  
  958. local tips = nil
  959. for k, v in pairs(currentLanguage.errors) do
  960. if e.display:find(k) then tips = v break end
  961. end
  962.  
  963. term.setBackgroundColor(colors[theme.err])
  964. for i = 6, 8 do
  965. term.setCursorPos(5, i)
  966. term.write(string.rep(" ", 35))
  967. end
  968.  
  969. term.setBackgroundColor(colors[theme.prompt])
  970. for i = 10, 18 do
  971. term.setCursorPos(5, i)
  972. term.write(string.rep(" ", 46))
  973. end
  974.  
  975. if tips then
  976. term.setBackgroundColor(colors[theme.err])
  977. term.setCursorPos(6, 7)
  978. term.write("Error Help")
  979.  
  980. term.setBackgroundColor(colors[theme.prompt])
  981. for i, v in ipairs(tips) do
  982. term.setCursorPos(7, i + 10)
  983. term.write("- " .. currentLanguage.helpTips[v])
  984. end
  985. else
  986. term.setBackgroundColor(colors[theme.err])
  987. term.setCursorPos(6, 7)
  988. term.write("No Error Tips Available!")
  989.  
  990. term.setBackgroundColor(colors[theme.prompt])
  991. term.setCursorPos(6, 11)
  992. term.write("There are no error tips available, but")
  993. term.setCursorPos(6, 12)
  994. term.write("you could see if it was any of these:")
  995.  
  996. for i, v in ipairs(currentLanguage.defaultHelpTips) do
  997. term.setCursorPos(7, i + 12)
  998. term.write("- " .. currentLanguage.helpTips[v])
  999. end
  1000. end
  1001.  
  1002. prompt({{"Back", w - 8, 7}}, "horizontal")
  1003. end
  1004.  
  1005. local function run(path, lines, useArgs)
  1006. local ar = {}
  1007. if useArgs then
  1008. title("LuaIDE - Run " .. fs.getName(path))
  1009. local s = centerRead(w - 13, fs.getName(path) .. " ")
  1010. for m in string.gmatch(s, "[^ \t]+") do ar[#ar + 1] = m:gsub("^%s*(.-)%s*$", "%1") end
  1011. end
  1012.  
  1013. saveFile(path, lines)
  1014. term.setCursorBlink(false)
  1015. term.setBackgroundColor(colors.black)
  1016. term.setTextColor(colors.white)
  1017. term.clear()
  1018. term.setCursorPos(1, 1)
  1019. local err = currentLanguage.run(path, ar)
  1020.  
  1021. term.setBackgroundColor(colors.black)
  1022. print("\n")
  1023. if err then
  1024. if isAdvanced() then term.setTextColor(colors.red) end
  1025. centerPrint("The program has crashed!")
  1026. end
  1027. term.setTextColor(colors.white)
  1028. centerPrint("Press any key to return to LuaIDE...")
  1029. while true do
  1030. local e = os.pullEvent()
  1031. if e == "key" then break end
  1032. end
  1033.  
  1034. -- To prevent key from showing up in editor
  1035. os.queueEvent("")
  1036. os.pullEvent()
  1037.  
  1038. if err then
  1039. if currentLanguage == languages.lua and err:find("]") then
  1040. err = fs.getName(path) .. err:sub(err:find("]", 1, true) + 1, -1)
  1041. end
  1042.  
  1043. while true do
  1044. title("LuaIDE - Error!")
  1045.  
  1046. term.setBackgroundColor(colors[theme.err])
  1047. for i = 6, 8 do
  1048. term.setCursorPos(3, i)
  1049. term.write(string.rep(" ", w - 5))
  1050. end
  1051. term.setCursorPos(4, 7)
  1052. term.write("The program has crashed!")
  1053.  
  1054. term.setBackgroundColor(colors[theme.prompt])
  1055. for i = 10, 14 do
  1056. term.setCursorPos(3, i)
  1057. term.write(string.rep(" ", w - 5))
  1058. end
  1059.  
  1060. local formattedErr = currentLanguage.parseError(err)
  1061. term.setCursorPos(4, 11)
  1062. term.write("Line: " .. formattedErr.line)
  1063. term.setCursorPos(4, 12)
  1064. term.write("Error:")
  1065. term.setCursorPos(5, 13)
  1066.  
  1067. local a = formattedErr.display
  1068. local b = nil
  1069. if a:len() > w - 8 then
  1070. for i = a:len(), 1, -1 do
  1071. if a:sub(i, i) == " " then
  1072. b = a:sub(i + 1, -1)
  1073. a = a:sub(1, i)
  1074. break
  1075. end
  1076. end
  1077. end
  1078.  
  1079. term.write(a)
  1080. if b then
  1081. term.setCursorPos(5, 14)
  1082. term.write(b)
  1083. end
  1084.  
  1085. local opt = prompt({{"Error Help", w/2 - 15, 17}, {"Go To Line", w/2 + 2, 17}},
  1086. "horizontal")
  1087. if opt == "Error Help" then
  1088. viewErrorHelp(formattedErr)
  1089. elseif opt == "Go To Line" then
  1090. -- To prevent key from showing up in editor
  1091. os.queueEvent("")
  1092. os.pullEvent()
  1093.  
  1094. return "go to", tonumber(formattedErr.line)
  1095. end
  1096. end
  1097. end
  1098. end
  1099.  
  1100.  
  1101. -- -------- Functions
  1102.  
  1103. local function goto()
  1104. term.setBackgroundColor(colors[theme.backgroundHighlight])
  1105. term.setCursorPos(2, 1)
  1106. term.clearLine()
  1107. term.write("Line: ")
  1108. local line = modRead({visibleLength = w - 2})
  1109.  
  1110. local num = tonumber(line)
  1111. if num and num > 0 then return num
  1112. else
  1113. term.setCursorPos(2, 1)
  1114. term.clearLine()
  1115. term.write("Not a line number!")
  1116. sleep(1.6)
  1117. return nil
  1118. end
  1119. end
  1120.  
  1121. local function setsyntax()
  1122. local opts = {
  1123. "[Lua] Brainfuck None ",
  1124. " Lua [Brainfuck] None ",
  1125. " Lua Brainfuck [None]"
  1126. }
  1127. local sel = 1
  1128.  
  1129. term.setCursorBlink(false)
  1130. term.setBackgroundColor(colors[theme.backgroundHighlight])
  1131. term.setCursorPos(2, 1)
  1132. term.clearLine()
  1133. term.write(opts[sel])
  1134. while true do
  1135. local e, but, x, y = os.pullEvent("key")
  1136. if but == 203 then
  1137. sel = math.max(1, sel - 1)
  1138. term.setCursorPos(2, 1)
  1139. term.clearLine()
  1140. term.write(opts[sel])
  1141. elseif but == 205 then
  1142. sel = math.min(#opts, sel + 1)
  1143. term.setCursorPos(2, 1)
  1144. term.clearLine()
  1145. term.write(opts[sel])
  1146. elseif but == 28 then
  1147. if sel == 1 then currentLanguage = languages.lua
  1148. elseif sel == 2 then currentLanguage = languages.brainfuck
  1149. elseif sel == 3 then currentLanguage = languages.none end
  1150. term.setCursorBlink(true)
  1151. return
  1152. end
  1153. end
  1154. end
  1155.  
  1156.  
  1157. -- -------- Re-Indenting
  1158.  
  1159. local tabWidth = 2
  1160.  
  1161. local comments = {}
  1162. local strings = {}
  1163.  
  1164. local increment = {
  1165. "if%s+.+%s+then%s*$",
  1166. "for%s+.+%s+do%s*$",
  1167. "while%s+.+%s+do%s*$",
  1168. "repeat%s*$",
  1169. "function%s+[a-zA-Z_0-9]\(.*\)%s*$"
  1170. }
  1171.  
  1172. local decrement = {
  1173. "end",
  1174. "until%s+.+"
  1175. }
  1176.  
  1177. local special = {
  1178. "else%s*$",
  1179. "elseif%s+.+%s+then%s*$"
  1180. }
  1181.  
  1182. local function check(func)
  1183. for _, v in pairs(func) do
  1184. local cLineStart = v["lineStart"]
  1185. local cLineEnd = v["lineEnd"]
  1186. local cCharStart = v["charStart"]
  1187. local cCharEnd = v["charEnd"]
  1188.  
  1189. if line >= cLineStart and line <= cLineEnd then
  1190. if line == cLineStart then return cCharStart < charNumb
  1191. elseif line == cLineEnd then return cCharEnd > charNumb
  1192. else return true end
  1193. end
  1194. end
  1195. end
  1196.  
  1197. local function isIn(line, loc)
  1198. if check(comments) then return true end
  1199. if check(strings) then return true end
  1200. return false
  1201. end
  1202.  
  1203. local function setComment(ls, le, cs, ce)
  1204. comments[#comments + 1] = {}
  1205. comments[#comments].lineStart = ls
  1206. comments[#comments].lineEnd = le
  1207. comments[#comments].charStart = cs
  1208. comments[#comments].charEnd = ce
  1209. end
  1210.  
  1211. local function setString(ls, le, cs, ce)
  1212. strings[#strings + 1] = {}
  1213. strings[#strings].lineStart = ls
  1214. strings[#strings].lineEnd = le
  1215. strings[#strings].charStart = cs
  1216. strings[#strings].charEnd = ce
  1217. end
  1218.  
  1219. local function map(contents)
  1220. local inCom = false
  1221. local inStr = false
  1222.  
  1223. for i = 1, #contents do
  1224. if content[i]:find("%-%-%[%[") and not inStr and not inCom then
  1225. local cStart = content[i]:find("%-%-%[%[")
  1226. setComment(i, nil, cStart, nil)
  1227. inCom = true
  1228. elseif content[i]:find("%-%-%[=%[") and not inStr and not inCom then
  1229. local cStart = content[i]:find("%-%-%[=%[")
  1230. setComment(i, nil, cStart, nil)
  1231. inCom = true
  1232. elseif content[i]:find("%[%[") and not inStr and not inCom then
  1233. local cStart = content[i]:find("%[%[")
  1234. setString(i, nil, cStart, nil)
  1235. inStr = true
  1236. elseif content[i]:find("%[=%[") and not inStr and not inCom then
  1237. local cStart = content[i]:find("%[=%[")
  1238. setString(i, nil, cStart, nil)
  1239. inStr = true
  1240. end
  1241.  
  1242. if content[i]:find("%]%]") and inStr and not inCom then
  1243. local cStart, cEnd = content[i]:find("%]%]")
  1244. strings[#strings].lineEnd = i
  1245. strings[#strings].charEnd = cEnd
  1246. inStr = false
  1247. elseif content[i]:find("%]=%]") and inStr and not inCom then
  1248. local cStart, cEnd = content[i]:find("%]=%]")
  1249. strings[#strings].lineEnd = i
  1250. strings[#strings].charEnd = cEnd
  1251. inStr = false
  1252. end
  1253.  
  1254. if content[i]:find("%]%]") and not inStr and inCom then
  1255. local cStart, cEnd = content[i]:find("%]%]")
  1256. comments[#comments].lineEnd = i
  1257. comments[#comments].charEnd = cEnd
  1258. inCom = false
  1259. elseif content[i]:find("%]=%]") and not inStr and inCom then
  1260. local cStart, cEnd = content[i]:find("%]=%]")
  1261. comments[#comments].lineEnd = i
  1262. comments[#comments].charEnd = cEnd
  1263. inCom = false
  1264. end
  1265.  
  1266. if content[i]:find("%-%-") and not inStr and not inCom then
  1267. local cStart = content[i]:find("%-%-")
  1268. setComment(i, i, cStart, -1)
  1269. elseif content[i]:find("'") and not inStr and not inCom then
  1270. local cStart, cEnd = content[i]:find("'")
  1271. local nextChar = content[i]:sub(cEnd + 1, string.len(content[i]))
  1272. local _, cEnd = nextChar:find("'")
  1273. setString(i, i, cStart, cEnd)
  1274. elseif content[i]:find('"') and not inStr and not inCom then
  1275. local cStart, cEnd = content[i]:find('"')
  1276. local nextChar = content[i]:sub(cEnd + 1, string.len(content[i]))
  1277. local _, cEnd = nextChar:find('"')
  1278. setString(i, i, cStart, cEnd)
  1279. end
  1280. end
  1281. end
  1282.  
  1283. local function reindent(contents)
  1284. local err = nil
  1285. if currentLanguage ~= languages.lua then
  1286. err = "Cannot indent languages other than Lua!"
  1287. elseif currentLanguage.getCompilerErrors(table.concat(contents, "\n")).line ~= -1 then
  1288. err = "Cannot indent a program with errors!"
  1289. end
  1290.  
  1291. if err then
  1292. term.setCursorBlink(false)
  1293. term.setCursorPos(2, 1)
  1294. term.setBackgroundColor(colors[theme.backgroundHighlight])
  1295. term.clearLine()
  1296. term.write(err)
  1297. sleep(1.6)
  1298. return contents
  1299. end
  1300.  
  1301. local new = {}
  1302. local level = 0
  1303. for k, v in pairs(contents) do
  1304. local incrLevel = false
  1305. local foundIncr = false
  1306. for _, incr in pairs(increment) do
  1307. if v:find(incr) and not isIn(k, v:find(incr)) then
  1308. incrLevel = true
  1309. end
  1310. if v:find(incr:sub(1, -2)) and not isIn(k, v:find(incr)) then
  1311. foundIncr = true
  1312. end
  1313. end
  1314.  
  1315. local decrLevel = false
  1316. if not incrLevel then
  1317. for _, decr in pairs(decrement) do
  1318. if v:find(decr) and not isIn(k, v:find(decr)) and not foundIncr then
  1319. level = math.max(0, level - 1)
  1320. decrLevel = true
  1321. end
  1322. end
  1323. end
  1324.  
  1325. if not decrLevel then
  1326. for _, sp in pairs(special) do
  1327. if v:find(sp) and not isIn(k, v:find(sp)) then
  1328. incrLevel = true
  1329. level = math.max(0, level - 1)
  1330. end
  1331. end
  1332. end
  1333.  
  1334. new[k] = string.rep(" ", level * tabWidth) .. v
  1335. if incrLevel then level = level + 1 end
  1336. end
  1337.  
  1338. return new
  1339. end
  1340.  
  1341.  
  1342. -- -------- Menu
  1343.  
  1344. local menu = {
  1345. [1] = {"File",
  1346. -- "About",
  1347. -- "Settings",
  1348. -- "",
  1349. "New File ^+N",
  1350. "Open File ^+O",
  1351. "Save File ^+S",
  1352. "Close ^+W",
  1353. "Print ^+P",
  1354. "Quit ^+Q"
  1355. }, [2] = {"Edit",
  1356. "Cut Line ^+X",
  1357. "Copy Line ^+C",
  1358. "Paste Line ^+V",
  1359. "Delete Line",
  1360. "Clear Line"
  1361. }, [3] = {"Functions",
  1362. "Go To Line ^+G",
  1363. "Re-Indent ^+I",
  1364. "Set Syntax ^+E",
  1365. "Start of Line ^+<",
  1366. "End of Line ^+>"
  1367. }, [4] = {"Run",
  1368. "Run Program ^+R",
  1369. "Run w/ Args ^+Shift+R"
  1370. }
  1371. }
  1372.  
  1373. local shortcuts = {
  1374. -- File
  1375. ["ctrl n"] = "New File ^+N",
  1376. ["ctrl o"] = "Open File ^+O",
  1377. ["ctrl s"] = "Save File ^+S",
  1378. ["ctrl w"] = "Close ^+W",
  1379. ["ctrl p"] = "Print ^+P",
  1380. ["ctrl q"] = "Quit ^+Q",
  1381.  
  1382. -- Edit
  1383. ["ctrl x"] = "Cut Line ^+X",
  1384. ["ctrl c"] = "Copy Line ^+C",
  1385. ["ctrl v"] = "Paste Line ^+V",
  1386.  
  1387. -- Functions
  1388. ["ctrl g"] = "Go To Line ^+G",
  1389. ["ctrl i"] = "Re-Indent ^+I",
  1390. ["ctrl e"] = "Set Syntax ^+E",
  1391. ["ctrl 203"] = "Start of Line ^+<",
  1392. ["ctrl 205"] = "End of Line ^+>",
  1393.  
  1394. -- Run
  1395. ["ctrl r"] = "Run Program ^+R",
  1396. ["ctrl shift r"] = "Run w/ Args ^+Shift+R"
  1397. }
  1398.  
  1399. local menuFunctions = {
  1400. -- File
  1401. -- ["About"] = function() end,
  1402. -- ["Settings"] = function() end,
  1403. ["New File ^+N"] = function(path, lines) saveFile(path, lines) return "new" end,
  1404. ["Open File ^+O"] = function(path, lines) saveFile(path, lines) return "open" end,
  1405. ["Save File ^+S"] = function(path, lines) saveFile(path, lines) end,
  1406. ["Close ^+W"] = function(path, lines) saveFile(path, lines) return "menu" end,
  1407. ["Print ^+P"] = function(path, lines) saveFile(path, lines) return nil end,
  1408. ["Quit ^+Q"] = function(path, lines) saveFile(path, lines) return "exit" end,
  1409.  
  1410. -- Edit
  1411. ["Cut Line ^+X"] = function(path, lines, y)
  1412. clipboard = lines[y] table.remove(lines, y) return nil, lines end,
  1413. ["Copy Line ^+C"] = function(path, lines, y) clipboard = lines[y] end,
  1414. ["Paste Line ^+V"] = function(path, lines, y)
  1415. if clipboard then table.insert(lines, y, clipboard) end return nil, lines end,
  1416. ["Delete Line"] = function(path, lines, y) table.remove(lines, y) return nil, lines end,
  1417. ["Clear Line"] = function(path, lines, y) lines[y] = "" return nil, lines, "cursor" end,
  1418.  
  1419. -- Functions
  1420. ["Go To Line ^+G"] = function() return nil, "go to", goto() end,
  1421. ["Re-Indent ^+I"] = function(path, lines)
  1422. local a = reindent(lines) saveFile(path, lines) return nil, a
  1423. end,
  1424. ["Set Syntax ^+E"] = function(path, lines)
  1425. setsyntax()
  1426. if currentLanguage == languages.brainfuck and lines[1] ~= "-- Syntax: Brainfuck" then
  1427. table.insert(lines, 1, "-- Syntax: Brainfuck")
  1428. return nil, lines
  1429. end
  1430. end,
  1431. ["Start of Line ^+<"] = function() os.queueEvent("key", 199) end,
  1432. ["End of Line ^+>"] = function() os.queueEvent("key", 207) end,
  1433.  
  1434. -- Run
  1435. ["Run Program ^+R"] = function(path, lines)
  1436. saveFile(path, lines)
  1437. return nil, run(path, lines, false)
  1438. end,
  1439. ["Run w/ Args ^+Shift+R"] = function(path, lines)
  1440. saveFile(path, lines)
  1441. return nil, run(path, lines, true)
  1442. end,
  1443. }
  1444.  
  1445. local function drawMenu(open)
  1446. term.setCursorPos(1, 1)
  1447. term.setTextColor(colors[theme.textColor])
  1448. term.setBackgroundColor(colors[theme.backgroundHighlight])
  1449. term.clearLine()
  1450. local curX = 0
  1451. for _, v in pairs(menu) do
  1452. term.setCursorPos(3 + curX, 1)
  1453. term.write(v[1])
  1454. curX = curX + v[1]:len() + 3
  1455. end
  1456.  
  1457. if open then
  1458. local it = {}
  1459. local x = 1
  1460. for _, v in pairs(menu) do
  1461. if open == v[1] then
  1462. it = v
  1463. break
  1464. end
  1465. x = x + v[1]:len() + 3
  1466. end
  1467. x = x + 1
  1468.  
  1469. local items = {}
  1470. for i = 2, #it do
  1471. table.insert(items, it[i])
  1472. end
  1473.  
  1474. local len = 1
  1475. for _, v in pairs(items) do if v:len() + 2 > len then len = v:len() + 2 end end
  1476.  
  1477. for i, v in ipairs(items) do
  1478. term.setCursorPos(x, i + 1)
  1479. term.write(string.rep(" ", len))
  1480. term.setCursorPos(x + 1, i + 1)
  1481. term.write(v)
  1482. end
  1483. term.setCursorPos(x, #items + 2)
  1484. term.write(string.rep(" ", len))
  1485. return items, len
  1486. end
  1487. end
  1488.  
  1489. local function triggerMenu(cx, cy)
  1490. -- Determine clicked menu
  1491. local curX = 0
  1492. local open = nil
  1493. for _, v in pairs(menu) do
  1494. if cx >= curX + 3 and cx <= curX + v[1]:len() + 2 then
  1495. open = v[1]
  1496. break
  1497. end
  1498. curX = curX + v[1]:len() + 3
  1499. end
  1500. local menux = curX + 2
  1501. if not open then return false end
  1502.  
  1503. -- Flash menu item
  1504. term.setCursorBlink(false)
  1505. term.setCursorPos(menux, 1)
  1506. term.setBackgroundColor(colors[theme.background])
  1507. term.write(string.rep(" ", open:len() + 2))
  1508. term.setCursorPos(menux + 1, 1)
  1509. term.write(open)
  1510. sleep(0.1)
  1511. local items, len = drawMenu(open)
  1512.  
  1513. local ret = true
  1514.  
  1515. -- Pull events on menu
  1516. local ox, oy = term.getCursorPos()
  1517. while type(ret) ~= "string" do
  1518. local e, but, x, y = os.pullEvent()
  1519. if e == "mouse_click" then
  1520. -- If clicked outside menu
  1521. if x < menux - 1 or x > menux + len - 1 then break
  1522. elseif y > #items + 2 then break
  1523. elseif y == 1 then break end
  1524.  
  1525. for i, v in ipairs(items) do
  1526. if y == i + 1 and x >= menux and x <= menux + len - 2 then
  1527. -- Flash when clicked
  1528. term.setCursorPos(menux, y)
  1529. term.setBackgroundColor(colors[theme.background])
  1530. term.write(string.rep(" ", len))
  1531. term.setCursorPos(menux + 1, y)
  1532. term.write(v)
  1533. sleep(0.1)
  1534. drawMenu(open)
  1535.  
  1536. -- Return item
  1537. ret = v
  1538. break
  1539. end
  1540. end
  1541. end
  1542. end
  1543.  
  1544. term.setCursorPos(ox, oy)
  1545. term.setCursorBlink(true)
  1546. return ret
  1547. end
  1548.  
  1549.  
  1550. -- -------- Editing
  1551.  
  1552. local standardsCompletions = {
  1553. "if%s+.+%s+then%s*$",
  1554. "for%s+.+%s+do%s*$",
  1555. "while%s+.+%s+do%s*$",
  1556. "repeat%s*$",
  1557. "function%s+[a-zA-Z_0-9]?\(.*\)%s*$",
  1558. "=%s*function%s*\(.*\)%s*$",
  1559. "else%s*$",
  1560. "elseif%s+.+%s+then%s*$"
  1561. }
  1562.  
  1563. local liveCompletions = {
  1564. ["("] = ")",
  1565. ["{"] = "}",
  1566. ["["] = "]",
  1567. ["\""] = "\"",
  1568. ["'"] = "'",
  1569. }
  1570.  
  1571. local x, y = 0, 0
  1572. local edw, edh = 0, h - 1
  1573. local offx, offy = 0, 1
  1574. local scrollx, scrolly = 0, 0
  1575. local lines = {}
  1576. local liveErr = currentLanguage.parseError(nil)
  1577. local displayCode = true
  1578. local lastEventClock = os.clock()
  1579.  
  1580. local function attemptToHighlight(line, regex, col)
  1581. local match = string.match(line, regex)
  1582. if match then
  1583. if type(col) == "number" then term.setTextColor(col)
  1584. elseif type(col) == "function" then term.setTextColor(col(match)) end
  1585. term.write(match)
  1586. term.setTextColor(colors[theme.textColor])
  1587. return line:sub(match:len() + 1, -1)
  1588. end
  1589. return nil
  1590. end
  1591.  
  1592. local function writeHighlighted(line) -- TODO understand this code and make multi-line comments work
  1593. if currentLanguage == languages.lua then
  1594. while line:len() > 0 do
  1595. line = attemptToHighlight(line, "^%-%-%[%[.-%]%]", colors[theme.comment]) or
  1596. attemptToHighlight(line, "^%-%-.*", colors[theme.comment]) or
  1597. attemptToHighlight(line, "^\".-[^\\]\"", colors[theme.string]) or
  1598. attemptToHighlight(line, "^\'.-[^\\]\'", colors[theme.string]) or
  1599. attemptToHighlight(line, "^%[%[.-[^\\]%]%]", colors[theme.string]) or
  1600. attemptToHighlight(line, "^[%w_]+", function(match)
  1601. if currentLanguage.keywords[match] then
  1602. return colors[theme[currentLanguage.keywords[match]]]
  1603. end
  1604. return colors[theme.textColor]
  1605. end) or
  1606. attemptToHighlight(line, "^[^%w_]", colors[theme.textColor])
  1607. end
  1608. else
  1609. term.write(line)
  1610. end
  1611. end
  1612.  
  1613. local function draw()
  1614. -- Menu
  1615. term.setTextColor(colors[theme.textColor])
  1616. term.setBackgroundColor(colors[theme.editorBackground])
  1617. term.clear()
  1618. drawMenu()
  1619.  
  1620. -- Line numbers
  1621. offx, offy = tostring(#lines):len() + 1, 1
  1622. edw, edh = w - offx, h - 1
  1623.  
  1624. -- Draw text
  1625. for i = 1, edh do
  1626. local a = lines[scrolly + i]
  1627. if a then
  1628. local ln = string.rep(" ", offx - 1 - tostring(scrolly + i):len()) .. tostring(scrolly + i)
  1629. local l = a:sub(scrollx + 1, edw + scrollx + 1)
  1630. ln = ln .. ":"
  1631.  
  1632. if liveErr.line == scrolly + i then ln = string.rep(" ", offx - 2) .. "!:" end
  1633.  
  1634. term.setCursorPos(1, i + offy)
  1635. term.setBackgroundColor(colors[theme.editorBackground])
  1636. if scrolly + i == y then
  1637. if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then
  1638. term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1639. else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
  1640. term.clearLine()
  1641. elseif scrolly + i == liveErr.line then
  1642. term.setBackgroundColor(colors[theme.editorError])
  1643. term.clearLine()
  1644. end
  1645.  
  1646. term.setCursorPos(1 - scrollx + offx, i + offy)
  1647. if scrolly + i == y then
  1648. if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then
  1649. term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1650. else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
  1651. elseif scrolly + i == liveErr.line then term.setBackgroundColor(colors[theme.editorError])
  1652. else term.setBackgroundColor(colors[theme.editorBackground]) end
  1653. if scrolly + i == liveErr.line then
  1654. if displayCode then term.write(a)
  1655. else term.write(liveErr.display) end
  1656. else writeHighlighted(a) end
  1657.  
  1658. term.setCursorPos(1, i + offy)
  1659. if scrolly + i == y then
  1660. if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then
  1661. term.setBackgroundColor(colors[theme.editorError])
  1662. else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end
  1663. elseif scrolly + i == liveErr.line then
  1664. term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1665. else term.setBackgroundColor(colors[theme.editorLineNumbers]) end
  1666. term.write(ln)
  1667. end
  1668. end
  1669. term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  1670. end
  1671.  
  1672. local function drawLine(...)
  1673. local ls = {...}
  1674. offx = tostring(#lines):len() + 1
  1675. for _, ly in pairs(ls) do
  1676. local a = lines[ly]
  1677. if a then
  1678. local ln = string.rep(" ", offx - 1 - tostring(ly):len()) .. tostring(ly)
  1679. local l = a:sub(scrollx + 1, edw + scrollx + 1)
  1680. ln = ln .. ":"
  1681.  
  1682. if liveErr.line == ly then ln = string.rep(" ", offx - 2) .. "!:" end
  1683.  
  1684. term.setCursorPos(1, (ly - scrolly) + offy)
  1685. term.setBackgroundColor(colors[theme.editorBackground])
  1686. if ly == y then
  1687. if ly == liveErr.line and os.clock() - lastEventClock > 3 then
  1688. term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1689. else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
  1690. elseif ly == liveErr.line then
  1691. term.setBackgroundColor(colors[theme.editorError])
  1692. end
  1693. term.clearLine()
  1694.  
  1695. term.setCursorPos(1 - scrollx + offx, (ly - scrolly) + offy)
  1696. if ly == y then
  1697. if ly == liveErr.line and os.clock() - lastEventClock > 3 then
  1698. term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1699. else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
  1700. elseif ly == liveErr.line then term.setBackgroundColor(colors[theme.editorError])
  1701. else term.setBackgroundColor(colors[theme.editorBackground]) end
  1702. if ly == liveErr.line then
  1703. if displayCode then term.write(a)
  1704. else term.write(liveErr.display) end
  1705. else writeHighlighted(a) end
  1706.  
  1707. term.setCursorPos(1, (ly - scrolly) + offy)
  1708. if ly == y then
  1709. if ly == liveErr.line and os.clock() - lastEventClock > 3 then
  1710. term.setBackgroundColor(colors[theme.editorError])
  1711. else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end
  1712. elseif ly == liveErr.line then
  1713. term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1714. else term.setBackgroundColor(colors[theme.editorLineNumbers]) end
  1715. term.write(ln)
  1716. end
  1717. end
  1718. term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  1719. end
  1720.  
  1721. local function cursorLoc(x, y, force)
  1722. local sx, sy = x - scrollx, y - scrolly
  1723. local redraw = false
  1724. if sx < 1 then
  1725. scrollx = x - 1
  1726. sx = 1
  1727. redraw = true
  1728. elseif sx > edw then
  1729. scrollx = x - edw
  1730. sx = edw
  1731. redraw = true
  1732. end if sy < 1 then
  1733. scrolly = y - 1
  1734. sy = 1
  1735. redraw = true
  1736. elseif sy > edh then
  1737. scrolly = y - edh
  1738. sy = edh
  1739. redraw = true
  1740. end if redraw or force then draw() end
  1741. term.setCursorPos(sx + offx, sy + offy)
  1742. end
  1743.  
  1744. local function executeMenuItem(a, path)
  1745. if type(a) == "string" and menuFunctions[a] then
  1746. local opt, nl, gtln = menuFunctions[a](path, lines, y)
  1747. if type(opt) == "string" then term.setCursorBlink(false) return opt end
  1748. if type(nl) == "table" then
  1749. if #lines < 1 then table.insert(lines, "") end
  1750. y = math.min(y, #lines)
  1751. x = math.min(x, lines[y]:len() + 1)
  1752. lines = nl
  1753. elseif type(nl) == "string" then
  1754. if nl == "go to" and gtln then
  1755. x, y = 1, math.min(#lines, gtln)
  1756. cursorLoc(x, y)
  1757. end
  1758. end
  1759. end
  1760. term.setCursorBlink(true)
  1761. draw()
  1762. term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  1763. end
  1764.  
  1765. local function edit(path)
  1766. -- Variables
  1767. x, y = 1, 1
  1768. offx, offy = 0, 1
  1769. scrollx, scrolly = 0, 0
  1770. lines = loadFile(path)
  1771. if not lines then return "menu" end
  1772.  
  1773. -- Enable brainfuck
  1774. if lines[1] == "-- Syntax: Brainfuck" then
  1775. currentLanguage = languages.brainfuck
  1776. end
  1777.  
  1778. -- Clocks
  1779. local autosaveClock = os.clock()
  1780. local scrollClock = os.clock() -- To prevent redraw flicker
  1781. local liveErrorClock = os.clock()
  1782. local hasScrolled = false
  1783.  
  1784. -- Draw
  1785. draw()
  1786. term.setCursorPos(x + offx, y + offy)
  1787. term.setCursorBlink(true)
  1788.  
  1789. -- Main loop
  1790. local tid = os.startTimer(3)
  1791. while true do
  1792. local e, key, cx, cy = os.pullEvent()
  1793. if e == "key" and allowEditorEvent then
  1794. if key == 200 and y > 1 then
  1795. -- Up
  1796. x, y = math.min(x, lines[y - 1]:len() + 1), y - 1
  1797. drawLine(y, y + 1)
  1798. cursorLoc(x, y)
  1799. elseif key == 208 and y < #lines then
  1800. -- Down
  1801. x, y = math.min(x, lines[y + 1]:len() + 1), y + 1
  1802. drawLine(y, y - 1)
  1803. cursorLoc(x, y)
  1804. elseif key == 203 then
  1805. -- Left
  1806. if x > 1 then
  1807. local force = false
  1808. x = x - 1
  1809. if y - scrolly + offy < offy + 1 then force = true end
  1810. cursorLoc(x, y, force)
  1811. elseif doCursorWrap and y ~= 1 then
  1812. y = y - 1
  1813. x = #lines[y] + 1
  1814. drawLine(y, y + 1)
  1815. cursorLoc(x, y)
  1816. end
  1817. elseif key == 205 then
  1818. -- Right
  1819. if x < lines[y]:len() + 1 then
  1820. local force = false
  1821. x = x + 1
  1822. if y - scrolly + offy < offy + 1 then force = true end
  1823. cursorLoc(x, y, force)
  1824. elseif doCursorWrap and y ~= #lines then
  1825. x = 1
  1826. y = y + 1
  1827. drawLine(y, y - 1)
  1828. cursorLoc(x, y)
  1829. end
  1830. elseif (key == 28 or key == 156) and (displayCode and true or y + scrolly - 1 ==
  1831. liveErr.line) then
  1832. -- Enter
  1833. local f = nil
  1834. for _, v in pairs(standardsCompletions) do
  1835. if lines[y]:find(v) and x == #lines[y] + 1 then f = v end
  1836. end
  1837.  
  1838. local skip = false
  1839. if lines[y]:sub(x, string.len(lines[y])) ~= "]]" then
  1840. skip = true
  1841. for i = x, string.len(lines[y]) do
  1842. local match = false
  1843. for _, v in pairs(liveCompletions) do
  1844. if lines[y]:sub(i, i) == v then match = true break end
  1845. end
  1846. if match == false then skip = false break end
  1847. end
  1848.  
  1849. end
  1850.  
  1851. local _, spaces = lines[y]:find("^[ ]+")
  1852. if not spaces then spaces = 0 end
  1853. if f then
  1854. table.insert(lines, y + 1, string.rep(" ", spaces + 2))
  1855. if not f:find("else", 1, true) and not f:find("elseif", 1, true) then
  1856. table.insert(lines, y + 2, string.rep(" ", spaces) ..
  1857. (f:find("repeat", 1, true) and "until " or f:find("{", 1, true) and "}" or
  1858. "end"))
  1859. end
  1860. x, y = spaces + 3, y + 1
  1861. cursorLoc(x, y, true)
  1862. else
  1863. local oldLine = lines[y]
  1864.  
  1865. if skip then
  1866. table.insert(lines, y + 1, string.rep(" ", spaces))
  1867. else
  1868. lines[y] = lines[y]:sub(1, x - 1)
  1869. table.insert(lines, y + 1, string.rep(" ", spaces) .. oldLine:sub(x, -1))
  1870. end
  1871.  
  1872. x, y = spaces + 1, y + 1
  1873. cursorLoc(x, y, true)
  1874. end
  1875. elseif key == 14 and (displayCode and true or y + scrolly - 1 == liveErr.line) then
  1876. -- Backspace
  1877. if x > 1 then
  1878. local f = false
  1879. for k, v in pairs(liveCompletions) do
  1880. if
  1881. lines[y]:sub(x - 1, x - 1) == k and
  1882. lines[y]:sub(x, x) == v and
  1883. lines[y]:sub(x - 2, x - 2) ~= "\\"
  1884. then
  1885. f = true
  1886. break
  1887. end
  1888. end
  1889.  
  1890. lines[y] = lines[y]:sub(1, x - 2) .. lines[y]:sub(x + (f and 1 or 0), -1)
  1891. drawLine(y)
  1892. x = x - 1
  1893. cursorLoc(x, y)
  1894. elseif y > 1 then
  1895. local prevLen = lines[y - 1]:len() + 1
  1896. lines[y - 1] = lines[y - 1] .. lines[y]
  1897. table.remove(lines, y)
  1898. x, y = prevLen, y - 1
  1899. cursorLoc(x, y, true)
  1900. end
  1901. elseif key == 199 then
  1902. -- Home
  1903. x = 1
  1904. local force = false
  1905. if y - scrolly + offy < offy + 1 then force = true end
  1906. cursorLoc(x, y, force)
  1907. elseif key == 207 then
  1908. -- End
  1909. x = lines[y]:len() + 1
  1910. local force = false
  1911. if y - scrolly + offy < offy + 1 then force = true end
  1912. cursorLoc(x, y, force)
  1913. elseif key == 211 and (displayCode and true or y + scrolly - 1 == liveErr.line) then
  1914. -- Forward Delete
  1915. if x < lines[y]:len() + 1 then
  1916. lines[y] = lines[y]:sub(1, x - 1) .. lines[y]:sub(x + 1)
  1917. local force = false
  1918. if y - scrolly + offy < offy + 1 then force = true end
  1919. drawLine(y)
  1920. cursorLoc(x, y, force)
  1921. elseif y < #lines then
  1922. lines[y] = lines[y] .. lines[y + 1]
  1923. table.remove(lines, y + 1)
  1924. draw()
  1925. cursorLoc(x, y)
  1926. end
  1927. elseif key == 15 and (displayCode and true or y + scrolly - 1 == liveErr.line) then
  1928. -- Tab
  1929. lines[y] = string.rep(" ", tabWidth) .. lines[y]
  1930. x = x + 2
  1931. local force = false
  1932. if y - scrolly + offy < offy + 1 then force = true end
  1933. drawLine(y)
  1934. cursorLoc(x, y, force)
  1935. elseif key == 201 then
  1936. -- Page up
  1937. y = math.min(math.max(y - edh, 1), #lines)
  1938. x = math.min(lines[y]:len() + 1, x)
  1939. cursorLoc(x, y, true)
  1940. elseif key == 209 then
  1941. -- Page down
  1942. y = math.min(math.max(y + edh, 1), #lines)
  1943. x = math.min(lines[y]:len() + 1, x)
  1944. cursorLoc(x, y, true)
  1945. end
  1946. elseif e == "char" and allowEditorEvent and (displayCode and true or
  1947. y + scrolly - 1 == liveErr.line) then
  1948. local shouldIgnore = false
  1949. for k, v in pairs(liveCompletions) do
  1950. if key == v and lines[y]:find(k, 1, true) and lines[y]:sub(x, x) == v and lines[y]:sub(x - 1, x - 1) ~= "\\" then
  1951. shouldIgnore = true
  1952. end
  1953. end
  1954.  
  1955. local addOne = false
  1956. if not shouldIgnore then
  1957. for k, v in pairs(liveCompletions) do
  1958. if key == k and lines[y]:sub(x, x) ~= k and lines[y]:sub(x - 1, x - 1) ~= "\\" then
  1959. key = key .. v
  1960. addOne = true
  1961. end
  1962. end
  1963. lines[y] = lines[y]:sub(1, x - 1) .. key .. lines[y]:sub(x, -1)
  1964. end
  1965.  
  1966. x = x + (addOne and 1 or key:len())
  1967. local force = false
  1968. if y - scrolly + offy < offy + 1 then force = true end
  1969. drawLine(y)
  1970. cursorLoc(x, y, force)
  1971. elseif e == "mouse_click" and key == 1 then
  1972. if cy > 1 then
  1973. if cx <= offx and cy - offy == liveErr.line - scrolly then
  1974. displayCode = not displayCode
  1975. drawLine(liveErr.line)
  1976. else
  1977. local oldy = y
  1978. y = math.min(math.max(scrolly + cy - offy, 1), #lines)
  1979. x = math.min(math.max(scrollx + cx - offx, 1), lines[y]:len() + 1)
  1980. if oldy ~= y then drawLine(oldy, y) end
  1981. cursorLoc(x, y)
  1982. end
  1983. else
  1984. local a = triggerMenu(cx, cy)
  1985. if a then
  1986. local opt = executeMenuItem(a, path)
  1987. if opt then return opt end
  1988. end
  1989. end
  1990. elseif e == "shortcut" then
  1991. local a = shortcuts[key .. " " .. cx]
  1992. if a then
  1993. local parent = nil
  1994. local curx = 0
  1995. for i, mv in ipairs(menu) do
  1996. for _, iv in pairs(mv) do
  1997. if iv == a then
  1998. parent = menu[i][1]
  1999. break
  2000. end
  2001. end
  2002. if parent then break end
  2003. curx = curx + mv[1]:len() + 3
  2004. end
  2005. local menux = curx + 2
  2006.  
  2007. -- Flash menu item
  2008. term.setCursorBlink(false)
  2009. term.setCursorPos(menux, 1)
  2010. term.setBackgroundColor(colors[theme.background])
  2011. term.write(string.rep(" ", parent:len() + 2))
  2012. term.setCursorPos(menux + 1, 1)
  2013. term.write(parent)
  2014. sleep(0.1)
  2015. drawMenu()
  2016.  
  2017. -- Execute item
  2018. local opt = executeMenuItem(a, path)
  2019. if opt then return opt end
  2020. end
  2021. elseif e == "mouse_scroll" then
  2022. if key == -1 and scrolly > 0 then
  2023. scrolly = scrolly - 1
  2024. if os.clock() - scrollClock > 0.0005 then
  2025. draw()
  2026. term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  2027. end
  2028. scrollClock = os.clock()
  2029. hasScrolled = true
  2030. elseif key == 1 and scrolly < #lines - edh then
  2031. scrolly = scrolly + 1
  2032. if os.clock() - scrollClock > 0.0005 then
  2033. draw()
  2034. term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  2035. end
  2036. scrollClock = os.clock()
  2037. hasScrolled = true
  2038. end
  2039. elseif e == "timer" and key == tid then
  2040. drawLine(y)
  2041. tid = os.startTimer(3)
  2042. end
  2043.  
  2044. -- Draw
  2045. if hasScrolled and os.clock() - scrollClock > 0.1 then
  2046. draw()
  2047. term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  2048. hasScrolled = false
  2049. end
  2050.  
  2051. -- Autosave
  2052. if os.clock() - autosaveClock > autosaveInterval then
  2053. saveFile(path, lines)
  2054. autosaveClock = os.clock()
  2055. end
  2056.  
  2057. -- Errors
  2058. if os.clock() - liveErrorClock > 1 then
  2059. local prevLiveErr = liveErr
  2060. liveErr = currentLanguage.parseError(nil)
  2061. local code = ""
  2062. for _, v in pairs(lines) do code = code .. v .. "\n" end
  2063.  
  2064. liveErr = currentLanguage.getCompilerErrors(code)
  2065. liveErr.line = math.min(liveErr.line - 2, #lines)
  2066. if liveErr ~= prevLiveErr then draw() end
  2067. liveErrorClock = os.clock()
  2068. end
  2069. end
  2070.  
  2071. return "menu"
  2072. end
  2073.  
  2074.  
  2075. -- -------- Open File
  2076.  
  2077. local function newFile()
  2078. local wid = w - 13
  2079.  
  2080. -- Get name
  2081. title("Lua IDE - New File")
  2082. local name = centerRead(wid, "/")
  2083. if not name or name == "" then return "menu" end
  2084. name = "/" .. name
  2085.  
  2086. -- Clear
  2087. title("Lua IDE - New File")
  2088. term.setTextColor(colors[theme.textColor])
  2089. term.setBackgroundColor(colors[theme.promptHighlight])
  2090. for i = 8, 10 do
  2091. term.setCursorPos(w/2 - wid/2, i)
  2092. term.write(string.rep(" ", wid))
  2093. end
  2094. term.setCursorPos(1, 9)
  2095. if fs.isDir(name) then
  2096. centerPrint("Cannot Edit a Directory!")
  2097. sleep(1.6)
  2098. return "menu"
  2099. elseif fs.exists(name) then
  2100. centerPrint("File Already Exists!")
  2101. local opt = prompt({{"Open", w/2 - 9, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal")
  2102. if opt == "Open" then return "edit", name
  2103. elseif opt == "Cancel" then return "menu" end
  2104. else return "edit", name end
  2105. end
  2106.  
  2107. local function openFile()
  2108. local wid = w - 13
  2109.  
  2110. -- Get name
  2111. title("Lua IDE - Open File")
  2112. local name = centerRead(wid, "/")
  2113. if not name or name == "" then return "menu" end
  2114. name = "/" .. name
  2115.  
  2116. -- Clear
  2117. title("Lua IDE - New File")
  2118. term.setTextColor(colors[theme.textColor])
  2119. term.setBackgroundColor(colors[theme.promptHighlight])
  2120. for i = 8, 10 do
  2121. term.setCursorPos(w/2 - wid/2, i)
  2122. term.write(string.rep(" ", wid))
  2123. end
  2124. term.setCursorPos(1, 9)
  2125. if fs.isDir(name) then
  2126. centerPrint("Cannot Open a Directory!")
  2127. sleep(1.6)
  2128. return "menu"
  2129. elseif not fs.exists(name) then
  2130. centerPrint("File Doesn't Exist!")
  2131. local opt = prompt({{"Create", w/2 - 11, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal")
  2132. if opt == "Create" then return "edit", name
  2133. elseif opt == "Cancel" then return "menu" end
  2134. else return "edit", name end
  2135. end
  2136.  
  2137.  
  2138. -- -------- Settings
  2139.  
  2140. local function update() -- TODO disable update when LuaIDE is in rom/programs
  2141. local function draw(status)
  2142. title("LuaIDE - Update")
  2143. term.setBackgroundColor(colors[theme.prompt])
  2144. term.setTextColor(colors[theme.textColor])
  2145. for i = 8, 10 do
  2146. term.setCursorPos(w/2 - (status:len() + 4), i)
  2147. write(string.rep(" ", status:len() + 4))
  2148. end
  2149. term.setCursorPos(w/2 - (status:len() + 4), 9)
  2150. term.write(" - " .. status .. " ")
  2151.  
  2152. term.setBackgroundColor(colors[theme.errHighlight])
  2153. for i = 8, 10 do
  2154. term.setCursorPos(w/2 + 2, i)
  2155. term.write(string.rep(" ", 10))
  2156. end
  2157. term.setCursorPos(w/2 + 2, 9)
  2158. term.write(" > Cancel ")
  2159. end
  2160.  
  2161. if not http then
  2162. draw("HTTP API Disabled!")
  2163. sleep(1.6)
  2164. return "settings"
  2165. end
  2166.  
  2167. draw("Updating...")
  2168. local tID = os.startTimer(10)
  2169. http.request(updateURL)
  2170. while true do
  2171. local e, but, x, y = os.pullEvent()
  2172. if (e == "key" and but == 28) or
  2173. (e == "mouse_click" and x >= w/2 + 2 and x <= w/2 + 12 and y == 9) then
  2174. draw("Cancelled")
  2175. sleep(1.6)
  2176. break
  2177. elseif e == "http_success" and but == updateURL then
  2178. local new = x.readAll()
  2179. local curf = io.open(ideLocation, "r")
  2180. local cur = curf:read("*a")
  2181. curf:close()
  2182.  
  2183. if cur ~= new then
  2184. draw("Update Found")
  2185. sleep(1.6)
  2186. local f = io.open(ideLocation, "w")
  2187. f:write(new)
  2188. f:close()
  2189.  
  2190. draw("Click to Exit")
  2191. while true do
  2192. local e = os.pullEvent()
  2193. if e == "mouse_click" or (not isAdvanced() and e == "key") then break end
  2194. end
  2195. return "exit"
  2196. else
  2197. draw("No Updates Found!")
  2198. sleep(1.6)
  2199. break
  2200. end
  2201. elseif e == "http_failure" or (e == "timer" and but == tID) then
  2202. draw("Update Failed!")
  2203. sleep(1.6)
  2204. break
  2205. end
  2206. end
  2207.  
  2208. return "settings"
  2209. end
  2210.  
  2211. local function changeTheme()
  2212. title("LuaIDE - Theme")
  2213.  
  2214. if isAdvanced() then
  2215. local disThemes = {"Back"}
  2216. for _, v in pairs(availableThemes) do table.insert(disThemes, v[1]) end
  2217. local t = promptMenu(disThemes)
  2218. local url = nil
  2219. for _, v in pairs(availableThemes) do if v[1] == t then url = v[2] end end
  2220.  
  2221. if not url then return "settings" end
  2222. if t == "Dawn (Default)" then
  2223. term.setBackgroundColor(colors[theme.backgroundHighlight])
  2224. term.setCursorPos(3, 3)
  2225. term.clearLine()
  2226. term.write("LuaIDE - Loaded Theme!")
  2227. sleep(1.6)
  2228.  
  2229. fs.delete(themeLocation)
  2230. theme = defaultTheme
  2231. return "menu"
  2232. end
  2233.  
  2234. term.setBackgroundColor(colors[theme.backgroundHighlight])
  2235. term.setCursorPos(3, 3)
  2236. term.clearLine()
  2237. term.write("LuaIDE - Downloading...")
  2238.  
  2239. fs.delete("/.LuaIDE_temp_theme_file")
  2240. download(url, "/.LuaIDE_temp_theme_file")
  2241. local a = loadTheme("/.LuaIDE_temp_theme_file")
  2242.  
  2243. term.setCursorPos(3, 3)
  2244. term.clearLine()
  2245. if a then
  2246. term.write("LuaIDE - Loaded Theme!")
  2247. fs.delete(themeLocation)
  2248. fs.move("/.LuaIDE_temp_theme_file", themeLocation)
  2249. theme = a
  2250. sleep(1.6)
  2251. return "settings"
  2252. end
  2253.  
  2254. term.write("LuaIDE - Could Not Load Theme!")
  2255. fs.delete("/.LuaIDE_temp_theme_file")
  2256. sleep(1.6)
  2257. return "settings"
  2258. else
  2259. term.setCursorPos(1, h / 2 - 1)
  2260. if w < 27 then --Fixes message on smaller screens
  2261. centerPrint("Themes are only")
  2262. centerPrint("available on")
  2263. centerPrint("advanced computers")
  2264. else
  2265. centerPrint("Themes are only available")
  2266. centerPrint("on advanced computers")
  2267. end
  2268. os.startTimer(5)
  2269. sleep(0.1)
  2270. os.pullEvent()
  2271. return "settings"
  2272. end
  2273. end
  2274.  
  2275. local function settings()
  2276. title("LuaIDE - Settings")
  2277.  
  2278. local opt = promptMenu({"Change Theme","Check for Updates","Return to Menu", "Exit IDE"})
  2279. if opt == "Change Theme" then return changeTheme()
  2280. elseif opt == "Check for Updates" then return update()
  2281. elseif opt == "Return to Menu" then return "menu"
  2282. elseif opt == "Exit IDE" then return "exit" end
  2283. end
  2284.  
  2285.  
  2286. -- -------- Menu
  2287.  
  2288. local function menu()
  2289. title("Welcome to LuaIDE " .. version)
  2290.  
  2291. local opt = promptMenu({"New File","Open File","Settings","Exit IDE"})
  2292. if opt == "New File" then return "new"
  2293. elseif opt == "Open File" then return "open"
  2294. elseif opt == "Settings" then return "settings"
  2295. elseif opt == "Exit IDE" then return "exit" end
  2296. end
  2297.  
  2298.  
  2299. -- -------- Main
  2300.  
  2301. local function main(arguments)
  2302. local opt, data = "menu", nil
  2303.  
  2304. -- Check arguments
  2305. if type(arguments) == "table" and #arguments > 0 then
  2306. local f = "/" .. shell.resolve(arguments[1])
  2307. if fs.isDir(f) then print("Cannot edit a directory.") end
  2308. opt, data = "edit", f
  2309. end
  2310.  
  2311. -- Main run loop
  2312. while true do
  2313. -- Menu
  2314. if opt == "menu" then opt = menu() end
  2315.  
  2316. -- Other
  2317. if opt == "new" then opt, data = newFile()
  2318. elseif opt == "open" then opt, data = openFile()
  2319. elseif opt == "settings" then opt = settings()
  2320. end if opt == "exit" then break end
  2321.  
  2322. -- Edit
  2323. if opt == "edit" and data then opt = edit(data) end
  2324. end
  2325. end
  2326.  
  2327. -- Load Theme
  2328. if fs.exists(themeLocation) then theme = loadTheme(themeLocation) end
  2329. if not theme and isAdvanced() then theme = defaultTheme
  2330. elseif not theme then theme = normalTheme end
  2331.  
  2332. -- Run
  2333. local _, err = pcall(function()
  2334. parallel.waitForAny(function() main(arguments) end, monitorKeyboardShortcuts)
  2335. end)
  2336.  
  2337. -- Catch errors
  2338. if err and not err:find("Terminated") then
  2339. term.setCursorBlink(false)
  2340. title("LuaIDE - Crash! D:")
  2341.  
  2342. term.setBackgroundColor(colors[theme.err])
  2343. for i = 6, 8 do
  2344. term.setCursorPos(5, i)
  2345. term.write(string.rep(" ", 36))
  2346. end
  2347. term.setCursorPos(6, 7)
  2348. term.write("LuaIDE Has Crashed! D:")
  2349.  
  2350. term.setBackgroundColor(colors[theme.background])
  2351. term.setCursorPos(2, 10)
  2352. print(err)
  2353.  
  2354. term.setBackgroundColor(colors[theme.prompt])
  2355. local _, cy = term.getCursorPos()
  2356. for i = cy + 1, cy + 4 do
  2357. term.setCursorPos(5, i)
  2358. term.write(string.rep(" ", 36))
  2359. end
  2360. term.setCursorPos(6, cy + 2)
  2361. term.write("Please report this error to")
  2362. term.setCursorPos(6, cy + 3)
  2363. term.write("GravityScore! ")
  2364.  
  2365. term.setBackgroundColor(colors[theme.background])
  2366. if isAdvanced() then centerPrint("Click to Exit...", h - 1)
  2367. else centerPrint("Press Any Key to Exit...", h - 1) end
  2368. while true do
  2369. local e = os.pullEvent()
  2370. if e == "mouse_click" or e == "key" then break end
  2371. end
  2372.  
  2373. -- Prevent key from being shown
  2374. os.queueEvent("")
  2375. os.pullEvent()
  2376. end
  2377.  
  2378. -- Exit
  2379. term.setBackgroundColor(colors.black)
  2380. term.setTextColor(colors.white)
  2381. term.clear()
  2382. term.setCursorPos(1, 1)
  2383. if w < 27 then --Fixes message on smaller screens
  2384. centerPrint("Thank You for")
  2385. centerPrint("using Lua IDE " .. version)
  2386. centerPrint("Made by GravityScore")
  2387. else
  2388. centerPrint("Thank You for using Lua IDE " .. version)
  2389. centerPrint("Made by GravityScore")
  2390. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement