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