| /* |
| * Copyright (c) 2018 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <ctype.h> |
| #include "shell_ops.h" |
| |
| #define CMD_CURSOR_LEN 8 |
| void z_shell_op_cursor_vert_move(const struct shell *sh, int32_t delta) |
| { |
| char dir = delta > 0 ? 'A' : 'B'; |
| |
| if (delta == 0) { |
| return; |
| } |
| |
| if (delta < 0) { |
| delta = -delta; |
| } |
| |
| Z_SHELL_VT100_CMD(sh, "\e[%d%c", delta, dir); |
| } |
| |
| void z_shell_op_cursor_horiz_move(const struct shell *sh, int32_t delta) |
| { |
| char dir = delta > 0 ? 'C' : 'D'; |
| |
| if (delta == 0) { |
| return; |
| } |
| |
| if (delta < 0) { |
| delta = -delta; |
| } |
| |
| Z_SHELL_VT100_CMD(sh, "\e[%d%c", delta, dir); |
| } |
| |
| /* Function returns true if command length is equal to multiplicity of terminal |
| * width. |
| */ |
| static inline bool full_line_cmd(const struct shell *sh) |
| { |
| return ((sh->ctx->cmd_buff_len + z_shell_strlen(sh->ctx->prompt)) |
| % sh->ctx->vt100_ctx.cons.terminal_wid == 0U); |
| } |
| |
| /* Function returns true if cursor is at beginning of an empty line. */ |
| bool z_shell_cursor_in_empty_line(const struct shell *sh) |
| { |
| return (((sh->ctx->cmd_buff_pos * sh->ctx->cfg.flags.echo) + |
| z_shell_strlen(sh->ctx->prompt)) % |
| sh->ctx->vt100_ctx.cons.terminal_wid == |
| 0U); |
| } |
| |
| void z_shell_op_cond_next_line(const struct shell *sh) |
| { |
| if (z_shell_cursor_in_empty_line(sh) || full_line_cmd(sh)) { |
| z_cursor_next_line_move(sh); |
| } |
| } |
| |
| void z_shell_op_cursor_position_synchronize(const struct shell *sh) |
| { |
| struct shell_multiline_cons *cons = &sh->ctx->vt100_ctx.cons; |
| bool last_line; |
| |
| z_shell_multiline_data_calc(cons, sh->ctx->cmd_buff_pos, |
| sh->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(sh)) { |
| z_cursor_next_line_move(sh); |
| } |
| |
| if (last_line) { |
| z_shell_op_cursor_horiz_move(sh, cons->cur_x - |
| cons->cur_x_end); |
| } else { |
| z_shell_op_cursor_vert_move(sh, cons->cur_y_end - cons->cur_y); |
| z_shell_op_cursor_horiz_move(sh, cons->cur_x - |
| cons->cur_x_end); |
| } |
| } |
| |
| void z_shell_op_cursor_move(const struct shell *sh, int16_t val) |
| { |
| struct shell_multiline_cons *cons = &sh->ctx->vt100_ctx.cons; |
| uint16_t new_pos = sh->ctx->cmd_buff_pos + val; |
| int32_t row_span; |
| int32_t col_span; |
| |
| z_shell_multiline_data_calc(cons, sh->ctx->cmd_buff_pos, |
| sh->ctx->cmd_buff_len); |
| |
| /* Calculate the new cursor. */ |
| row_span = z_row_span_with_buffer_offsets_get( |
| &sh->ctx->vt100_ctx.cons, |
| sh->ctx->cmd_buff_pos, |
| new_pos); |
| col_span = z_column_span_with_buffer_offsets_get( |
| &sh->ctx->vt100_ctx.cons, |
| sh->ctx->cmd_buff_pos, |
| new_pos); |
| |
| z_shell_op_cursor_vert_move(sh, -row_span); |
| z_shell_op_cursor_horiz_move(sh, col_span); |
| sh->ctx->cmd_buff_pos = new_pos; |
| } |
| |
| static uint16_t shift_calc(const char *str, uint16_t pos, uint16_t len, int16_t sign) |
| { |
| bool found = false; |
| uint16_t ret = 0U; |
| uint16_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 z_shell_op_cursor_word_move(const struct shell *sh, int16_t val) |
| { |
| int16_t shift; |
| int16_t sign; |
| |
| if (val < 0) { |
| val = -val; |
| sign = -1; |
| } else { |
| sign = 1; |
| } |
| |
| while (val--) { |
| shift = shift_calc(sh->ctx->cmd_buff, |
| sh->ctx->cmd_buff_pos, |
| sh->ctx->cmd_buff_len, sign); |
| z_shell_op_cursor_move(sh, sign * shift); |
| } |
| } |
| |
| void z_shell_op_word_remove(const struct shell *sh) |
| { |
| /* Line must not be empty and cursor must not be at 0 to continue. */ |
| if ((sh->ctx->cmd_buff_len == 0) || |
| (sh->ctx->cmd_buff_pos == 0)) { |
| return; |
| } |
| |
| char *str = &sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos - 1]; |
| char *str_start = &sh->ctx->cmd_buff[0]; |
| uint16_t chars_to_delete; |
| |
| /* 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, |
| sh->ctx->cmd_buff_len - chars_to_delete); |
| sh->ctx->cmd_buff_len -= chars_to_delete; |
| sh->ctx->cmd_buff[sh->ctx->cmd_buff_len] = '\0'; |
| |
| /* Update display. */ |
| z_shell_op_cursor_move(sh, -chars_to_delete); |
| z_cursor_save(sh); |
| z_shell_fprintf(sh, SHELL_NORMAL, "%s", str + 1); |
| z_clear_eos(sh); |
| z_cursor_restore(sh); |
| } |
| |
| void z_shell_op_cursor_home_move(const struct shell *sh) |
| { |
| z_shell_op_cursor_move(sh, -sh->ctx->cmd_buff_pos); |
| } |
| |
| void z_shell_op_cursor_end_move(const struct shell *sh) |
| { |
| z_shell_op_cursor_move(sh, sh->ctx->cmd_buff_len - |
| sh->ctx->cmd_buff_pos); |
| } |
| |
| void z_shell_op_left_arrow(const struct shell *sh) |
| { |
| if (sh->ctx->cmd_buff_pos > 0) { |
| z_shell_op_cursor_move(sh, -1); |
| } |
| } |
| |
| void z_shell_op_right_arrow(const struct shell *sh) |
| { |
| if (sh->ctx->cmd_buff_pos < sh->ctx->cmd_buff_len) { |
| z_shell_op_cursor_move(sh, 1); |
| } |
| } |
| |
| static void reprint_from_cursor(const struct shell *sh, uint16_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) { |
| z_clear_eos(sh); |
| } |
| |
| if (z_flag_obscure_get(sh)) { |
| int len = strlen(&sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos]); |
| |
| while (len--) { |
| z_shell_raw_fprintf(sh->fprintf_ctx, "*"); |
| } |
| } else { |
| z_shell_fprintf(sh, SHELL_NORMAL, "%s", |
| &sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos]); |
| } |
| sh->ctx->cmd_buff_pos = sh->ctx->cmd_buff_len; |
| |
| if (full_line_cmd(sh)) { |
| if (((data_removed) && (diff > 0)) || (!data_removed)) { |
| z_cursor_next_line_move(sh); |
| } |
| } |
| |
| z_shell_op_cursor_move(sh, -diff); |
| } |
| |
| static void data_insert(const struct shell *sh, const char *data, uint16_t len) |
| { |
| uint16_t after = sh->ctx->cmd_buff_len - sh->ctx->cmd_buff_pos; |
| char *curr_pos = &sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos]; |
| |
| if ((sh->ctx->cmd_buff_len + len) >= CONFIG_SHELL_CMD_BUFF_SIZE) { |
| return; |
| } |
| |
| memmove(curr_pos + len, curr_pos, after); |
| memcpy(curr_pos, data, len); |
| sh->ctx->cmd_buff_len += len; |
| sh->ctx->cmd_buff[sh->ctx->cmd_buff_len] = '\0'; |
| |
| if (!z_flag_echo_get(sh)) { |
| sh->ctx->cmd_buff_pos += len; |
| return; |
| } |
| |
| reprint_from_cursor(sh, after, false); |
| } |
| |
| static void char_replace(const struct shell *sh, char data) |
| { |
| sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos++] = data; |
| |
| if (!z_flag_echo_get(sh)) { |
| return; |
| } |
| if (z_flag_obscure_get(sh)) { |
| data = '*'; |
| } |
| |
| z_shell_raw_fprintf(sh->fprintf_ctx, "%c", data); |
| if (z_shell_cursor_in_empty_line(sh)) { |
| z_cursor_next_line_move(sh); |
| } |
| } |
| |
| void z_shell_op_char_insert(const struct shell *sh, char data) |
| { |
| if (z_flag_insert_mode_get(sh) && |
| (sh->ctx->cmd_buff_len != sh->ctx->cmd_buff_pos)) { |
| char_replace(sh, data); |
| } else { |
| data_insert(sh, &data, 1); |
| } |
| } |
| |
| void z_shell_op_char_backspace(const struct shell *sh) |
| { |
| if ((sh->ctx->cmd_buff_len == 0) || |
| (sh->ctx->cmd_buff_pos == 0)) { |
| return; |
| } |
| |
| z_shell_op_cursor_move(sh, -1); |
| z_shell_op_char_delete(sh); |
| } |
| |
| void z_shell_op_char_delete(const struct shell *sh) |
| { |
| uint16_t diff = sh->ctx->cmd_buff_len - sh->ctx->cmd_buff_pos; |
| char *str = &sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos]; |
| |
| if (diff == 0U) { |
| return; |
| } |
| |
| memmove(str, str + 1, diff); |
| --sh->ctx->cmd_buff_len; |
| reprint_from_cursor(sh, --diff, true); |
| } |
| |
| void z_shell_op_delete_from_cursor(const struct shell *sh) |
| { |
| sh->ctx->cmd_buff_len = sh->ctx->cmd_buff_pos; |
| sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos] = '\0'; |
| |
| z_clear_eos(sh); |
| } |
| |
| void z_shell_op_completion_insert(const struct shell *sh, |
| const char *compl, |
| uint16_t compl_len) |
| { |
| data_insert(sh, compl, compl_len); |
| } |
| |
| void z_shell_cmd_line_erase(const struct shell *sh) |
| { |
| z_shell_multiline_data_calc(&sh->ctx->vt100_ctx.cons, |
| sh->ctx->cmd_buff_pos, |
| sh->ctx->cmd_buff_len); |
| z_shell_op_cursor_horiz_move(sh, |
| -(sh->ctx->vt100_ctx.cons.cur_x - 1)); |
| z_shell_op_cursor_vert_move(sh, sh->ctx->vt100_ctx.cons.cur_y - 1); |
| |
| z_clear_eos(sh); |
| } |
| |
| static void print_prompt(const struct shell *sh) |
| { |
| z_shell_fprintf(sh, SHELL_INFO, "%s", sh->ctx->prompt); |
| } |
| |
| void z_shell_print_cmd(const struct shell *sh) |
| { |
| int beg_offset = 0; |
| int end_offset = 0; |
| int cmd_width = z_shell_strlen(sh->ctx->cmd_buff); |
| int adjust = sh->ctx->vt100_ctx.cons.name_len; |
| char ch; |
| |
| while (cmd_width > sh->ctx->vt100_ctx.cons.terminal_wid - adjust) { |
| end_offset += sh->ctx->vt100_ctx.cons.terminal_wid - adjust; |
| ch = sh->ctx->cmd_buff[end_offset]; |
| sh->ctx->cmd_buff[end_offset] = '\0'; |
| |
| z_shell_raw_fprintf(sh->fprintf_ctx, "%s\n", |
| &sh->ctx->cmd_buff[beg_offset]); |
| |
| sh->ctx->cmd_buff[end_offset] = ch; |
| cmd_width -= (sh->ctx->vt100_ctx.cons.terminal_wid - adjust); |
| beg_offset = end_offset; |
| adjust = 0; |
| } |
| if (cmd_width > 0) { |
| z_shell_raw_fprintf(sh->fprintf_ctx, "%s", |
| &sh->ctx->cmd_buff[beg_offset]); |
| } |
| } |
| |
| void z_shell_print_prompt_and_cmd(const struct shell *sh) |
| { |
| print_prompt(sh); |
| |
| if (z_flag_echo_get(sh)) { |
| z_shell_print_cmd(sh); |
| z_shell_op_cursor_position_synchronize(sh); |
| } |
| } |
| |
| static void shell_pend_on_txdone(const struct shell *sh) |
| { |
| if (IS_ENABLED(CONFIG_MULTITHREADING) && |
| (sh->ctx->state < SHELL_STATE_PANIC_MODE_ACTIVE)) { |
| struct k_poll_event event; |
| |
| k_poll_event_init(&event, |
| K_POLL_TYPE_SIGNAL, |
| K_POLL_MODE_NOTIFY_ONLY, |
| &sh->ctx->signals[SHELL_SIGNAL_TXDONE]); |
| k_poll(&event, 1, K_FOREVER); |
| k_poll_signal_reset(&sh->ctx->signals[SHELL_SIGNAL_TXDONE]); |
| } else { |
| /* Blocking wait in case of bare metal. */ |
| while (!z_flag_tx_rdy_get(sh)) { |
| } |
| z_flag_tx_rdy_set(sh, false); |
| } |
| } |
| |
| void z_shell_write(const struct shell *sh, const void *data, |
| size_t length) |
| { |
| __ASSERT_NO_MSG(sh && data); |
| |
| size_t offset = 0; |
| size_t tmp_cnt; |
| |
| while (length) { |
| int err = sh->iface->api->write(sh->iface, |
| &((const uint8_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 && |
| (sh->ctx->state != SHELL_STATE_PANIC_MODE_ACTIVE)) { |
| shell_pend_on_txdone(sh); |
| } |
| } |
| } |
| |
| /* Function shall be only used by the fprintf module. */ |
| void z_shell_print_stream(const void *user_ctx, const char *data, size_t len) |
| { |
| z_shell_write((const struct shell *) user_ctx, data, len); |
| } |
| |
| static void vt100_bgcolor_set(const struct shell *sh, |
| enum shell_vt100_color bgcolor) |
| { |
| if (!IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) { |
| return; |
| } |
| |
| if (bgcolor >= VT100_COLOR_END) { |
| return; |
| } |
| |
| if ((bgcolor == SHELL_NORMAL) || |
| (sh->ctx->vt100_ctx.col.bgcol == bgcolor)) { |
| return; |
| } |
| |
| sh->ctx->vt100_ctx.col.bgcol = bgcolor; |
| Z_SHELL_VT100_CMD(sh, "\e[403%dm", bgcolor); |
| } |
| |
| void z_shell_vt100_color_set(const struct shell *sh, |
| enum shell_vt100_color color) |
| { |
| if (!IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) { |
| return; |
| } |
| |
| if (color >= VT100_COLOR_END) { |
| return; |
| } |
| |
| if (sh->ctx->vt100_ctx.col.col == color) { |
| return; |
| } |
| |
| sh->ctx->vt100_ctx.col.col = color; |
| |
| if (color != SHELL_NORMAL) { |
| Z_SHELL_VT100_CMD(sh, "\e[1;3%dm", color); |
| } else { |
| Z_SHELL_VT100_CMD(sh, SHELL_VT100_MODESOFF); |
| } |
| } |
| |
| void z_shell_vt100_colors_restore(const struct shell *sh, |
| const struct shell_vt100_colors *color) |
| { |
| if (!IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) { |
| return; |
| } |
| |
| z_shell_vt100_color_set(sh, color->col); |
| vt100_bgcolor_set(sh, color->bgcol); |
| } |
| |
| void z_shell_vfprintf(const struct shell *sh, enum shell_vt100_color color, |
| const char *fmt, va_list args) |
| { |
| if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS) && |
| z_flag_use_colors_get(sh) && |
| (color != sh->ctx->vt100_ctx.col.col)) { |
| struct shell_vt100_colors col; |
| |
| z_shell_vt100_colors_store(sh, &col); |
| z_shell_vt100_color_set(sh, color); |
| |
| z_shell_fprintf_fmt(sh->fprintf_ctx, fmt, args); |
| |
| z_shell_vt100_colors_restore(sh, &col); |
| } else { |
| z_shell_fprintf_fmt(sh->fprintf_ctx, fmt, args); |
| } |
| } |
| |
| void z_shell_fprintf(const struct shell *sh, |
| enum shell_vt100_color color, |
| const char *fmt, ...) |
| { |
| __ASSERT_NO_MSG(sh); |
| __ASSERT_NO_MSG(sh->ctx); |
| __ASSERT_NO_MSG(sh->fprintf_ctx); |
| __ASSERT_NO_MSG(fmt); |
| __ASSERT(z_flag_sync_mode_get(sh) || !k_is_in_isr(), |
| "Thread context required."); |
| |
| va_list args; |
| |
| va_start(args, fmt); |
| z_shell_vfprintf(sh, color, fmt, args); |
| va_end(args); |
| } |