From 8e4d311cd934e93d7a09ba0c2133d76880d1a1f6 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sun, 15 Sep 2019 18:48:40 +0100 Subject: [PATCH] Refactor shell completion into a separate module (#281) - Adds cc.completions module, with a couple of helper functions for working with the more general completion functionality (i.e. that provided by read). - Adds cc.shell.completions module, which provides shell-specific completion functions. - Add a "program completion builder", which allows you to write stuff like this: shell.setCompletionFunction( "rom/programs/redstone.lua", completion.build( { completion.choice, { "probe", "set ", "pulse " } }, completion.side) ) Closes #232 --- .../lua/rom/modules/main/cc/completion.lua | 105 +++++++ .../rom/modules/main/cc/shell/completion.lua | 151 ++++++++++ .../assets/computercraft/lua/rom/startup.lua | 274 +++++------------- .../spec/modules/cc/completion_spec.lua | 57 ++++ .../spec/modules/cc/shell/completion_spec.lua | 41 +++ .../test-rom/spec/programs/mkdir_spec.lua | 4 +- 6 files changed, 428 insertions(+), 204 deletions(-) create mode 100644 src/main/resources/assets/computercraft/lua/rom/modules/main/cc/completion.lua create mode 100644 src/main/resources/assets/computercraft/lua/rom/modules/main/cc/shell/completion.lua create mode 100644 src/test/resources/test-rom/spec/modules/cc/completion_spec.lua create mode 100644 src/test/resources/test-rom/spec/modules/cc/shell/completion_spec.lua diff --git a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/completion.lua b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/completion.lua new file mode 100644 index 000000000..4634bcc98 --- /dev/null +++ b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/completion.lua @@ -0,0 +1,105 @@ +--- A collection of helper methods for working with input completion, such +-- as that require by @{read}. +-- +-- @module craftos.completion +-- @see cc.shell.completion For additional helpers to use with +-- @{shell.setCompletionFunction}. + +local expect = require "cc.expect".expect + +local function choice_impl(text, choices, add_space) + local results = {} + for n = 1, #choices do + local option = choices[n] + if #option + (add_space and 1 or 0) > #text and option:sub(1, #text) == text then + local result = option:sub(#text + 1) + if add_space then + table.insert(results, result .. " ") + else + table.insert(results, result) + end + end + end + return results +end + +--- Complete from a choice of one or more strings. +-- +-- @tparam string text The input string to complete. +-- @tparam { string... } choices The list of choices to complete from. +-- @tparam[opt] boolean add_space Whether to add a space after the completed item. +-- @treturn { string... } A list of suffixes of matching strings. +-- @usage Call @{read}, completing the names of various animals. +-- +-- local animals = { "dog", "cat", "lion", "unicorn" } +-- read(nil, nil, function(text) return choice(text, animals) end) +local function choice(text, choices, add_space) + expect(1, text, "string") + expect(2, choices, "table") + expect(3, add_space, "boolean", "nil") + return choice_impl(text, choices, add_space) +end + +--- Complete the name of a currently attached peripheral. +-- +-- @tparam string text The input string to complete. +-- @tparam[opt] boolean add_space Whether to add a space after the completed name. +-- @treturn { string... } A list of suffixes of matching peripherals. +-- @usage read(nil, nil, peripheral) +local function peripheral_(text, add_space) + expect(1, text, "string") + expect(2, add_space, "boolean", "nil") + return choice_impl(text, peripheral.getNames(), add_space) +end + +local sides = redstone.getSides() + +--- Complete the side of a computer. +-- +-- @tparam string text The input string to complete. +-- @tparam[opt] boolean add_space Whether to add a space after the completed side. +-- @treturn { string... } A list of suffixes of matching sides. +-- @usage read(nil, nil, side) +local function side(text, add_space) + expect(1, text, "string") + expect(2, add_space, "boolean", "nil") + return choice_impl(text, sides, add_space) +end + +--- Complete a @{settings|setting}. +-- +-- @tparam string text The input string to complete. +-- @tparam[opt] boolean add_space Whether to add a space after the completed settings. +-- @treturn { string... } A list of suffixes of matching settings. +-- @usage read(nil, nil, setting) +local function setting(text, add_space) + expect(1, text, "string") + expect(2, add_space, "boolean", "nil") + return choice_impl(text, settings.getNames(), add_space) +end + +local command_list + +--- Complete the name of a Minecraft @{commands|command}. +-- +-- @tparam string text The input string to complete. +-- @tparam[opt] boolean add_space Whether to add a space after the completed command. +-- @treturn { string... } A list of suffixes of matching commands. +-- @usage read(nil, nil, command) +local function command(text, add_space) + expect(1, text, "string") + expect(2, add_space, "boolean", "nil") + if command_list == nil then + command_list = commands and commands.list() or {} + end + + return choice_impl(text, command_list, add_space) +end + +return { + choice = choice, + peripheral = peripheral_, + side = side, + setting = setting, + command = command, +} diff --git a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/shell/completion.lua b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/shell/completion.lua new file mode 100644 index 000000000..023e500fc --- /dev/null +++ b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/shell/completion.lua @@ -0,0 +1,151 @@ +--- A collection of helper methods for working with shell completion. +-- +-- Most programs may be completed using the @{build} helper method, rather than +-- manually switching on the argument index. +-- +-- Note, the helper functions within this module do not accept an argument index, +-- and so are not directly usable with the @{shell.setCompletionFunction}. Instead, +-- wrap them using @{build}, or your own custom function. +-- +-- @module craftos.shell.completion +-- @see cc.completion For more general helpers, suitable for use with @{read}. +-- @see shell.setCompletionFunction + +local expect = require "cc.expect".expect +local completion = require "cc.completion" + +--- Complete the name of a file relative to the current working directory. +-- +-- @tparam shell shell The shell we're completing in +-- @tparam { string... } choices The list of choices to complete from. +-- @treturn { string... } A list of suffixes of matching files. +local function file(shell, text) + return fs.complete(text, shell.dir(), true, false) +end + +--- Complete the name of a directory relative to the current working directory. +-- +-- @tparam shell shell The shell we're completing in +-- @tparam { string... } choices The list of choices to complete from. +-- @treturn { string... } A list of suffixes of matching directories. +local function dir(shell, text) + return fs.complete(text, shell.dir(), false, true) +end + +--- Complete the name of a file or directory relative to the current working +-- directory. +-- +-- @tparam shell shell The shell we're completing in +-- @tparam { string... } choices The list of choices to complete from. +-- @tparam { string... } previous The shell arguments before this one. +-- @tparam[opt] boolean add_space Whether to add a space after the completed item. +-- @treturn { string... } A list of suffixes of matching files and directories. +local function dirOrFile(shell, text, previous, add_space) + local results = fs.complete(text, shell.dir(), true, true) + if add_space then + for n = 1, #results do + local result = results[n] + if result:sub(-1) ~= "/" then + results[n] = result .. " " + end + end + end + return results +end + +local function wrap(func) + return function(shell, text, previous, ...) + return func(text, ...) + end +end + +--- Complete the name of a program. +-- +-- @tparam shell shell The shell we're completing in +-- @tparam { string... } choices The list of choices to complete from. +-- @treturn { string... } A list of suffixes of matching programs. +local function program(shell, text) + return shell.completeProgram(text) +end + +--- A helper function for building shell completion arguments. +-- +-- This accepts a series of single-argument completion functions, and combines +-- them into a function suitable for use with @{shell.setCompletionFunction}. +-- +-- @tparam nil|table|function ... Every argument to @{build} represents an argument +-- to the program you wish to complete. Each argument can be one of three types: +-- +-- - `nil`: This argument will not be completed. +-- +-- - A function: This argument will be completed with the given function. It is +-- called with the @{shell} object, the string to complete and the arguments +-- before this one. +-- +-- - A table: This acts as a more powerful version of the function case. The table +-- must have a function as the first item - this will be called with the shell, +-- string and preceding arguments as above, but also followed by any additional +-- items in the table. This provides a more convenient interface to pass +-- options to your completion functions. +-- +-- If this table is the last argument, it may also set the `many` key to true, +-- which states this function should be used to complete any remaining arguments. +-- +-- @usage Prompt for a choice of options, followed by a directory, and then multiple +-- files. +-- +-- complete.build( +-- { complete.choice, { "get", "put" } }, +-- complete.dir, +-- } complete.file, many = true } +-- ) +local function build(...) + local arguments = table.pack(...) + for i = 1, arguments.n do + local arg = arguments[i] + if arg ~= nil then + expect(i, arg, "table", "function") + if type(arg) == "function" then + arg = { arg } + arguments[i] = arg + end + + if type(arg[1]) ~= "function" then + error(("Bad table entry #1 at argument #%d (expected function, got %s)"):format(i, type(arg[1])), 2) + end + + if arg.many and i < arguments.n then + error(("Unexpected 'many' field on argument #%d (should only occur on the last argument)"):format(i), 2) + end + end + end + + return function(shell, index, text, previous) + local arg = arguments[index] + if not arg then + if index <= arguments.n then return end + + arg = arguments[arguments.n] + if not arg or not arg.many then return end + end + + return arg[1](shell, text, previous, table.unpack(arg, 2)) + end +end + +return { + file = file, + dir = dir, + dirOrFile = dirOrFile, + program = program, + + -- Re-export various other functions + help = wrap(help.completeTopic), + choice = wrap(completion.choice), + peripheral = wrap(completion.peripheral), + side = wrap(completion.side), + setting = wrap(completion.setting), + command = wrap(completion.command), + + build = build, +} diff --git a/src/main/resources/assets/computercraft/lua/rom/startup.lua b/src/main/resources/assets/computercraft/lua/rom/startup.lua index 2bc19adca..e6b396b39 100644 --- a/src/main/resources/assets/computercraft/lua/rom/startup.lua +++ b/src/main/resources/assets/computercraft/lua/rom/startup.lua @@ -1,3 +1,4 @@ +local completion = require "cc.shell.completion" -- Setup paths local sPath = ".:/rom/programs" @@ -39,217 +40,86 @@ if term.isColor() then end -- Setup completion functions -local function completeMultipleChoice( sText, tOptions, bAddSpaces ) - local tResults = {} - for n=1,#tOptions do - local sOption = tOptions[n] - if #sOption + (bAddSpaces and 1 or 0) > #sText and string.sub( sOption, 1, #sText ) == sText then - local sResult = string.sub( sOption, #sText + 1 ) - if bAddSpaces then - table.insert( tResults, sResult .. " " ) - else - table.insert( tResults, sResult ) - end - end - end - return tResults -end -local function completePeripheralName( sText, bAddSpaces ) - return completeMultipleChoice( sText, peripheral.getNames(), bAddSpaces ) -end -local tRedstoneSides = redstone.getSides() -local function completeSide( sText, bAddSpaces ) - return completeMultipleChoice( sText, tRedstoneSides, bAddSpaces ) -end -local function completeFile( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return fs.complete( sText, shell.dir(), true, false ) + +local function completePastebinPut(shell, text, previous) + if previous[2] == "put" then + return fs.complete(text, shell.dir(), true, false ) end end -local function completeFileMany( shell, nIndex, sText, tPreviousText ) - return fs.complete( sText, shell.dir(), true, false ) -end -local function completeDir( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return fs.complete( sText, shell.dir(), false, true ) - end -end -local function completeEither( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return fs.complete( sText, shell.dir(), true, true ) - end -end -local function completeEitherMany( shell, nIndex, sText, tPreviousText ) - return fs.complete( sText, shell.dir(), true, true ) -end -local function completeEitherEither( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - local tResults = fs.complete( sText, shell.dir(), true, true ) - for n=1,#tResults do - local sResult = tResults[n] - if string.sub( sResult, #sResult, #sResult ) ~= "/" then - tResults[n] = sResult .. " " - end - end - return tResults - elseif nIndex == 2 then - return fs.complete( sText, shell.dir(), true, true ) - end -end -local function completeProgram( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return shell.completeProgram( sText ) - end -end -local function completeHelp( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return help.completeTopic( sText ) - end -end -local function completeAlias( shell, nIndex, sText, tPreviousText ) - if nIndex == 2 then - return shell.completeProgram( sText ) - end -end -local function completePeripheral( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return completePeripheralName( sText ) - end -end -local tGPSOptions = { "host", "host ", "locate" } -local function completeGPS( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return completeMultipleChoice( sText, tGPSOptions ) - end -end -local tLabelOptions = { "get", "get ", "set ", "clear", "clear " } -local function completeLabel( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return completeMultipleChoice( sText, tLabelOptions ) - elseif nIndex == 2 then - return completePeripheralName( sText ) - end -end -local function completeMonitor( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return completePeripheralName( sText, true ) - elseif nIndex == 2 then - return shell.completeProgram( sText ) - end -end -local tRedstoneOptions = { "probe", "set ", "pulse " } -local function completeRedstone( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return completeMultipleChoice( sText, tRedstoneOptions ) - elseif nIndex == 2 then - return completeSide( sText ) - end -end -local tDJOptions = { "play", "play ", "stop " } -local function completeDJ( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return completeMultipleChoice( sText, tDJOptions ) - elseif nIndex == 2 then - return completePeripheralName( sText ) - end -end -local tPastebinOptions = { "put ", "get ", "run " } -local function completePastebin( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return completeMultipleChoice( sText, tPastebinOptions ) - elseif nIndex == 2 then - if tPreviousText[2] == "put" then - return fs.complete( sText, shell.dir(), true, false ) - end - end -end -local tChatOptions = { "host ", "join " } -local function completeChat( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return completeMultipleChoice( sText, tChatOptions ) - end -end -local function completeSet( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return completeMultipleChoice( sText, settings.getNames(), true ) - end -end -local tCommands -if commands then - tCommands = commands.list() -end -local function completeExec( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 and commands then - return completeMultipleChoice( sText, tCommands, true ) - end -end -local tWgetOptions = { "run" } -local function completeWget( shell, nIndex, sText, tPreviousText ) - if nIndex == 1 then - return completeMultipleChoice( sText, tWgetOptions, true ) - end -end -shell.setCompletionFunction( "rom/programs/alias.lua", completeAlias ) -shell.setCompletionFunction( "rom/programs/cd.lua", completeDir ) -shell.setCompletionFunction( "rom/programs/copy.lua", completeEitherEither ) -shell.setCompletionFunction( "rom/programs/delete.lua", completeEitherMany ) -shell.setCompletionFunction( "rom/programs/drive.lua", completeDir ) -shell.setCompletionFunction( "rom/programs/edit.lua", completeFile ) -shell.setCompletionFunction( "rom/programs/eject.lua", completePeripheral ) -shell.setCompletionFunction( "rom/programs/gps.lua", completeGPS ) -shell.setCompletionFunction( "rom/programs/help.lua", completeHelp ) -shell.setCompletionFunction( "rom/programs/id.lua", completePeripheral ) -shell.setCompletionFunction( "rom/programs/label.lua", completeLabel ) -shell.setCompletionFunction( "rom/programs/list.lua", completeDir ) -shell.setCompletionFunction( "rom/programs/mkdir.lua", completeFileMany ) -shell.setCompletionFunction( "rom/programs/monitor.lua", completeMonitor ) -shell.setCompletionFunction( "rom/programs/move.lua", completeEitherEither ) -shell.setCompletionFunction( "rom/programs/redstone.lua", completeRedstone ) -shell.setCompletionFunction( "rom/programs/rename.lua", completeEitherEither ) -shell.setCompletionFunction( "rom/programs/shell.lua", completeProgram ) -shell.setCompletionFunction( "rom/programs/type.lua", completeEither ) -shell.setCompletionFunction( "rom/programs/set.lua", completeSet ) -shell.setCompletionFunction( "rom/programs/advanced/bg.lua", completeProgram ) -shell.setCompletionFunction( "rom/programs/advanced/fg.lua", completeProgram ) -shell.setCompletionFunction( "rom/programs/fun/dj.lua", completeDJ ) -shell.setCompletionFunction( "rom/programs/fun/advanced/paint.lua", completeFile ) -shell.setCompletionFunction( "rom/programs/http/pastebin.lua", completePastebin ) -shell.setCompletionFunction( "rom/programs/rednet/chat.lua", completeChat ) -shell.setCompletionFunction( "rom/programs/command/exec.lua", completeExec ) -shell.setCompletionFunction( "rom/programs/http/wget.lua", completeWget ) + +shell.setCompletionFunction( "rom/programs/alias.lua", completion.build(nil, completion.program) ) +shell.setCompletionFunction( "rom/programs/cd.lua", completion.build(completion.dir) ) +shell.setCompletionFunction( "rom/programs/copy.lua", completion.build( + { completion.dirOrFile, true }, + completion.dirOrFile +) ) +shell.setCompletionFunction( "rom/programs/delete.lua", completion.build({ completion.dirOrFile, many = true }) ) +shell.setCompletionFunction( "rom/programs/drive.lua", completion.build(completion.dir) ) +shell.setCompletionFunction( "rom/programs/edit.lua", completion.build(completion.file) ) +shell.setCompletionFunction( "rom/programs/eject.lua", completion.build(completion.peripheral) ) +shell.setCompletionFunction( "rom/programs/gps.lua", completion.build({ completion.choice, { "host", "host ", "locate" } }) ) +shell.setCompletionFunction( "rom/programs/help.lua", completion.build(completion.help) ) +shell.setCompletionFunction( "rom/programs/id.lua", completion.build(completion.peripheral) ) +shell.setCompletionFunction( "rom/programs/label.lua", completion.build( + { completion.choice, { "get", "get ", "set ", "clear", "clear " } }, + completion.peripheral +) ) +shell.setCompletionFunction( "rom/programs/list.lua", completion.build(completion.dir) ) +shell.setCompletionFunction( "rom/programs/mkdir.lua", completion.build({ completion.dir, many = true }) ) +shell.setCompletionFunction( "rom/programs/monitor.lua", completion.build( + { completion.peripheral, true }, + completion.program +) ) +shell.setCompletionFunction( "rom/programs/move.lua", completion.build( + { completion.dirOrFile, true }, + completion.dirOrFile +) ) +shell.setCompletionFunction( "rom/programs/redstone.lua", completion.build( + { completion.choice, { "probe", "set ", "pulse " } }, + completion.side +) ) +shell.setCompletionFunction( "rom/programs/rename.lua", completion.build( + { completion.dirOrFile, true }, + completion.dirOrFile +) ) +shell.setCompletionFunction( "rom/programs/shell.lua", completion.build(completion.program) ) +shell.setCompletionFunction( "rom/programs/type.lua", completion.build(completion.dirOrFile) ) +shell.setCompletionFunction( "rom/programs/set.lua", completion.build({ completion.setting, true }) ) +shell.setCompletionFunction( "rom/programs/advanced/bg.lua", completion.build(completion.program) ) +shell.setCompletionFunction( "rom/programs/advanced/fg.lua", completion.build(completion.program) ) +shell.setCompletionFunction( "rom/programs/fun/dj.lua", completion.build( + { completion.choice, { "play", "play ", "stop " } }, + completion.peripheral +) ) +shell.setCompletionFunction( "rom/programs/fun/advanced/paint.lua", completion.build(completion.file) ) +shell.setCompletionFunction( "rom/programs/http/pastebin.lua", completion.build( + { completion.choice, { "put ", "get ", "run " } }, + completePastebinPut +) ) +shell.setCompletionFunction( "rom/programs/rednet/chat.lua", completion.build({ completion.choice, { "host ", "join " } }) ) +shell.setCompletionFunction( "rom/programs/command/exec.lua", completion.build(completion.command) ) +shell.setCompletionFunction( "rom/programs/http/wget.lua", completion.build({ completion.choice, { "run " } }) ) if turtle then - local tGoOptions = { "left", "right", "forward", "back", "down", "up" } - local function completeGo( shell, nIndex, sText ) - return completeMultipleChoice( sText, tGoOptions, true) - end - local tTurnOptions = { "left", "right" } - local function completeTurn( shell, nIndex, sText ) - return completeMultipleChoice( sText, tTurnOptions, true ) - end - local tEquipOptions = { "left", "right" } - local function completeEquip( shell, nIndex, sText ) - if nIndex == 2 then - return completeMultipleChoice( sText, tEquipOptions ) - end - end - local function completeUnequip( shell, nIndex, sText ) - if nIndex == 1 then - return completeMultipleChoice( sText, tEquipOptions ) - end - end - shell.setCompletionFunction( "rom/programs/turtle/go.lua", completeGo ) - shell.setCompletionFunction( "rom/programs/turtle/turn.lua", completeTurn ) - shell.setCompletionFunction( "rom/programs/turtle/equip.lua", completeEquip ) - shell.setCompletionFunction( "rom/programs/turtle/unequip.lua", completeUnequip ) + shell.setCompletionFunction( "rom/programs/turtle/go.lua", completion.build( + { completion.choice, { "left", "right", "forward", "back", "down", "up" }, true, many = true } + ) ) + shell.setCompletionFunction( "rom/programs/turtle/turn.lua", completion.build( + { completion.choice, { "left", "right" }, true, many = true } + )) + shell.setCompletionFunction( "rom/programs/turtle/equip.lua", completion.build( + nil, + { completion.choice, { "left", "right" } } + ) ) + shell.setCompletionFunction( "rom/programs/turtle/unequip.lua", completion.build( + { completion.choice, { "left", "right" } } + ) ) end - -- Run autorun files if fs.exists( "/rom/autorun" ) and fs.isDir( "/rom/autorun" ) then local tFiles = fs.list( "/rom/autorun" ) - table.sort( tFiles ) - for n, sFile in ipairs( tFiles ) do + for _, sFile in ipairs( tFiles ) do if string.sub( sFile, 1, 1 ) ~= "." then local sPath = "/rom/autorun/"..sFile if not fs.isDir( sPath ) then diff --git a/src/test/resources/test-rom/spec/modules/cc/completion_spec.lua b/src/test/resources/test-rom/spec/modules/cc/completion_spec.lua new file mode 100644 index 000000000..41e7ae5bc --- /dev/null +++ b/src/test/resources/test-rom/spec/modules/cc/completion_spec.lua @@ -0,0 +1,57 @@ +describe("cc.completion", function() + local c = require("cc.completion") + + describe("choice", function() + it("provides all choices", function() + expect(c.choice("", { "some text", "some other", "other" })) + :same { "some text", "some other", "other" } + end) + + it("provides a filtered list of choices", function() + expect(c.choice("som", { "some text", "some other", "other" })) + :same { "e text", "e other" } + + expect(c.choice("none", { "some text", "some other", "other" })) + :same { } + end) + + it("adds text if needed", function() + expect(c.choice("som", { "some text", "some other", "other" }, true)) + :same { "e text ", "e other " } + end) + end) + + describe("peripheral", function() + it("provides a choice of peripherals", function() + stub(peripheral, "getNames", function() return { "drive_0", "left" } end) + + expect(c.peripheral("dri")):same { "ve_0" } + expect(c.peripheral("dri", true)):same { "ve_0 " } + end) + end) + + describe("side", function() + it("provides a choice of sides", function() + expect(c.side("le")):same { "ft" } + expect(c.side("le", true)):same { "ft " } + end) + end) + + describe("setting", function() + it("provides a choice of setting names", function() + stub(settings, "getNames", function() return { "shell.allow_startup", "list.show_hidden" } end) + + expect(c.setting("li")):same { "st.show_hidden" } + expect(c.setting("li", true)):same { "st.show_hidden " } + end) + end) + + describe("command", function() + it("provides a choice of command names", function() + stub(_G, "commands", { list = function() return { "list", "say" } end }) + + expect(c.command("li")):same { "st" } + expect(c.command("li", true)):same { "st " } + end) + end) +end) diff --git a/src/test/resources/test-rom/spec/modules/cc/shell/completion_spec.lua b/src/test/resources/test-rom/spec/modules/cc/shell/completion_spec.lua new file mode 100644 index 000000000..7f7fd0a40 --- /dev/null +++ b/src/test/resources/test-rom/spec/modules/cc/shell/completion_spec.lua @@ -0,0 +1,41 @@ +describe("cc.shell.completion", function() + local c = require "cc.shell.completion" + + describe("dirOrFile", function() + it("completes both", function() + expect(c.dirOrFile(shell, "rom/")):same { + "apis/", "apis", "autorun/", "autorun", "help/", "help", + "modules/", "modules", "motd.txt", "programs/", "programs", "startup.lua" + } + end) + + it("adds a space", function() + expect(c.dirOrFile(shell, "rom/", nil, true)):same { + "apis/", "apis ", "autorun/", "autorun ", "help/", "help ", + "modules/", "modules ", "motd.txt ", "programs/", "programs ", "startup.lua ", + } + end) + end) + + describe("build", function() + it("completes multiple arguments", function() + local spec = c.build( + function() return { "a", "b", "c" } end, + nil, + { c.choice, { "d", "e", "f"} } + ) + + expect(spec(shell, 1, "")):same { "a", "b", "c" } + expect(spec(shell, 2, "")):same(nil) + expect(spec(shell, 3, "")):same { "d", "e", "f" } + expect(spec(shell, 4, "")):same(nil) + end) + + it("supports variadic completions", function() + local spec = c.build({ function() return { "a", "b", "c" } end, many = true }) + + expect(spec(shell, 1, "")):same({ "a", "b", "c" }) + expect(spec(shell, 2, "")):same({ "a", "b", "c" }) + end) + end) +end) diff --git a/src/test/resources/test-rom/spec/programs/mkdir_spec.lua b/src/test/resources/test-rom/spec/programs/mkdir_spec.lua index 91dcaf437..fb7327519 100644 --- a/src/test/resources/test-rom/spec/programs/mkdir_spec.lua +++ b/src/test/resources/test-rom/spec/programs/mkdir_spec.lua @@ -23,7 +23,7 @@ describe("The mkdir program", function() io.open("/test-files.a.txt", "w"):close() local complete = shell.getCompletionInfo()["rom/programs/mkdir.lua"].fnComplete - expect(complete(shell, 1, "/test-files/", {})):same { "a/", "b/" } - expect(complete(shell, 2, "/test-files/", { "/" })):same { "a/", "b/" } + expect(complete(shell, 1, "/test-files/", {})):same { "a/", "a", "b/", "b" } + expect(complete(shell, 2, "/test-files/", { "/" })):same { "a/", "a", "b/", "b" } end) end)