terminal.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. --The terminal !--
  2. local _LIKO_Version, _LIKO_Old = BIOS.getVersion()
  3. local _LIKO_TAG = _LIKO_Version:sub(-3,-1)
  4. local _LIKO_DEV = (_LIKO_TAG == "DEV")
  5. local _LIKO_BUILD = _LIKO_Version:sub(3,-5)
  6. local PATH = "D:/Programs/;C:/Programs/;" --The system PATH variable, used by the terminal to search for programs.
  7. local curdrive, curdir, curpath = "D", "/", "D:/" --The current active path in the terminal.
  8. local editor --The editors api, will be loaded later in term.init()
  9. --Creates an iterator which returns each path in the PATH provided.
  10. local function nextPath(p)
  11. if p:sub(-1)~=";" then p=p..";" end
  12. return p:gmatch("(.-);")
  13. end
  14. local fw, fh = fontSize() --The LIKO-12 GPU Font size.
  15. local history = {} --The history of commands.
  16. local hispos --The current item in the history.
  17. local btimer, btime, blink = 0, 0.5, true --The terminal cursor blink timer.
  18. local ecommand --The editor command, used by the Ctrl-R hotkey to execute the 'run' program.
  19. local buffer = "" --The terminal input buffer
  20. local inputPos = 1 --The next input character position in the terminal buffer.
  21. --Checks if the cursor is in the bounds of the screen.
  22. local function checkCursor()
  23. local cx, cy = printCursor()
  24. local tw, th = termSize()
  25. if cx > tw then cx = tw end
  26. if cx < 0 then cx = 0 end
  27. if cy > th then cy = th end
  28. if cy < 0 then cy = 0 end
  29. printCursor(cx,cy,0)
  30. rect(cx*(fw+1)+1,blink and cy*(fh+2)+1 or cy*(fh+2),fw+1,blink and fh or fh+3,false,blink and 4 or 0) --The blink
  31. if inputPos <= buffer:len() then
  32. printCursor(cx,cy,-1)
  33. print(buffer:sub(inputPos,inputPos),false)
  34. printCursor(cx,cy,0)
  35. end
  36. end
  37. --Splits a string at each white space.
  38. local function split(str)
  39. local t = {}
  40. for val in str:gmatch("%S+") do
  41. table.insert(t, val)
  42. end
  43. return unpack(t)
  44. end
  45. local term = {} --The terminal API
  46. function term.init()
  47. editor = require("Editors") --Load the editors
  48. clear() fs.drive("D") --Set the HDD api active drive to D
  49. SpriteGroup(25,1,1,5,1,1,1,0,editor.editorsheet)
  50. printCursor(0,1,0)
  51. color(_LIKO_DEV and 8 or 9) print(_LIKO_TAG,5*8+1,3) flip() sleep(0.125)
  52. cam("translate",0,3) color(12) print("D",false) color(6) print("isk",false) color(12) print("OS",false) color(6) cam("translate",0,-1) print(" ".._LIKO_BUILD) editor.editorsheet:draw(60,(fw+1)*6+1,fh+2) flip() sleep(0.125) cam()
  53. color(6) print("\nhttp://github.com/ramilego4game/liko12")
  54. flip() sleep(0.0625)
  55. if fs.exists("D:/autoexec.lua") then
  56. term.executeFile("D:/autoexec.lua")
  57. elseif fs.exists("C:/autoexec.lua") then
  58. term.executeFile("C:/autoexec.lua")
  59. else
  60. if _LIKO_Old then
  61. color(7) print("\n Updated LIKO-12 Successfully.\n Type ",false)
  62. color(6) print("help Whatsnew",false)
  63. color(7) print(" for changelog.\n")
  64. else
  65. term.execute("tip")
  66. end
  67. color(9) print("Type help for help")
  68. flip() sleep(0.0625)
  69. end
  70. end
  71. --Reload the system
  72. function term.reload()
  73. package.loaded = {} --Reset the package system
  74. package.loaded["C:/terminal.lua"] = term --Restore the current terminal instance
  75. --Reload the APIS
  76. for k, file in ipairs(fs.getDirectoryItems("C:/APIS/")) do
  77. dofile("C:/APIS/"..file)
  78. end
  79. editor = require("Editors") --Re initialize the editors
  80. end
  81. function term.setdrive(d)
  82. if type(d) ~= "string" then return error("DriveLetter must be a string, provided: "..type(d)) end
  83. if not fs.drives()[d] then return error("Drive '"..d.."' doesn't exist !") end
  84. curdrive = d
  85. curpath = curdrive..":/"..curdir
  86. end
  87. function term.setdirectory(d)
  88. if type(d) ~= "string" then return error("Directory must be a string, provided: "..type(d)) end
  89. local p = term.resolve(d)
  90. if not fs.exists(p) then return error("Directory doesn't exist !") end
  91. if not fs.isDirectory(p) then return error("It must be a directory, not a file") end
  92. term.setpath(p)
  93. end
  94. function term.setpath(p)
  95. if type(p) ~= "string" then return error("Path must be a string, provided: "..type(p)) end
  96. p = term.resolve(p)
  97. if not fs.exists(p) then return error("Directory doesn't exist !") end
  98. if not fs.isDirectory(p) then return error("It must be a directory, not a file") end
  99. local drive, path
  100. if p:sub(-2,-1) == ":/" then
  101. drive = p:sub(1,-3)
  102. path = ""
  103. else
  104. drive,path = p:match("(.+):/(.+)")
  105. end
  106. if p:sub(-1,-1) ~= "/" then p = p.."/" end
  107. curdrive, curdir, curpath = drive, "/"..path, p
  108. end
  109. function term.getpath() return curpath end
  110. function term.getdrive() return curdrive end
  111. function term.getdirectory() return curdir end
  112. function term.setPATH(p) PATH = p end
  113. function term.getPATH() return PATH end
  114. function term.prompt()
  115. color(7) print(term.getpath().."> ",false)
  116. end
  117. function term.resolve(path)
  118. path = path:gsub("\\","/") --Windows users :P
  119. if path:sub(-1,-1) == ":" then -- C:
  120. path = path.."/"
  121. return path, fs.exists(path)
  122. end
  123. if path:sub(-2,-1) == ":/" then -- C:/
  124. return path, fs.exists(path)
  125. end
  126. if not path:match("(.+):/(.+)") then
  127. if path:sub(1,1) == "/" then -- /Programs
  128. path = curdrive..":"..path
  129. else
  130. if curpath:sub(-1,-1) == "/" then
  131. path = curpath..path -- Demos/bump
  132. else
  133. path = curpath.."/"..path -- Demos/bump
  134. end
  135. end
  136. end
  137. local d, p = path:match("(.+):/(.+)") --C:/./Programs/../Editors
  138. if d and p then
  139. p = "/"..p.."/"; local dirs = {}
  140. p = p:gsub("/","//"):sub(2,-1)
  141. for dir in string.gmatch(p,"/(.-)/") do
  142. if dir == "." then
  143. --Do nothing, it's useless
  144. elseif dir == ".." then
  145. if #dirs > 0 then
  146. table.remove(dirs,#dirs) --remove the last directory
  147. end
  148. elseif dir ~= "" then
  149. table.insert(dirs,dir)
  150. end
  151. end
  152. path = d..":/"..table.concat(dirs,"/")
  153. return path, fs.exists(path)
  154. end
  155. end
  156. function term.executeFile(file,...)
  157. local chunk, err = fs.load(file)
  158. if not chunk then color(7) return 3, "\nL-ERR:"..tostring(err) end
  159. local ok, err, e = pcall(chunk,...)
  160. color(7) pal() palt() cam() clip() patternFill()
  161. if not ok then color(7) cprint("Program Error:",err) return 2, "\nERR: "..tostring(err) end
  162. if not fs.exists(curpath) then curdir, curpath = "/", curdrive..":/" end
  163. return tonumber(err) or 0, e
  164. end
  165. --[[
  166. Exit codes:
  167. -----------
  168. 0 -> Success
  169. 1 -> Failure
  170. 2 -> Execution Error
  171. 3 -> Compilation Error
  172. 4 -> Command not found 404
  173. ]]
  174. function term.execute(command,...)
  175. if not command then return 4, "No command" end
  176. if fs.exists(curpath..command..".lua") then
  177. local exitCode, err = term.executeFile(curpath..command..".lua",...)
  178. if exitCode > 0 then color(8) print(err or "Failed !") color(7) end
  179. textinput(true)
  180. return exitCode, err
  181. end
  182. for path in nextPath(PATH) do
  183. if fs.exists(path) then
  184. local files = fs.getDirectoryItems(path)
  185. for _,file in ipairs(files) do
  186. if file == command..".lua" then
  187. local exitCode, err = term.executeFile(path..file,...)
  188. if exitCode > 0 then color(8) print(err or "Failed !") color(7) end
  189. textinput(true)
  190. return exitCode, err
  191. end
  192. end
  193. end
  194. end
  195. color(9) print("Command not found: " .. command) color(7) return 4, "Command not found"
  196. end
  197. function term.ecommand(command) --Editor post command
  198. ecommand = command
  199. end
  200. local function splitFilePath(path) return path:match("(.-)([^\\/]-%.?([^%.\\/]*))$") end --A function to split path to path, name, extension.
  201. function term.loop() --Enter the while loop of the terminal
  202. cursor("none")
  203. clearEStack()
  204. checkCursor() term.prompt()
  205. buffer, inputPos = "", 1
  206. for event, a,b,c,d,e,f in pullEvent do
  207. checkCursor() --Which also draws the cursor blink
  208. if event == "filedropped" then
  209. local p, n, e = splitFilePath(a)
  210. if e == "png" or e == "lk12" then
  211. if b then
  212. fs.write("C:/.temp/"..n,b)
  213. blink = false; checkCursor()
  214. print("load "..n)
  215. term.execute("load","C:/.temp/"..n)
  216. term.prompt()
  217. blink = true; checkCursor()
  218. else
  219. blink = false; checkCursor()
  220. print("load "..n)
  221. color(8) print("Failed to read file.") color(7)
  222. term.prompt()
  223. blink = true; checkCursor()
  224. end
  225. end
  226. elseif event == "textinput" then
  227. print(a..buffer:sub(inputPos,-1),false)
  228. for i=inputPos,buffer:len() do printBackspace(-1) end
  229. buffer = buffer:sub(1,inputPos-1)..a..buffer:sub(inputPos,-1)
  230. inputPos = inputPos + a:len()
  231. elseif event == "keypressed" then
  232. if a == "return" then
  233. if hispos then table.remove(history,#history) hispos = false end
  234. table.insert(history, buffer)
  235. blink = false; checkCursor()
  236. print("") -- insert newline after Enter
  237. term.execute(split(buffer)) buffer, inputPos = "", 1
  238. checkCursor() term.prompt() blink = true cursor("none")
  239. elseif a == "backspace" then
  240. blink = false; checkCursor()
  241. if buffer:len() > 0 then
  242. --Remove the character
  243. printBackspace()
  244. --Re print the buffer
  245. for char in string.gmatch(buffer:sub(inputPos,-1),".") do
  246. print(char,false)
  247. end
  248. --Erase the last character
  249. print("-",false) printBackspace()
  250. --Go back to the input position
  251. for i=#buffer,inputPos,-1 do
  252. printBackspace(-1)
  253. end
  254. --Remove the character from the buffer
  255. buffer = buffer:sub(1,inputPos-2) .. buffer:sub(inputPos,-1)
  256. --Update input postion
  257. inputPos = inputPos-1
  258. end
  259. blink = true; checkCursor()
  260. elseif a == "delete" then
  261. blink = false; checkCursor()
  262. print(buffer:sub(inputPos,-1),false)
  263. for i=1,buffer:len() do
  264. printBackspace()
  265. end
  266. buffer, inputPos = "", 1
  267. blink = true; checkCursor()
  268. elseif a == "escape" then
  269. local screenbk = screenshot()
  270. local oldx, oldy, oldbk = printCursor()
  271. editor:loop() cursor("none")
  272. printCursor(oldx,oldy,oldbk)
  273. palt(0,false) screenbk:image():draw(0,0) color(7) palt(0,true) flip()
  274. if ecommand then
  275. term.execute(split(ecommand))
  276. checkCursor() term.prompt() blink = true cursor("none")
  277. ecommand = false
  278. end
  279. elseif a == "up" then
  280. if not hispos then
  281. table.insert(history,buffer)
  282. hispos = #history
  283. end
  284. if hispos > 1 then
  285. hispos = hispos-1
  286. blink = false; checkCursor()
  287. print(buffer:sub(inputPos,-1),false)
  288. for i=1,buffer:len() do
  289. printBackspace()
  290. end
  291. buffer = history[hispos]
  292. inputPos = buffer:len() + 1
  293. for char in string.gmatch(buffer,".") do
  294. print(char,false)
  295. end
  296. blink = true; checkCursor()
  297. end
  298. elseif a == "down" then
  299. if hispos and hispos < #history then
  300. hispos = hispos+1
  301. blink = false; checkCursor()
  302. print(buffer:sub(inputPos,-1),false)
  303. for i=1,buffer:len() do
  304. printBackspace()
  305. end
  306. buffer = history[hispos]
  307. inputPos = buffer:len() + 1
  308. for char in string.gmatch(buffer,".") do
  309. print(char,false)
  310. end
  311. if hispos == #history then table.remove(history,#history) hispos = false end
  312. blink = true; checkCursor()
  313. end
  314. elseif a == "left" then
  315. blink = false; checkCursor()
  316. if inputPos > 1 then
  317. inputPos = inputPos - 1
  318. printBackspace(-1)
  319. end
  320. blink = true; checkCursor()
  321. elseif a == "right" then
  322. blink = false; checkCursor()
  323. if inputPos <= buffer:len() then
  324. print(buffer:sub(inputPos,inputPos),false)
  325. inputPos = inputPos + 1
  326. end
  327. blink = true; checkCursor()
  328. elseif a == "c" then
  329. if isKDown("lctrl","rctrl") then
  330. clipboard(buffer)
  331. end
  332. elseif a == "v" then
  333. if isKDown("lctrl","rctrl") then
  334. local paste = clipboard() or ""
  335. for char in string.gmatch(paste..buffer:sub(inputPos,-1),".") do
  336. print(char,false)
  337. end
  338. for i=inputPos,buffer:len() do printBackspace(-1) end
  339. buffer = buffer:sub(1,inputPos-1)..paste..buffer:sub(inputPos,-1)
  340. inputPos = inputPos + paste:len()
  341. end
  342. end
  343. elseif event == "touchpressed" then
  344. textinput(true)
  345. elseif event == "update" then
  346. btimer = btimer + a
  347. if btimer > btime then
  348. btimer = btimer%btime
  349. blink = not blink
  350. end
  351. end
  352. end
  353. end
  354. return term