Skip to content

Commit

Permalink
add CLI tab-to-autocomplete ability and implement for nrfconnect (#13630
Browse files Browse the repository at this point in the history
)

* implement cli autocompletion for ESP32 and nRFConnect

* avoid STL;revert implementation for esp32

* update to register with nullptr prefix

* cleanup comment

* revert an unnecessary change

* backfill two new commands; address CI failures
  • Loading branch information
lmpprk authored and pull[bot] committed Dec 22, 2023
1 parent 87ae89f commit 1101579
Show file tree
Hide file tree
Showing 26 changed files with 362 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ static CHIP_ERROR SwitchCommandHandler(int argc, char ** argv)
static void RegisterSwitchCommands()
{
static const shell_command_t sSwitchCommand = { SwitchCommandHandler, "switch", "Switch commands. Usage: switch [on|off]" };
Engine::Root().RegisterCommands(&sSwitchCommand, 1);
Engine::Root().RegisterCommands(&sSwitchCommand, 1, nullptr);
return;
}
#endif // defined(ENABLE_CHIP_SHELL)
Expand Down
4 changes: 2 additions & 2 deletions examples/all-clusters-app/esp32/main/OnOffCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ void OnOffCommands::Register()
static const shell_command_t subCommands[] = { { &OnLightHandler, "on", "Usage: OnOff on endpoint-id" },
{ &OffLightHandler, "off", "Usage: OnOff off endpoint-id" },
{ &ToggleLightHandler, "toggle", "Usage: OnOff toggle endpoint-id" } };
sSubShell.RegisterCommands(subCommands, ArraySize(subCommands));
sSubShell.RegisterCommands(subCommands, ArraySize(subCommands), "OnOff");

// Register the root `OnOff` command in the top-level shell.
static const shell_command_t onOffCommand = { &OnOffHandler, "OnOff", "OnOff commands" };

Engine::Root().RegisterCommands(&onOffCommand, 1);
Engine::Root().RegisterCommands(&onOffCommand, 1, nullptr);
}

} // namespace Shell
Expand Down
4 changes: 2 additions & 2 deletions examples/lighting-app/cyw30739/src/AppShellCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ void RegisterAppShellCommands(void)
.cmd_help = "App commands",
};

sAppSubcommands.RegisterCommands(sAppSubCommands, ArraySize(sAppSubCommands));
sAppSubcommands.RegisterCommands(sAppSubCommands, ArraySize(sAppSubCommands), "app");

Engine::Root().RegisterCommands(&sAppCommand, 1);
Engine::Root().RegisterCommands(&sAppCommand, 1, nullptr);
}

CHIP_ERROR AppCommandHelpHandler(int argc, char * argv[])
Expand Down
4 changes: 2 additions & 2 deletions examples/ota-provider-app/esp32/main/OTAProviderCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ void OTAProviderCommands::Register()
"Usage: OTAProvider delay <delay in seconds>" },
};

sSubShell.RegisterCommands(subCommands, ArraySize(subCommands));
sSubShell.RegisterCommands(subCommands, ArraySize(subCommands), "OTAProvider");

// Register the root `OTA Provider` command in the top-level shell.
static const shell_command_t otaProviderCommand = { &OTAProviderHandler, "OTAProvider", "OTA Provider commands" };

Engine::Root().RegisterCommands(&otaProviderCommand, 1);
Engine::Root().RegisterCommands(&otaProviderCommand, 1, nullptr);
}

// Set Example OTA provider
Expand Down
4 changes: 2 additions & 2 deletions examples/platform/esp32/shell_extension/heap_trace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,15 @@ void RegisterHeapTraceCommands()
{ &HeapTraceTaskHandler, "task", "Dump heap usage of each task" },
#endif // CONFIG_HEAP_TASK_TRACKING
};
sShellHeapSubCommands.RegisterCommands(sHeapSubCommands, ArraySize(sHeapSubCommands));
sShellHeapSubCommands.RegisterCommands(sHeapSubCommands, ArraySize(sHeapSubCommands), "heap-trace");

#if CONFIG_HEAP_TRACING_STANDALONE
ESP_ERROR_CHECK(heap_trace_init_standalone(sTraceRecords, kNumHeapTraceRecords));
ESP_ERROR_CHECK(heap_trace_start(HEAP_TRACE_LEAKS));
#endif // CONFIG_HEAP_TRACING_STANDALONE

static const shell_command_t sHeapCommand = { &HeapTraceDispatch, "heap-trace", "Heap debug tracing" };
Engine::Root().RegisterCommands(&sHeapCommand, 1);
Engine::Root().RegisterCommands(&sHeapCommand, 1, nullptr);
}

} // namespace chip
Expand Down
2 changes: 1 addition & 1 deletion examples/platform/linux/CommissioneeShellCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ void RegisterCommissioneeCommands()
"Commissionee commands. Usage: commissionee [command_name]" };

// Register the root `device` command with the top-level shell.
Engine::Root().RegisterCommands(&sDeviceComand, 1);
Engine::Root().RegisterCommands(&sDeviceComand, 1, nullptr);
return;
}

