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 		k_event_wait(&sh->ctx->signal_event, SHELL_SIGNAL_TXDONE, false, K_FOREVER);
436 		k_event_clear(&sh->ctx->signal_event, SHELL_SIGNAL_TXDONE);
437 	} else {
438 		/* Blocking wait in case of bare metal. */
439 		while (!z_flag_tx_rdy_get(sh)) {
440 		}
441 		z_flag_tx_rdy_set(sh, false);
442 	}
443 }
444 
z_shell_write(const struct shell * sh,const void * data,size_t length)445 void z_shell_write(const struct shell *sh, const void *data,
446 		 size_t length)
447 {
448 	__ASSERT_NO_MSG(sh && data);
449 
450 	size_t offset = 0;
451 	size_t tmp_cnt;
452 
453 	while (length) {
454 		int err = sh->iface->api->write(sh->iface,
455 				&((const uint8_t *) data)[offset], length,
456 				&tmp_cnt);
457 		(void)err;
458 		__ASSERT_NO_MSG(err == 0);
459 		__ASSERT_NO_MSG(length >= tmp_cnt);
460 		offset += tmp_cnt;
461 		length -= tmp_cnt;
462 		if (tmp_cnt == 0 &&
463 		    (sh->ctx->state != SHELL_STATE_PANIC_MODE_ACTIVE)) {
464 			shell_pend_on_txdone(sh);
465 		}
466 	}
467 }
468 
469 /* Function shall be only used by the fprintf module. */
z_shell_print_stream(const void * user_ctx,const char * data,size_t len)470 void z_shell_print_stream(const void *user_ctx, const char *data, size_t len)
471 {
472 	z_shell_write((const struct shell *) user_ctx, data, len);
473 }
474 
vt100_bgcolor_set(const struct shell * sh,enum shell_vt100_color bgcolor)475 static void vt100_bgcolor_set(const struct shell *sh,
476 			      enum shell_vt100_color bgcolor)
477 {
478 	if (!IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) {
479 		return;
480 	}
481 
482 	if (bgcolor >= VT100_COLOR_END) {
483 		return;
484 	}
485 
486 	if ((bgcolor == SHELL_NORMAL) ||
487 	    (sh->ctx->vt100_ctx.col.bgcol == bgcolor)) {
488 		return;
489 	}
490 
491 	sh->ctx->vt100_ctx.col.bgcol = bgcolor;
492 	Z_SHELL_VT100_CMD(sh, "\e[403%dm", bgcolor);
493 }
494 
z_shell_vt100_color_set(const struct shell * sh,enum shell_vt100_color color)495 void z_shell_vt100_color_set(const struct shell *sh,
496 			     enum shell_vt100_color color)
497 {
498 	if (!IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) {
499 		return;
500 	}
501 
502 	if (color >= VT100_COLOR_END) {
503 		return;
504 	}
505 
506 	if (sh->ctx->vt100_ctx.col.col == color) {
507 		return;
508 	}
509 
510 	sh->ctx->vt100_ctx.col.col = color;
511 
512 	if (color != SHELL_NORMAL) {
513 		Z_SHELL_VT100_CMD(sh, "\e[1;3%dm", color);
514 	} else {
515 		Z_SHELL_VT100_CMD(sh, SHELL_VT100_MODESOFF);
516 	}
517 }
518 
z_shell_vt100_colors_restore(const struct shell * sh,const struct shell_vt100_colors * color)519 void z_shell_vt100_colors_restore(const struct shell *sh,
520 				  const struct shell_vt100_colors *color)
521 {
522 	if (!IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) {
523 		return;
524 	}
525 
526 	z_shell_vt100_color_set(sh, color->col);
527 	vt100_bgcolor_set(sh, color->bgcol);
528 }
529 
z_shell_vfprintf(const struct shell * sh,enum shell_vt100_color color,const char * fmt,va_list args)530 void z_shell_vfprintf(const struct shell *sh, enum shell_vt100_color color,
531 		      const char *fmt, va_list args)
532 {
533 	if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS) &&
534 	    z_flag_use_colors_get(sh)	  &&
535 	    (color != sh->ctx->vt100_ctx.col.col)) {
536 		struct shell_vt100_colors col;
537 
538 		z_shell_vt100_colors_store(sh, &col);
539 		z_shell_vt100_color_set(sh, color);
540 
541 		z_shell_fprintf_fmt(sh->fprintf_ctx, fmt, args);
542 
543 		z_shell_vt100_colors_restore(sh, &col);
544 	} else {
545 		z_shell_fprintf_fmt(sh->fprintf_ctx, fmt, args);
546 	}
547 }
548 
z_shell_fprintf(const struct shell * sh,enum shell_vt100_color color,const char * fmt,...)549 void z_shell_fprintf(const struct shell *sh,
550 		     enum shell_vt100_color color,
551 		     const char *fmt, ...)
552 {
553 	__ASSERT_NO_MSG(sh);
554 	__ASSERT_NO_MSG(sh->ctx);
555 	__ASSERT_NO_MSG(sh->fprintf_ctx);
556 	__ASSERT_NO_MSG(fmt);
557 	__ASSERT(z_flag_sync_mode_get(sh) || !k_is_in_isr(),
558 		 "Thread context required.");
559 
560 	va_list args;
561 
562 	va_start(args, fmt);
563 	z_shell_vfprintf(sh, color, fmt, args);
564 	va_end(args);
565 }
566 
z_shell_backend_rx_buffer_flush(const struct shell * sh)567 void z_shell_backend_rx_buffer_flush(const struct shell *sh)
568 {
569 	__ASSERT_NO_MSG(sh);
570 
571 	int32_t max_iterations = 1000;
572 	uint8_t buf[64];
573 	size_t count = 0;
574 	int err;
575 
576 	do {
577 		err = sh->iface->api->read(sh->iface, buf, sizeof(buf), &count);
578 	} while (count != 0 && err == 0 && --max_iterations > 0);
579 }
580