blob: 577a8937c28730a6e080f5da9c5845332f341fb4 [file] [log] [blame]
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +02001/*
2 * Copyright (c) 2018 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
Jakub Rzeszutko63696962019-01-11 15:08:55 +01007#include <ctype.h>
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +02008#include "shell_ops.h"
9
10void shell_op_cursor_vert_move(const struct shell *shell, s32_t delta)
11{
12 if (delta != 0) {
13 shell_raw_fprintf(shell->fprintf_ctx, "\033[%d%c",
Jakub Rzeszutkoce6be862019-01-29 11:40:08 +010014 delta > 0 ? delta : -delta,
15 delta > 0 ? 'A' : 'B');
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +020016 }
17}
18
19void shell_op_cursor_horiz_move(const struct shell *shell, s32_t delta)
20{
21 if (delta != 0) {
22 shell_raw_fprintf(shell->fprintf_ctx, "\033[%d%c",
Jakub Rzeszutkoce6be862019-01-29 11:40:08 +010023 delta > 0 ? delta : -delta,
24 delta > 0 ? 'C' : 'D');
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +020025 }
26}
27
28/* Function returns true if command length is equal to multiplicity of terminal
29 * width.
30 */
31static inline bool full_line_cmd(const struct shell *shell)
32{
Jakub Rzeszutko090ef042019-02-08 15:37:40 +010033 return ((shell->ctx->cmd_buff_len + shell_strlen(shell->ctx->prompt))
Patrik Flykt24d71432019-03-26 19:57:45 -060034 % shell->ctx->vt100_ctx.cons.terminal_wid == 0U);
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +020035}
36
37/* Function returns true if cursor is at beginning of an empty line. */
38bool shell_cursor_in_empty_line(const struct shell *shell)
39{
Jakub Rzeszutko090ef042019-02-08 15:37:40 +010040 return ((shell->ctx->cmd_buff_pos + shell_strlen(shell->ctx->prompt))
Patrik Flykt24d71432019-03-26 19:57:45 -060041 % shell->ctx->vt100_ctx.cons.terminal_wid == 0U);
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +020042}
43
44void shell_op_cond_next_line(const struct shell *shell)
45{
46 if (shell_cursor_in_empty_line(shell) || full_line_cmd(shell)) {
47 cursor_next_line_move(shell);
48 }
49}
50
51void shell_op_cursor_position_synchronize(const struct shell *shell)
52{
53 struct shell_multiline_cons *cons = &shell->ctx->vt100_ctx.cons;
54 bool last_line;
55
56 shell_multiline_data_calc(cons, shell->ctx->cmd_buff_pos,
57 shell->ctx->cmd_buff_len);
58 last_line = (cons->cur_y == cons->cur_y_end);
59
60 /* In case cursor reaches the bottom line of a terminal, it will
61 * be moved to the next line.
62 */
63 if (full_line_cmd(shell)) {
64 cursor_next_line_move(shell);
65 }
66
67 if (last_line) {
68 shell_op_cursor_horiz_move(shell, cons->cur_x -
69 cons->cur_x_end);
70 } else {
71 shell_op_cursor_vert_move(shell, cons->cur_y_end - cons->cur_y);
72 shell_op_cursor_horiz_move(shell, cons->cur_x -
73 cons->cur_x_end);
74 }
75}
76
77void shell_op_cursor_move(const struct shell *shell, s16_t val)
78{
79 struct shell_multiline_cons *cons = &shell->ctx->vt100_ctx.cons;
80 u16_t new_pos = shell->ctx->cmd_buff_pos + val;
81 s32_t row_span;
82 s32_t col_span;
83
84 shell_multiline_data_calc(cons, shell->ctx->cmd_buff_pos,
85 shell->ctx->cmd_buff_len);
86
87 /* Calculate the new cursor. */
88 row_span = row_span_with_buffer_offsets_get(&shell->ctx->vt100_ctx.cons,
89 shell->ctx->cmd_buff_pos,
90 new_pos);
91 col_span = column_span_with_buffer_offsets_get(
92 &shell->ctx->vt100_ctx.cons,
93 shell->ctx->cmd_buff_pos,
94 new_pos);
95
96 shell_op_cursor_vert_move(shell, -row_span);
97 shell_op_cursor_horiz_move(shell, col_span);
98 shell->ctx->cmd_buff_pos = new_pos;
99}
100
Jakub Rzeszutko63696962019-01-11 15:08:55 +0100101static u16_t shift_calc(const char *str, u16_t pos, u16_t len, s16_t sign)
102{
103 bool found = false;
Patrik Flykt24d71432019-03-26 19:57:45 -0600104 u16_t ret = 0U;
Jakub Rzeszutko63696962019-01-11 15:08:55 +0100105 u16_t idx;
106
107 while (1) {
108 idx = pos + ret * sign;
Patrik Flykt24d71432019-03-26 19:57:45 -0600109 if (((idx == 0U) && (sign < 0)) ||
Jakub Rzeszutko63696962019-01-11 15:08:55 +0100110 ((idx == len) && (sign > 0))) {
111 break;
112 }
113 if (isalnum((int)str[idx]) != 0) {
114 found = true;
115 } else {
116 if (found) {
117 break;
118 }
119 }
120 ret++;
121 }
122
123 return ret;
124}
125
126void shell_op_cursor_word_move(const struct shell *shell, s16_t val)
127{
128 s16_t shift;
129 s16_t sign;
130
131 if (val < 0) {
132 val = -val;
133 sign = -1;
134 } else {
135 sign = 1;
136 }
137
138 while (val--) {
139 shift = shift_calc(shell->ctx->cmd_buff,
140 shell->ctx->cmd_buff_pos,
141 shell->ctx->cmd_buff_len, sign);
142 shell_op_cursor_move(shell, sign * shift);
143 }
144}
145
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +0200146void shell_op_word_remove(const struct shell *shell)
147{
148 char *str = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos - 1];
149 char *str_start = &shell->ctx->cmd_buff[0];
150 u16_t chars_to_delete;
151
152 /* Line must not be empty and cursor must not be at 0 to continue. */
153 if ((shell->ctx->cmd_buff_len == 0) ||
154 (shell->ctx->cmd_buff_pos == 0)) {
155 return;
156 }
157
158 /* Start at the current position. */
Patrik Flyktb97db522018-11-29 11:23:03 -0800159 chars_to_delete = 0U;
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +0200160
161 /* Look back for all spaces then for non-spaces. */
162 while ((str >= str_start) && (*str == ' ')) {
163 ++chars_to_delete;
164 --str;
165 }
166
167 while ((str >= str_start) && (*str != ' ')) {
168 ++chars_to_delete;
169 --str;
170 }
171
172 /* Manage the buffer. */
173 memmove(str + 1, str + 1 + chars_to_delete,
174 shell->ctx->cmd_buff_len - chars_to_delete);
175 shell->ctx->cmd_buff_len -= chars_to_delete;
176 shell->ctx->cmd_buff[shell->ctx->cmd_buff_len] = '\0';
177
178 /* Update display. */
179 shell_op_cursor_move(shell, -chars_to_delete);
180 cursor_save(shell);
Jakub Rzeszutko46a02322019-02-12 15:38:16 +0100181 shell_internal_fprintf(shell, SHELL_NORMAL, "%s", str + 1);
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +0200182 clear_eos(shell);
183 cursor_restore(shell);
184}
185
186void shell_op_cursor_home_move(const struct shell *shell)
187{
188 shell_op_cursor_move(shell, -shell->ctx->cmd_buff_pos);
189}
190
191void shell_op_cursor_end_move(const struct shell *shell)
192{
193 shell_op_cursor_move(shell, shell->ctx->cmd_buff_len -
194 shell->ctx->cmd_buff_pos);
195}
196
197
198void shell_op_left_arrow(const struct shell *shell)
199{
200 if (shell->ctx->cmd_buff_pos > 0) {
201 shell_op_cursor_move(shell, -1);
202 }
203}
204
205void shell_op_right_arrow(const struct shell *shell)
206{
207 if (shell->ctx->cmd_buff_pos < shell->ctx->cmd_buff_len) {
208 shell_op_cursor_move(shell, 1);
209 }
210}
211
212static void reprint_from_cursor(const struct shell *shell, u16_t diff,
213 bool data_removed)
214{
215 /* Clear eos is needed only when newly printed command is shorter than
216 * previously printed command. This can happen when delete or backspace
217 * was called.
218 *
219 * Such condition is useful for Bluetooth devices to save number of
220 * bytes transmitted between terminal and device.
221 */
222 if (data_removed) {
223 clear_eos(shell);
224 }
225
Jakub Rzeszutko46a02322019-02-12 15:38:16 +0100226 shell_internal_fprintf(shell, SHELL_NORMAL, "%s",
Jakub Rzeszutkoce6be862019-01-29 11:40:08 +0100227 &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos]);
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +0200228 shell->ctx->cmd_buff_pos = shell->ctx->cmd_buff_len;
229
230 if (full_line_cmd(shell)) {
231 if (((data_removed) && (diff > 0)) || (!data_removed)) {
232 cursor_next_line_move(shell);
233 }
234 }
235
236 shell_op_cursor_move(shell, -diff);
237}
238
239static void data_insert(const struct shell *shell, const char *data, u16_t len)
240{
241 u16_t after = shell->ctx->cmd_buff_len - shell->ctx->cmd_buff_pos;
242 char *curr_pos = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos];
243
244 if ((shell->ctx->cmd_buff_len + len) >= CONFIG_SHELL_CMD_BUFF_SIZE) {
245 return;
246 }
247
248 memmove(curr_pos + len, curr_pos, after);
249 memcpy(curr_pos, data, len);
250 shell->ctx->cmd_buff_len += len;
251 shell->ctx->cmd_buff[shell->ctx->cmd_buff_len] = '\0';
252
Jakub Rzeszutkof7f4fe32018-12-13 10:26:49 +0100253 if (!flag_echo_get(shell)) {
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +0200254 shell->ctx->cmd_buff_pos += len;
255 return;
256 }
257
258 reprint_from_cursor(shell, after, false);
259}
260
Jakub Rzeszutko77026102019-01-28 19:06:58 +0100261static void char_replace(const struct shell *shell, char data)
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +0200262{
263 shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos++] = data;
Jakub Rzeszutkoe7b55a62019-01-27 13:09:39 +0100264
265 if (!flag_echo_get(shell)) {
266 return;
267 }
268
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +0200269 shell_raw_fprintf(shell->fprintf_ctx, "%c", data);
270 if (shell_cursor_in_empty_line(shell)) {
271 cursor_next_line_move(shell);
272 }
273}
274
275void shell_op_char_insert(const struct shell *shell, char data)
276{
277 if (shell->ctx->internal.flags.insert_mode &&
278 (shell->ctx->cmd_buff_len != shell->ctx->cmd_buff_pos)) {
279 char_replace(shell, data);
280 } else {
281 data_insert(shell, &data, 1);
282 }
283}
284
285void shell_op_char_backspace(const struct shell *shell)
286{
287 if ((shell->ctx->cmd_buff_len == 0) ||
288 (shell->ctx->cmd_buff_pos == 0)) {
289 return;
290 }
291
292 shell_op_cursor_move(shell, -1);
293 shell_op_char_delete(shell);
294}
295
296void shell_op_char_delete(const struct shell *shell)
297{
298 u16_t diff = shell->ctx->cmd_buff_len - shell->ctx->cmd_buff_pos;
299 char *str = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos];
300
Patrik Flykt24d71432019-03-26 19:57:45 -0600301 if (diff == 0U) {
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +0200302 return;
303 }
304
305 memmove(str, str + 1, diff);
306 --shell->ctx->cmd_buff_len;
307 reprint_from_cursor(shell, --diff, true);
308}
309
Jakub Rzeszutko63696962019-01-11 15:08:55 +0100310void shell_op_delete_from_cursor(const struct shell *shell)
311{
312 shell->ctx->cmd_buff_len = shell->ctx->cmd_buff_pos;
313 shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos] = '\0';
314
315 clear_eos(shell);
316}
317
Krzysztof Chruscinski6aed72e2018-08-09 09:56:10 +0200318void shell_op_completion_insert(const struct shell *shell,
319 const char *compl,
320 u16_t compl_len)
321{
322 data_insert(shell, compl, compl_len);
323}
Jakub Rzeszutko3064ca42018-11-26 17:09:56 +0100324
Jakub Rzeszutkof7f4fe32018-12-13 10:26:49 +0100325void shell_cmd_line_erase(const struct shell *shell)
326{
327 shell_multiline_data_calc(&shell->ctx->vt100_ctx.cons,
328 shell->ctx->cmd_buff_pos,
329 shell->ctx->cmd_buff_len);
Jakub Rzeszutkoce6be862019-01-29 11:40:08 +0100330 shell_op_cursor_horiz_move(shell,
331 -(shell->ctx->vt100_ctx.cons.cur_x - 1));
Jakub Rzeszutkof7f4fe32018-12-13 10:26:49 +0100332 shell_op_cursor_vert_move(shell, shell->ctx->vt100_ctx.cons.cur_y - 1);
333
334 clear_eos(shell);
335}
336
Krzysztof Chruscinski4c2deeb2019-02-01 14:15:44 +0100337static void print_prompt(const struct shell *shell)
338{
Jakub Rzeszutko46a02322019-02-12 15:38:16 +0100339 shell_internal_fprintf(shell, SHELL_INFO, "%s", shell->ctx->prompt);
Krzysztof Chruscinski4c2deeb2019-02-01 14:15:44 +0100340}
341
342void shell_print_cmd(const struct shell *shell)
343{
344 shell_raw_fprintf(shell->fprintf_ctx, "%s", shell->ctx->cmd_buff);
345}
346
347void shell_print_prompt_and_cmd(const struct shell *shell)
348{
349 print_prompt(shell);
350
351 if (flag_echo_get(shell)) {
352 shell_print_cmd(shell);
353 shell_op_cursor_position_synchronize(shell);
354 }
355}
356
Jakub Rzeszutko3064ca42018-11-26 17:09:56 +0100357static void shell_pend_on_txdone(const struct shell *shell)
358{
Krzysztof Chruscinskid653a512018-12-05 13:25:43 +0100359 if (IS_ENABLED(CONFIG_MULTITHREADING) &&
360 (shell->ctx->state < SHELL_STATE_PANIC_MODE_ACTIVE)) {
Jakub Rzeszutko3064ca42018-11-26 17:09:56 +0100361 k_poll(&shell->ctx->events[SHELL_SIGNAL_TXDONE], 1, K_FOREVER);
362 k_poll_signal_reset(&shell->ctx->signals[SHELL_SIGNAL_TXDONE]);
363 } else {
364 /* Blocking wait in case of bare metal. */
Jakub Rzeszutkof7f4fe32018-12-13 10:26:49 +0100365 while (!flag_tx_rdy_get(shell)) {
Jakub Rzeszutko3064ca42018-11-26 17:09:56 +0100366 }
Jakub Rzeszutkof7f4fe32018-12-13 10:26:49 +0100367 flag_tx_rdy_set(shell, false);
Jakub Rzeszutko3064ca42018-11-26 17:09:56 +0100368 }
369}
370
371void shell_write(const struct shell *shell, const void *data,
372 size_t length)
373{
374 __ASSERT_NO_MSG(shell && data);
375
376 size_t offset = 0;
377 size_t tmp_cnt;
378
379 while (length) {
380 int err = shell->iface->api->write(shell->iface,
381 &((const u8_t *) data)[offset], length,
382 &tmp_cnt);
383 (void)err;
384 __ASSERT_NO_MSG(err == 0);
385 __ASSERT_NO_MSG(length >= tmp_cnt);
386 offset += tmp_cnt;
387 length -= tmp_cnt;
388 if (tmp_cnt == 0 &&
389 (shell->ctx->state != SHELL_STATE_PANIC_MODE_ACTIVE)) {
390 shell_pend_on_txdone(shell);
391 }
392 }
393}
394
395/* Function shall be only used by the fprintf module. */
396void shell_print_stream(const void *user_ctx, const char *data,
397 size_t data_len)
398{
399 shell_write((const struct shell *) user_ctx, data, data_len);
400}
Jakub Rzeszutkof7f4fe32018-12-13 10:26:49 +0100401
402static void vt100_bgcolor_set(const struct shell *shell,
403 enum shell_vt100_color bgcolor)
404{
405 if ((bgcolor == SHELL_NORMAL) ||
406 (shell->ctx->vt100_ctx.col.bgcol == bgcolor)) {
407 return;
408 }
409
410 /* -1 because default value is first in enum */
411 u8_t cmd[] = SHELL_VT100_BGCOLOR(bgcolor - 1);
412
413 shell->ctx->vt100_ctx.col.bgcol = bgcolor;
414 shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd);
415
416}
417
418void shell_vt100_color_set(const struct shell *shell,
419 enum shell_vt100_color color)
420{
421
422 if (shell->ctx->vt100_ctx.col.col == color) {
423 return;
424 }
425
426 shell->ctx->vt100_ctx.col.col = color;
427
428 if (color != SHELL_NORMAL) {
429
430 u8_t cmd[] = SHELL_VT100_COLOR(color - 1);
431
432 shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd);
433 } else {
434 static const u8_t cmd[] = SHELL_VT100_MODESOFF;
435
436 shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd);
437 }
438}
439
440void shell_vt100_colors_restore(const struct shell *shell,
441 const struct shell_vt100_colors *color)
442{
443 shell_vt100_color_set(shell, color->col);
444 vt100_bgcolor_set(shell, color->bgcol);
445}
Jakub Rzeszutko46a02322019-02-12 15:38:16 +0100446
447void shell_internal_vfprintf(const struct shell *shell,
448 enum shell_vt100_color color, const char *fmt,
449 va_list args)
450{
451 if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS) &&
452 shell->ctx->internal.flags.use_colors &&
453 (color != shell->ctx->vt100_ctx.col.col)) {
454 struct shell_vt100_colors col;
455
456 shell_vt100_colors_store(shell, &col);
457 shell_vt100_color_set(shell, color);
458
459 shell_fprintf_fmt(shell->fprintf_ctx, fmt, args);
460
461 shell_vt100_colors_restore(shell, &col);
462 } else {
463 shell_fprintf_fmt(shell->fprintf_ctx, fmt, args);
464 }
465}
466
467void shell_internal_fprintf(const struct shell *shell,
468 enum shell_vt100_color color,
469 const char *fmt, ...)
470{
471 __ASSERT_NO_MSG(shell);
472 __ASSERT(!k_is_in_isr(), "Thread context required.");
473 __ASSERT_NO_MSG(shell->ctx);
474 __ASSERT_NO_MSG(shell->fprintf_ctx);
475 __ASSERT_NO_MSG(fmt);
476
Jakob Olesen71260d82019-06-06 11:27:12 -0700477 va_list args;
Jakub Rzeszutko46a02322019-02-12 15:38:16 +0100478
479 va_start(args, fmt);
480 shell_internal_vfprintf(shell, color, fmt, args);
481 va_end(args);
482}