Expand Down
2 changes: 1 addition & 1 deletion examples/platform/linux/ControllerShellCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ void RegisterControllerCommands()
"Controller commands. Usage: controller [command_name]" };

// Register the root `device` command with the top-level shell.
Engine::Root().RegisterCommands(&sDeviceComand, 1);
Engine::Root().RegisterCommands(&sDeviceComand, 1, nullptr);
return;
}

Expand Down
2 changes: 1 addition & 1 deletion examples/shell/shell_common/cmd_misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,5 @@ static shell_command_t cmds_misc[] = {

void cmd_misc_init()
{
Engine::Root().RegisterCommands(cmds_misc, ArraySize(cmds_misc));
Engine::Root().RegisterCommands(cmds_misc, ArraySize(cmds_misc), nullptr);
}
2 changes: 1 addition & 1 deletion examples/shell/shell_common/cmd_otcli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,6 @@ void cmd_otcli_init()
#endif

// Register the root otcli command with the top-level shell.
Engine::Root().RegisterCommands(&cmds_otcli_root, 1);
Engine::Root().RegisterCommands(&cmds_otcli_root, 1, nullptr);
#endif // CHIP_ENABLE_OPENTHREAD
}
2 changes: 1 addition & 1 deletion examples/shell/shell_common/cmd_ping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -483,5 +483,5 @@ static shell_command_t cmds_ping[] = {

void cmd_ping_init()
{
Engine::Root().RegisterCommands(cmds_ping, ArraySize(cmds_ping));
Engine::Root().RegisterCommands(cmds_ping, ArraySize(cmds_ping), nullptr);
}
2 changes: 1 addition & 1 deletion examples/shell/shell_common/cmd_send.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -406,5 +406,5 @@ static shell_command_t cmds_send[] = {

void cmd_send_init()
{
Engine::Root().RegisterCommands(cmds_send, ArraySize(cmds_send));
Engine::Root().RegisterCommands(cmds_send, ArraySize(cmds_send), nullptr);
}
4 changes: 2 additions & 2 deletions examples/shell/shell_common/cmd_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,8 @@ void cmd_app_server_init()
std::atexit(CmdAppServerAtExit);

// Register `server` subcommands with the local shell dispatcher.
sShellServerSubcommands.RegisterCommands(sServerSubCommands, ArraySize(sServerSubCommands));
sShellServerSubcommands.RegisterCommands(sServerSubCommands, ArraySize(sServerSubCommands), "server");

// Register the root `server` command with the top-level shell.
Engine::Root().RegisterCommands(&sServerComand, 1);
Engine::Root().RegisterCommands(&sServerComand, 1, nullptr);
}
2 changes: 1 addition & 1 deletion examples/tv-app/linux/AppPlatformShellCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ void RegisterAppPlatformCommands()
static const shell_command_t sDeviceComand = { &AppPlatformHandler, "app", "App commands. Usage: app [command_name]" };

// Register the root `device` command with the top-level shell.
Engine::Root().RegisterCommands(&sDeviceComand, 1);
Engine::Root().RegisterCommands(&sDeviceComand, 1, nullptr);
return;
}

Expand Down
187 changes: 164 additions & 23 deletions src/lib/shell/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@
* Source implementation for a generic shell API for CHIP examples.
*/

#include <lib/shell/Engine.h>

#include <lib/core/CHIPError.h>
#include <lib/shell/Commands.h>
#include <lib/shell/Engine.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
Expand All @@ -35,14 +34,14 @@
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

using namespace chip::Logging;

