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