blob: 33dd4fe87e338c6a5efe8baaf36bfd796cd5f622 [file] [log] [blame]
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/shell/shell.h>
#include "shell_utils.h"
#include "shell_help.h"
#include "shell_ops.h"
#include "shell_vt100.h"
#define SHELL_MSG_CMD_NOT_SUPPORTED "Command not supported.\n"
#define SHELL_HELP_CLEAR "Clear screen."
#define SHELL_HELP_BACKSPACE_MODE "Toggle backspace key mode.\n" \
"Some terminals are not sending separate escape code for " \
"backspace and delete button. This command forces shell to interpret" \
" delete key as backspace."
#define SHELL_HELP_BACKSPACE_MODE_BACKSPACE "Set different escape" \
" code for backspace and delete key."
#define SHELL_HELP_BACKSPACE_MODE_DELETE "Set the same escape" \
" code for backspace and delete key."
#define SHELL_HELP_COLORS "Toggle colored syntax."
#define SHELL_HELP_COLORS_OFF "Disable colored syntax."
#define SHELL_HELP_COLORS_ON "Enable colored syntax."
#define SHELL_HELP_STATISTICS "Shell statistics."
#define SHELL_HELP_STATISTICS_SHOW \
"Get shell statistics for the Logger module."
#define SHELL_HELP_STATISTICS_RESET \
"Reset shell statistics for the Logger module."
#define SHELL_HELP_RESIZE \
"Console gets terminal screen size or assumes default in case" \
" the readout fails. It must be executed after each terminal" \
" width change to ensure correct text display."
#define SHELL_HELP_RESIZE_DEFAULT \
"Assume 80 chars screen width and send this setting " \
"to the terminal."
#define SHELL_HELP_HISTORY "Command history."
#define SHELL_HELP_ECHO "Toggle shell echo."
#define SHELL_HELP_ECHO_ON "Enable shell echo."
#define SHELL_HELP_ECHO_OFF \
"Disable shell echo. Editing keys and meta-keys are not handled"
#define SHELL_HELP_SELECT "Selects new root command. In order for the " \
"command to be selected, it must meet the criteria:\n" \
" - it is a static command\n" \
" - it is not preceded by a dynamic command\n" \
" - it accepts arguments\n" \
"Return to the main command tree is done by pressing alt+r."
#define SHELL_HELP_SHELL "Useful, not Unix-like shell commands."
#define SHELL_HELP_HELP "Prints help message."
#define SHELL_MSG_UNKNOWN_PARAMETER " unknown parameter: "
#define SHELL_MAX_TERMINAL_SIZE (250u)
/* 10 == {esc, [, 2, 5, 0, ;, 2, 5, 0, '\0'} */
#define SHELL_CURSOR_POSITION_BUFFER (10u)
#define SHELL_DEFAULT_TERMINAL_WIDTH 80
#define SHELL_DEFAULT_TERMINAL_HEIGHT 24
/* Function reads cursor position from terminal. */
static int cursor_position_get(const struct shell *shell, uint16_t *x, uint16_t *y)
{
uint16_t buff_idx = 0U;
size_t cnt;
char c = 0;
*x = 0U;
*y = 0U;
memset(shell->ctx->temp_buff, 0, sizeof(shell->ctx->temp_buff));
/* escape code asking terminal about its size */
static char const cmd_get_terminal_size[] = "\033[6n";
z_shell_raw_fprintf(shell->fprintf_ctx, cmd_get_terminal_size);
/* fprintf buffer needs to be flushed to start sending prepared
* escape code to the terminal.
*/
z_transport_buffer_flush(shell);
/* timeout for terminal response = ~1s */
for (uint16_t i = 0; i < 1000; i++) {
do {
(void)shell->iface->api->read(shell->iface, &c,
sizeof(c), &cnt);
if (cnt == 0) {
k_busy_wait(1000);
break;
}
if ((c != SHELL_VT100_ASCII_ESC) &&
(shell->ctx->temp_buff[0] !=
SHELL_VT100_ASCII_ESC)) {
continue;
}
if (c == 'R') { /* End of response from the terminal. */
shell->ctx->temp_buff[buff_idx] = '\0';
if (shell->ctx->temp_buff[1] != '[') {
shell->ctx->temp_buff[0] = 0;
return -EIO;
}
/* Index start position in the buffer where 'y'
* is stored.
*/
buff_idx = 2U;
while (shell->ctx->temp_buff[buff_idx] != ';') {
*y = *y * 10U +
(shell->ctx->temp_buff[buff_idx++] -
'0');
if (buff_idx >=
CONFIG_SHELL_CMD_BUFF_SIZE) {
return -EMSGSIZE;
}
}
if (++buff_idx >= CONFIG_SHELL_CMD_BUFF_SIZE) {
return -EIO;
}
while (shell->ctx->temp_buff[buff_idx]
!= '\0') {
*x = *x * 10U +
(shell->ctx->temp_buff[buff_idx++] -
'0');
if (buff_idx >=
CONFIG_SHELL_CMD_BUFF_SIZE) {
return -EMSGSIZE;
}
}
/* horizontal cursor position */
if (*x > SHELL_MAX_TERMINAL_SIZE) {
*x = SHELL_MAX_TERMINAL_SIZE;
}
/* vertical cursor position */
if (*y > SHELL_MAX_TERMINAL_SIZE) {
*y = SHELL_MAX_TERMINAL_SIZE;
}
shell->ctx->temp_buff[0] = 0;
return 0;
}
shell->ctx->temp_buff[buff_idx] = c;
if (++buff_idx > SHELL_CURSOR_POSITION_BUFFER - 1) {
shell->ctx->temp_buff[0] = 0;
/* data_buf[SHELL_CURSOR_POSITION_BUFFER - 1]
* is reserved for '\0'
*/
return -ENOMEM;
}
} while (cnt > 0);
}
return -ETIMEDOUT;
}
/* Function gets terminal width and height. */
static int terminal_size_get(const struct shell *shell)
{
uint16_t x; /* horizontal position */
uint16_t y; /* vertical position */
int ret_val = 0;
z_cursor_save(shell);
/* Assumption: terminal width and height < 999. */
/* Move to last column. */
z_shell_op_cursor_vert_move(shell, -SHELL_MAX_TERMINAL_SIZE);
/* Move to last row. */
z_shell_op_cursor_horiz_move(shell, SHELL_MAX_TERMINAL_SIZE);
if (cursor_position_get(shell, &x, &y) == 0) {
shell->ctx->vt100_ctx.cons.terminal_wid = x;
shell->ctx->vt100_ctx.cons.terminal_hei = y;
} else {
ret_val = -ENOTSUP;
}
z_cursor_restore(shell);
return ret_val;
}
static int cmd_clear(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argv);
Z_SHELL_VT100_CMD(shell, SHELL_VT100_CURSORHOME);
Z_SHELL_VT100_CMD(shell, SHELL_VT100_CLEARSCREEN);
return 0;
}
static int cmd_bacskpace_mode_backspace(const struct shell *shell, size_t argc,
char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
z_flag_mode_delete_set(shell, false);
return 0;
}
static int cmd_bacskpace_mode_delete(const struct shell *shell, size_t argc,
char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
z_flag_mode_delete_set(shell, true);
return 0;
}
static int cmd_colors_off(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
z_flag_use_colors_set(shell, false);
return 0;
}
static int cmd_colors_on(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argv);
ARG_UNUSED(argv);
z_flag_use_colors_set(shell, true);
return 0;
}
static int cmd_echo_off(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
z_flag_echo_set(shell, false);
return 0;
}
static int cmd_echo_on(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
z_flag_echo_set(shell, true);
return 0;
}
static int cmd_echo(const struct shell *shell, size_t argc, char **argv)
{
if (argc == 2) {
shell_error(shell, "%s:%s%s", argv[0],
SHELL_MSG_UNKNOWN_PARAMETER, argv[1]);
return -EINVAL;
}
shell_print(shell, "Echo status: %s",
z_flag_echo_get(shell) ? "on" : "off");
return 0;
}
static int cmd_history(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
size_t i = 0;
uint16_t len;
while (1) {
z_shell_history_get(shell->history, true,
shell->ctx->temp_buff, &len);
if (len) {
shell_print(shell, "[%3d] %s",
(int)i++, shell->ctx->temp_buff);
} else {
break;
}
}
shell->ctx->temp_buff[0] = '\0';
return 0;
}
static int cmd_shell_stats_show(const struct shell *shell, size_t argc,
char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
shell_print(shell, "Lost logs: %lu", shell->stats->log_lost_cnt);
return 0;
}
static int cmd_shell_stats_reset(const struct shell *shell,
size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
shell->stats->log_lost_cnt = 0;
return 0;
}
static int cmd_resize_default(const struct shell *shell,
size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
Z_SHELL_VT100_CMD(shell, SHELL_VT100_SETCOL_80);
shell->ctx->vt100_ctx.cons.terminal_wid = SHELL_DEFAULT_TERMINAL_WIDTH;
shell->ctx->vt100_ctx.cons.terminal_hei = SHELL_DEFAULT_TERMINAL_HEIGHT;
return 0;
}
static int cmd_resize(const struct shell *shell, size_t argc, char **argv)
{
int err;
if (argc != 1) {
shell_error(shell, "%s:%s%s", argv[0],
SHELL_MSG_UNKNOWN_PARAMETER, argv[1]);
return -EINVAL;
}
err = terminal_size_get(shell);
if (err != 0) {
shell->ctx->vt100_ctx.cons.terminal_wid =
CONFIG_SHELL_DEFAULT_TERMINAL_WIDTH;
shell->ctx->vt100_ctx.cons.terminal_hei =
CONFIG_SHELL_DEFAULT_TERMINAL_HEIGHT;
shell_warn(shell, "No response from the terminal, assumed 80x24"
" screen size");
return -ENOEXEC;
}
return 0;
}
static bool no_args(const struct shell_static_entry *entry)
{
return (entry->args.mandatory == 1) && (entry->args.optional == 0);
}
static int cmd_select(const struct shell *shell, size_t argc, char **argv)
{
const struct shell_static_entry *candidate = NULL;
struct shell_static_entry entry;
size_t matching_argc;
argc--;
argv = argv + 1;
candidate = z_shell_get_last_command(shell->ctx->selected_cmd,
argc, (const char **)argv,
&matching_argc, &entry, true);
if ((candidate != NULL) && !no_args(candidate)
&& (argc == matching_argc)) {
shell->ctx->selected_cmd = candidate;
return 0;
}
shell_error(shell, "Cannot select command");
return -EINVAL;
}
SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_colors,
SHELL_COND_CMD_ARG(CONFIG_SHELL_VT100_COMMANDS, off, NULL,
SHELL_HELP_COLORS_OFF, cmd_colors_off, 1, 0),
SHELL_COND_CMD_ARG(CONFIG_SHELL_VT100_COMMANDS, on, NULL,
SHELL_HELP_COLORS_ON, cmd_colors_on, 1, 0),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_echo,
SHELL_CMD_ARG(off, NULL, SHELL_HELP_ECHO_OFF, cmd_echo_off, 1, 0),
SHELL_CMD_ARG(on, NULL, SHELL_HELP_ECHO_ON, cmd_echo_on, 1, 0),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_shell_stats,
SHELL_CMD_ARG(reset, NULL, SHELL_HELP_STATISTICS_RESET,
cmd_shell_stats_reset, 1, 0),
SHELL_CMD_ARG(show, NULL, SHELL_HELP_STATISTICS_SHOW,
cmd_shell_stats_show, 1, 0),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_backspace_mode,
SHELL_CMD_ARG(backspace, NULL, SHELL_HELP_BACKSPACE_MODE_BACKSPACE,
cmd_bacskpace_mode_backspace, 1, 0),
SHELL_CMD_ARG(delete, NULL, SHELL_HELP_BACKSPACE_MODE_DELETE,
cmd_bacskpace_mode_delete, 1, 0),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_shell,
SHELL_CMD(backspace_mode, &m_sub_backspace_mode,
SHELL_HELP_BACKSPACE_MODE, NULL),
SHELL_COND_CMD(CONFIG_SHELL_VT100_COMMANDS, colors, &m_sub_colors,
SHELL_HELP_COLORS, NULL),
SHELL_CMD_ARG(echo, &m_sub_echo, SHELL_HELP_ECHO, cmd_echo, 1, 1),
SHELL_COND_CMD(CONFIG_SHELL_STATS, stats, &m_sub_shell_stats,
SHELL_HELP_STATISTICS, NULL),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_resize,
SHELL_CMD_ARG(default, NULL, SHELL_HELP_RESIZE_DEFAULT,
cmd_resize_default, 1, 0),
SHELL_SUBCMD_SET_END
);
SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_VT100_COMMANDS, clear, NULL,
SHELL_HELP_CLEAR, cmd_clear, 1, 0);
SHELL_CMD_REGISTER(shell, &m_sub_shell, SHELL_HELP_SHELL, NULL);
SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_HISTORY, history, NULL,
SHELL_HELP_HISTORY, cmd_history, 1, 0);
SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_CMDS_RESIZE, resize, &m_sub_resize,
SHELL_HELP_RESIZE, cmd_resize, 1, 1);
SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_CMDS_SELECT, select, NULL,
SHELL_HELP_SELECT, cmd_select, 2,
SHELL_OPT_ARG_CHECK_SKIP);