namespace chip {
namespace Shell {

Engine Engine::theEngineRoot;
shell_map * Engine::theShellMapListHead = NULL;

int Engine::Init()
{
Expand All @@ -56,47 +55,189 @@ int Engine::Init()

void Engine::ForEachCommand(shell_command_iterator_t * on_command, void * arg)
{
for (unsigned i = 0; i < _commandSetCount; i++)
for (unsigned i = 0; i < _commandCount; i++)
{
for (unsigned j = 0; j < _commandSetSize[i]; j++)
if (on_command(_commands[i], arg) != CHIP_NO_ERROR)
{
if (on_command(&_commandSet[i][j], arg) != CHIP_NO_ERROR)
return;
}
}
}

void Engine::RegisterCommands(shell_command_t * command_set, unsigned count, const char * prefix)
{
if (this == &Engine::Root() || prefix == nullptr)
{
prefix = "";
}

if (_commandCount + count > CHIP_SHELL_MAX_MODULES)
{
ChipLogError(Shell, "Max number of commands registered to this shell");
assert(0);
}

for (unsigned i = 0; i < count; i++)
{
_commands[_commandCount + i] = &command_set[i];
}
_commandCount += count;

Engine::InsertShellMap(prefix, this);
}

void Engine::InsertShellMap(char const * prefix, Engine * shell)
{
shell_map_t * map = Engine::theShellMapListHead;
while (map != NULL)
{
if (strcmp(prefix, map->prefix) == 0)
{
for (size_t i = 0; i < map->enginec; i++)
{
return;
if (map->enginev[i] == shell)
{
return;
}
}
if (map->enginec == sizeof(map->enginev))
{
ChipLogError(Shell, "Max number of shells registered under this prefix");
assert(0);
}
map->enginev[map->enginec] = shell;
map->enginec++;
return;
}
map = map->next;
}
shell_map_t * new_map = new shell_map_t;
new_map->prefix = prefix;
new_map->enginev[0] = shell;
new_map->enginec = 1;
new_map->next = Engine::theShellMapListHead;
Engine::theShellMapListHead = new_map;
}

void Engine::RegisterCommands(shell_command_t * command_set, unsigned count)
CHIP_ERROR Engine::GetCommandCompletions(cmd_completion_context * context)
{
if (_commandSetCount >= CHIP_SHELL_MAX_MODULES)

if (Engine::theShellMapListHead == NULL)
{
ChipLogError(Shell, "Max number of modules reached\n");
assert(0);
ChipLogDetail(Shell, "There isn't any command registered yet.");
return CHIP_NO_ERROR;
}
const char * buf = context->line_buf;
if (buf == nullptr)
{
buf = "";
}

int last_space_idx = -1;
int buf_len = strlen(buf);

_commandSet[_commandSetCount] = command_set;
_commandSetSize[_commandSetCount] = count;
++_commandSetCount;
// find space in buf
for (int i = buf_len - 1; i > -1; i--)
{
if (buf[i] == ' ')
{
last_space_idx = i;
break;
}
}

// check whether the buf perfectly matches a prefix
bool perfect_match = false;
shell_map_t * map = Engine::theShellMapListHead;

while (map != NULL)
{

if (strcmp(buf, map->prefix) == 0)
{
perfect_match = true;
break;
}
map = map->next;
}

char * prefix;
char * incomplete_cmd;

if (perfect_match)
{
// If it's a perfect match
// - use the whole buf as the prefix
// - there is no "incomplete command" so set it to empty
prefix = new char[buf_len + 1];
incomplete_cmd = new char[1];
strncpy(prefix, buf, buf_len + 1);
strncpy(incomplete_cmd, "", 1);
}
else
{
// If it's not a perfect match:
// - prefix is up to the last space
// - incomplete_cmd is what's after the last space
bool no_space = (last_space_idx == -1) ? true : false;
prefix = new char[last_space_idx + 1 + no_space];
incomplete_cmd = new char[buf_len - last_space_idx];
strncpy(prefix, buf, last_space_idx + 1 + no_space);
strncpy(incomplete_cmd, &buf[last_space_idx + 1], buf_len - last_space_idx);
}

// Array to pass arguments into the ForEachCommand call
void * lambda_args[] = { context, incomplete_cmd };
map = Engine::theShellMapListHead;

while (map != NULL && context->cmdc < CHIP_SHELL_MAX_CMD_COMPLETIONS)
{
if (strcmp(prefix, map->prefix) == 0)
{
context->ret_prefix = map->prefix;
for (unsigned i = 0; i < map->enginec; i++)
{
map->enginev[i]->ForEachCommand(
[](shell_command_t * cmd, void * arg) -> CHIP_ERROR {
cmd_completion_context * ctx = (cmd_completion_context *) ((void **) arg)[0];
char * _incomplete_cmd = (char *) ((void **) arg)[1];
// For end nodes like "ble adv", need to avoid duplicate returns.
// Return for prefix="ble adv" cmd=""; reject for prefix="ble" cmd="adv"
if ((strcmp(cmd->cmd_name, _incomplete_cmd) != 0 &&
strncmp(cmd->cmd_name, _incomplete_cmd, strlen(_incomplete_cmd)) == 0) ||
strcmp(_incomplete_cmd, "") == 0)
{
ctx->cmdc++;
ctx->cmdv[ctx->cmdc - 1] = cmd;
}
return CHIP_NO_ERROR;
},
lambda_args);
}
break;
}

map = map->next;
}

delete[] prefix;
delete[] incomplete_cmd;

return CHIP_NO_ERROR;
}

CHIP_ERROR Engine::ExecCommand(int argc, char * argv[])
{
CHIP_ERROR retval = CHIP_ERROR_INVALID_ARGUMENT;

VerifyOrReturnError(argc > 0, retval);
// Find the command
for (unsigned i = 0; i < _commandSetCount; i++)
for (unsigned i = 0; i < _commandCount; i++)
{
for (unsigned j = 0; j < _commandSetSize[i]; j++)
if (strcmp(argv[0], _commands[i]->cmd_name) == 0)
{
if (strcmp(argv[0], _commandSet[i][j].cmd_name) == 0)
{
// Execute the command!
retval = _commandSet[i][j].cmd_func(argc - 1, argv + 1);
break;
}
// Execute the command!
retval = _commands[i]->cmd_func(argc - 1, argv + 1);
break;
}
}

Expand Down

0 comments on commit 1101579

Please sign in to comment.