1 /*
2 * Copyright (c) 2018 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <ctype.h>
8 #include "shell_ops.h"
9
10 #define CMD_CURSOR_LEN 8
z_shell_op_cursor_vert_move(const struct shell * sh,int32_t delta)11 void z_shell_op_cursor_vert_move(const struct shell *sh, int32_t delta)
12 {
13 char dir = delta > 0 ? 'A' : 'B';
14
15 if (delta == 0) {
16 return;
17 }
18
19 if (delta < 0) {
20 delta = -delta;
21 }
22
23 Z_SHELL_VT100_CMD(sh, "\e[%d%c", delta, dir);
24 }
25
z_shell_op_cursor_horiz_move(const struct shell * sh,int32_t delta)26 void z_shell_op_cursor_horiz_move(const struct shell *sh, int32_t delta)
27 {
28 char dir = delta > 0 ? 'C' : 'D';
29
30 if (delta == 0) {
31 return;
32 }
33
34 if (delta < 0) {
35 delta = -delta;
36 }
37
38 Z_SHELL_VT100_CMD(sh, "\e[%d%c", delta, dir);
39 }
40
41 /* Function returns true if command length is equal to multiplicity of terminal
42 * width.
43 */
full_line_cmd(const struct shell * sh)44 static inline bool full_line_cmd(const struct shell *sh)
45 {
46 size_t line_length = sh->ctx->cmd_buff_len + z_shell_strlen(sh->ctx->prompt);
47
48 if (line_length == 0) {
49 return false;
50 }
51
52 return (line_length % sh->ctx->vt100_ctx.cons.terminal_wid == 0U);
53 }
54
55 /* Function returns true if cursor is at beginning of an empty line. */
z_shell_cursor_in_empty_line(const struct shell * sh)56 bool z_shell_cursor_in_empty_line(const struct shell *sh)
57 {
58 return (((sh->ctx->cmd_buff_pos * sh->ctx->cfg.flags.echo) +
59 z_shell_strlen(sh->ctx->prompt)) %
60 sh->ctx->vt100_ctx.cons.terminal_wid ==
61 0U);
62 }
63
z_shell_op_cond_next_line(const struct shell * sh)64 void z_shell_op_cond_next_line(const struct shell *sh)
65 {
66 if (z_shell_cursor_in_empty_line(sh) || full_line_cmd(sh)) {
67 z_cursor_next_line_move(sh);
68 }
69 }
70
z_shell_op_cursor_position_synchronize(const struct shell * sh)71 void z_shell_op_cursor_position_synchronize(const struct shell *sh)
72 {
73 struct shell_multiline_cons *cons = &sh->ctx->vt100_ctx.cons;
74 bool last_line;
75
76 z_shell_multiline_data_calc(cons, sh->ctx->cmd_buff_pos,
77 sh->ctx->cmd_buff_len);
78 last_line = (cons->cur_y == cons->cur_y_end);
79
80 /* In case cursor reaches the bottom line of a terminal, it will
81 * be moved to the next line.
82 */
83 if (full_line_cmd(sh)) {
84 z_cursor_next_line_move(sh);
85 }
86
87 if (last_line) {
88 z_shell_op_cursor_horiz_move(sh, cons->cur_x -
89 cons->cur_x_end);
90 } else {
91 z_shell_op_cursor_vert_move(sh, cons->cur_y_end - cons->cur_y);
92 z_shell_op_cursor_horiz_move(sh, cons->cur_x -
93 cons->cur_x_end);
94 }
95 }
96
z_shell_op_cursor_move(const struct shell * sh,int16_t val)97 void z_shell_op_cursor_move(const struct shell *sh, int16_t val)
98 {
99 struct shell_multiline_cons *cons = &sh->ctx->vt100_ctx.cons;
100 uint16_t new_pos = sh->ctx->cmd_buff_pos + val;
101 int32_t row_span;
102 int32_t col_span;
103
104 z_shell_multiline_data_calc(cons, sh->ctx->cmd_buff_pos,
105 sh->ctx->cmd_buff_len);
106
107 /* Calculate the new cursor. */
108 row_span = z_row_span_with_buffer_offsets_get(
109 &sh->ctx->vt100_ctx.cons,
110 sh->ctx->cmd_buff_pos,
111 new_pos);
112 col_span = z_column_span_with_buffer_offsets_get(
113 &sh->ctx->vt100_ctx.cons,
114 sh->ctx->cmd_buff_pos,
115 new_pos);
116
117 z_shell_op_cursor_vert_move(sh, -row_span);
118 z_shell_op_cursor_horiz_move(sh, col_span);
119 sh->ctx->cmd_buff_pos = new_pos;
120 }
121
shift_calc(const char * str,uint16_t pos,uint16_t len,int16_t sign)122 static uint16_t shift_calc(const char *str, uint16_t pos, uint16_t len, int16_t sign)
123 {
124 bool found = false;
125 uint16_t ret = 0U;
126 uint16_t idx;
127
128 while (1) {
129 idx = pos + ret * sign;
130 if (((idx == 0U) && (sign < 0)) ||
131 ((idx == len) && (sign > 0))) {
132 break;
133 }
134 if (isalnum((int)str[idx]) != 0) {
135 found = true;
136 } else {
137 if (found) {
138 break;
139 }
140 }
141 ret++;
142 }
143
144 return ret;
145 }
146
z_shell_op_cursor_word_move(const struct shell * sh,int16_t val)147 void z_shell_op_cursor_word_move(const struct shell *sh, int16_t val)
148 {
149 int16_t shift;
150 int16_t sign;
151
152 if (val < 0) {
153 val = -val;
154 sign = -1;
155 } else {
156 sign = 1;
157 }
158
159 while (val--) {
160 shift = shift_calc(sh->ctx->cmd_buff,
161 sh->ctx->cmd_buff_pos,
162 sh->ctx->cmd_buff_len, sign);
163 z_shell_op_cursor_move(sh, sign * shift);
164 }
165 }
166
z_shell_op_word_remove(const struct shell * sh)167 void z_shell_op_word_remove(const struct shell *sh)
168 {
169 /* Line must not be empty and cursor must not be at 0 to continue. */
170 if ((sh->ctx->cmd_buff_len == 0) ||
171 (sh->ctx->cmd_buff_pos == 0)) {
172 return;
173 }
174
175 char *str = &sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos - 1];
176 char *str_start = &sh->ctx->cmd_buff[0];
177 uint16_t chars_to_delete;
178
179 /* Start at the current position. */
180 chars_to_delete = 0U;
181
182 /* Look back for all spaces then for non-spaces. */
183 while ((str >= str_start) && (*str == ' ')) {
184 ++chars_to_delete;
185 --str;
186 }
187
188 while ((str >= str_start) && (*str != ' ')) {
189 ++chars_to_delete;
190 --str;
191 }
192
193 /* Manage the buffer. */
194 memmove(str + 1, str + 1 + chars_to_delete,
195 sh->ctx->cmd_buff_len - chars_to_delete);
196 sh->ctx->cmd_buff_len -= chars_to_delete;
197 sh->ctx->cmd_buff[sh->ctx->cmd_buff_len] = '\0';
198
199 /* Update display. */
200 z_shell_op_cursor_move(sh, -chars_to_delete);
201 z_cursor_save(sh);
202 z_shell_fprintf(sh, SHELL_NORMAL, "%s", str + 1);
203 z_clear_eos(sh);
204 z_cursor_restore(sh);
205 }
206
z_shell_op_cursor_home_move(const struct shell * sh)207 void z_shell_op_cursor_home_move(const struct shell *sh)
208 {
209 z_shell_op_cursor_move(sh, -sh->ctx->cmd_buff_pos);
210 }
211
z_shell_op_cursor_end_move(const struct shell * sh)212 void z_shell_op_cursor_end_move(const struct shell *sh)
213 {
214 z_shell_op_cursor_move(sh, sh->ctx->cmd_buff_len -
215 sh->ctx->cmd_buff_pos);
216 }
217
z_shell_op_left_arrow(const struct shell * sh)218 void z_shell_op_left_arrow(const struct shell *sh)
219 {
220 if (sh->ctx->cmd_buff_pos > 0) {
221 z_shell_op_cursor_move(sh, -1);
222 }
223 }
224
z_shell_op_right_arrow(const struct shell * sh)225 void z_shell_op_right_arrow(const struct shell *sh)
226 {
227 if (sh->ctx->cmd_buff_pos < sh->ctx->cmd_buff_len) {
228 z_shell_op_cursor_move(sh, 1);
229 }
230 }
231
reprint_from_cursor(const struct shell * sh,uint16_t diff,bool data_removed)232 static void reprint_from_cursor(const struct shell *sh, uint16_t diff,
233 bool data_removed)
234 {
235 /* Clear eos is needed only when newly printed command is shorter than
236 * previously printed command. This can happen when delete or backspace
237 * was called.
238 *
239 * Such condition is useful for Bluetooth devices to save number of
240 * bytes transmitted between terminal and device.
241 */
242 if (data_removed) {
243 z_clear_eos(sh);
244 }
245
246 if (z_flag_obscure_get(sh)) {
247 int len = strlen(&sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos]);
248
249 while (len--) {
250 z_shell_raw_fprintf(sh->fprintf_ctx, "*");
251 }
252 } else {
253 /* Check if the reprint will cross a line boundary */
254 int line_len = sh->ctx->cmd_buff_len + z_shell_strlen(sh->ctx->prompt);
255 int buff_pos = sh->ctx->cmd_buff_pos + z_shell_strlen(sh->ctx->prompt);
256
257 if ((buff_pos / sh->ctx->vt100_ctx.cons.terminal_wid) !=
258 (line_len / sh->ctx->vt100_ctx.cons.terminal_wid)) {
259 /*
260 * Reprint will take multiple lines.
261 * Print each character directly.
262 */
263 int pos = sh->ctx->cmd_buff_pos;
264
265 while (buff_pos < line_len) {
266 if (buff_pos++ % sh->ctx->vt100_ctx.cons.terminal_wid == 0U) {
267 z_cursor_next_line_move(sh);
268 }
269 z_shell_fprintf(sh, SHELL_NORMAL, "%c",
270 sh->ctx->cmd_buff[pos++]);
271 }
272 } else {
273 z_shell_fprintf(sh, SHELL_NORMAL, "%s",
274 &sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos]);
275 }
276 }
277 sh->ctx->cmd_buff_pos = sh->ctx->cmd_buff_len;
278
279 if (full_line_cmd(sh)) {
280 if (((data_removed) && (diff > 0)) || (!data_removed)) {
281 z_cursor_next_line_move(sh);
282 }
283 }
284
285 z_shell_op_cursor_move(sh, -diff);
286 }
287
data_insert(const struct shell * sh,const char * data,uint16_t len)288 static void data_insert(const struct shell *sh, const char *data, uint16_t len)
289 {
290 uint16_t after = sh->ctx->cmd_buff_len - sh->ctx->cmd_buff_pos;
291 char *curr_pos = &sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos];
292
293 if ((sh->ctx->cmd_buff_len + len) >= CONFIG_SHELL_CMD_BUFF_SIZE) {
294 return;
295 }
296
297 memmove(curr_pos + len, curr_pos, after);
298 memcpy(curr_pos, data, len);
299 sh->ctx->cmd_buff_len += len;
300 sh->ctx->cmd_buff[sh->ctx->cmd_buff_len] = '\0';
301
302 if (!z_flag_echo_get(sh)) {
303 sh->ctx->cmd_buff_pos += len;
304 return;
305 }
306
307 reprint_from_cursor(sh, after, false);
308 }
309
char_replace(const struct shell * sh,char data)310 static void char_replace(const struct shell *sh, char data)
311 {
312 sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos++] = data;
313
314 if (!z_flag_echo_get(sh)) {
315 return;
316 }
317 if (z_flag_obscure_get(sh)) {
318 data = '*';
319 }
320
321 z_shell_raw_fprintf(sh->fprintf_ctx, "%c", data);
322 if (z_shell_cursor_in_empty_line(sh)) {
323 z_cursor_next_line_move(sh);
324 }
325 }
326
z_shell_op_char_insert(const struct shell * sh,char data)327 void z_shell_op_char_insert(const struct shell *sh, char data)
328 {
329 if (z_flag_insert_mode_get(sh) &&
330 (sh->ctx->cmd_buff_len != sh->ctx->cmd_buff_pos)) {
331 char_replace(sh, data);
332 } else {
333 data_insert(sh, &data, 1);
334 }
335 }
336
z_shell_op_char_backspace(const struct shell * sh)337 void z_shell_op_char_backspace(const struct shell *sh)
338 {
339 if ((sh->ctx->cmd_buff_len == 0) ||
340 (sh->ctx->cmd_buff_pos == 0)) {
341 return;
342 }
343
344 z_shell_op_cursor_move(sh, -1);
345 z_shell_op_char_delete(sh);
346 }
347
z_shell_op_char_delete(const struct shell * sh)348 void z_shell_op_char_delete(const struct shell *sh)
349 {
350 uint16_t diff = sh->ctx->cmd_buff_len - sh->ctx->cmd_buff_pos;
351 char *str = &sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos];
352
353 if (diff == 0U) {
354 return;
355 }
356
357 memmove(str, str + 1, diff);
358 --sh->ctx->cmd_buff_len;
359 reprint_from_cursor(sh, --diff, true);
360 }
361
z_shell_op_delete_from_cursor(const struct shell * sh)362 void z_shell_op_delete_from_cursor(const struct shell *sh)
363 {
364 sh->ctx->cmd_buff_len = sh->ctx->cmd_buff_pos;
365 sh->ctx->cmd_buff[sh->ctx->cmd_buff_pos] = '\0';
366
367 z_clear_eos(sh);
368 }
369
z_shell_op_completion_insert(const struct shell * sh,const char * compl,uint16_t compl_len)370 void z_shell_op_completion_insert(const struct shell *sh,
371 const char *compl,
372 uint16_t compl_len)
373 {
374 data_insert(sh, compl, compl_len);
375 }
376
z_shell_cmd_line_erase(const struct shell * sh)377 void z_shell_cmd_line_erase(const struct shell *sh)
378 {
379 z_shell_multiline_data_calc(&sh->ctx->vt100_ctx.cons,
380 sh->ctx->cmd_buff_pos,
381 sh->ctx->cmd_buff_len);
382 z_shell_op_cursor_horiz_move(sh,
383 -(sh->ctx->vt100_ctx.cons.cur_x - 1));
384 z_shell_op_cursor_vert_move(sh, sh->ctx->vt100_ctx.cons.cur_y - 1);
385
386 z_clear_eos(sh);
387 }
388
print_prompt(const struct shell * sh)389 static void print_prompt(const struct shell *sh)
390 {
391 z_shell_fprintf(sh, SHELL_INFO, "%s", sh->ctx->prompt);
392 }
393
z_shell_print_cmd(const struct shell * sh)394 void z_shell_print_cmd(const struct shell *sh)
395 {
396 int beg_offset = 0;
397 int end_offset = 0;
398 int cmd_width = z_shell_strlen(sh->ctx->cmd_buff);
399 int adjust = sh->ctx->vt100_ctx.cons.name_len;
400 char ch;
401
402 while (cmd_width > sh->ctx->vt100_ctx.cons.terminal_wid - adjust) {
403 end_offset += sh->ctx->vt100_ctx.cons.terminal_wid - adjust;
404 ch = sh->ctx->cmd_buff[end_offset];
405 sh->ctx->cmd_buff[end_offset] = '\0';
406
407 z_shell_raw_fprintf(sh->fprintf_ctx, "%s\n",
408 &sh->ctx->cmd_buff[beg_offset]);
409
410 sh->ctx->cmd_buff[end_offset] = ch;
411 cmd_width -= (sh->ctx->vt100_ctx.cons.terminal_wid - adjust);
412 beg_offset = end_offset;
413 adjust = 0;
414 }
415 if (cmd_width > 0) {
416 z_shell_raw_fprintf(sh->fprintf_ctx, "%s",
417 &sh->ctx->cmd_buff[beg_offset]);
418 }
419 }
420
z_shell_print_prompt_and_cmd(const struct shell * sh)421 void z_shell_print_prompt_and_cmd(const struct shell *sh)
422 {
423 print_prompt(sh);
424
425 if (z_flag_echo_get(sh)) {
426 z_shell_print_cmd(sh);
427 z_shell_op_cursor_position_synchronize(sh);
428 }
429 }
430
shell_pend_on_txdone(const struct shell * sh)431 static void shell_pend_on_txdone(const struct shell *sh)
432 {
433 if (IS_ENABLED(CONFIG_MULTITHREADING) &&
434 (sh->ctx->state < SHELL_STATE_PANIC_MODE_ACTIVE)) {
435 struct k_poll_event event;
436
437 k_poll_event_init(&event,
438 K_POLL_TYPE_SIGNAL,
439 K_POLL_MODE_NOTIFY_ONLY,
440 &sh->ctx->signals[SHELL_SIGNAL_TXDONE]);
441 k_poll(&event, 1, K_FOREVER);
442 k_poll_signal_reset(&sh->ctx->signals[SHELL_SIGNAL_TXDONE]);
443 } else {
444 /* Blocking wait in case of bare metal. */
445 while (!z_flag_tx_rdy_get(sh)) {
446 }
447 z_flag_tx_rdy_set(sh, false);
448 }
449 }
450
z_shell_write(const struct shell * sh,const void * data,size_t length)451 void z_shell_write(const struct shell *sh, const void *data,
452 size_t length)
453 {
454 __ASSERT_NO_MSG(sh && data);
455
456 size_t offset = 0;
457 size_t tmp_cnt;
458
459 while (length) {
460 int err = sh->iface->api->write(sh->iface,
461 &((const uint8_t *) data)[offset], length,
462 &tmp_cnt);
463 (void)err;
464 __ASSERT_NO_MSG(err == 0);
465 __ASSERT_NO_MSG(length >= tmp_cnt);
466 offset += tmp_cnt;
467 length -= tmp_cnt;
468 if (tmp_cnt == 0 &&
469 (sh->ctx->state != SHELL_STATE_PANIC_MODE_ACTIVE)) {
470 shell_pend_on_txdone(sh);
471 }
472 }
473 }
474
475 /* Function shall be only used by the fprintf module. */
z_shell_print_stream(const void * user_ctx,const char * data,size_t len)476 void z_shell_print_stream(const void *user_ctx, const char *data, size_t len)
477 {
478 z_shell_write((const struct shell *) user_ctx, data, len);
479 }
480
vt100_bgcolor_set(const struct shell * sh,enum shell_vt100_color bgcolor)481 static void vt100_bgcolor_set(const struct shell *sh,
482 enum shell_vt100_color bgcolor)
483 {
484 if (!IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) {
485 return;
486 }
487
488 if (bgcolor >= VT100_COLOR_END) {
489 return;
490 }
491
492 if ((bgcolor == SHELL_NORMAL) ||
493 (sh->ctx->vt100_ctx.col.bgcol == bgcolor)) {
494 return;
495 }
496
497 sh->ctx->vt100_ctx.col.bgcol = bgcolor;
498 Z_SHELL_VT100_CMD(sh, "\e[403%dm", bgcolor);
499 }
500
z_shell_vt100_color_set(const struct shell * sh,enum shell_vt100_color color)501 void z_shell_vt100_color_set(const struct shell *sh,
502 enum shell_vt100_color color)
503 {
504 if (!IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) {
505 return;
506 }
507
508 if (color >= VT100_COLOR_END) {
509 return;
510 }
511
512 if (sh->ctx->vt100_ctx.col.col == color) {
513 return;
514 }
515
516 sh->ctx->vt100_ctx.col.col = color;
517
518 if (color != SHELL_NORMAL) {
519 Z_SHELL_VT100_CMD(sh, "\e[1;3%dm", color);
520 } else {
521 Z_SHELL_VT100_CMD(sh, SHELL_VT100_MODESOFF);
522 }
523 }
524
z_shell_vt100_colors_restore(const struct shell * sh,const struct shell_vt100_colors * color)525 void z_shell_vt100_colors_restore(const struct shell *sh,
526 const struct shell_vt100_colors *color)
527 {
528 if (!IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) {
529 return;
530 }
531
532 z_shell_vt100_color_set(sh, color->col);
533 vt100_bgcolor_set(sh, color->bgcol);
534 }
535
z_shell_vfprintf(const struct shell * sh,enum shell_vt100_color color,const char * fmt,va_list args)536 void z_shell_vfprintf(const struct shell *sh, enum shell_vt100_color color,
537 const char *fmt, va_list args)
538 {
539 if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS) &&
540 z_flag_use_colors_get(sh) &&
541 (color != sh->ctx->vt100_ctx.col.col)) {
542 struct shell_vt100_colors col;
543
544 z_shell_vt100_colors_store(sh, &col);
545 z_shell_vt100_color_set(sh, color);
546
547 z_shell_fprintf_fmt(sh->fprintf_ctx, fmt, args);
548
549 z_shell_vt100_colors_restore(sh, &col);
550 } else {
551 z_shell_fprintf_fmt(sh->fprintf_ctx, fmt, args);
552 }
553 }
554
z_shell_fprintf(const struct shell * sh,enum shell_vt100_color color,const char * fmt,...)555 void z_shell_fprintf(const struct shell *sh,
556 enum shell_vt100_color color,
557 const char *fmt, ...)
558 {
559 __ASSERT_NO_MSG(sh);
560 __ASSERT_NO_MSG(sh->ctx);
561 __ASSERT_NO_MSG(sh->fprintf_ctx);
562 __ASSERT_NO_MSG(fmt);
563 __ASSERT(z_flag_sync_mode_get(sh) || !k_is_in_isr(),
564 "Thread context required.");
565
566 va_list args;
567
568 va_start(args, fmt);
569 z_shell_vfprintf(sh, color, fmt, args);
570 va_end(args);
571 }
572
z_shell_backend_rx_buffer_flush(const struct shell * sh)573 void z_shell_backend_rx_buffer_flush(const struct shell *sh)
574 {
575 __ASSERT_NO_MSG(sh);
576
577 int32_t max_iterations = 1000;
578 uint8_t buf[64];
579 size_t count = 0;
580 int err;
581
582 do {
583 err = sh->iface->api->read(sh->iface, buf, sizeof(buf), &count);
584 } while (count != 0 && err == 0 && --max_iterations > 0);
585 }
586