blob: 42cac67eb5ad64c6668f1d328fe7370f6ff34bf1 [file] [log] [blame]
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ctype.h>
#include "shell_ops.h"
void shell_op_cursor_vert_move(const struct shell *shell, s32_t delta)
{
if (delta != 0) {
shell_raw_fprintf(shell->fprintf_ctx, "\033[%d%c",
delta > 0 ? delta : -delta,
delta > 0 ? 'A' : 'B');
}
}
void shell_op_cursor_horiz_move(const struct shell *shell, s32_t delta)
{
if (delta != 0) {
shell_raw_fprintf(shell->fprintf_ctx, "\033[%d%c",
delta > 0 ? delta : -delta,
delta > 0 ? 'C' : 'D');
}
}
/* Function returns true if command length is equal to multiplicity of terminal
* width.
*/
static inline bool full_line_cmd(const struct shell *shell)
{
return ((shell->ctx->cmd_buff_len + shell_strlen(shell->ctx->prompt))
% shell->ctx->vt100_ctx.cons.terminal_wid == 0U);
}
/* Function returns true if cursor is at beginning of an empty line. */
bool shell_cursor_in_empty_line(const struct shell *shell)
{
return ((shell->ctx->cmd_buff_pos + shell_strlen(shell->ctx->prompt))
% shell->ctx->vt100_ctx.cons.terminal_wid == 0U);
}
void shell_op_cond_next_line(const struct shell *shell)
{
if (shell_cursor_in_empty_line(shell) || full_line_cmd(shell)) {
cursor_next_line_move(shell);
}
}
void shell_op_cursor_position_synchronize(const struct shell *shell)
{
struct shell_multiline_cons *cons = &shell->ctx->vt100_ctx.cons;
bool last_line;
shell_multiline_data_calc(cons, shell->ctx->cmd_buff_pos,
shell->ctx->cmd_buff_len);
last_line = (cons->cur_y == cons->cur_y_end);
/* In case cursor reaches the bottom line of a terminal, it will
* be moved to the next line.
*/
if (full_line_cmd(shell)) {
cursor_next_line_move(shell);
}
if (last_line) {
shell_op_cursor_horiz_move(shell, cons->cur_x -
cons->cur_x_end);
} else {
shell_op_cursor_vert_move(shell, cons->cur_y_end - cons->cur_y);
shell_op_cursor_horiz_move(shell, cons->cur_x -
cons->cur_x_end);
}
}
void shell_op_cursor_move(const struct shell *shell, s16_t val)
{
struct shell_multiline_cons *cons = &shell->ctx->vt100_ctx.cons;
u16_t new_pos = shell->ctx->cmd_buff_pos + val;
s32_t row_span;
s32_t col_span;
shell_multiline_data_calc(cons, shell->ctx->cmd_buff_pos,
shell->ctx->cmd_buff_len);
/* Calculate the new cursor. */
row_span = row_span_with_buffer_offsets_get(&shell->ctx->vt100_ctx.cons,
shell->ctx->cmd_buff_pos,
new_pos);
col_span = column_span_with_buffer_offsets_get(
&shell->ctx->vt100_ctx.cons,
shell->ctx->cmd_buff_pos,
new_pos);
shell_op_cursor_vert_move(shell, -row_span);
shell_op_cursor_horiz_move(shell, col_span);
shell->ctx->cmd_buff_pos = new_pos;
}
static u16_t shift_calc(const char *str, u16_t pos, u16_t len, s16_t sign)
{
bool found = false;
u16_t ret = 0U;
u16_t idx;
while (1) {
idx = pos + ret * sign;
if (((idx == 0U) && (sign < 0)) ||
((idx == len) && (sign > 0))) {
break;
}
if (isalnum((int)str[idx]) != 0) {
found = true;
} else {
if (found) {
break;
}
}
ret++;
}
return ret;
}
void shell_op_cursor_word_move(const struct shell *shell, s16_t val)
{
s16_t shift;
s16_t sign;
if (val < 0) {
val = -val;
sign = -1;
} else {
sign = 1;
}
while (val--) {
shift = shift_calc(shell->ctx->cmd_buff,
shell->ctx->cmd_buff_pos,
shell->ctx->cmd_buff_len, sign);
shell_op_cursor_move(shell, sign * shift);
}
}
void shell_op_word_remove(const struct shell *shell)
{
char *str = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos - 1];
char *str_start = &shell->ctx->cmd_buff[0];
u16_t chars_to_delete;
/* Line must not be empty and cursor must not be at 0 to continue. */
if ((shell->ctx->cmd_buff_len == 0) ||
(shell->ctx->cmd_buff_pos == 0)) {
return;
}
/* Start at the current position. */
chars_to_delete = 0U;
/* Look back for all spaces then for non-spaces. */
while ((str >= str_start) && (*str == ' ')) {
++chars_to_delete;
--str;
}
while ((str >= str_start) && (*str != ' ')) {
++chars_to_delete;
--str;
}
/* Manage the buffer. */
memmove(str + 1, str + 1 + chars_to_delete,
shell->ctx->cmd_buff_len - chars_to_delete);
shell->ctx->cmd_buff_len -= chars_to_delete;
shell->ctx->cmd_buff[shell->ctx->cmd_buff_len] = '\0';
/* Update display. */
shell_op_cursor_move(shell, -chars_to_delete);
cursor_save(shell);
shell_internal_fprintf(shell, SHELL_NORMAL, "%s", str + 1);
clear_eos(shell);
cursor_restore(shell);
}
void shell_op_cursor_home_move(const struct shell *shell)
{
shell_op_cursor_move(shell, -shell->ctx->cmd_buff_pos);
}
void shell_op_cursor_end_move(const struct shell *shell)
{
shell_op_cursor_move(shell, shell->ctx->cmd_buff_len -
shell->ctx->cmd_buff_pos);
}
void shell_op_left_arrow(const struct shell *shell)
{
if (shell->ctx->cmd_buff_pos > 0) {
shell_op_cursor_move(shell, -1);
}
}
void shell_op_right_arrow(const struct shell *shell)
{
if (shell->ctx->cmd_buff_pos < shell->ctx->cmd_buff_len) {
shell_op_cursor_move(shell, 1);
}
}
static void reprint_from_cursor(const struct shell *shell, u16_t diff,
bool data_removed)
{
/* Clear eos is needed only when newly printed command is shorter than
* previously printed command. This can happen when delete or backspace
* was called.
*
* Such condition is useful for Bluetooth devices to save number of
* bytes transmitted between terminal and device.
*/
if (data_removed) {
clear_eos(shell);
}
shell_internal_fprintf(shell, SHELL_NORMAL, "%s",
&shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos]);
shell->ctx->cmd_buff_pos = shell->ctx->cmd_buff_len;
if (full_line_cmd(shell)) {
if (((data_removed) && (diff > 0)) || (!data_removed)) {
cursor_next_line_move(shell);
}
}
shell_op_cursor_move(shell, -diff);
}
static void data_insert(const struct shell *shell, const char *data, u16_t len)
{
u16_t after = shell->ctx->cmd_buff_len - shell->ctx->cmd_buff_pos;
char *curr_pos = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos];
if ((shell->ctx->cmd_buff_len + len) >= CONFIG_SHELL_CMD_BUFF_SIZE) {
return;
}
memmove(curr_pos + len, curr_pos, after);
memcpy(curr_pos, data, len);
shell->ctx->cmd_buff_len += len;
shell->ctx->cmd_buff[shell->ctx->cmd_buff_len] = '\0';
if (!flag_echo_get(shell)) {
shell->ctx->cmd_buff_pos += len;
return;
}
reprint_from_cursor(shell, after, false);
}
static void char_replace(const struct shell *shell, char data)
{
shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos++] = data;
if (!flag_echo_get(shell)) {
return;
}
shell_raw_fprintf(shell->fprintf_ctx, "%c", data);
if (shell_cursor_in_empty_line(shell)) {
cursor_next_line_move(shell);
}
}
void shell_op_char_insert(const struct shell *shell, char data)
{
if (shell->ctx->internal.flags.insert_mode &&
(shell->ctx->cmd_buff_len != shell->ctx->cmd_buff_pos)) {
char_replace(shell, data);
} else {
data_insert(shell, &data, 1);
}
}
void shell_op_char_backspace(const struct shell *shell)
{
if ((shell->ctx->cmd_buff_len == 0) ||
(shell->ctx->cmd_buff_pos == 0)) {
return;
}
shell_op_cursor_move(shell, -1);
shell_op_char_delete(shell);
}
void shell_op_char_delete(const struct shell *shell)
{
u16_t diff = shell->ctx->cmd_buff_len - shell->ctx->cmd_buff_pos;
char *str = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos];
if (diff == 0U) {
return;
}
memmove(str, str + 1, diff);
--shell->ctx->cmd_buff_len;
reprint_from_cursor(shell, --diff, true);
}
void shell_op_delete_from_cursor(const struct shell *shell)
{
shell->ctx->cmd_buff_len = shell->ctx->cmd_buff_pos;
shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos] = '\0';
clear_eos(shell);
}
void shell_op_completion_insert(const struct shell *shell,
const char *compl,
u16_t compl_len)
{
data_insert(shell, compl, compl_len);
}
void shell_cmd_line_erase(const struct shell *shell)
{
shell_multiline_data_calc(&shell->ctx->vt100_ctx.cons,
shell->ctx->cmd_buff_pos,
shell->ctx->cmd_buff_len);
shell_op_cursor_horiz_move(shell,
-(shell->ctx->vt100_ctx.cons.cur_x - 1));
shell_op_cursor_vert_move(shell, shell->ctx->vt100_ctx.cons.cur_y - 1);
clear_eos(shell);
}
static void print_prompt(const struct shell *shell)
{
shell_internal_fprintf(shell, SHELL_INFO, "%s", shell->ctx->prompt);
}
void shell_print_cmd(const struct shell *shell)
{
shell_raw_fprintf(shell->fprintf_ctx, "%s", shell->ctx->cmd_buff);
}
void shell_print_prompt_and_cmd(const struct shell *shell)
{
print_prompt(shell);
if (flag_echo_get(shell)) {
shell_print_cmd(shell);
shell_op_cursor_position_synchronize(shell);
}
}
static void shell_pend_on_txdone(const struct shell *shell)
{
if (IS_ENABLED(CONFIG_MULTITHREADING) &&
(shell->ctx->state < SHELL_STATE_PANIC_MODE_ACTIVE)) {
k_poll(&shell->ctx->events[SHELL_SIGNAL_TXDONE], 1, K_FOREVER);
k_poll_signal_reset(&shell->ctx->signals[SHELL_SIGNAL_TXDONE]);
} else {
/* Blocking wait in case of bare metal. */
while (!flag_tx_rdy_get(shell)) {
}
flag_tx_rdy_set(shell, false);
}
}
void shell_write(const struct shell *shell, const void *data,
size_t length)
{
__ASSERT_NO_MSG(shell && data);
size_t offset = 0;
size_t tmp_cnt;
while (length) {
int err = shell->iface->api->write(shell->iface,
&((const u8_t *) data)[offset], length,
&tmp_cnt);
(void)err;
__ASSERT_NO_MSG(err == 0);
__ASSERT_NO_MSG(length >= tmp_cnt);
offset += tmp_cnt;
length -= tmp_cnt;
if (tmp_cnt == 0 &&
(shell->ctx->state != SHELL_STATE_PANIC_MODE_ACTIVE)) {
shell_pend_on_txdone(shell);
}
}
}
/* Function shall be only used by the fprintf module. */
void shell_print_stream(const void *user_ctx, const char *data,
size_t data_len)
{
shell_write((const struct shell *) user_ctx, data, data_len);
}
static void vt100_bgcolor_set(const struct shell *shell,
enum shell_vt100_color bgcolor)
{
if ((bgcolor == SHELL_NORMAL) ||
(shell->ctx->vt100_ctx.col.bgcol == bgcolor)) {
return;
}
/* -1 because default value is first in enum */
u8_t cmd[] = SHELL_VT100_BGCOLOR(bgcolor - 1);
shell->ctx->vt100_ctx.col.bgcol = bgcolor;
shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd);
}
void shell_vt100_color_set(const struct shell *shell,
enum shell_vt100_color color)
{
if (shell->ctx->vt100_ctx.col.col == color) {
return;
}
shell->ctx->vt100_ctx.col.col = color;
if (color != SHELL_NORMAL) {
u8_t cmd[] = SHELL_VT100_COLOR(color - 1);
shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd);
} else {
static const u8_t cmd[] = SHELL_VT100_MODESOFF;
shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd);
}
}
void shell_vt100_colors_restore(const struct shell *shell,
const struct shell_vt100_colors *color)
{
shell_vt100_color_set(shell, color->col);
vt100_bgcolor_set(shell, color->bgcol);
}
void shell_internal_vfprintf(const struct shell *shell,
enum shell_vt100_color color, const char *fmt,
va_list args)
{
if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS) &&
shell->ctx->internal.flags.use_colors &&
(color != shell->ctx->vt100_ctx.col.col)) {
struct shell_vt100_colors col;
shell_vt100_colors_store(shell, &col);
shell_vt100_color_set(shell, color);
shell_fprintf_fmt(shell->fprintf_ctx, fmt, args);
shell_vt100_colors_restore(shell, &col);
} else {
shell_fprintf_fmt(shell->fprintf_ctx, fmt, args);
}
}
void shell_internal_fprintf(const struct shell *shell,
enum shell_vt100_color color,
const char *fmt, ...)
{
__ASSERT_NO_MSG(shell);
__ASSERT(!k_is_in_isr(), "Thread context required.");
__ASSERT_NO_MSG(shell->ctx);
__ASSERT_NO_MSG(shell->fprintf_ctx);
__ASSERT_NO_MSG(fmt);
va_list args = { 0 };
va_start(args, fmt);
shell_internal_vfprintf(shell, color, fmt, args);
va_end(args);
}