· 6 years ago · Oct 26, 2019, 08:56 AM
1_G._OSNAME = "proOS"
2_G._OSVER = "0.6.1.5"
3_G._OSVERSION = _OSNAME .. " " .. _OSVER
4_G._OSCREDIT = "proOS by Oleg, based off of OpenOS code from OpenComputers.\nproOS code is under BSD 2-clause license, OpenOS code is under the MIT liecence."
5
6--component code
7function component_code()
8local adding = {}
9local removing = {}
10local primaries = {}
11
12-------------------------------------------------------------------------------
13
14-- This allows writing component.modem.open(123) instead of writing
15-- component.getPrimary("modem").open(123), which may be nicer to read.
16setmetatable(component, { __index = function(_, key)
17 return component.getPrimary(key)
18 end})
19
20function component.get(address, componentType)
21 checkArg(1, address, "string")
22 checkArg(2, componentType, "string", "nil")
23 for c in component.list(componentType, true) do
24 if c:sub(1, address:len()) == address then
25 return c
26 end
27 end
28 return nil, "no such component"
29end
30
31function component.isAvailable(componentType)
32 checkArg(1, componentType, "string")
33 if not primaries[componentType] then
34 -- This is mostly to avoid out of memory errors preventing proxy
35 -- creation cause confusion by trying to create the proxy again,
36 -- causing the oom error to be thrown again.
37 component.setPrimary(componentType, component.list(componentType, true)())
38 end
39 return primaries[componentType] ~= nil
40end
41
42function component.isPrimary(address)
43 local componentType = component.type(address)
44 if componentType then
45 if component.isAvailable(componentType) then
46 return primaries[componentType].address == address
47 end
48 end
49 return false
50end
51
52function component.getPrimary(componentType)
53 checkArg(1, componentType, "string")
54 assert(component.isAvailable(componentType),
55 "no primary '" .. componentType .. "' available")
56 return primaries[componentType]
57end
58
59function component.setPrimary(componentType, address)
60 checkArg(1, componentType, "string")
61 checkArg(2, address, "string", "nil")
62 if address ~= nil then
63 address = component.get(address, componentType)
64 assert(address, "no such component")
65 end
66
67 local wasAvailable = primaries[componentType]
68 if wasAvailable and address == wasAvailable.address then
69 return
70 end
71 local wasAdding = adding[componentType]
72 if wasAdding and address == wasAdding.address then
73 return
74 end
75 if wasAdding then
76 event.cancel(wasAdding.timer)
77 end
78 primaries[componentType] = nil
79 adding[componentType] = nil
80
81 local primary = address and component.proxy(address) or nil
82 if wasAvailable then
83 computer.pushSignal("component_unavailable", componentType)
84 end
85 if primary then
86 if wasAvailable or wasAdding then
87 adding[componentType] = {
88 address=address,
89 timer=event.timer(0.1, function()
90 adding[componentType] = nil
91 primaries[componentType] = primary
92 computer.pushSignal("component_available", componentType)
93 end)
94 }
95 else
96 primaries[componentType] = primary
97 computer.pushSignal("component_available", componentType)
98 end
99 end
100end
101
102-------------------------------------------------------------------------------
103
104local function onComponentAdded(_, address, componentType)
105 if not (primaries[componentType] or adding[componentType]) then
106 component.setPrimary(componentType, address)
107 end
108end
109
110local function onComponentRemoved(_, address, componentType)
111 if primaries[componentType] and primaries[componentType].address == address or
112 adding[componentType] and adding[componentType].address == address
113 then
114 component.setPrimary(componentType, component.list(componentType, true)())
115 end
116end
117
118event.listen("component_added", onComponentAdded)
119event.listen("component_removed", onComponentRemoved)
120end
121--text libary
122function text_code()
123 local text = {}
124
125 function text.detab(value, tabWidth)
126 checkArg(1, value, "string")
127 checkArg(2, tabWidth, "number", "nil")
128 tabWidth = tabWidth or 8
129 local function rep(match)
130 local spaces = tabWidth - match:len() % tabWidth
131 return match .. string.rep(" ", spaces)
132 end
133 local result = value:gsub("([^\n]-)\t", rep) -- truncate results
134 return result
135 end
136
137 function text.padRight(value, length)
138 checkArg(1, value, "string", "nil")
139 checkArg(2, length, "number")
140 if not value or unicode.len(value) == 0 then
141 return string.rep(" ", length)
142 else
143 return value .. string.rep(" ", length - unicode.len(value))
144 end
145 end
146
147 function text.padLeft(value, length)
148 checkArg(1, value, "string", "nil")
149 checkArg(2, length, "number")
150 if not value or unicode.len(value) == 0 then
151 return string.rep(" ", length)
152 else
153 return string.rep(" ", length - unicode.len(value)) .. value
154 end
155 end
156
157 function text.trim(value) -- from http://lua-users.org/wiki/StringTrim
158 local from = string.match(value, "^%s*()")
159 return from > #value and "" or string.match(value, ".*%S", from)
160 end
161
162 function text.wrap(value, width, maxWidth)
163 checkArg(1, value, "string")
164 checkArg(2, width, "number")
165 checkArg(3, maxWidth, "number")
166 local line, nl = value:match("([^\r\n]*)(\r?\n?)") -- read until newline
167 if unicode.len(line) > width then -- do we even need to wrap?
168 local partial = unicode.sub(line, 1, width)
169 local wrapped = partial:match("(.*[^a-zA-Z0-9._()'`=])")
170 if wrapped or unicode.len(line) > maxWidth then
171 partial = wrapped or partial
172 return partial, unicode.sub(value, unicode.len(partial) + 1), true
173 else
174 return "", value, true -- write in new line.
175 end
176 end
177 local start = unicode.len(line) + unicode.len(nl) + 1
178 return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0
179 end
180
181 function text.wrappedLines(value, width, maxWidth)
182 local line, nl
183 return function()
184 if value then
185 line, value, nl = text.wrap(value, width, maxWidth)
186 return line
187 end
188 end
189 end
190
191 -------------------------------------------------------------------------------
192
193 function text.tokenize(value)
194 checkArg(1, value, "string")
195 local tokens, token = {}, ""
196 local escaped, quoted, start = false, false, -1
197 for i = 1, unicode.len(value) do
198 local char = unicode.sub(value, i, i)
199 if escaped then -- escaped character
200 escaped = false
201 token = token .. char
202 elseif char == "\\" and quoted ~= "'" then -- escape character?
203 escaped = true
204 token = token .. char
205 elseif char == quoted then -- end of quoted string
206 quoted = false
207 token = token .. char
208 elseif (char == "'" or char == '"') and not quoted then
209 quoted = char
210 start = i
211 token = token .. char
212 elseif string.find(char, "%s") and not quoted then -- delimiter
213 if token ~= "" then
214 table.insert(tokens, token)
215 token = ""
216 end
217 else -- normal char
218 token = token .. char
219 end
220 end
221 if quoted then
222 return nil, "unclosed quote at index " .. start
223 end
224 if token ~= "" then
225 table.insert(tokens, token)
226 end
227 return tokens
228 end
229
230 -------------------------------------------------------------------------------
231
232 function text.endswith(s, send)
233 return #s >= #send and s:find(send, #s-#send+1, true) and true or false
234 end
235
236 return text
237end
238--event code
239function event_code()
240 local event, listeners, timers = {}, {}, {}
241 local lastInterrupt = -math.huge
242
243 local function matches(signal, name, filter)
244 if name and not (type(signal[1]) == "string" and signal[1]:match(name))
245 then
246 return false
247 end
248 for i = 1, filter.n do
249 if filter[i] ~= nil and filter[i] ~= signal[i + 1] then
250 return false
251 end
252 end
253 return true
254 end
255
256 local function call(callback, ...)
257 local result, message = pcall(callback, ...)
258 if not result and type(event.onError) == "function" then
259 pcall(event.onError, message)
260 return
261 end
262 return message
263 end
264
265 local function dispatch(signal, ...)
266 if listeners[signal] then
267 local function callbacks()
268 local list = {}
269 for index, listener in ipairs(listeners[signal]) do
270 list[index] = listener
271 end
272 return list
273 end
274 for _, callback in ipairs(callbacks()) do
275 if call(callback, signal, ...) == false then
276 event.ignore(signal, callback) -- alternative method of removing a listener
277 end
278 end
279 end
280 end
281
282 local function tick()
283 local function elapsed()
284 local list = {}
285 for id, timer in pairs(timers) do
286 if timer.after <= computer.uptime() then
287 table.insert(list, timer.callback)
288 timer.times = timer.times - 1
289 if timer.times <= 0 then
290 timers[id] = nil
291 else
292 timer.after = computer.uptime() + timer.interval
293 end
294 end
295 end
296 return list
297 end
298 for _, callback in ipairs(elapsed()) do
299 call(callback)
300 end
301 end
302
303 -------------------------------------------------------------------------------
304
305 function event.cancel(timerId)
306 checkArg(1, timerId, "number")
307 if timers[timerId] then
308 timers[timerId] = nil
309 return true
310 end
311 return false
312 end
313
314 function event.ignore(name, callback)
315 checkArg(1, name, "string")
316 checkArg(2, callback, "function")
317 if listeners[name] then
318 for i = 1, #listeners[name] do
319 if listeners[name][i] == callback then
320 table.remove(listeners[name], i)
321 if #listeners[name] == 0 then
322 listeners[name] = nil
323 end
324 return true
325 end
326 end
327 end
328 return false
329 end
330
331 function event.listen(name, callback)
332 checkArg(1, name, "string")
333 checkArg(2, callback, "function")
334 if listeners[name] then
335 for i = 1, #listeners[name] do
336 if listeners[name][i] == callback then
337 return false
338 end
339 end
340 else
341 listeners[name] = {}
342 end
343 table.insert(listeners[name], callback)
344 return true
345 end
346
347 function event.onError(message)
348 local log = io.open("/tmp/event.log", "a")
349 if log then
350 log:write(message .. "\n")
351 log:close()
352 end
353 end
354
355 function event.pull(...)
356 local args = table.pack(...)
357 local seconds, name, filter
358 if type(args[1]) == "string" then
359 name = args[1]
360 filter = table.pack(table.unpack(args, 2, args.n))
361 else
362 checkArg(1, args[1], "number", "nil")
363 checkArg(2, args[2], "string", "nil")
364 seconds = args[1]
365 name = args[2]
366 filter = table.pack(table.unpack(args, 3, args.n))
367 end
368
369 local hasFilter = name ~= nil
370 if not hasFilter then
371 for i = 1, filter.n do
372 hasFilter = hasFilter or filter[i] ~= nil
373 end
374 end
375
376 local deadline = seconds and
377 (computer.uptime() + seconds) or
378 (hasFilter and math.huge or 0)
379 repeat
380 local closest = seconds and deadline or math.huge
381 for _, timer in pairs(timers) do
382 closest = math.min(closest, timer.after)
383 end
384 local signal = table.pack(computer.pullSignal(closest - computer.uptime()))
385 if signal.n > 0 then
386 dispatch(table.unpack(signal, 1, signal.n))
387 end
388 tick()
389 if event.shouldInterrupt() then
390 lastInterrupt = computer.uptime()
391 error("interrupted", 0)
392 end
393 if not (seconds or hasFilter) or matches(signal, name, filter) then
394 return table.unpack(signal, 1, signal.n)
395 end
396 until computer.uptime() >= deadline
397 end
398
399 function event.shouldInterrupt()
400 return computer.uptime() - lastInterrupt > 1 and
401 keyboard.isControlDown() and
402 keyboard.isAltDown() and
403 keyboard.isKeyDown(keyboard.keys.c)
404 end
405
406 function event.timer(interval, callback, times)
407 checkArg(1, interval, "number")
408 checkArg(2, callback, "function")
409 checkArg(3, times, "number", "nil")
410 local id
411 repeat
412 id = math.floor(math.random(1, 0x7FFFFFFF))
413 until not timers[id]
414 timers[id] = {
415 interval = interval,
416 after = computer.uptime() + interval,
417 callback = callback,
418 times = times or 1
419 }
420 return id
421 end
422
423 -------------------------------------------------------------------------------
424
425 return event
426end
427--filesystem code
428function fs_code()
429 local fs = {}
430 fs.drive = {}
431 --drive mapping table, initilized later
432 fs.drive._map = {}
433 --converts a drive letter into a proxy
434 function fs.drive.letterToProxy(letter)
435 return fs.drive._map[letter]
436 end
437 --finds the proxy associated with the letter
438 function fs.drive.proxyToLetter(proxy)
439 for l,p in pairs(fs.drive._map) do
440 if p == proxy then return l end
441 end
442 return nil
443 end
444 --maps a proxy to a letter
445 function fs.drive.mapProxy(letter, proxy)
446 fs.drive._map[letter] = proxy
447 end
448 --finds the address of a drive letter.
449 function fs.drive.toAddress(letter)
450 return fs.drive._map[letter].address
451 end
452 --finds the drive letter mapped to an address
453 function fs.drive.toLetter(address)
454 for l,p in pairs(fs.drive._map) do
455 if p.address == address then return l end
456 end
457 return nil
458 end
459 function fs.drive.mapAddress(letter, address)
460 --print("mapAddress")
461 fs.drive._map[letter] = fs.proxy(address)
462 end
463 function fs.drive.autoMap(address) --returns the letter if mapped OR already mapped, false if not.
464 --print("autoMap")
465 --we get the address and see if it already is mapped...
466 local l = fs.drive.toLetter(address)
467 if l then return l end
468 --then we take the address and attempt to map it
469 --start at A:
470 l = "A"
471 while true do
472 --see if it is mapped and then go to the next letter...
473 if fs.drive._map[l] then l = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ_'):match(l..'(.)') else fs.drive.mapAddress(l, address) return l end
474 --if we got to the end, fail
475 if l == "_" then return false end
476 end
477 end
478 function fs.drive.listProxy()
479 local t = fs.drive._map
480 local p = {}
481 for n in pairs(t) do table.insert(p, n) end
482 table.sort(p, f)
483 local i = 0 -- iterator variable
484 local iter = function () -- iterator function
485 i = i + 1
486 if p[i] == nil then return nil
487 else return p[i], t[p[i]]
488 end
489 end
490 return iter
491 end
492 function fs.drive.list()
493 local i = 0 -- iterator variable
494 local proxyIter = fs.drive.listProxy()
495 local iter = function () -- iterator function
496 l, p = proxyIter()
497 if not l then return nil end
498 return l, p.address
499 end
500 return iter
501 end
502 fs.drive._current = "A" --as the boot drive is A:
503 function fs.drive.setcurrent(letter)
504 letter = letter:upper()
505 if not fs.drive._map[letter] then error("Invalid Drive", 2) end
506 fs.drive._current = letter
507 end
508 function fs.drive.drivepathSplit(mixed)
509 local drive = fs.drive._current
510 local path
511 if string.sub(mixed, 2,2) == ":" then
512 drive = string.sub(mixed, 1,1):upper()
513 path = string.sub(mixed, 3)
514 else
515 path = mixed
516 end
517 return drive, path
518 end
519 function fs.drive.getcurrent() return fs.drive._current end
520 function fs.invoke(method, ...) return fs.drive._map[fs.drive._current][method](...) end
521 function fs.proxy(filter)
522 checkArg(1, filter, "string")
523 local address
524 for c in component.list("filesystem") do
525 if component.invoke(c, "getLabel") == filter then
526 address = c
527 break
528 end
529 if filter:sub(2,2) == ":" then
530 if fs.drive.toAddress(filter:sub(1,1)) == c then address = c break end
531 end
532 if c:sub(1, filter:len()) == filter then
533 address = c
534 break
535 end
536 end
537 if not address then
538 return nil, "no such file system"
539 end
540 return component.proxy(address)
541 end
542 function fs.open(file, mode)
543 local drive, handle, proxy
544 drive, path = fs.drive.drivepathSplit(file)
545 proxy = fs.drive.letterToProxy(drive)
546 handle, reason = proxy.open(path, mode or 'r')
547 if not handle then return nil, reason end
548 return {_handle = handle, _proxy = proxy}
549 end
550 function fs.write(handle, data) return handle._proxy.write(handle._handle, data) end
551 function fs.read(handle, length) return handle._proxy.read(handle._handle, length or math.huge) end
552 function fs.close(handle) return handle._proxy.close(handle._handle) end
553 function fs.isDirectory(path)
554 local drive
555 drive, path = fs.drive.drivepathSplit(path)
556 return fs.drive.letterToProxy(drive).isDirectory(path)
557 end
558 function fs.exists(path)
559 local drive
560 drive, path = fs.drive.drivepathSplit(path)
561 return fs.drive.letterToProxy(drive).exists(path)
562 end
563 function fs.remove(path)
564 local drive
565 drive, path = fs.drive.drivepathSplit(path)
566 return fs.drive.letterToProxy(drive).remove(path)
567 end
568 function fs.copy(fromPath, toPath)
569 if fs.isDirectory(fromPath) then
570 return nil, "cannot copy folders"
571 end
572 local input, reason = fs.open(fromPath, "rb")
573 if not input then
574 return nil, reason
575 end
576 local output, reason = fs.open(toPath, "wb")
577 if not output then
578 fs.close(input)
579 return nil, reason
580 end
581 repeat
582 local buffer, reason = filesystem.read(input)
583 if not buffer and reason then
584 return nil, reason
585 elseif buffer then
586 local result, reason = filesystem.write(output, buffer)
587 if not result then
588 filesystem.close(input)
589 filesystem.close(output)
590 return nil, reason
591 end
592 end
593 until not buffer
594 filesystem.close(input)
595 filesystem.close(output)
596 return true
597 end
598 function fs.rename(path1, path2)
599 local drive
600 drive, path = fs.drive.drivepathSplit(path)
601 return fs.drive.letterToProxy(drive).rename(path1, path2)
602 end
603 function fs.makeDirectory(path)
604 local drive
605 drive, path = fs.drive.drivepathSplit(path)
606 return fs.drive.letterToProxy(drive).makeDirectory(path)
607 end
608 function fs.list(path)
609 local drive
610 drive, path = fs.drive.drivepathSplit(path)
611
612 local i = 0
613 local t = fs.drive.letterToProxy(drive).list(path)
614 local n = #t
615 return function()
616 i = i + 1
617 if i <= n then return t[i] end
618 return nil
619 end
620 end
621 function fs.get(path)
622 local drive
623 drive, path = fs.drive.drivepathSplit(path)
624 drive = fs.drive.letterToProxy(drive)
625 if not drive then return nil, "no such file system"
626 else return drive, path end
627 end
628
629 --handle inserted and removed filesystems
630 local function onComponentAdded(_, address, componentType)
631 if componentType == "filesystem" then
632 fs.drive.autoMap(address)
633 end
634 end
635 local function onComponentRemoved(_, address, componentType)
636 if componentType == "filesystem" then
637 fs.drive.mapAddress(fs.drive.toLetter(address), nil)
638 end
639 end
640 event.listen("component_added", onComponentAdded)
641 event.listen("component_removed", onComponentRemoved)
642 local function driveInit()
643 local boot = fs.proxy(computer.getBootAddress())
644 local temp = fs.proxy(computer.tmpAddress())
645 fs.drive._map = { ["A"]=boot, ["X"]=temp }
646 end
647 driveInit()
648 --return the API
649 return fs
650end
651--terminal code
652function terminal_code()
653 local term = {}
654 local cursorX, cursorY = 1, 1
655 local cursorBlink = nil
656 --- quick and dirty hacks that allow newer programs to run while not actually writing new code
657 term.gpu = function() return component.gpu end
658 term.getViewport = function() return 0, 0, component.gpu.getResolution() end
659 term.getGlobalArea = function(ignored)
660 local w,h,dx,dy = term.getViewport(window)
661 return dx+1,dy+1,w,h
662 end
663 term.pull = event.pull
664 term.keyboard = function() return component.keyboard end
665 term.screen = function() return term.gpu().getScreen() end
666
667 local function toggleBlink()
668 if term.isAvailable() then
669 cursorBlink.state = not cursorBlink.state
670 if cursorBlink.state then
671 cursorBlink.alt = component.gpu.get(cursorX, cursorY)
672 component.gpu.set(cursorX, cursorY, "_")
673 else
674 component.gpu.set(cursorX, cursorY, cursorBlink.alt)
675 end
676 end
677 end
678
679 -------------------------------------------------------------------------------
680
681 function term.clear()
682 if term.isAvailable() then
683 local w, h = component.gpu.getResolution()
684 component.gpu.fill(1, 1, w, h, " ")
685 end
686 cursorX, cursorY = 1, 1
687 end
688
689 function term.clearLine()
690 if term.isAvailable() then
691 local w = component.gpu.getResolution()
692 component.gpu.fill(1, cursorY, w, 1, " ")
693 end
694 cursorX = 1
695 end
696
697 function term.getCursor()
698 return cursorX, cursorY
699 end
700
701 function term.setCursor(col, row)
702 checkArg(1, col, "number")
703 checkArg(2, row, "number")
704 if cursorBlink and cursorBlink.state then
705 toggleBlink()
706 end
707 cursorX = math.floor(col)
708 cursorY = math.floor(row)
709 end
710
711 function term.getCursorBlink()
712 return cursorBlink ~= nil
713 end
714
715 function term.setCursorBlink(enabled)
716 checkArg(1, enabled, "boolean")
717 if enabled then
718 if not cursorBlink then
719 cursorBlink = {}
720 cursorBlink.id = event.timer(0.5, toggleBlink, math.huge)
721 cursorBlink.state = false
722 elseif not cursorBlink.state then
723 toggleBlink()
724 end
725 elseif cursorBlink then
726 event.cancel(cursorBlink.id)
727 if cursorBlink.state then
728 toggleBlink()
729 end
730 cursorBlink = nil
731 end
732 end
733
734 function term.isAvailable()
735 return component.isAvailable("gpu") and component.isAvailable("screen")
736 end
737
738 function term.readKey(echo)
739 local blink = term.getCursorBlink()
740 term.setCursorBlink(true)
741 local ok, name, address, charOrValue, code = pcall(event.pull, "key_down")
742 if not ok then
743 term.setCursorBlink(blink)
744 error("interrupted", 0)
745 end
746 if name == "key_down" then
747 if echo then term.write(charOrValue) end
748 term.setCursorBlink(blink)
749 end
750 end
751
752 function term.read(history, dobreak)
753 checkArg(1, history, "table", "nil")
754 history = history or {}
755 table.insert(history, "")
756 local offset = term.getCursor() - 1
757 local scrollX, scrollY = 0, #history - 1
758
759 local function getCursor()
760 local cx, cy = term.getCursor()
761 return cx - offset + scrollX, 1 + scrollY
762 end
763
764 local function line()
765 local cbx, cby = getCursor()
766 return history[cby]
767 end
768
769 local function setCursor(nbx, nby)
770 local w, h = component.gpu.getResolution()
771 local cx, cy = term.getCursor()
772
773 scrollY = nby - 1
774
775 nbx = math.max(1, math.min(unicode.len(history[nby]) + 1, nbx))
776 local ncx = nbx + offset - scrollX
777 if ncx > w then
778 local sx = nbx - (w - offset)
779 local dx = math.abs(scrollX - sx)
780 scrollX = sx
781 component.gpu.copy(1 + offset + dx, cy, w - offset - dx, 1, -dx, 0)
782 local str = unicode.sub(history[nby], nbx - (dx - 1), nbx)
783 str = text.padRight(str, dx)
784 component.gpu.set(1 + math.max(offset, w - dx), cy, unicode.sub(str, 1 + math.max(0, dx - (w - offset))))
785 elseif ncx < 1 + offset then
786 local sx = nbx - 1
787 local dx = math.abs(scrollX - sx)
788 scrollX = sx
789 component.gpu.copy(1 + offset, cy, w - offset - dx, 1, dx, 0)
790 local str = unicode.sub(history[nby], nbx, nbx + dx)
791 --str = text.padRight(str, dx)
792 component.gpu.set(1 + offset, cy, str)
793 end
794
795 term.setCursor(nbx - scrollX + offset, cy)
796 end
797
798 local function copyIfNecessary()
799 local cbx, cby = getCursor()
800 if cby ~= #history then
801 history[#history] = line()
802 setCursor(cbx, #history)
803 end
804 end
805
806 local function redraw()
807 local cx, cy = term.getCursor()
808 local bx, by = 1 + scrollX, 1 + scrollY
809 local w, h = component.gpu.getResolution()
810 local l = w - offset
811 local str = unicode.sub(history[by], bx, bx + l)
812 str = text.padRight(str, l)
813 component.gpu.set(1 + offset, cy, str)
814 end
815
816 local function home()
817 local cbx, cby = getCursor()
818 setCursor(1, cby)
819 end
820
821 local function ende()
822 local cbx, cby = getCursor()
823 setCursor(unicode.len(line()) + 1, cby)
824 end
825
826 local function left()
827 local cbx, cby = getCursor()
828 if cbx > 1 then
829 setCursor(cbx - 1, cby)
830 return true -- for backspace
831 end
832 end
833
834 local function right(n)
835 n = n or 1
836 local cbx, cby = getCursor()
837 local be = unicode.len(line()) + 1
838 if cbx < be then
839 setCursor(math.min(be, cbx + n), cby)
840 end
841 end
842
843 local function up()
844 local cbx, cby = getCursor()
845 if cby > 1 then
846 setCursor(1, cby - 1)
847 redraw()
848 ende()
849 end
850 end
851
852 local function down()
853 local cbx, cby = getCursor()
854 if cby < #history then
855 setCursor(1, cby + 1)
856 redraw()
857 ende()
858 end
859 end
860
861 local function delete()
862 copyIfNecessary()
863 local cbx, cby = getCursor()
864 if cbx <= unicode.len(line()) then
865 history[cby] = unicode.sub(line(), 1, cbx - 1) ..
866 unicode.sub(line(), cbx + 1)
867 local cx, cy = term.getCursor()
868 local w, h = component.gpu.getResolution()
869 component.gpu.copy(cx + 1, cy, w - cx, 1, -1, 0)
870 local br = cbx + (w - cx)
871 local char = unicode.sub(line(), br, br)
872 if not char or unicode.len(char) == 0 then
873 char = " "
874 end
875 component.gpu.set(w, cy, char)
876 end
877 end
878
879 local function insert(value)
880 copyIfNecessary()
881 local cx, cy = term.getCursor()
882 local cbx, cby = getCursor()
883 local w, h = component.gpu.getResolution()
884 history[cby] = unicode.sub(line(), 1, cbx - 1) ..
885 value ..
886 unicode.sub(line(), cbx)
887 local len = unicode.len(value)
888 local n = w - (cx - 1) - len
889 if n > 0 then
890 component.gpu.copy(cx, cy, n, 1, len, 0)
891 end
892 component.gpu.set(cx, cy, value)
893 right(len)
894 end
895
896 local function onKeyDown(char, code)
897 term.setCursorBlink(false)
898 if code == keyboard.keys.back then
899 if left() then delete() end
900 elseif code == keyboard.keys.delete then
901 delete()
902 elseif code == keyboard.keys.left then
903 left()
904 elseif code == keyboard.keys.right then
905 right()
906 elseif code == keyboard.keys.home then
907 home()
908 elseif code == keyboard.keys["end"] then
909 ende()
910 elseif code == keyboard.keys.up then
911 up()
912 elseif code == keyboard.keys.down then
913 down()
914 elseif code == keyboard.keys.enter then
915 local cbx, cby = getCursor()
916 if cby ~= #history then -- bring entry to front
917 history[#history] = line()
918 table.remove(history, cby)
919 end
920 return true, history[#history] .. "\n"
921 elseif keyboard.isControlDown() and code == keyboard.keys.d then
922 if line() == "" then
923 history[#history] = ""
924 return true, nil
925 end
926 elseif keyboard.isControlDown() and code == keyboard.keys.c then
927 history[#history] = ""
928 return true, nil
929 elseif not keyboard.isControl(char) then
930 insert(unicode.char(char))
931 end
932 term.setCursorBlink(true)
933 term.setCursorBlink(true) -- force toggle to caret
934 end
935
936 local function onClipboard(value)
937 copyIfNecessary()
938 term.setCursorBlink(false)
939 local cbx, cby = getCursor()
940 local l = value:find("\n", 1, true)
941 if l then
942 history[cby] = unicode.sub(line(), 1, cbx - 1)
943 redraw()
944 insert(unicode.sub(value, 1, l - 1))
945 return true, line() .. "\n"
946 else
947 insert(value)
948 term.setCursorBlink(true)
949 term.setCursorBlink(true) -- force toggle to caret
950 end
951 end
952
953 local function cleanup()
954 if history[#history] == "" then
955 table.remove(history)
956 end
957 term.setCursorBlink(false)
958 if term.getCursor() > 1 and dobreak ~= false then
959 print()
960 end
961 end
962
963 term.setCursorBlink(true)
964 while term.isAvailable() do
965 local ocx, ocy = getCursor()
966 local ok, name, address, charOrValue, code = pcall(event.pull)
967 if not ok then
968 cleanup()
969 error("interrupted", 0)
970 end
971 local ncx, ncy = getCursor()
972 if ocx ~= ncx or ocy ~= ncy then
973 cleanup()
974 return "" -- soft fail the read if someone messes with the term
975 end
976 if term.isAvailable() and -- may have changed since pull
977 type(address) == "string" and
978 component.isPrimary(address)
979 then
980 local done, result
981 if name == "key_down" then
982 done, result = onKeyDown(charOrValue, code)
983 elseif name == "clipboard" then
984 done, result = onClipboard(charOrValue)
985 end
986 if done then
987 cleanup()
988 return result
989 end
990 end
991 end
992 cleanup()
993 return nil -- fail the read if term becomes unavailable
994 end
995
996 function term.write(value, wrap)
997 if not term.isAvailable() then
998 return
999 end
1000 value = tostring(value)
1001 if unicode.len(value) == 0 then
1002 return
1003 end
1004 do
1005 local noBell = value:gsub("\a", "")
1006 if #noBell ~= #value then
1007 value = noBell
1008 computer.beep()
1009 end
1010 end
1011 value = text.detab(value)
1012 local w, h = component.gpu.getResolution()
1013 if not w then
1014 return -- gpu lost its screen but the signal wasn't processed yet.
1015 end
1016 local blink = term.getCursorBlink()
1017 term.setCursorBlink(false)
1018 local line, nl
1019 repeat
1020 local wrapAfter, margin = math.huge, math.huge
1021 if wrap then
1022 wrapAfter, margin = w - (cursorX - 1), w
1023 end
1024 line, value, nl = text.wrap(value, wrapAfter, margin)
1025 component.gpu.set(cursorX, cursorY, line)
1026 cursorX = cursorX + unicode.len(line)
1027 if nl or (cursorX > w and wrap) then
1028 cursorX = 1
1029 cursorY = cursorY + 1
1030 end
1031 if cursorY > h then
1032 component.gpu.copy(1, 1, w, h, 0, -1)
1033 component.gpu.fill(1, h, w, 1, " ")
1034 cursorY = h
1035 end
1036 until not value
1037 term.setCursorBlink(blink)
1038 end
1039
1040 -------------------------------------------------------------------------------
1041
1042 return term
1043end
1044
1045function keyboard_code(keyboard_data)
1046 local keyboard = {pressedChars = {}, pressedCodes = {}, keys = keyboard_data.keys}
1047
1048 -- Create inverse mapping for name lookup.
1049 setmetatable(keyboard.keys, {
1050 __index = function(tbl, k)
1051 if type(k) ~= "number" then return end
1052 for name,value in pairs(tbl) do
1053 if value == k then
1054 return name
1055 end
1056 end
1057 end
1058 })
1059
1060 local function getKeyboardAddress(address)
1061 return address or term.keyboard().address
1062 end
1063
1064 local function getPressedCodes(address)
1065 address = getKeyboardAddress(address)
1066 return address and keyboard.pressedCodes[address] or false
1067 end
1068
1069 local function getPressedChars(address)
1070 address = getKeyboardAddress(address)
1071 return address and keyboard.pressedChars[address] or false
1072 end
1073
1074 function keyboard.isAltDown(address)
1075 checkArg(1, address, "string", "nil")
1076 local pressedCodes = getPressedCodes(address)
1077 return pressedCodes and (pressedCodes[keyboard.keys.lmenu] or pressedCodes[keyboard.keys.rmenu]) ~= nil
1078 end
1079
1080 function keyboard.isControl(char)
1081 return type(char) == "number" and (char < 0x20 or (char >= 0x7F and char <= 0x9F))
1082 end
1083
1084 function keyboard.isControlDown(address)
1085 checkArg(1, address, "string", "nil")
1086 local pressedCodes = getPressedCodes(address)
1087 return pressedCodes and (pressedCodes[keyboard.keys.lcontrol] or pressedCodes[keyboard.keys.rcontrol]) ~= nil
1088 end
1089
1090 function keyboard.isKeyDown(charOrCode, address)
1091 checkArg(1, charOrCode, "string", "number")
1092 checkArg(2, address, "string", "nil")
1093 if type(charOrCode) == "string" then
1094 local pressedChars = getPressedChars(address)
1095 return pressedChars and pressedChars[utf8 and utf8.codepoint(charOrCode) or charOrCode:byte()]
1096 elseif type(charOrCode) == "number" then
1097 local pressedCodes = getPressedCodes(address)
1098 return pressedCodes and pressedCodes[charOrCode]
1099 end
1100 end
1101
1102 function keyboard.isShiftDown(address)
1103 checkArg(1, address, "string", "nil")
1104 local pressedCodes = getPressedCodes(address)
1105 return pressedCodes and (pressedCodes[keyboard.keys.lshift] or pressedCodes[keyboard.keys.rshift]) ~= nil
1106 end
1107
1108 local function onKeyDown(_, address, char, code)
1109 if keyboard.pressedChars[address] then
1110 keyboard.pressedChars[address][char] = true
1111 keyboard.pressedCodes[address][code] = true
1112 end
1113 end
1114
1115 local function onKeyUp(_, address, char, code)
1116 if keyboard.pressedChars[address] then
1117 keyboard.pressedChars[address][char] = nil
1118 keyboard.pressedCodes[address][code] = nil
1119 end
1120 end
1121
1122 local function onComponentAdded(_, address, componentType)
1123 if componentType == "keyboard" then
1124 keyboard.pressedChars[address] = {}
1125 keyboard.pressedCodes[address] = {}
1126 end
1127 end
1128
1129 local function onComponentRemoved(_, address, componentType)
1130 if componentType == "keyboard" then
1131 keyboard.pressedChars[address] = nil
1132 keyboard.pressedCodes[address] = nil
1133 end
1134 end
1135
1136 for address in component.list("keyboard", true) do
1137 onComponentAdded("component_added", address, "keyboard")
1138 end
1139
1140 event.listen("key_down", onKeyDown)
1141 event.listen("key_up", onKeyUp)
1142 event.listen("component_added", onComponentAdded)
1143 event.listen("component_removed", onComponentRemoved)
1144
1145 return keyboard
1146end
1147
1148local function printProcess(...)
1149 local args = table.pack(...)
1150 local argstr = ""
1151 for i = 1, args.n do
1152 local arg = tostring(args[i])
1153 if i > 1 then
1154 arg = "\t" .. arg
1155 end
1156 argstr = argstr .. arg
1157 end
1158 return argstr
1159end
1160function print(...)
1161 term.write(printProcess(...) .. "\n", true)
1162end
1163function printErr(...)
1164 local c = component.gpu.getForeground()
1165 component.gpu.setForeground(0xFF0000)
1166 print(...)
1167 component.gpu.setForeground(c)
1168end
1169function printPaged(...)
1170 argstr = printProcess(...) .. "\n"
1171 local i = 0
1172 local p = 0
1173 function readline()
1174 i = string.find(argstr, "\n", i+1) -- find 'next' newline
1175 if i == nil then return nil end
1176 local out = argstr:sub(p,i)
1177 p = i + 1
1178 return out
1179 end
1180 local function readlines(file, line, num)
1181 local w, h = component.gpu.getResolution()
1182 num = num or (h - 1)
1183 --num = num or (h)
1184 term.setCursorBlink(false)
1185 for _ = 1, num do
1186 if not line then
1187 line = readline()
1188 if not line then -- eof
1189 return nil
1190 end
1191 end
1192 local wrapped
1193 wrapped, line = text.wrap(text.detab(line), w, w)
1194 term.write(wrapped .. "\n")
1195 end
1196 term.setCursor(1, h)
1197 term.write("Press enter or space to continue:")
1198 term.setCursorBlink(true)
1199 return true
1200 end
1201
1202 local line = nil
1203 while true do
1204 if not readlines(file, line) then
1205 return
1206 end
1207 while true do
1208 local event, address, char, code = event.pull("key_down")
1209 if component.isPrimary(address) then
1210 if code == keyboard.keys.q then
1211 term.setCursorBlink(false)
1212 term.clearLine()
1213 return
1214 elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then
1215 term.clearLine()
1216 break
1217 elseif code == keyboard.keys.enter or code == keyboard.keys.down then
1218 term.clearLine()
1219 if not readlines(file, line, 1) then
1220 return
1221 end
1222 end
1223 end
1224 end
1225 end
1226end
1227--load programs
1228function loadfile(file, mode, env)
1229 local handle, reason = filesystem.open(file)
1230 if not handle then
1231 error(reason, 2)
1232 end
1233 local buffer = ""
1234 repeat
1235 local data, reason = filesystem.read(handle)
1236 if not data and reason then
1237 error(reason)
1238 end
1239 buffer = buffer .. (data or "")
1240 until not data
1241 filesystem.close(handle)
1242 if mode == nil then mode = "bt" end
1243 if env == nil then env = _G end
1244 return load(buffer, "=" .. file)
1245end
1246
1247function dofile(file)
1248 local program, reason = loadfile(file)
1249 if program then
1250 local result = table.pack(pcall(program))
1251 if result[1] then
1252 return table.unpack(result, 2, result.n)
1253 else
1254 error(result[2])
1255 end
1256 else
1257 error(reason)
1258 end
1259end
1260
1261--set up libs
1262event = event_code()
1263component_code()
1264text = text_code()
1265filesystem = fs_code()
1266fs = filesystem
1267keyboard = keyboard_code(dofile("keyboard.lua"))
1268term = terminal_code()
1269
1270-- set up other functions...
1271os.sleep = function(timeout)
1272 checkArg(1, timeout, "number", "nil")
1273 local deadline = computer.uptime() + (timeout or 0)
1274 repeat
1275 event.pull(deadline - computer.uptime())
1276 until computer.uptime() >= deadline
1277end
1278
1279os.exit = function(code)
1280 error({[1]="INTERRUPT", [2]="EXIT", [3]=code})
1281end
1282
1283--set up terminal
1284if term.isAvailable() then
1285 component.gpu.bind(component.screen.address)
1286 component.gpu.setResolution(component.gpu.getResolution())
1287 component.gpu.setBackground(0x000000)
1288 component.gpu.setForeground(0xFFFFFF)
1289 term.setCursorBlink(true)
1290 term.clear()
1291end
1292
1293print("Starting " .. _OSNAME .. "...\n")
1294print(_OSCREDIT .. "\n")
1295
1296--clean up libs
1297event_code, component_code, text_code, fs_code, terminal_code, keyboard_code = nil, nil, nil, nil, nil, nil
1298
1299--map the drives
1300for address, componentType in component.list() do
1301 if componentType == "filesystem" then filesystem.drive.autoMap(address) end
1302end
1303
1304miniOS = {}
1305local function interrupt(data)
1306 --print("INTERRUPT!")
1307 if data[2] == "RUN" then return miniOS.runfile(data[3], table.unpack(data[4])) end
1308 if data[2] == "ERR" then error("This error is for testing!") end
1309 if data[2] == "EXIT" then return data[3] end
1310end
1311local function runfile(file, ...)
1312 local program, reason = loadfile(file)
1313 if program then
1314 local targs = {...}
1315 local traceback
1316 local result = table.pack(xpcall(program,
1317 function(err) traceback = debug.traceback(nil, 2); return err end,
1318 ...))
1319 --local result = table.pack(pcall(program, ...))
1320 if traceback then
1321 local function dropsame(s1,s2)
1322 t1,t2={},{}
1323 for l in s1:gmatch("(.-)\n") do t1[#t1+1] = l end
1324 for l in s2:gmatch("(.-)\n") do t2[#t2+1] = l end
1325 for i = #t1,1,-1 do
1326 if t1[i] == t2[i] then
1327 t1[i] = nil
1328 t2[i] = nil
1329 else
1330 break
1331 end
1332 end
1333 os1,os2 = "",""
1334 for k,v in ipairs(t1) do
1335 os1 = os1 .. v .. "\n"
1336 end
1337 for k,v in ipairs(t2) do
1338 os2 = os2 .. v .. "\n"
1339 end
1340 return os1,os2
1341 end
1342 traceback = dropsame(traceback, debug.traceback(nil, 2)) .. "\t..."
1343 end
1344 if result[1] then
1345 return table.unpack(result, 2, result.n)
1346 else
1347 if type(result[2]) == "table" then if result[2][1] then if result[2][1] == "INTERRUPT" then
1348 result = {interrupt(result[2])}
1349 --if not result[1] then
1350 --error(result[2], 3)
1351 --else
1352 --return table.unpack(result, 2, result.n)
1353 --end
1354 return
1355 end end end
1356 error(result[2] .. "\n" .. traceback, 3)
1357 end
1358 else
1359 error(reason, 3)
1360 end
1361end
1362local function kernelError()
1363 printErr("\nPress any key to try again.")
1364 term.readKey()
1365end
1366function miniOS.saferunfile(...)
1367 local r = {pcall(runfile, ...)}
1368 if not r[1] then
1369 local c = component.gpu.getForeground()
1370 component.gpu.setForeground(0xFF0000)
1371 printPaged(r[2])
1372 component.gpu.setForeground(c)
1373 end
1374 return r
1375end
1376function miniOS.runfile(...)
1377 local r = miniOS.saferunfile(...)
1378 return table.unpack(r, 2, r.n)
1379end
1380
1381local function tryrunlib(lib)
1382 local ret
1383 local opt = {lib .. ".lua", lib}
1384 for _,o in ipairs(opt) do
1385 if fs.exists(o) then
1386 return miniOS.runfile(o)
1387 end
1388 end
1389 error("Can't find the library specified: `" .. lib .. "`", 3)
1390end
1391function require(lib)
1392 return _G[lib] or _G[string.lower(lib)] or tryrunlib(lib)
1393end
1394local function shellrun(...)
1395 local success = miniOS.saferunfile(...)[1]
1396 if not success then
1397 printErr("\n\nError in running command interpreter.")
1398 return false
1399 end
1400 return true
1401end
1402
1403miniOS.freeMem = computer.freeMemory()
1404
1405--start command and keep it running.
1406local fallback_drive = fs.drive.getcurrent()
1407if filesystem.exists("autoexec.bat") then shellrun("command.lua", "autoexec.bat") else shellrun("command.lua") end
1408while true do
1409 miniOS.freeMem = computer.freeMemory()
1410 print()
1411 fs.drive.setcurrent(fallback_drive)
1412 if not shellrun("command.lua", "-c") then printErr("Will restart command interpreter..."); kernelError() end
1413end