· 5 years ago · Sep 18, 2020, 10:34 PM
1--- The shell API provides access to CraftOS's command line interface.
2--
3-- It allows you to @{run|start programs}, @{setCompletionFunction|add
4-- completion for a program}, and much more.
5--
6-- @{shell} is not a "true" API. Instead, it is a standard program, which its
7-- API into the programs that it launches. This allows for multiple shells to
8-- run at the same time, but means that the API is not available in the global
9-- environment, and so is unavailable to other @{os.loadAPI|APIs}.
10--
11-- @module[module] shell
12
13term.clear()
14term.setCursorPos(1,1)
15
16local expect = dofile("rom/modules/main/cc/expect.lua").expect
17local make_package = dofile("rom/modules/main/cc/require.lua").make
18
19function read2(_sReplaceChar, _tHistory, _fnComplete, _sDefault)
20 expect(1, _sReplaceChar, "string", "nil")
21 expect(2, _tHistory, "table", "nil")
22 expect(3, _fnComplete, "function", "nil")
23 expect(4, _sDefault, "string", "nil")
24
25 term.setCursorBlink(false)
26
27 local sLine
28 if type(_sDefault) == "string" then
29 sLine = _sDefault
30 else
31 sLine = ""
32 end
33 local nHistoryPos
34 local nPos, nScroll = #sLine, 0
35 if _sReplaceChar then
36 _sReplaceChar = string.sub(_sReplaceChar, 1, 1)
37 end
38
39 local tCompletions
40 local nCompletion
41 local function recomplete()
42 if _fnComplete and nPos == #sLine then
43 tCompletions = _fnComplete(sLine)
44 if tCompletions and #tCompletions > 0 then
45 nCompletion = 1
46 else
47 nCompletion = nil
48 end
49 else
50 tCompletions = nil
51 nCompletion = nil
52 end
53 end
54
55 local function uncomplete()
56 tCompletions = nil
57 nCompletion = nil
58 end
59
60 local w = term.getSize()
61 local sx = term.getCursorPos()
62
63 local function redraw(_bClear)
64 local cursor_pos = nPos - nScroll
65 if sx + cursor_pos >= w then
66 -- We've moved beyond the RHS, ensure we're on the edge.
67 nScroll = sx + nPos - w
68 elseif cursor_pos < 0 then
69 -- We've moved beyond the LHS, ensure we're on the edge.
70 nScroll = nPos
71 end
72
73 local _, cy = term.getCursorPos()
74 term.setCursorPos(sx, cy)
75 local sReplace = _bClear and " " or _sReplaceChar
76 if sReplace then
77 term.write(string.rep(sReplace, math.max(#sLine - nScroll, 0)))
78 else
79 term.write(string.sub(sLine, nScroll + 1))
80 end
81
82 if nCompletion then
83 local sCompletion = tCompletions[nCompletion]
84 local oldText, oldBg
85 if not _bClear then
86 oldText = term.getTextColor()
87 oldBg = term.getBackgroundColor()
88 term.setTextColor(colors.white)
89 term.setBackgroundColor(colors.gray)
90 end
91 if sReplace then
92 term.write(string.rep(sReplace, #sCompletion))
93 else
94 term.write(sCompletion)
95 end
96 if not _bClear then
97 term.setTextColor(oldText)
98 term.setBackgroundColor(oldBg)
99 end
100 end
101
102 term.setCursorPos(sx + nPos - nScroll, cy)
103 end
104
105 local function clear()
106 redraw(true)
107 end
108
109 recomplete()
110 redraw()
111
112 local function acceptCompletion()
113 if nCompletion then
114 -- Clear
115 clear()
116
117 -- Find the common prefix of all the other suggestions which start with the same letter as the current one
118 local sCompletion = tCompletions[nCompletion]
119 sLine = sLine .. sCompletion
120 nPos = #sLine
121
122 -- Redraw
123 recomplete()
124 redraw()
125 end
126 end
127 while true do
128 local sEvent, param, param1, param2 = os.pullEvent()
129 if sEvent == "char" then
130 -- Typed key
131 clear()
132 sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
133 nPos = nPos + 1
134 recomplete()
135 redraw()
136
137 elseif sEvent == "paste" then
138 -- Pasted text
139 clear()
140 sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
141 nPos = nPos + #param
142 recomplete()
143 redraw()
144
145 elseif sEvent == "key" then
146 if param == keys.enter then
147 -- Enter
148 if nCompletion then
149 clear()
150 uncomplete()
151 redraw()
152 end
153 break
154
155 elseif param == keys.left then
156 -- Left
157 if nPos > 0 then
158 clear()
159 nPos = nPos - 1
160 recomplete()
161 redraw()
162 end
163
164 elseif param == keys.right then
165 -- Right
166 if nPos < #sLine then
167 -- Move right
168 clear()
169 nPos = nPos + 1
170 recomplete()
171 redraw()
172 else
173 -- Accept autocomplete
174 acceptCompletion()
175 end
176
177 elseif param == keys.up or param == keys.down then
178 -- Up or down
179 if nCompletion then
180 -- Cycle completions
181 clear()
182 if param == keys.up then
183 nCompletion = nCompletion - 1
184 if nCompletion < 1 then
185 nCompletion = #tCompletions
186 end
187 elseif param == keys.down then
188 nCompletion = nCompletion + 1
189 if nCompletion > #tCompletions then
190 nCompletion = 1
191 end
192 end
193 redraw()
194
195 elseif _tHistory then
196 -- Cycle history
197 clear()
198 if param == keys.up then
199 -- Up
200 if nHistoryPos == nil then
201 if #_tHistory > 0 then
202 nHistoryPos = #_tHistory
203 end
204 elseif nHistoryPos > 1 then
205 nHistoryPos = nHistoryPos - 1
206 end
207 else
208 -- Down
209 if nHistoryPos == #_tHistory then
210 nHistoryPos = nil
211 elseif nHistoryPos ~= nil then
212 nHistoryPos = nHistoryPos + 1
213 end
214 end
215 if nHistoryPos then
216 sLine = _tHistory[nHistoryPos]
217 nPos, nScroll = #sLine, 0
218 else
219 sLine = ""
220 nPos, nScroll = 0, 0
221 end
222 uncomplete()
223 redraw()
224
225 end
226
227 elseif param == keys.backspace then
228 -- Backspace
229 if nPos > 0 then
230 clear()
231 sLine = string.sub(sLine, 1, nPos - 1) .. string.sub(sLine, nPos + 1)
232 nPos = nPos - 1
233 if nScroll > 0 then nScroll = nScroll - 1 end
234 recomplete()
235 redraw()
236 end
237
238 elseif param == keys.home then
239 -- Home
240 if nPos > 0 then
241 clear()
242 nPos = 0
243 recomplete()
244 redraw()
245 end
246
247 elseif param == keys.delete then
248 -- Delete
249 if nPos < #sLine then
250 clear()
251 sLine = string.sub(sLine, 1, nPos) .. string.sub(sLine, nPos + 2)
252 recomplete()
253 redraw()
254 end
255
256 elseif param == keys["end"] then
257 -- End
258 if nPos < #sLine then
259 clear()
260 nPos = #sLine
261 recomplete()
262 redraw()
263 end
264
265 elseif param == keys.tab then
266 -- Tab (accept autocomplete)
267 acceptCompletion()
268
269 end
270
271 elseif sEvent == "mouse_click" or sEvent == "mouse_drag" and param == 1 then
272 local _, cy = term.getCursorPos()
273 if param1 >= sx and param1 <= w and param2 == cy then
274 -- Ensure we don't scroll beyond the current line
275 nPos = math.min(math.max(nScroll + param1 - sx, 0), #sLine)
276 redraw()
277 end
278
279 elseif sEvent == "term_resize" then
280 -- Terminal resized
281 w = term.getSize()
282 redraw()
283
284 end
285 end
286
287 local _, cy = term.getCursorPos()
288 term.setCursorBlink(false)
289 term.setCursorPos(w + 1, cy)
290 print()
291
292 return sLine
293end
294
295
296local multishell = multishell
297local parentShell = shell
298local parentTerm = term.current()
299
300if multishell then
301 multishell.setTitle(multishell.getCurrent(), "shell")
302end
303
304local bExit = false
305local sDir = parentShell and parentShell.dir() or ""
306local sPath = parentShell and parentShell.path() or ".:/rom/programs"
307local tAliases = parentShell and parentShell.aliases() or {}
308local tCompletionInfo = parentShell and parentShell.getCompletionInfo() or {}
309local tProgramStack = {}
310
311local shell = {} --- @export
312local function createShellEnv(dir)
313 local env = { shell = shell, multishell = multishell }
314 env.require, env.package = make_package(env, dir)
315 return env
316end
317
318-- Colours
319local promptColour, textColour, bgColour
320if term.isColour() then
321 promptColour = colours.yellow
322 textColour = colours.white
323 bgColour = colours.black
324else
325 promptColour = colours.white
326 textColour = colours.white
327 bgColour = colours.black
328end
329
330--- Run a program with the supplied arguments.
331--
332-- Unlike @{shell.run}, each argument is passed to the program verbatim. While
333-- `shell.run("echo", "b c")` runs `echo` with `b` and `c`,
334-- `shell.execute("echo", "b c")` runs `echo` with a single argument `b c`.
335--
336-- @tparam string command The program to execute.
337-- @tparam string ... Arguments to this program.
338-- @treturn boolean Whether the program exited successfully.
339-- @usage Run `paint my-image` from within your program:
340--
341-- shell.execute("paint", "my-image")
342function shell.execute(command, ...)
343 expect(1, command, "string")
344 for i = 1, select('#', ...) do
345 expect(i + 1, select(i, ...), "string")
346 end
347
348 local sPath = shell.resolveProgram(command)
349 if sPath ~= nil then
350 tProgramStack[#tProgramStack + 1] = sPath
351 if multishell then
352 local sTitle = fs.getName(sPath)
353 if sTitle:sub(-4) == ".lua" then
354 sTitle = sTitle:sub(1, -5)
355 end
356 multishell.setTitle(multishell.getCurrent(), sTitle)
357 end
358
359 local sDir = fs.getDir(sPath)
360 local env = createShellEnv(sDir)
361 env.arg = { [0] = command, ... }
362 local result = os.run(env, sPath, ...)
363
364 tProgramStack[#tProgramStack] = nil
365 if multishell then
366 if #tProgramStack > 0 then
367 local sTitle = fs.getName(tProgramStack[#tProgramStack])
368 if sTitle:sub(-4) == ".lua" then
369 sTitle = sTitle:sub(1, -5)
370 end
371 multishell.setTitle(multishell.getCurrent(), sTitle)
372 else
373 multishell.setTitle(multishell.getCurrent(), "shell")
374 end
375 end
376 return result
377 else
378 printError("No such program")
379 return false
380 end
381end
382
383local function tokenise(...)
384 local sLine = table.concat({ ... }, " ")
385 local tWords = {}
386 local bQuoted = false
387 for match in string.gmatch(sLine .. "\"", "(.-)\"") do
388 if bQuoted then
389 table.insert(tWords, match)
390 else
391 for m in string.gmatch(match, "[^ \t]+") do
392 table.insert(tWords, m)
393 end
394 end
395 bQuoted = not bQuoted
396 end
397 return tWords
398end
399
400-- Install shell API
401
402--- Run a program with the supplied arguments.
403--
404-- All arguments are concatenated together and then parsed as a command line. As
405-- a result, `shell.run("program a b")` is the same as `shell.run("program",
406-- "a", "b")`.
407--
408-- @tparam string ... The program to run and its arguments.
409-- @treturn boolean Whether the program exited successfully.
410-- @usage Run `paint my-image` from within your program:
411--
412-- shell.run("paint", "my-image")
413-- @see shell.execute Run a program directly without parsing the arguments.
414function shell.run(...)
415 local tWords = tokenise(...)
416 local sCommand = tWords[1]
417 if sCommand then
418 return shell.execute(sCommand, table.unpack(tWords, 2))
419 end
420 return false
421end
422
423--- Exit the current shell.
424--
425-- This does _not_ terminate your program, it simply makes the shell terminate
426-- after your program has finished. If this is the toplevel shell, then the
427-- computer will be shutdown.
428function shell.exit()
429 bExit = true
430end
431
432--- Return the current working directory. This is what is displayed before the
433-- `> ` of the shell prompt, and is used by @{shell.resolve} to handle relative
434-- paths.
435--
436-- @treturn string The current working directory.
437-- @see setDir To change the working directory.
438function shell.dir()
439 return sDir
440end
441
442--- Set the current working directory.
443--
444-- @tparam string dir The new working directory.
445-- @throws If the path does not exist or is not a directory.
446-- @usage Set the working directory to "rom"
447--
448-- shell.setDir("rom")
449function shell.setDir(dir)
450 expect(1, dir, "string")
451 if not fs.isDir(dir) then
452 error("Not a directory", 2)
453 end
454 sDir = fs.combine(dir, "")
455end
456
457--- Set the path where programs are located.
458--
459-- The path is composed of a list of directory names in a string, each separated
460-- by a colon (`:`). On normal turtles will look in the current directory (`.`),
461-- `/rom/programs` and `/rom/programs/turtle` folder, making the path
462-- `.:/rom/programs:/rom/programs/turtle`.
463--
464-- @treturn string The current shell's path.
465-- @see setPath To change the current path.
466function shell.path()
467 return sPath
468end
469
470--- Set the @{path|current program path}.
471--
472-- Be careful to prefix directories with a `/`. Otherwise they will be searched
473-- for from the @{shell.dir|current directory}, rather than the computer's root.
474--
475-- @tparam string path The new program path.
476function shell.setPath(path)
477 expect(1, path, "string")
478 sPath = path
479end
480
481--- Resolve a relative path to an absolute path.
482--
483-- The @{fs} and @{io} APIs work using absolute paths, and so we must convert
484-- any paths relative to the @{dir|current directory} to absolute ones. This
485-- does nothing when the path starts with `/`.
486--
487-- @tparam string path The path to resolve.
488-- @usage Resolve `startup.lua` when in the `rom` folder.
489--
490-- shell.setDir("rom")
491-- print(shell.resolve("startup.lua"))
492-- -- => rom/startup.lua
493function shell.resolve(path)
494 expect(1, path, "string")
495 local sStartChar = string.sub(path, 1, 1)
496 if sStartChar == "/" or sStartChar == "\\" then
497 return fs.combine("", path)
498 else
499 return fs.combine(sDir, path)
500 end
501end
502
503local function pathWithExtension(_sPath, _sExt)
504 local nLen = #sPath
505 local sEndChar = string.sub(_sPath, nLen, nLen)
506 -- Remove any trailing slashes so we can add an extension to the path safely
507 if sEndChar == "/" or sEndChar == "\\" then
508 _sPath = string.sub(_sPath, 1, nLen - 1)
509 end
510 return _sPath .. "." .. _sExt
511end
512
513--- Resolve a program, using the @{path|program path} and list of @{aliases|aliases}.
514--
515-- @tparam string command The name of the program
516-- @treturn string|nil The absolute path to the program, or @{nil} if it could
517-- not be found.
518-- @usage Locate the `hello` program.
519--
520-- shell.resolveProgram("hello")
521-- -- => rom/programs/fun/hello.lua
522function shell.resolveProgram(command)
523 expect(1, command, "string")
524 -- Substitute aliases firsts
525 if tAliases[command] ~= nil then
526 command = tAliases[command]
527 end
528
529 -- If the path is a global path, use it directly
530 if command:find("/") or command:find("\\") then
531 local sPath = shell.resolve(command)
532 if fs.exists(sPath) and not fs.isDir(sPath) then
533 return sPath
534 else
535 local sPathLua = pathWithExtension(sPath, "lua")
536 if fs.exists(sPathLua) and not fs.isDir(sPathLua) then
537 return sPathLua
538 end
539 end
540 return nil
541 end
542
543 -- Otherwise, look on the path variable
544 for sPath in string.gmatch(sPath, "[^:]+") do
545 sPath = fs.combine(shell.resolve(sPath), command)
546 if fs.exists(sPath) and not fs.isDir(sPath) then
547 return sPath
548 else
549 local sPathLua = pathWithExtension(sPath, "lua")
550 if fs.exists(sPathLua) and not fs.isDir(sPathLua) then
551 return sPathLua
552 end
553 end
554 end
555
556 -- Not found
557 return nil
558end
559
560--- Return a list of all programs on the @{shell.path|path}.
561--
562-- @tparam[opt] boolean include_hidden Include hidden files. Namely, any which
563-- start with `.`.
564-- @treturn { string } A list of available programs.
565-- @usage textutils.tabulate(shell.programs())
566function shell.programs(include_hidden)
567 expect(1, include_hidden, "boolean", "nil")
568
569 local tItems = {}
570
571 -- Add programs from the path
572 for sPath in string.gmatch(sPath, "[^:]+") do
573 sPath = shell.resolve(sPath)
574 if fs.isDir(sPath) then
575 local tList = fs.list(sPath)
576 for n = 1, #tList do
577 local sFile = tList[n]
578 if not fs.isDir(fs.combine(sPath, sFile)) and
579 (include_hidden or string.sub(sFile, 1, 1) ~= ".") then
580 if #sFile > 4 and sFile:sub(-4) == ".lua" then
581 sFile = sFile:sub(1, -5)
582 end
583 tItems[sFile] = true
584 end
585 end
586 end
587 end
588
589 -- Sort and return
590 local tItemList = {}
591 for sItem in pairs(tItems) do
592 table.insert(tItemList, sItem)
593 end
594 table.sort(tItemList)
595 return tItemList
596end
597
598local function completeProgram(sLine)
599 if #sLine > 0 and (sLine:find("/") or sLine:find("\\")) then
600 -- Add programs from the root
601 return fs.complete(sLine, sDir, true, false)
602
603 else
604 local tResults = {}
605 local tSeen = {}
606
607 -- Add aliases
608 for sAlias in pairs(tAliases) do
609 if #sAlias > #sLine and string.sub(sAlias, 1, #sLine) == sLine then
610 local sResult = string.sub(sAlias, #sLine + 1)
611 if not tSeen[sResult] then
612 table.insert(tResults, sResult)
613 tSeen[sResult] = true
614 end
615 end
616 end
617
618 -- Add all subdirectories. We don't include files as they will be added in the block below
619 local tDirs = fs.complete(sLine, sDir, false, false)
620 for i = 1, #tDirs do
621 local sResult = tDirs[i]
622 if not tSeen[sResult] then
623 table.insert (tResults, sResult)
624 tSeen [sResult] = true
625 end
626 end
627
628 -- Add programs from the path
629 local tPrograms = shell.programs()
630 for n = 1, #tPrograms do
631 local sProgram = tPrograms[n]
632 if #sProgram > #sLine and string.sub(sProgram, 1, #sLine) == sLine then
633 local sResult = string.sub(sProgram, #sLine + 1)
634 if not tSeen[sResult] then
635 table.insert(tResults, sResult)
636 tSeen[sResult] = true
637 end
638 end
639 end
640
641 -- Sort and return
642 table.sort(tResults)
643 return tResults
644 end
645end
646
647local function completeProgramArgument(sProgram, nArgument, sPart, tPreviousParts)
648 local tInfo = tCompletionInfo[sProgram]
649 if tInfo then
650 return tInfo.fnComplete(shell, nArgument, sPart, tPreviousParts)
651 end
652 return nil
653end
654
655--- Complete a shell command line.
656--
657-- This accepts an incomplete command, and completes the program name or
658-- arguments. For instance, `l` will be completed to `ls`, and `ls ro` will be
659-- completed to `ls rom/`.
660--
661-- Completion handlers for your program may be registered with
662-- @{shell.setCompletionFunction}.
663--
664-- @tparam string sLine The input to complete.
665-- @treturn { string }|nil The list of possible completions.
666-- @see read For more information about completion.
667-- @see shell.completeProgram
668-- @see shell.setCompletionFunction
669-- @see shell.getCompletionInfo
670function shell.complete(sLine)
671 expect(1, sLine, "string")
672 if #sLine > 0 then
673 local tWords = tokenise(sLine)
674 local nIndex = #tWords
675 if string.sub(sLine, #sLine, #sLine) == " " then
676 nIndex = nIndex + 1
677 end
678 if nIndex == 1 then
679 local sBit = tWords[1] or ""
680 local sPath = shell.resolveProgram(sBit)
681 if tCompletionInfo[sPath] then
682 return { " " }
683 else
684 local tResults = completeProgram(sBit)
685 for n = 1, #tResults do
686 local sResult = tResults[n]
687 local sPath = shell.resolveProgram(sBit .. sResult)
688 if tCompletionInfo[sPath] then
689 tResults[n] = sResult .. " "
690 end
691 end
692 return tResults
693 end
694
695 elseif nIndex > 1 then
696 local sPath = shell.resolveProgram(tWords[1])
697 local sPart = tWords[nIndex] or ""
698 local tPreviousParts = tWords
699 tPreviousParts[nIndex] = nil
700 return completeProgramArgument(sPath , nIndex - 1, sPart, tPreviousParts)
701
702 end
703 end
704 return nil
705end
706
707--- Complete the name of a program.
708--
709-- @tparam string program The name of a program to complete.
710-- @treturn { string } A list of possible completions.
711-- @see cc.shell.completion.program
712function shell.completeProgram(program)
713 expect(1, program, "string")
714 return completeProgram(program)
715end
716
717--- Set the completion function for a program. When the program is entered on
718-- the command line, this program will be called to provide auto-complete
719-- information.
720--
721-- The completion function accepts four arguments:
722--
723-- 1. The current shell. As completion functions are inherited, this is not
724-- guaranteed to be the shell you registered this function in.
725-- 2. The index of the argument currently being completed.
726-- 3. The current argument. This may be the empty string.
727-- 4. A list of the previous arguments.
728--
729-- For instance, when completing `pastebin put rom/st` our pastebin completion
730-- function will receive the shell API, an index of 2, `rom/st` as the current
731-- argument, and a "previous" table of `{ "put" }`. This function may then wish
732-- to return a table containing `artup.lua`, indicating the entire command
733-- should be completed to `pastebin put rom/startup.lua`.
734--
735-- You completion entries may also be followed by a space, if you wish to
736-- indicate another argument is expected.
737--
738-- @tparam string program The path to the program. This should be an absolute path
739-- _without_ the leading `/`.
740-- @tparam function(shell: table, index: number, argument: string, previous: { string }):({ string }|nil) complete
741-- The completion function.
742-- @see cc.shell.completion Various utilities to help with writing completion functions.
743-- @see shell.complete
744-- @see read For more information about completion.
745function shell.setCompletionFunction(program, complete)
746 expect(1, program, "string")
747 expect(2, complete, "function")
748 tCompletionInfo[program] = {
749 fnComplete = complete,
750 }
751end
752
753--- Get a table containing all completion functions.
754--
755-- This should only be needed when building custom shells. Use
756-- @{setCompletionFunction} to add a completion function.
757--
758-- @treturn { [string] = { fnComplete = function } } A table mapping the
759-- absolute path of programs, to their completion functions.
760function shell.getCompletionInfo()
761 return tCompletionInfo
762end
763
764--- Returns the path to the currently running program.
765--
766-- @treturn string The absolute path to the running program.
767function shell.getRunningProgram()
768 if #tProgramStack > 0 then
769 return tProgramStack[#tProgramStack]
770 end
771 return nil
772end
773
774--- Add an alias for a program.
775--
776-- @tparam string command The name of the alias to add.
777-- @tparam string program The name or path to the program.
778-- @usage Alias `vim` to the `edit` program
779--
780-- shell.setAlias("vim", "edit")
781function shell.setAlias(command, program)
782 expect(1, command, "string")
783 expect(2, program, "string")
784 tAliases[command] = program
785end
786
787--- Remove an alias.
788--
789-- @tparam string command The alias name to remove.
790function shell.clearAlias(command)
791 expect(1, command, "string")
792 tAliases[command] = nil
793end
794
795--- Get the current aliases for this shell.
796--
797-- Aliases are used to allow multiple commands to refer to a single program. For
798-- instance, the `list` program is aliased `dir` or `ls`. Running `ls`, `dir` or
799-- `list` in the shell will all run the `list` program.
800--
801-- @treturn { [string] = string } A table, where the keys are the names of
802-- aliases, and the values are the path to the program.
803-- @see shell.setAlias
804-- @see shell.resolveProgram This uses aliases when resolving a program name to
805-- an absolute path.
806function shell.aliases()
807 -- Copy aliases
808 local tCopy = {}
809 for sAlias, sCommand in pairs(tAliases) do
810 tCopy[sAlias] = sCommand
811 end
812 return tCopy
813end
814
815if multishell then
816 --- Open a new @{multishell} tab running a command.
817 --
818 -- This behaves similarly to @{shell.run}, but instead returns the process
819 -- index.
820 --
821 -- This function is only available if the @{multishell} API is.
822 --
823 -- @tparam string ... The command line to run.
824 -- @see shell.run
825 -- @see multishell.launch
826 -- @usage Launch the Lua interpreter and switch to it.
827 --
828 -- local id = shell.openTab("lua")
829 -- shell.switchTab(id)
830 function shell.openTab(...)
831 local tWords = tokenise(...)
832 local sCommand = tWords[1]
833 if sCommand then
834 local sPath = shell.resolveProgram(sCommand)
835 if sPath == "rom/programs/shell.lua" then
836 return multishell.launch(createShellEnv("rom/programs"), sPath, table.unpack(tWords, 2))
837 elseif sPath ~= nil then
838 return multishell.launch(createShellEnv("rom/programs"), "rom/programs/shell.lua", sCommand, table.unpack(tWords, 2))
839 else
840 printError("No such program")
841 end
842 end
843 end
844
845 --- Switch to the @{multishell} tab with the given index.
846 --
847 -- @tparam number id The tab to switch to.
848 -- @see multishell.setFocus
849 function shell.switchTab(id)
850 expect(1, id, "number")
851 multishell.setFocus(id)
852 end
853end
854
855local tArgs = { ... }
856if #tArgs > 0 then
857 -- "shell x y z"
858 -- Run the program specified on the commandline
859 shell.run(...)
860
861else
862 -- "shell"
863 -- Print the header
864 term.setBackgroundColor(bgColour)
865 term.setTextColour(promptColour)
866 term.setTextColour(textColour)
867
868 -- Run the startup program
869 if parentShell == nil then
870 shell.run("/rom/startup.lua")
871 end
872
873 -- Read commands and execute them
874 local tCommandHistory = {}
875 while not bExit do
876 term.redirect(parentTerm)
877 term.setBackgroundColor(bgColour)
878 term.setTextColour(promptColour)
879 term.setCursorBlink(false)
880 term.setTextColour(textColour)
881
882
883 local sLine
884 if settings.get("shell.autocomplete") then
885 sLine = read2(nil, tCommandHistory, shell.complete)
886 else
887 sLine = read2(nil, tCommandHistory)
888 end
889 if sLine:match("%S") and tCommandHistory[#tCommandHistory] ~= sLine then
890 table.insert(tCommandHistory, sLine)
891 end
892 shell.run(sLine)
893 end
894end